Skip to content

Commit 4bb8242

Browse files
Add chainlink keepers interface for rebalancing (#92)
1 parent 712675c commit 4bb8242

File tree

12 files changed

+701
-12
lines changed

12 files changed

+701
-12
lines changed
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/*
2+
Copyright 2022 Index Cooperative.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
pragma solidity 0.6.10;
18+
pragma experimental ABIEncoderV2;
19+
20+
interface IFlexibleLeverageStrategyExtension {
21+
enum ShouldRebalance {
22+
NONE, // Indicates no rebalance action can be taken
23+
REBALANCE, // Indicates rebalance() function can be successfully called
24+
ITERATE_REBALANCE, // Indicates iterateRebalance() function can be successfully called
25+
RIPCORD // Indicates ripcord() function can be successfully called
26+
}
27+
28+
function shouldRebalance() external view returns (string[] memory, ShouldRebalance[] memory);
29+
function rebalance(string memory _exchangeName) external;
30+
function iterateRebalance(string memory _exchageName) external;
31+
function ripcord(string memory _exchangeName) external;
32+
function shouldRebalanceWithBounds(uint256 _customMinLeverageRatio, uint256 _customMaxLeverageRatio) external view returns (string[] memory, ShouldRebalance[] memory);
33+
}
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
/*
2+
Copyright 2022 Index Cooperative.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
pragma solidity 0.6.10;
18+
pragma experimental ABIEncoderV2;
19+
20+
import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
21+
import { Address } from "@openzeppelin/contracts/utils/Address.sol";
22+
import { KeeperCompatibleInterface } from "@chainlink/contracts/src/v0.6/KeeperCompatible.sol";
23+
import { IFlexibleLeverageStrategyExtension } from "../interfaces/IFlexibleLeverageStrategyExtension.sol";
24+
25+
/**
26+
* @title RebalanceKeeper
27+
* @author Index Cooperative
28+
*
29+
* Chainlink Keeper which automatically rebalances FLI SetTokens.
30+
*/
31+
contract FliRebalanceKeeper is Ownable, KeeperCompatibleInterface {
32+
33+
using Address for address;
34+
35+
/* ============ Structs ============ */
36+
37+
struct LeverageSettings {
38+
uint256 customMinLeverageRatio; // The minimum leverage ratio
39+
uint256 customMaxLeverageRatio; // The maximum leverage ratio
40+
}
41+
42+
/* ============ Modifiers ============ */
43+
44+
modifier onlyRegistry() {
45+
require(msg.sender == registryAddress, "Only registry address can call this function");
46+
_;
47+
}
48+
49+
/* ============ State Variables ============ */
50+
51+
IFlexibleLeverageStrategyExtension public fliExtension; // Address of the fli extension contract
52+
address public registryAddress; // Address of the chainlink keeper registry
53+
uint256 public exchangeIndex; // The index of the exchange to use
54+
LeverageSettings public leverageSettings; // The leverage settings to check whether should rebalance
55+
56+
/* ============ Constructor ============ */
57+
58+
constructor(
59+
IFlexibleLeverageStrategyExtension _fliExtension,
60+
address _registryAddress,
61+
uint256 _exchangeIndex,
62+
LeverageSettings memory _leverageSettings
63+
) public {
64+
fliExtension = _fliExtension;
65+
registryAddress = _registryAddress;
66+
exchangeIndex = _exchangeIndex;
67+
leverageSettings = _leverageSettings;
68+
}
69+
70+
/**
71+
* As checkUpkeep is not a view function, calling this function will actually consume gas.
72+
* As such if a keeper calls this function, it will always return true so that performUpkeep will be called.
73+
*/
74+
function checkUpkeep(bytes calldata /* checkData */) external override returns (bool, bytes memory) {
75+
(string[] memory exchangeNames, IFlexibleLeverageStrategyExtension.ShouldRebalance[] memory shouldRebalances) = fliExtension.shouldRebalanceWithBounds(
76+
leverageSettings.customMinLeverageRatio,
77+
leverageSettings.customMaxLeverageRatio
78+
);
79+
IFlexibleLeverageStrategyExtension.ShouldRebalance shouldRebalance = shouldRebalances[exchangeIndex];
80+
bytes memory performData = abi.encode(shouldRebalance, exchangeNames[exchangeIndex]);
81+
return (shouldRebalance != IFlexibleLeverageStrategyExtension.ShouldRebalance.NONE, performData);
82+
}
83+
84+
/**
85+
* performUpkeep checks that a rebalance is required. Otherwise the contract call will revert.
86+
*/
87+
function performUpkeep(bytes calldata performData) external override onlyRegistry {
88+
require(performData.length > 0, "Invalid performData");
89+
(IFlexibleLeverageStrategyExtension.ShouldRebalance shouldRebalance, string memory exchangeName) = abi.decode(
90+
performData,
91+
(IFlexibleLeverageStrategyExtension.ShouldRebalance, string)
92+
);
93+
if (shouldRebalance == IFlexibleLeverageStrategyExtension.ShouldRebalance.REBALANCE) {
94+
fliExtension.rebalance(exchangeName);
95+
return;
96+
} else if (shouldRebalance == IFlexibleLeverageStrategyExtension.ShouldRebalance.ITERATE_REBALANCE) {
97+
fliExtension.iterateRebalance(exchangeName);
98+
return;
99+
} else if (shouldRebalance == IFlexibleLeverageStrategyExtension.ShouldRebalance.RIPCORD) {
100+
fliExtension.ripcord(exchangeName);
101+
return;
102+
}
103+
revert("FliRebalanceKeeper: invalid shouldRebalance or no rebalance required");
104+
}
105+
function setExchangeIndex(uint256 _exchangeIndex) external onlyOwner {
106+
exchangeIndex = _exchangeIndex;
107+
}
108+
109+
function setLeverageSettings(LeverageSettings memory _leverageSettings) external onlyOwner {
110+
leverageSettings = _leverageSettings;
111+
}
112+
}
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
// SPDX-License-Identifier: Apache License, Version 2.0
2+
pragma solidity 0.6.10;
3+
pragma experimental ABIEncoderV2;
4+
5+
6+
import { IBaseManager } from "../interfaces/IBaseManager.sol";
7+
import { BaseExtension } from "../lib/BaseExtension.sol";
8+
9+
contract FlexibleLeverageStrategyExtensionMock is BaseExtension {
10+
11+
/* ============ Enums ============ */
12+
13+
enum ShouldRebalance {
14+
NONE, // Indicates no rebalance action can be taken
15+
REBALANCE, // Indicates rebalance() function can be successfully called
16+
ITERATE_REBALANCE, // Indicates iterateRebalance() function can be successfully called
17+
RIPCORD // Indicates ripcord() function can be successfully called
18+
}
19+
20+
/* ============ State Variables ============ */
21+
uint256 public currentLeverageRatio; // The current leverage ratio
22+
string public exchangeName; // The exchange name
23+
24+
/* ============ Events ============ */
25+
event RebalanceEvent(ShouldRebalance _rebalance); // A rebalance occurred
26+
27+
/**
28+
* Instantiate addresses, methodology parameters, execution parameters, and incentive parameters.
29+
*
30+
* @param _manager Address of IBaseManager contract
31+
*/
32+
constructor(IBaseManager _manager, uint256 _currentLeverageRatio, string memory _exchangeName) public BaseExtension(_manager) {
33+
currentLeverageRatio = _currentLeverageRatio;
34+
exchangeName = _exchangeName;
35+
}
36+
37+
/**
38+
* Helper that checks if conditions are met for rebalance or ripcord. Returns an enum with 0 = no rebalance, 1 = call rebalance(), 2 = call iterateRebalance()
39+
* 3 = call ripcord()
40+
*
41+
* @return (string[] memory, ShouldRebalance[] memory) List of exchange names and a list of enums representing whether that exchange should rebalance
42+
*/
43+
function shouldRebalance() external view returns (string[] memory, ShouldRebalance[] memory) {
44+
return _shouldRebalance();
45+
}
46+
47+
function shouldRebalanceWithBounds(uint256 _customMinLeverageRatio, uint256 _customMaxLeverageRatio) external view returns (string[] memory, ShouldRebalance[] memory) {
48+
return _shouldRebalance();
49+
}
50+
51+
/**
52+
* @param _exchangeName the exchange used for trading
53+
*/
54+
function rebalance(string memory _exchangeName) external onlyAllowedCaller(msg.sender) {
55+
require(keccak256(abi.encodePacked(_exchangeName)) == keccak256(abi.encodePacked(exchangeName)), "Exchange names are not equal");
56+
emit RebalanceEvent(ShouldRebalance.REBALANCE);
57+
}
58+
59+
/**
60+
* @param _exchangeName the exchange used for trading
61+
*/
62+
function iterateRebalance(string memory _exchangeName) external onlyAllowedCaller(msg.sender) {
63+
require(keccak256(abi.encodePacked(_exchangeName)) == keccak256(abi.encodePacked(exchangeName)), "Exchange names are not equal");
64+
emit RebalanceEvent(ShouldRebalance.ITERATE_REBALANCE);
65+
}
66+
67+
/**
68+
*
69+
* @param _exchangeName the exchange used for trading
70+
*/
71+
function ripcord(string memory _exchangeName) external {
72+
require(keccak256(abi.encodePacked(_exchangeName)) == keccak256(abi.encodePacked(exchangeName)), "Exchange names are not equal");
73+
emit RebalanceEvent(ShouldRebalance.RIPCORD);
74+
}
75+
76+
function _shouldRebalance() private view returns (string[] memory, ShouldRebalance[] memory) {
77+
ShouldRebalance rebalanceStrategy = ShouldRebalance.NONE;
78+
if (currentLeverageRatio == 1) {
79+
rebalanceStrategy = ShouldRebalance.REBALANCE;
80+
} else if (currentLeverageRatio == 2) {
81+
rebalanceStrategy = ShouldRebalance.ITERATE_REBALANCE;
82+
} else if (currentLeverageRatio == 3) {
83+
rebalanceStrategy = ShouldRebalance.RIPCORD;
84+
}
85+
string[] memory exchangeNames = new string[](1);
86+
exchangeNames[0] = exchangeName;
87+
88+
ShouldRebalance[] memory shouldRebalances = new ShouldRebalance[](1);
89+
shouldRebalances[0] = rebalanceStrategy;
90+
return (exchangeNames, shouldRebalances);
91+
}
92+
}

hardhat.config.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ const config: HardhatUserConfig = {
7878
};
7979

8080
function getHardhatPrivateKeys() {
81-
return privateKeys.map((key) => {
81+
return privateKeys.map(key => {
8282
const TEN_MILLION_ETH = "10000000000000000000000000";
8383
return {
8484
privateKey: key,

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@
9090
"typescript": "^3.9.3"
9191
},
9292
"dependencies": {
93+
"@chainlink/contracts": "^0.3.1",
9394
"@ethersproject/abstract-signer": "5.0.9",
9495
"@ethersproject/bignumber": "5.0.12",
9596
"@ethersproject/providers": "5.0.17",

0 commit comments

Comments
 (0)