An options token representing the right to exercise any one of the whitelisted exercise contracts, allowing the user to receive different forms of discounted assets in return for the appropriate payment. The option does not expire. The options token receives user input and a specified exercise contract address, passing through to the exercise contract to execute the option. We fork https://github.com/timeless-fi/options-token, which is a simple implementation of an option for discounted tokens at an adjusted oracle rate. Here, we divorce the exercise functionality from the token contract, and allow an admin to whitelist and fund exercise contracts as the desired. We also implement more potential oracle types, and make several other minor changes.
We want to ensure there are no attacks on pricing in DiscountExercise, atomically or otherwise, in each oracle implementation. We want to ensure users will never pay more than maxPaymentAmount. When properly specified, this should ensure users experience no more deviation in price than they specify.
Given the nature of this token, it is fine for the admin to have some centralized permissions (admin can mint tokens, admin is the one who funds exercise contracts, etc). The team is responsible for refilling the exercise contracts. We limit the amount of funds we leave in an exercise contract at any given time to limit risk.
The user will always interact with the OptionsToken itself, and never with any exercise contract directly.
- User calls exercise on the OptionsToken, specifying their desired exercise contract and encoding exercise parameters
- OptionsToken validates the exercise contract.
- DiscountExercise decodes the parameters for the exercise function on the chosen exercise contract, and calls the specified function. In the case of zapping in DiscountExercise, the parameters are maxPaymentAmount, deadline, and isInstantExit set to true.
- OptionsTokens are burnt.
- A penalty fee in the form of underlying tokens (available in the contract) is calculated, then conditionally swapped to the desired token and distributed to the fee recipients.
- Swapping and distribution occur only when the fee amount exceeds a specified trigger to avoid swapping small amounts.
- The transaction reverts if the minimal desired amount of desired tokens is not obtained.
- The underlying tokens available in the DiscountExercise contract are sent to the user. The amount of underlying tokens is discounted by the multiplier and reduced by the penalty fee.
- The user approves OptionsToken the amount of Payment Token they wish to spend
- User calls exercise on the OptionsToken, specifying their desired exercise contract and encoding exercise parameters
- OptionsToken validates the exercise contract, decodes the parameters for the exercise function on the exercise contract of choice, and calls said function. In the case of DiscountExercise, the params are maxPaymentAmount, deadline and isInstantExit set to false.
- oTokens are burnt, WETH is sent to the treasury, and underlyingTokens, discounted by the multiplier, are sent to the user exercising
- Can be priced using balancer, thena, univ3 twap oracles
- Reverts above maxPaymentAmount or past deadline
The Compounder platform facilitates the utilization of flash loans to exercise the option, enabling the acquisition of the underlying token at a discounted rate via payment token.
- Admin configures swap paths, oracles, initializer args, etc
- Strategy has an oToken balance
- Keeper calls harvestOTokens
- Calculate Payment Amount from Discount Exercise given oToken balance
- Flashloan the necessary amount of funds to exercise in paymentToken
- Callback from flashloan is called
- oTokens are exercised using paymentToken that was flash loaned
- Underlying token is received by the strategy
- Calculate minAmountOut by directly querying the same oracle consumed by the DiscountExercise we interact with
- Swap entire amount into payment token to repay flashloan
- Assess profitability in units of paymentToken, swap profits to want of the strategy if not same token as paymentToken
- Emit event that reflects the oTokens compounded
To install with DappTools:
dapp install timeless-fi/options-token
To install with Foundry:
forge install timeless-fi/options-token
This project uses Foundry as the development framework.
forge install
forge build
forge test
--report lcov
- coverage which can be turned on in code using "Coverage Gutters"
slither . --include-path src/<targetFile>
--checklist
- report in md
--print inheritance-graph
- generate inheritance graph in xdot
xdot inheritance-graph.dot
- open inheritance graph
Inside ./scripts
there is "config.json" where can be defined deployment configurations.
You can choose which contract to deploy by adding/removing string in CONTRACTS_TO_DEPLOY. If some contracts are removed from CONTRACTS_TO_DEPLOY, there must be defined the address for already existing contract on the chain (example: SWAPPER, OPTIONS_COMPOUNDER).
There are 2 deployment scripts. One is for swapper and paths updates and second for all optionsToken infra (swapper address must be passed here).
{
"VERSION": "1.1.0",
"OWNER": <OWNER ADDRESS>,
"ORACLE_SOURCE": <PRICE TWAP ORACLE>,
"ORACLE_SECS": 1800,
"ORACLE_MIN_PRICE": 10000000,
"OT_NAME": "ICL Call Option Token",
"OT_SYMBOL": "oICL",
"OT_PAYMENT_TOKEN": "0xDfc7C877a950e49D2610114102175A06C2e3167a",
"OT_UNDERLYING_TOKEN": "0x95177295a394f2b9b04545fff58f4af0673e839d",
"OT_TOKEN_ADMIN": "0xF29dA3595351dBFd0D647857C46F8D63Fc2e68C5",
"VELO_ROUTER": "0x3a63171DD9BebF4D07BC782FECC7eb0b890C2A45",
"ADDRESS_PROVIDER": "0xEDc83309549e36f3c7FD8c2C5C54B4c8e5FA00FC",
"MULTIPLIER": 5000,
"FEE_RECIPIENTS": [
<TREASURY>
],
"FEE_BPS": [
10000
],
"STRATS": [
<STRATEGY ADDRESS>
],
"INSTANT_EXIT_FEE": 1000,
"MIN_AMOUNT_TO_TRIGGER_SWAP": 1e15,
"CONTRACTS_TO_DEPLOY": [],
"SWAPPER": "0x63D170618A8Ed1987F3CA6391b5e2F6a4554Cf53",
"DISCOUNT_EXERCISE": "0xcb727532e24dFe22E74D3892b998f5e915676Da8",
"OPTIONS_TOKEN": "0x3B6eA0fA8A487c90007ce120a83920fd52b06f6D",
"OPTIONS_COMPOUNDER": "0xf6cf2065C35595c12B532e54ACDe5A4597e32e6e",
"ORACLE": "0xDaA2c821428f62e1B08009a69CE824253CCEE5f9"
}
{
"VERSION": "1.1.0",
"OWNER": <OWNER ADDRESS>,
"ORACLE_SOURCE": <PRICE TWAP ORACLE>,
"ORACLE_SECS": 1800,
"ORACLE_MIN_PRICE": 10000000,
"OT_NAME": "ICL Call Option Token",
"OT_SYMBOL": "oICL",
"OT_PAYMENT_TOKEN": "0xDfc7C877a950e49D2610114102175A06C2e3167a",
"OT_UNDERLYING_TOKEN": "0x95177295a394f2b9b04545fff58f4af0673e839d",
"OT_TOKEN_ADMIN": "0xF29dA3595351dBFd0D647857C46F8D63Fc2e68C5",
"VELO_ROUTER": "0x3a63171DD9BebF4D07BC782FECC7eb0b890C2A45",
"ADDRESS_PROVIDER": "0xEDc83309549e36f3c7FD8c2C5C54B4c8e5FA00FC",
"MULTIPLIER": 5000,
"FEE_RECIPIENTS": [
<TREASURY>
],
"FEE_BPS": [
10000
],
"STRATS": [
<STRATEGY ADDRESS>
],
"INSTANT_EXIT_FEE": 1000,
"MIN_AMOUNT_TO_TRIGGER_SWAP": 1e15,
"CONTRACTS_TO_DEPLOY": [
"OptionsToken",
"DiscountExercise",
"ThenaOracle"
],
"SWAPPER": "0x63D170618A8Ed1987F3CA6391b5e2F6a4554Cf53",
"DISCOUNT_EXERCISE": "undefined",
"OPTIONS_TOKEN": "undefined",
"OPTIONS_COMPOUNDER": "undefined",
"ORACLE": "undefined"
}
-
All functionality that touches funds can be paused
-
Pause function called by 2/7 Guardian
-
Guardian has 7 members globally dispersed
-
Arithmetic errors
-
Re-entrancy
-
Flashloans
-
Access Control
-
(N/A) Unchecked External Calls
-
(N/A) Account abstraction/multicall issues
-
Static analysis -> Slither
-
Contracts pass all tests
-
Contracts deployed to testnet
-
Unchecked External Calls
-
Account abstraction/multicall issues
-
USE SLITHER
-
Does this deployment have access to funds, either directly or indirectly (zappers, leveragers, etc.)?
Minimum security if Yes:
- Internal Audit (not the author, minimum 1x Junior review + minimum 1x Senior review)
- External Audit (impact scope)
Action items in support of deployment:
- Minimum two people present for deployment
- All developers who worked on and reviewed the contract should be included in the readme
- Developers involved: xRave110 (change owner), Eidolon (reviewer), Zokunei (reviewer), Goober (reviewer), Beirao (reviewer)
- Documentation of deployment procedure if non-standard (i.e. if multiple scripts are necessary)
Summary
- reentrancy-no-eth (1 results) (Medium)
- unused-return (2 results) (Medium)
- reentrancy-benign (1 results) (Low)
- reentrancy-events (4 results) (Low)
- timestamp (2 results) (Low)
- pragma (1 results) (Informational)
- solc-version (1 results) (Informational)
- naming-convention (3 results) (Informational)
- unused-import (2 results) (Informational)
Impact: Medium Confidence: Medium
- ID-0 Reentrancy in DiscountExercise._zap(address,uint256,address,DiscountExerciseParams): External calls: - underlyingToken.approve(swapProps.swapper,feeAmount) - amountOut = _generalSwap(swapProps.exchangeTypes,address(underlyingToken),address(paymentToken),feeAmount,minAmountOut,swapProps.exchangeAddress) - _swapper.swapUniV2(tokenIn,tokenOut,amount,minAmountOutData,exchangeAddress) - _swapper.swapBal(tokenIn,tokenOut,amount,minAmountOutData,exchangeAddress) - _swapper.swapVelo(tokenIn,tokenOut,amount,minAmountOutData,exchangeAddress) - _swapper.swapUniV3(tokenIn,tokenOut,amount,minAmountOutData,exchangeAddress) State variables written after the call(s): - feeAmount = 0 DiscountExercise.feeAmount can be used in cross function reentrancies: - DiscountExercise._zap(address,uint256,address,DiscountExerciseParams)
src/exercise/DiscountExercise.sol#L211-L249
Justification: External call is happening to well known dexes which are verified against any reentrancy attacks but fix may be implemented using additional temporary variable.
Impact: Medium Confidence: Medium
- ID-1 DiscountExercise._zap(address,uint256,address,DiscountExerciseParams) ignores return value by underlyingToken.approve(swapProps.swapper,feeAmount)
src/exercise/DiscountExercise.sol#L211-L249
- ID-2 DiscountExercise._zap(address,uint256,address,DiscountExerciseParams) ignores return value by underlyingToken.approve(swapProps.swapper,0)
src/exercise/DiscountExercise.sol#L211-L249
Justification: It is just potential DOS which is very unlikely.
Impact: Low Confidence: Medium
- ID-3 Reentrancy in DiscountExercise._pay(address,uint256): External calls: - underlyingToken.safeTransfer(to,balance) - underlyingToken.safeTransfer(to,amount) State variables written after the call(s): - credit[to] += remainingAmount
src/exercise/DiscountExercise.sol#L269-L278
Justification: Tokens are set by the addresses with special access role who knows that onReceiveErc20 hook might lead to the potential reentrancy attack.
Impact: Low Confidence: Medium
- ID-4 Reentrancy in DiscountExercise._zap(address,uint256,address,DiscountExerciseParams): External calls: - underlyingToken.approve(swapProps.swapper,feeAmount) - amountOut = _generalSwap(swapProps.exchangeTypes,address(underlyingToken),address(paymentToken),feeAmount,minAmountOut,swapProps.exchangeAddress) - _swapper.swapUniV2(tokenIn,tokenOut,amount,minAmountOutData,exchangeAddress) - _swapper.swapBal(tokenIn,tokenOut,amount,minAmountOutData,exchangeAddress) - _swapper.swapVelo(tokenIn,tokenOut,amount,minAmountOutData,exchangeAddress) - _swapper.swapUniV3(tokenIn,tokenOut,amount,minAmountOutData,exchangeAddress) - underlyingToken.approve(swapProps.swapper,0) - distributeFees(paymentToken.balanceOf(address(this)),paymentToken) - returndata = address(token).functionCall(data,SafeERC20: low-level call failed) - (success,returndata) = target.call{value: value}(data) - token.safeTransfer(feeRecipients[i],feeAmount) - token.safeTransfer(feeRecipients[feeRecipients.length - 1],remaining) - _pay(recipient,underlyingAmount) - returndata = address(token).functionCall(data,SafeERC20: low-level call failed) - underlyingToken.safeTransfer(to,balance) - (success,returndata) = target.call{value: value}(data) - underlyingToken.safeTransfer(to,amount) External calls sending eth: - distributeFees(paymentToken.balanceOf(address(this)),paymentToken) - (success,returndata) = target.call{value: value}(data) - _pay(recipient,underlyingAmount) - (success,returndata) = target.call{value: value}(data) Event emitted after the call(s): - Exercised(from,recipient,underlyingAmount,paymentAmount)
src/exercise/DiscountExercise.sol#L211-L249
- ID-5 Reentrancy in DiscountExercise.claim(address): External calls: - underlyingToken.safeTransfer(to,amount) Event emitted after the call(s): - Claimed(amount)
src/exercise/DiscountExercise.sol#L134-L140
- ID-6 Reentrancy in DiscountExercise._zap(address,uint256,address,DiscountExerciseParams): External calls: - underlyingToken.approve(swapProps.swapper,feeAmount) - amountOut = _generalSwap(swapProps.exchangeTypes,address(underlyingToken),address(paymentToken),feeAmount,minAmountOut,swapProps.exchangeAddress) - _swapper.swapUniV2(tokenIn,tokenOut,amount,minAmountOutData,exchangeAddress) - _swapper.swapBal(tokenIn,tokenOut,amount,minAmountOutData,exchangeAddress) - _swapper.swapVelo(tokenIn,tokenOut,amount,minAmountOutData,exchangeAddress) - _swapper.swapUniV3(tokenIn,tokenOut,amount,minAmountOutData,exchangeAddress) - underlyingToken.approve(swapProps.swapper,0) - distributeFees(paymentToken.balanceOf(address(this)),paymentToken) - returndata = address(token).functionCall(data,SafeERC20: low-level call failed) - (success,returndata) = target.call{value: value}(data) - token.safeTransfer(feeRecipients[i],feeAmount) - token.safeTransfer(feeRecipients[feeRecipients.length - 1],remaining) External calls sending eth: - distributeFees(paymentToken.balanceOf(address(this)),paymentToken) - (success,returndata) = target.call{value: value}(data) Event emitted after the call(s): - DistributeFees(feeRecipients,feeBPS,totalAmount) - distributeFees(paymentToken.balanceOf(address(this)),paymentToken)
src/exercise/DiscountExercise.sol#L211-L249
- ID-7 Reentrancy in DiscountExercise._redeem(address,uint256,address,DiscountExerciseParams): External calls: - distributeFeesFrom(paymentAmount,paymentToken,from) - returndata = address(token).functionCall(data,SafeERC20: low-level call failed) - (success,returndata) = target.call{value: value}(data) - token.safeTransferFrom(from,feeRecipients[i],feeAmount) - token.safeTransferFrom(from,feeRecipients[feeRecipients.length - 1],remaining) - _pay(recipient,amount) - returndata = address(token).functionCall(data,SafeERC20: low-level call failed) - underlyingToken.safeTransfer(to,balance) - (success,returndata) = target.call{value: value}(data) - underlyingToken.safeTransfer(to,amount) External calls sending eth: - distributeFeesFrom(paymentAmount,paymentToken,from) - (success,returndata) = target.call{value: value}(data) - _pay(recipient,amount) - (success,returndata) = target.call{value: value}(data) Event emitted after the call(s): - Exercised(from,recipient,amount,paymentAmount)
src/exercise/DiscountExercise.sol#L252-L267
Impact: Low Confidence: Medium
- ID-8 DiscountExercise._redeem(address,uint256,address,DiscountExerciseParams) uses timestamp for comparisons Dangerous comparisons: - block.timestamp > params.deadline
src/exercise/DiscountExercise.sol#L252-L267
- ID-9 DiscountExercise._zap(address,uint256,address,DiscountExerciseParams) uses timestamp for comparisons Dangerous comparisons: - block.timestamp > params.deadline
src/exercise/DiscountExercise.sol#L211-L249
Impact: Informational Confidence: High
- ID-10 11 different versions of Solidity are used: - Version constraint ^0.8.0 is used by: -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 - Version constraint ^0.8.2 is used by: -^0.8.2 -^0.8.2 - Version constraint ^0.8.1 is used by: -^0.8.1 -^0.8.1 - Version constraint >=0.8.0 is used by: ->=0.8.0 ->=0.8.0 ->=0.8.0 - Version constraint >=0.5.0 is used by: ->=0.5.0 ->=0.5.0 ->=0.5.0 ->=0.5.0 ->=0.5.0 ->=0.5.0 ->=0.5.0 ->=0.5.0 ->=0.5.0 ->=0.5.0 ->=0.5.0 ->=0.5.0 - Version constraint >=0.7.5 is used by: ->=0.7.5 - Version constraint >=0.6.2 is used by: ->=0.6.2 - Version constraint >=0.6.0 is used by: ->=0.6.0 - Version constraint ^0.8.13 is used by: -^0.8.13 -^0.8.13 -^0.8.13 -^0.8.13 -^0.8.13 -^0.8.13 -^0.8.13 -^0.8.13 -^0.8.13 -^0.8.13 -^0.8.13 - Version constraint >=0.7.0<0.9.0 is used by: ->=0.7.0<0.9.0 ->=0.7.0<0.9.0 ->=0.7.0<0.9.0 - Version constraint >=0.5 is used by: ->=0.5
lib/openzeppelin-contracts-upgradeable/contracts/access/AccessControlEnumerableUpgradeable.sol#L4
Impact: Informational Confidence: High
- ID-11 Version constraint ^0.8.13 contains known severe issues (https://solidity.readthedocs.io/en/latest/bugs.html) - VerbatimInvalidDeduplication - FullInlinerNonExpressionSplitArgumentEvaluationOrder - MissingSideEffectsOnSelectorAccess - StorageWriteRemovalBeforeConditionalTermination - AbiReencodingHeadOverflowWithStaticArrayCleanup - DirtyBytesArrayToStorage - InlineAssemblyMemorySideEffects - DataLocationChangeInInternalOverride - NestedCalldataArrayAbiReencodingSizeValidation. It is used by: - ^0.8.13 - ^0.8.13 - ^0.8.13 - ^0.8.13 - ^0.8.13 - ^0.8.13 - ^0.8.13 - ^0.8.13 - ^0.8.13 - ^0.8.13 - ^0.8.13
src/OptionsToken.sol#L2
Impact: Informational Confidence: High
- ID-12 Parameter DiscountExercise.setSwapProps(SwapProps)._swapProps is not in mixedCase
src/exercise/DiscountExercise.sol#L143
- ID-13 Parameter DiscountExercise.setMinAmountToTriggerSwap(uint256)._minAmountToTriggerSwap is not in mixedCase
src/exercise/DiscountExercise.sol#L193
- ID-14 Parameter DiscountExercise.setInstantExitFee(uint256)._instantExitFee is not in mixedCase
src/exercise/DiscountExercise.sol#L179
Impact: Informational Confidence: High
-
ID-15 The following unused import(s) in src/interfaces/IOptionsCompounder.sol should be removed: -import {IOptionsToken} from "./IOptionsToken.sol"; (src/interfaces/IOptionsCompounder.sol#5)
-
ID-16 The following unused import(s) in src/OptionsCompounder.sol should be removed: -import {ReaperAccessControl} from "vault-v2/mixins/ReaperAccessControl.sol"; (src/OptionsCompounder.sol#11)
INFO:Slither:. analyzed (100 contracts with 94 detectors), 17 result(s) found
Summary
- arbitrary-send-erc20 (1 results) (High)
- reentrancy-eth (1 results) (High)
- incorrect-equality (1 results) (Medium)
- unused-return (6 results) (Medium)
- missing-zero-check (1 results) (Low)
- reentrancy-benign (1 results) (Low)
- reentrancy-events (1 results) (Low)
- timestamp (1 results) (Low)
- boolean-equal (3 results) (Informational)
- pragma (1 results) (Informational)
- solc-version (1 results) (Informational)
- missing-inheritance (1 results) (Informational)
- naming-convention (13 results) (Informational)
Impact: High Confidence: High
- ID-0 OptionsCompounder._exerciseOptionAndReturnDebt(address,uint256,uint256,bytes) uses arbitrary from in transferFrom: IERC20(address(optionsToken)).safeTransferFrom(flashloanParams.sender,address(this),flashloanParams.optionsAmount)
src/OptionsCompounder.sol#L241-L320
Impact: High Confidence: Medium
- ID-1 Reentrancy in OptionsCompounder.executeOperation(address[],uint256[],uint256[],address,bytes): External calls: - _exerciseOptionAndReturnDebt(assets[0],amounts[0],premiums[0],params) - returndata = address(token).functionCall(data,SafeERC20: low-level call failed) - (success,returndata) = target.call{value: value}(data) - _swapper.swapUniV2(tokenIn,tokenOut,amount,minAmountOutData,exchangeAddress) - _swapper.swapBal(tokenIn,tokenOut,amount,minAmountOutData,exchangeAddress) - _swapper.swapVelo(tokenIn,tokenOut,amount,minAmountOutData,exchangeAddress) - IERC20(address(optionsToken)).safeTransferFrom(flashloanParams.sender,address(this),flashloanParams.optionsAmount) - _swapper.swapUniV3(tokenIn,tokenOut,amount,minAmountOutData,exchangeAddress) - IERC20(asset).approve(flashloanParams.exerciserContract,amount) - optionsToken.exercise(flashloanParams.optionsAmount,address(this),flashloanParams.exerciserContract,exerciseParams) - IERC20(asset).approve(flashloanParams.exerciserContract,0) - underlyingToken.approve(swapper,balanceOfUnderlyingToken) - underlyingToken.approve(swapper,0) - IERC20(asset).approve(address(lendingPool),totalAmountToPay) - IERC20(asset).safeTransfer(flashloanParams.sender,gainInPaymentToken) External calls sending eth: - _exerciseOptionAndReturnDebt(assets[0],amounts[0],premiums[0],params) - (success,returndata) = target.call{value: value}(data) State variables written after the call(s): - flashloanFinished = true OptionsCompounder.flashloanFinished can be used in cross function reentrancies: - OptionsCompounder._harvestOTokens(uint256,address,uint256) - OptionsCompounder.executeOperation(address[],uint256[],uint256[],address,bytes) - OptionsCompounder.initialize(address,address,address,SwapProps,IOracle)
src/OptionsCompounder.sol#L214-L230
Impact: Medium Confidence: High
- ID-2 OptionsCompounder._exerciseOptionAndReturnDebt(address,uint256,uint256,bytes) uses a dangerous strict equality: - swapAmountOut == 0
src/OptionsCompounder.sol#L241-L320
Impact: Medium Confidence: Medium
- ID-3 OptionsCompounder._exerciseOptionAndReturnDebt(address,uint256,uint256,bytes) ignores return value by IERC20(asset).approve(flashloanParams.exerciserContract,0)
src/OptionsCompounder.sol#L241-L320
- ID-4 OptionsCompounder._exerciseOptionAndReturnDebt(address,uint256,uint256,bytes) ignores return value by IERC20(asset).approve(address(lendingPool),totalAmountToPay)
src/OptionsCompounder.sol#L241-L320
- ID-5 OptionsCompounder._exerciseOptionAndReturnDebt(address,uint256,uint256,bytes) ignores return value by underlyingToken.approve(swapper,balanceOfUnderlyingToken)
src/OptionsCompounder.sol#L241-L320
- ID-6 OptionsCompounder._exerciseOptionAndReturnDebt(address,uint256,uint256,bytes) ignores return value by underlyingToken.approve(swapper,0)
src/OptionsCompounder.sol#L241-L320
- ID-7 OptionsCompounder._exerciseOptionAndReturnDebt(address,uint256,uint256,bytes) ignores return value by IERC20(asset).approve(flashloanParams.exerciserContract,amount)
src/OptionsCompounder.sol#L241-L320
- ID-8 OptionsCompounder._exerciseOptionAndReturnDebt(address,uint256,uint256,bytes) ignores return value by optionsToken.exercise(flashloanParams.optionsAmount,address(this),flashloanParams.exerciserContract,exerciseParams)
src/OptionsCompounder.sol#L241-L320
Impact: Low Confidence: Medium
- ID-9 OptionsCompounder.initiateUpgradeCooldown(address)._nextImplementation lacks a zero-check on : - nextImplementation = _nextImplementation
src/OptionsCompounder.sol#L326
Impact: Low Confidence: Medium
- ID-10 Reentrancy in OptionsCompounder._harvestOTokens(uint256,address,uint256): External calls: - optionsToken.isExerciseContract(exerciseContract) == false State variables written after the call(s): - flashloanFinished = false
src/OptionsCompounder.sol#L164-L202
Impact: Low Confidence: Medium
- ID-11 Reentrancy in OptionsCompounder._exerciseOptionAndReturnDebt(address,uint256,uint256,bytes): External calls: - IERC20(address(optionsToken)).safeTransferFrom(flashloanParams.sender,address(this),flashloanParams.optionsAmount) - IERC20(asset).approve(flashloanParams.exerciserContract,amount) - optionsToken.exercise(flashloanParams.optionsAmount,address(this),flashloanParams.exerciserContract,exerciseParams) - IERC20(asset).approve(flashloanParams.exerciserContract,0) - underlyingToken.approve(swapper,balanceOfUnderlyingToken) - swapAmountOut = _generalSwap(swapProps.exchangeTypes,address(underlyingToken),asset,balanceOfUnderlyingToken,minAmountOut,swapProps.exchangeAddress) - _swapper.swapUniV2(tokenIn,tokenOut,amount,minAmountOutData,exchangeAddress) - _swapper.swapBal(tokenIn,tokenOut,amount,minAmountOutData,exchangeAddress) - _swapper.swapVelo(tokenIn,tokenOut,amount,minAmountOutData,exchangeAddress) - _swapper.swapUniV3(tokenIn,tokenOut,amount,minAmountOutData,exchangeAddress) - underlyingToken.approve(swapper,0) - IERC20(asset).approve(address(lendingPool),totalAmountToPay) - IERC20(asset).safeTransfer(flashloanParams.sender,gainInPaymentToken) Event emitted after the call(s): - OTokenCompounded(gainInPaymentToken,totalAmountToPay)
src/OptionsCompounder.sol#L241-L320
Impact: Low Confidence: Medium
- ID-12 OptionsCompounder._authorizeUpgrade(address) uses timestamp for comparisons Dangerous comparisons: - require(bool,string)(upgradeProposalTime + UPGRADE_TIMELOCK < block.timestamp,Upgrade cooldown not initiated or still ongoing)
src/OptionsCompounder.sol#L350-L354
Impact: Informational Confidence: High
- ID-13 OptionsCompounder._harvestOTokens(uint256,address,uint256) compares to a boolean constant: -optionsToken.isExerciseContract(exerciseContract) == false
src/OptionsCompounder.sol#L164-L202
- ID-14 OptionsCompounder._harvestOTokens(uint256,address,uint256) compares to a boolean constant: -flashloanFinished == false
src/OptionsCompounder.sol#L164-L202
- ID-15 OptionsCompounder.executeOperation(address[],uint256[],uint256[],address,bytes) compares to a boolean constant: -flashloanFinished != false || msg.sender != address(lendingPool)
src/OptionsCompounder.sol#L214-L230
Impact: Informational Confidence: High
- ID-16 11 different versions of Solidity are used: - Version constraint ^0.8.0 is used by: -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 - Version constraint ^0.8.2 is used by: -^0.8.2 -^0.8.2 - Version constraint ^0.8.1 is used by: -^0.8.1 -^0.8.1 - Version constraint >=0.8.0 is used by: ->=0.8.0 ->=0.8.0 ->=0.8.0 - Version constraint >=0.5.0 is used by: ->=0.5.0 ->=0.5.0 ->=0.5.0 ->=0.5.0 ->=0.5.0 ->=0.5.0 ->=0.5.0 ->=0.5.0 ->=0.5.0 ->=0.5.0 ->=0.5.0 ->=0.5.0 - Version constraint >=0.7.5 is used by: ->=0.7.5 - Version constraint >=0.6.2 is used by: ->=0.6.2 - Version constraint >=0.6.0 is used by: ->=0.6.0 - Version constraint ^0.8.13 is used by: -^0.8.13 -^0.8.13 -^0.8.13 -^0.8.13 -^0.8.13 -^0.8.13 -^0.8.13 -^0.8.13 -^0.8.13 -^0.8.13 -^0.8.13 - Version constraint >=0.7.0<0.9.0 is used by: ->=0.7.0<0.9.0 ->=0.7.0<0.9.0 ->=0.7.0<0.9.0 - Version constraint >=0.5 is used by: ->=0.5
lib/openzeppelin-contracts-upgradeable/contracts/access/AccessControlEnumerableUpgradeable.sol#L4
Impact: Informational Confidence: High
- ID-17 Version constraint ^0.8.0 contains known severe issues (https://solidity.readthedocs.io/en/latest/bugs.html) - FullInlinerNonExpressionSplitArgumentEvaluationOrder - MissingSideEffectsOnSelectorAccess - AbiReencodingHeadOverflowWithStaticArrayCleanup - DirtyBytesArrayToStorage - DataLocationChangeInInternalOverride - NestedCalldataArrayAbiReencodingSizeValidation - SignedImmutables - ABIDecodeTwoDimensionalArrayMemory - KeccakCaching. It is used by: - ^0.8.0 - ^0.8.0 - ^0.8.0 - ^0.8.0 - ^0.8.0 - ^0.8.0 - ^0.8.0 - ^0.8.0 - ^0.8.0 - ^0.8.0 - ^0.8.0 - ^0.8.0 - ^0.8.0 - ^0.8.0 - ^0.8.0 - ^0.8.0 - ^0.8.0 - ^0.8.0 - ^0.8.0 - ^0.8.0 - ^0.8.0 - ^0.8.0 - ^0.8.0 - ^0.8.0 - ^0.8.0 - ^0.8.0 - ^0.8.0 - ^0.8.0 - ^0.8.0 - ^0.8.0 - ^0.8.0 - ^0.8.0 - ^0.8.0 - ^0.8.0 - ^0.8.0 - ^0.8.0 - ^0.8.0 - ^0.8.0 - ^0.8.0 - ^0.8.0 - ^0.8.0 - ^0.8.0 - ^0.8.0 - ^0.8.0 - ^0.8.0 - ^0.8.0 - ^0.8.0 - ^0.8.0 - ^0.8.0 - ^0.8.0 - ^0.8.0 - ^0.8.0 - ^0.8.0 - ^0.8.0 - ^0.8.0 - ^0.8.0 - ^0.8.0 - ^0.8.0 - ^0.8.0 - ^0.8.0 - ^0.8.0
lib/openzeppelin-contracts-upgradeable/contracts/access/AccessControlEnumerableUpgradeable.sol#L4
Impact: Informational Confidence: High
- ID-18 OptionsCompounder should inherit from IOptionsCompounder
src/OptionsCompounder.sol#L25-L370
Impact: Informational Confidence: High
- ID-19 Parameter OptionsCompounder.initialize(address,address,address,SwapProps,IOracle)._optionsToken is not in mixedCase
src/OptionsCompounder.sol#L74
- ID-20 Parameter OptionsCompounder.initialize(address,address,address,SwapProps,IOracle)._swapper is not in mixedCase
src/OptionsCompounder.sol#L74
- ID-21 Parameter OptionsCompounder.initialize(address,address,address,SwapProps,IOracle)._oracle is not in mixedCase
src/OptionsCompounder.sol#L74
- ID-22 Parameter OptionsCompounder.initialize(address,address,address,SwapProps,IOracle)._swapProps is not in mixedCase
src/OptionsCompounder.sol#L74
- ID-23 Parameter OptionsCompounder.setSwapProps(SwapProps)._swapProps is not in mixedCase
src/OptionsCompounder.sol#L108
- ID-24 Parameter OptionsCompounder.setOracle(IOracle)._oracle is not in mixedCase
src/OptionsCompounder.sol#L112
- ID-25 Function OptionsCompounder.ADDRESSES_PROVIDER() is not in mixedCase
src/OptionsCompounder.sol#L363-L365
- ID-26 Parameter OptionsCompounder.initialize(address,address,address,SwapProps,IOracle)._addressProvider is not in mixedCase
src/OptionsCompounder.sol#L74
- ID-27 Parameter OptionsCompounder.initiateUpgradeCooldown(address)._nextImplementation is not in mixedCase
src/OptionsCompounder.sol#L326
- ID-28 Parameter OptionsCompounder.setOptionsToken(address)._optionsToken is not in mixedCase
src/OptionsCompounder.sol#L97
- ID-29 Parameter OptionsCompounder.setSwapper(address)._swapper is not in mixedCase
src/OptionsCompounder.sol#L123
- ID-30 Function OptionsCompounder.LENDING_POOL() is not in mixedCase
src/OptionsCompounder.sol#L367-L369
- ID-31 Parameter OptionsCompounder.setAddressProvider(address)._addressProvider is not in mixedCase
src/OptionsCompounder.sol#L134
Summary
- missing-zero-check (2 results) (Low)
- reentrancy-events (1 results) (Low)
- timestamp (1 results) (Low)
- pragma (1 results) (Informational)
- solc-version (1 results) (Informational)
- naming-convention (3 results) (Informational)
Impact: Low Confidence: Medium
- ID-0 OptionsToken.initialize(string,string,address).tokenAdmin_ lacks a zero-check on : - tokenAdmin = tokenAdmin_
src/OptionsToken.sol#L60
- ID-1 OptionsToken.initiateUpgradeCooldown(address)._nextImplementation lacks a zero-check on : - nextImplementation = _nextImplementation
src/OptionsToken.sol#L178
Impact: Low Confidence: Medium
- ID-2 Reentrancy in OptionsToken._exercise(uint256,address,address,bytes): External calls: - (paymentAmount,data0,data1,data2) = IExercise(option).exercise(msg.sender,amount,recipient,params) Event emitted after the call(s): - Exercise(msg.sender,recipient,amount,data0,data1,data2)
src/OptionsToken.sol#L144-L168
Impact: Low Confidence: Medium
- ID-3 OptionsToken._authorizeUpgrade(address) uses timestamp for comparisons Dangerous comparisons: - require(bool,string)(upgradeProposalTime + UPGRADE_TIMELOCK < block.timestamp,Upgrade cooldown not initiated or still ongoing)
src/OptionsToken.sol#L202-L206
Impact: Informational Confidence: High
- ID-4 11 different versions of Solidity are used: - Version constraint ^0.8.0 is used by: -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 - Version constraint ^0.8.2 is used by: -^0.8.2 -^0.8.2 - Version constraint ^0.8.1 is used by: -^0.8.1 -^0.8.1 - Version constraint >=0.8.0 is used by: ->=0.8.0 ->=0.8.0 ->=0.8.0 - Version constraint >=0.5.0 is used by: ->=0.5.0 ->=0.5.0 ->=0.5.0 ->=0.5.0 ->=0.5.0 ->=0.5.0 ->=0.5.0 ->=0.5.0 ->=0.5.0 ->=0.5.0 ->=0.5.0 ->=0.5.0 - Version constraint >=0.7.5 is used by: ->=0.7.5 - Version constraint >=0.6.2 is used by: ->=0.6.2 - Version constraint >=0.6.0 is used by: ->=0.6.0 - Version constraint ^0.8.13 is used by: -^0.8.13 -^0.8.13 -^0.8.13 -^0.8.13 -^0.8.13 -^0.8.13 -^0.8.13 -^0.8.13 -^0.8.13 -^0.8.13 -^0.8.13 - Version constraint >=0.7.0<0.9.0 is used by: ->=0.7.0<0.9.0 ->=0.7.0<0.9.0 ->=0.7.0<0.9.0 - Version constraint >=0.5 is used by: ->=0.5
lib/openzeppelin-contracts-upgradeable/contracts/access/AccessControlEnumerableUpgradeable.sol#L4
Impact: Informational Confidence: High
- ID-5 Version constraint ^0.8.13 contains known severe issues (https://solidity.readthedocs.io/en/latest/bugs.html) - VerbatimInvalidDeduplication - FullInlinerNonExpressionSplitArgumentEvaluationOrder - MissingSideEffectsOnSelectorAccess - StorageWriteRemovalBeforeConditionalTermination - AbiReencodingHeadOverflowWithStaticArrayCleanup - DirtyBytesArrayToStorage - InlineAssemblyMemorySideEffects - DataLocationChangeInInternalOverride - NestedCalldataArrayAbiReencodingSizeValidation. It is used by: - ^0.8.13 - ^0.8.13 - ^0.8.13 - ^0.8.13 - ^0.8.13 - ^0.8.13 - ^0.8.13 - ^0.8.13 - ^0.8.13 - ^0.8.13 - ^0.8.13
src/OptionsToken.sol#L2
Impact: Informational Confidence: High
- ID-6 Parameter OptionsToken.initiateUpgradeCooldown(address)._nextImplementation is not in mixedCase
src/OptionsToken.sol#L178
- ID-7 Parameter OptionsToken.setExerciseContract(address,bool)._isExercise is not in mixedCase
src/OptionsToken.sol#L125
- ID-8 Parameter OptionsToken.setExerciseContract(address,bool)._address is not in mixedCase
src/OptionsToken.sol#L125
Summary
- pragma (1 results) (Informational)
- solc-version (1 results) (Informational)
- immutable-states (1 results) (Optimization)
Impact: Informational Confidence: High
- ID-0 11 different versions of Solidity are used: - Version constraint ^0.8.0 is used by: -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 - Version constraint ^0.8.2 is used by: -^0.8.2 -^0.8.2 - Version constraint ^0.8.1 is used by: -^0.8.1 -^0.8.1 - Version constraint >=0.8.0 is used by: ->=0.8.0 ->=0.8.0 ->=0.8.0 - Version constraint >=0.5.0 is used by: ->=0.5.0 ->=0.5.0 ->=0.5.0 ->=0.5.0 ->=0.5.0 ->=0.5.0 ->=0.5.0 ->=0.5.0 ->=0.5.0 ->=0.5.0 ->=0.5.0 ->=0.5.0 - Version constraint >=0.7.5 is used by: ->=0.7.5 - Version constraint >=0.6.2 is used by: ->=0.6.2 - Version constraint >=0.6.0 is used by: ->=0.6.0 - Version constraint ^0.8.13 is used by: -^0.8.13 -^0.8.13 -^0.8.13 -^0.8.13 -^0.8.13 -^0.8.13 -^0.8.13 -^0.8.13 -^0.8.13 -^0.8.13 -^0.8.13 - Version constraint >=0.7.0<0.9.0 is used by: ->=0.7.0<0.9.0 ->=0.7.0<0.9.0 ->=0.7.0<0.9.0 - Version constraint >=0.5 is used by: ->=0.5
lib/openzeppelin-contracts-upgradeable/contracts/access/AccessControlEnumerableUpgradeable.sol#L4
Impact: Informational Confidence: High
- ID-1 Version constraint ^0.8.13 contains known severe issues (https://solidity.readthedocs.io/en/latest/bugs.html) - VerbatimInvalidDeduplication - FullInlinerNonExpressionSplitArgumentEvaluationOrder - MissingSideEffectsOnSelectorAccess - StorageWriteRemovalBeforeConditionalTermination - AbiReencodingHeadOverflowWithStaticArrayCleanup - DirtyBytesArrayToStorage - InlineAssemblyMemorySideEffects - DataLocationChangeInInternalOverride - NestedCalldataArrayAbiReencodingSizeValidation. It is used by: - ^0.8.13 - ^0.8.13 - ^0.8.13 - ^0.8.13 - ^0.8.13 - ^0.8.13 - ^0.8.13 - ^0.8.13 - ^0.8.13 - ^0.8.13 - ^0.8.13
src/OptionsToken.sol#L2
Impact: Optimization Confidence: High
- ID-2 ThenaOracle.isToken0 should be immutable
src/oracles/ThenaOracle.sol#L60
Summary
- arbitrary-send-erc20 (2 results) (High)
- reentrancy-events (5 results) (Low)
- pragma (1 results) (Informational)
- solc-version (1 results) (Informational)
- naming-convention (2 results) (Informational)
Impact: High Confidence: High
- ID-0 BaseExercise.distributeFeesFrom(uint256,IERC20,address) uses arbitrary from in transferFrom: token.safeTransferFrom(from,feeRecipients[i],feeAmount)
src/exercise/BaseExercise.sol#L73-L82
- ID-1 BaseExercise.distributeFeesFrom(uint256,IERC20,address) uses arbitrary from in transferFrom: token.safeTransferFrom(from,feeRecipients[feeRecipients.length - 1],remaining)
src/exercise/BaseExercise.sol#L73-L82
Impact: Low Confidence: Medium
- ID-2 Reentrancy in DiscountExercise._zap(address,uint256,address,DiscountExerciseParams): External calls: - underlyingToken.approve(swapProps.swapper,feeAmount) - amountOut = _generalSwap(swapProps.exchangeTypes,address(underlyingToken),address(paymentToken),feeAmount,minAmountOut,swapProps.exchangeAddress) - _swapper.swapUniV2(tokenIn,tokenOut,amount,minAmountOutData,exchangeAddress) - _swapper.swapBal(tokenIn,tokenOut,amount,minAmountOutData,exchangeAddress) - _swapper.swapVelo(tokenIn,tokenOut,amount,minAmountOutData,exchangeAddress) - _swapper.swapUniV3(tokenIn,tokenOut,amount,minAmountOutData,exchangeAddress) - underlyingToken.approve(swapProps.swapper,0) - distributeFees(paymentToken.balanceOf(address(this)),paymentToken) - returndata = address(token).functionCall(data,SafeERC20: low-level call failed) - (success,returndata) = target.call{value: value}(data) - token.safeTransfer(feeRecipients[i],feeAmount) - token.safeTransfer(feeRecipients[feeRecipients.length - 1],remaining) - _pay(recipient,underlyingAmount) - returndata = address(token).functionCall(data,SafeERC20: low-level call failed) - underlyingToken.safeTransfer(to,balance) - (success,returndata) = target.call{value: value}(data) - underlyingToken.safeTransfer(to,amount) External calls sending eth: - distributeFees(paymentToken.balanceOf(address(this)),paymentToken) - (success,returndata) = target.call{value: value}(data) - _pay(recipient,underlyingAmount) - (success,returndata) = target.call{value: value}(data) Event emitted after the call(s): - Exercised(from,recipient,underlyingAmount,paymentAmount)
src/exercise/DiscountExercise.sol#L211-L249
- ID-3 Reentrancy in BaseExercise.distributeFees(uint256,IERC20): External calls: - token.safeTransfer(feeRecipients[i],feeAmount) - token.safeTransfer(feeRecipients[feeRecipients.length - 1],remaining) Event emitted after the call(s): - DistributeFees(feeRecipients,feeBPS,totalAmount)
src/exercise/BaseExercise.sol#L86-L95
- ID-4 Reentrancy in DiscountExercise._zap(address,uint256,address,DiscountExerciseParams): External calls: - underlyingToken.approve(swapProps.swapper,feeAmount) - amountOut = _generalSwap(swapProps.exchangeTypes,address(underlyingToken),address(paymentToken),feeAmount,minAmountOut,swapProps.exchangeAddress) - _swapper.swapUniV2(tokenIn,tokenOut,amount,minAmountOutData,exchangeAddress) - _swapper.swapBal(tokenIn,tokenOut,amount,minAmountOutData,exchangeAddress) - _swapper.swapVelo(tokenIn,tokenOut,amount,minAmountOutData,exchangeAddress) - _swapper.swapUniV3(tokenIn,tokenOut,amount,minAmountOutData,exchangeAddress) - underlyingToken.approve(swapProps.swapper,0) - distributeFees(paymentToken.balanceOf(address(this)),paymentToken) - returndata = address(token).functionCall(data,SafeERC20: low-level call failed) - (success,returndata) = target.call{value: value}(data) - token.safeTransfer(feeRecipients[i],feeAmount) - token.safeTransfer(feeRecipients[feeRecipients.length - 1],remaining) External calls sending eth: - distributeFees(paymentToken.balanceOf(address(this)),paymentToken) - (success,returndata) = target.call{value: value}(data) Event emitted after the call(s): - DistributeFees(feeRecipients,feeBPS,totalAmount) - distributeFees(paymentToken.balanceOf(address(this)),paymentToken)
src/exercise/DiscountExercise.sol#L211-L249
- ID-5 Reentrancy in DiscountExercise._redeem(address,uint256,address,DiscountExerciseParams): External calls: - distributeFeesFrom(paymentAmount,paymentToken,from) - returndata = address(token).functionCall(data,SafeERC20: low-level call failed) - (success,returndata) = target.call{value: value}(data) - token.safeTransferFrom(from,feeRecipients[i],feeAmount) - token.safeTransferFrom(from,feeRecipients[feeRecipients.length - 1],remaining) - _pay(recipient,amount) - returndata = address(token).functionCall(data,SafeERC20: low-level call failed) - underlyingToken.safeTransfer(to,balance) - (success,returndata) = target.call{value: value}(data) - underlyingToken.safeTransfer(to,amount) External calls sending eth: - distributeFeesFrom(paymentAmount,paymentToken,from) - (success,returndata) = target.call{value: value}(data) - _pay(recipient,amount) - (success,returndata) = target.call{value: value}(data) Event emitted after the call(s): - Exercised(from,recipient,amount,paymentAmount)
src/exercise/DiscountExercise.sol#L252-L267
- ID-6 Reentrancy in BaseExercise.distributeFeesFrom(uint256,IERC20,address): External calls: - token.safeTransferFrom(from,feeRecipients[i],feeAmount) - token.safeTransferFrom(from,feeRecipients[feeRecipients.length - 1],remaining) Event emitted after the call(s): - DistributeFees(feeRecipients,feeBPS,totalAmount)
src/exercise/BaseExercise.sol#L73-L82
Impact: Informational Confidence: High
- ID-7 11 different versions of Solidity are used: - Version constraint ^0.8.0 is used by: -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 -^0.8.0 - Version constraint ^0.8.2 is used by: -^0.8.2 -^0.8.2 - Version constraint ^0.8.1 is used by: -^0.8.1 -^0.8.1 - Version constraint >=0.8.0 is used by: ->=0.8.0 ->=0.8.0 ->=0.8.0 - Version constraint >=0.5.0 is used by: ->=0.5.0 ->=0.5.0 ->=0.5.0 ->=0.5.0 ->=0.5.0 ->=0.5.0 ->=0.5.0 ->=0.5.0 ->=0.5.0 ->=0.5.0 ->=0.5.0 ->=0.5.0 - Version constraint >=0.7.5 is used by: ->=0.7.5 - Version constraint >=0.6.2 is used by: ->=0.6.2 - Version constraint >=0.6.0 is used by: ->=0.6.0 - Version constraint ^0.8.13 is used by: -^0.8.13 -^0.8.13 -^0.8.13 -^0.8.13 -^0.8.13 -^0.8.13 -^0.8.13 -^0.8.13 -^0.8.13 -^0.8.13 -^0.8.13 - Version constraint >=0.7.0<0.9.0 is used by: ->=0.7.0<0.9.0 ->=0.7.0<0.9.0 ->=0.7.0<0.9.0 - Version constraint >=0.5 is used by: ->=0.5
lib/openzeppelin-contracts-upgradeable/contracts/access/AccessControlEnumerableUpgradeable.sol#L4
Impact: Informational Confidence: High
- ID-8 Version constraint ^0.8.13 contains known severe issues (https://solidity.readthedocs.io/en/latest/bugs.html) - VerbatimInvalidDeduplication - FullInlinerNonExpressionSplitArgumentEvaluationOrder - MissingSideEffectsOnSelectorAccess - StorageWriteRemovalBeforeConditionalTermination - AbiReencodingHeadOverflowWithStaticArrayCleanup - DirtyBytesArrayToStorage - InlineAssemblyMemorySideEffects - DataLocationChangeInInternalOverride - NestedCalldataArrayAbiReencodingSizeValidation. It is used by: - ^0.8.13 - ^0.8.13 - ^0.8.13 - ^0.8.13 - ^0.8.13 - ^0.8.13 - ^0.8.13 - ^0.8.13 - ^0.8.13 - ^0.8.13 - ^0.8.13
src/OptionsToken.sol#L2
Impact: Informational Confidence: High
- ID-9 Parameter BaseExercise.setFees(address[],uint256[])._feeRecipients is not in mixedCase
src/exercise/BaseExercise.sol#L55
- ID-10 Parameter BaseExercise.setFees(address[],uint256[])._feeBPS is not in mixedCase
src/exercise/BaseExercise.sol#L55
Frontend shall allow to go through 3 different scenarios:
- Pay PaymentTokens to redeem UnderlyingTokens from OptionsTokens
- Zap OptionsTokens into the UnderlyingTokens
- Claim not exercised UnderlyingTokens (due to lack of funds in exercise contract) -> this is probably optional frontend feature
- Standard ERC20 approve action on PaymentToken
- Note:
getPaymentAmount(uint256 amount)
interface may be usefull to get correct amount of PaymentToken needed.
- Note:
- Exercise optionsToken with following parameters:
- amount of options tokens to spend (defined by user)
- recipient of the UnderlyingTokens transferred during exercise (user address)
- option of the exercise -> it is DiscountExercise contract address
- encoded params:
- maxPaymentAmount - calculated maximal payment amount (amount * price * multiplier). Price can be get from oracle contract using interface
getPrice()
. Multiplier can be get from DiscountExercise contract using interfacemultiplier()
- deadline - current block timestamp
- isInstantExit - determines whether it is redeem (false) or zap (true) action. Shall be hardcoded to false.
- maxPaymentAmount - calculated maximal payment amount (amount * price * multiplier). Price can be get from oracle contract using interface
- Events emitted:
Exercise(address indexed sender, address indexed recipient, uint256 amount, address data0, uint256 data1, uint256 data2)
- from OptionsToken contract. data0, data1, data2 - are not used in this case.Exercised(from, recipient, underlyingAmount, paymentAmount)
from DiscountExercise contract
- Note: Amount of UnderlyingTokens to receive from redeeming is the same amount that is specified for optionsTokens.
- Call
exercise(uint256 amount, address recipient, address option, bytes calldata params)
from optionsToken contract with following parameters:- amount of options tokens to spend (defined by user)
- recipient of the UnderlyingTokens transferred during exercise (user address)
- option of the exercise -> it is DiscountExercise contract address
- encoded params:
- maxPaymentAmount - calculated maximal payment amount (amount * price * multiplier). Price can be get from oracle contract using interface
getPrice()
. Multiplier can be get from DiscountExercise contract using interfacemultiplier()
- deadline - current block timestamp
- isInstantExit - determines whether it is redeem (false) or zap (true) action. Shall be hardcoded to true.
- maxPaymentAmount - calculated maximal payment amount (amount * price * multiplier). Price can be get from oracle contract using interface
- Events emitted:
Exercise(address indexed sender, address indexed recipient, uint256 amount, address data0, uint256 data1, uint256 data2)
- from OptionsToken contract. data0, data1, data2 - are not used in this case.Exercised(from, recipient, underlyingAmount, paymentAmount)
from DiscountExercise contract
- Note: Amount of UnderlyingTokens to receive from zapping is: amountOfOTokens * (1 - multiplier) * (1 - instantExitFee). Everything is denominated in BPS (10 000). InstantExitFee can be get by calling
instantExitFee()
. - Note:
getPaymentAmount(uint256 amount)
interface may be usefull to get correct amount of PaymentToken needed. - Note: Usually swap action happens here so standard events for swapping are here, but contract handles all actions like approvals etc
- Call
claim(address to)
with address of recipient of the UnderlyingTokens transferred during claiming process Claimed(amount)
event is emitted
Main change between version 1.0.0 deployed on Harbor is the zap feature which requires additional variable in params
argument (isInstantExit
) of the exercise(uint256 amount, address recipient, address option, bytes calldata params)
interface.
Now frontend shall allow to obtain UnderlyingTokens in two ways (redeem and zap).
Token used to pay for exercising options token. Ironclad -> MODE, Harbor -> WBNB
Token which can be obtained from exercising. Ironclad -> ICL, Harbor -> HBR