Skip to content

Conversation

@FlattestWhite
Copy link
Contributor

@FlattestWhite FlattestWhite commented Feb 2, 2022

First implementation of FliRebalanceKeeper.

For the FlexibleLeverageExtension, we need to remove the nonEOA and probably add nonRetrancy guard or something.

@FlattestWhite FlattestWhite requested a review from ncitron February 9, 2022 05:13
Copy link
Contributor

@ncitron ncitron left a 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()");
Copy link
Contributor

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?

Copy link
Contributor Author

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];
Copy link
Contributor

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?

Copy link
Contributor Author

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?

Copy link
Contributor

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.

Copy link
Contributor Author

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?

Copy link
Contributor

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]);
Copy link
Contributor

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

Copy link
Contributor Author

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) {
Copy link
Contributor

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.

Copy link
Contributor Author

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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2022

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

@FlattestWhite FlattestWhite force-pushed the richard-chainlink-keepers-1 branch from 1250537 to 26b227b Compare February 10, 2022 19:31
@FlattestWhite
Copy link
Contributor Author

ready for re-review @ncitron added the exchange index parameter

Copy link
Contributor

@ncitron ncitron left a 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 ============ */
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: newline

Copy link
Contributor Author

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()");
Copy link
Contributor

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.

Copy link
Contributor

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.

Copy link
Contributor Author

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);
Copy link
Contributor

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.

Copy link
Contributor Author

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;
Copy link
Contributor

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?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yep done

Copy link
Contributor

@ncitron ncitron left a 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

@FlattestWhite
Copy link
Contributor Author

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?

@FlattestWhite FlattestWhite force-pushed the richard-chainlink-keepers-1 branch from 2cc9230 to 3fc0268 Compare March 18, 2022 23:58
@FlattestWhite FlattestWhite merged commit 4bb8242 into master Mar 19, 2022
@FlattestWhite FlattestWhite deleted the richard-chainlink-keepers-1 branch March 19, 2022 00:15
@FlattestWhite
Copy link
Contributor Author

🎉 This PR is included in version 0.4.0 🎉

The release is available on:

Your semantic-release bot 📦🚀

ace3 pushed a commit to ace3/index-coop-smart-contracts that referenced this pull request Feb 2, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants