-
Notifications
You must be signed in to change notification settings - Fork 54
Add chainlink keepers interface for rebalancing #92
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
ncitron
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good start! Left a few comments.
| } | ||
|
|
||
| function getRebalanceCalldata() private returns (bytes memory) { | ||
| bytes memory shouldRebalanceCalldata = abi.encodeWithSignature("shouldRebalance()"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we create a new interface called for the leverage extension so we don't need to manually encode with strings here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
| bytes memory shouldRebalanceResponse = Address.functionCall(address(fliExtension), shouldRebalanceCalldata, "Failed to execute shouldRebalance()"); | ||
| (string[] memory exchangeNames, uint256[] memory shouldRebalances) = abi.decode(shouldRebalanceResponse, (string[], uint256[])); | ||
|
|
||
| uint256 shouldRebalance = shouldRebalances[0]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We might want to be able to select what exchange to use. Maybe we can store what index to use somewhere?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't the fliExtension contract determine which exchange to use? Rather than this contract? Or are you suggesting to have a whitelist of exchanges that this contract can call?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
At the moment we allow the caller of the FLI extension to determine what exchange to use (of a list of whitelisted exchanges). This is because determining the optimal exchange may be difficult or gas extensive to do on-chain. Lately we have just been hardcoding our bots to use the exchange we want, with a backup exchange already whitelisted in case we need to make changes quickly. For this contract since we have a bit less trust in the chainlink keepers, we probably want to hard code which exchange to use.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmmm - then we keep a list of allowable addresses that we can contact? What's the exchangeName that comes back from the fliExtension call - I'm assuming it's an address?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The exchange name is actually just a string which corresponds to the adapter that is used to do the trade part of the rebalance. I think its probably best to hardcode the index of the array that we want to use (and allow the owner of the contract to change it).
|
|
||
| uint256 shouldRebalance = shouldRebalances[0]; | ||
| if (shouldRebalance == 1) { | ||
| return abi.encodeWithSignature("rebalance(string)", exchangeNames[0]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we had created an interface like in my previous comments, we could replace this with
abi.encodeWithSelector(fliExtension.rebalance.selector, exchangeNames[0])Which is a bit cleaner
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
| * As checkUpkeep is not a view function, calling this function will actually consume gas. | ||
| * As such if a keeper calls this function, it will always return true so that performUpkeep will be called. | ||
| */ | ||
| function checkUpkeep(bytes calldata /* checkData */) external override onlyRegistry returns (bool, bytes memory) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Even though checkUpkeep isn't a view function, it is called as if it were by the keepers. This is because you can call a non-view method and it runs as if it were executed, except state changes are not reflected. We can then just have this function do the same thing as getRebalanceCalldata. This will save us on gas since it can be run off-chain.
Finally I don't think we need to make this onlyRegistry since it won't perform any gatekeeped actions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done - removed it
| @@ -0,0 +1,79 @@ | |||
| /* | |||
| Copyright 2021 Index Cooperative. | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
2022
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
1250537 to
26b227b
Compare
|
ready for re-review @ncitron added the exchange index parameter |
ncitron
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking pretty good. I just noticed one major change related to how we want to pass data between checkUpkeep and performUpkeep. Also we need to add support for allowing smart contracts to make rebalance calls on AaveLeverageStrategyExtension.
Everything else is looking pretty good. Great job on the tests.
| address public registryAddress; // Address of the chainlink keeper registry | ||
| uint256 public exchangeIndex; // The index of the exchange to use | ||
|
|
||
| /* ============ Constructor ============ */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: newline
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
|
|
||
| function getRebalanceCalldata() private returns (bytes memory) { | ||
| bytes memory shouldRebalanceCalldata = abi.encodeWithSelector(fliExtension.shouldRebalance.selector); | ||
| bytes memory shouldRebalanceResponse = Address.functionCall(address(fliExtension), shouldRebalanceCalldata, "Failed to execute shouldRebalance()"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We don't need to manually create this calldata. fliExtension.shouldRebalance() will work.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also can we use shouldRebalanceWithBounds instead and allow bounds to be stored in a storage struct with a function for the owner to update it? The reason this function is better is it allows us to prevent reverts. For example, if we are set to rebalance at 2.2x leverage, we might fire off a tx, just to watch the leverage ratio decrease to 2.199x by the time it is included in a block. Since this is below the max leverge. The tx will revert. On polygon, this isn't a big deal, but on mainnet these reverts add up gas-wise. If we do it with shouldRebalanceWithBounds we can set the upper bound to 2.25x, meaning it wont revert if the leverage ration decreases slightly between the time we broadcast the tx and when it is actually included.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added a LeverageSettings struct with options for bounds and added it as part of the constructor
| */ | ||
| function performUpkeep(bytes calldata performData) external override onlyRegistry { | ||
| require(performData.length > 0, "Invalid performData"); | ||
| Address.functionCall(address(fliExtension), performData); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This line scares me a bit since it allows a keeper to perform any call on fliExtension. While I don't think this is an immediate security concern here, I still don't love it since this contract has extra privileges with fliExtension. Can we instead have checkUpkeep return the shouldRebalance enum calculated on line 76. Then we can ensure that this contract can only ever do one of the three rebalance functions instead of any arbitrary call.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done - made the checkUpkeep return an encoded shouldRebalance enum and exchangeName. This is then decoded in performUpkeep before sending a transaction off to the fliExtension
| constructor(IFlexibleLeverageStrategyExtension _fliExtension, address _registryAddress) public { | ||
| fliExtension = _fliExtension; | ||
| registryAddress = _registryAddress; | ||
| exchangeIndex = 0; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we allow exchangeIndex to be set in the constructor?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yep done
ncitron
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This all looks great. We still need to update AaveLeverageStrategyExtension to allow non-EOA accounts to call it. I think the best way to do this is to update https://github.com/IndexCoop/index-coop-smart-contracts/blob/master/contracts/lib/BaseExtension.sol#L59 to check some stored flag that determines whether we want to skip the EOA check. Similar to how onlyAllowCaller works here https://github.com/IndexCoop/index-coop-smart-contracts/blob/master/contracts/lib/BaseExtension.sol#L145
Added a boolean to allow us to bypass the onlyEOA check. Although from the documentation, the check is there to stop sandwich/flash loan attacks. Wouldn't flipping the flag open us up to these type of attacks? Do we need to store an allowlist of contract addresses that are allowed to call this function? |
2cc9230 to
3fc0268
Compare
|
🎉 This PR is included in version 0.4.0 🎉 The release is available on: Your semantic-release bot 📦🚀 |
First implementation of FliRebalanceKeeper.
For the FlexibleLeverageExtension, we need to remove the nonEOA and probably add nonRetrancy guard or something.