-
Notifications
You must be signed in to change notification settings - Fork 0
/
ThecosomataETH.sol
166 lines (131 loc) · 5.3 KB
/
ThecosomataETH.sol
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
// SPDX-License-Identifier: AGPL-3.0-or-later
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
interface IBTRFLY is IERC20 {
function burn(uint256 amount) external;
function decimals() external view returns (uint8);
}
interface IRedactedTreasury {
function manage(address _token, uint256 _amount) external;
}
interface ICurveCryptoPool {
function add_liquidity(uint256[2] calldata amounts, uint256 min_mint_amount)
external
payable;
function calc_token_amount(uint256[2] calldata amounts)
external
view
returns (uint256);
// Would be replaced by Chainlink based oracle
function price_oracle() external view returns (uint256);
function token() external view returns (address);
}
contract ThecosomataETH is Ownable {
address public immutable BTRFLY;
address public immutable WETH;
address public immutable CURVEPOOL;
address public immutable TREASURY;
uint256 private immutable _btrflyDecimals;
uint256 private immutable _ethDecimals;
uint256 public slippage = 5; // in 1000th
event AddLiquidity(
uint256 ethLiquidity,
uint256 btrflyLiquidity,
uint256 btrflyBurned
);
constructor(
address _BTRFLY,
address _WETH,
address _TREASURY,
address _CURVEPOOL
) {
require(_BTRFLY != address(0), "Invalid BTRFLY address");
BTRFLY = _BTRFLY;
require(_WETH != address(0), "Invalid WETH address");
WETH = _WETH;
require(_CURVEPOOL != address(0), "Invalid POOL address");
CURVEPOOL = _CURVEPOOL;
require(_TREASURY != address(0), "Invalid TREASURY address");
TREASURY = _TREASURY;
IERC20(_BTRFLY).approve(_CURVEPOOL, 2**256 - 1);
IERC20(_WETH).approve(_CURVEPOOL, 2**256 - 1);
_btrflyDecimals = IBTRFLY(_BTRFLY).decimals();
_ethDecimals = IBTRFLY(_WETH).decimals();
}
// Update slippage percentage (in 1000th)
function setSlippage(uint256 _slippage) external onlyOwner {
// Make sure the slippage is less than 10%
require(_slippage < 100, "Slippage too high");
slippage = _slippage;
}
// Return whether we should perform an upkeep based on the contract's BTRFLY balance
function checkUpkeep()
public
view
returns (bool upkeepNeeded)
{
if (IBTRFLY(BTRFLY).balanceOf(address(this)) > 0) {
return true;
}
}
// Fetch the equivalent value of either specified BTRFLY/ETH amount
function calculateAmountRequiredForLP(uint256 amount, bool isBTRFLY)
internal
view
returns (uint256)
{
// Default price is based off "1 BTRFLY = X ETH", in 10^18 format
uint256 priceOracle = ICurveCryptoPool(CURVEPOOL).price_oracle();
if (isBTRFLY) {
return (((amount * priceOracle) / (10**18)) * (10**_ethDecimals)) /
(10**_btrflyDecimals);
}
return
(((amount * (10**18)) / priceOracle) *
(10**_btrflyDecimals)) / (10**_ethDecimals);
}
// Calculate the min. LP token amount (after slippage) and attempt to add liquidity
function addLiquidity(uint256 ethAmount, uint256 btrflyAmount) internal {
uint256[2] memory amounts = [ethAmount, btrflyAmount];
uint256 expectedAmount = ICurveCryptoPool(CURVEPOOL).calc_token_amount(
amounts
);
uint256 minAmount = expectedAmount - ((expectedAmount * slippage) / 1000);
ICurveCryptoPool(CURVEPOOL).add_liquidity(amounts, minAmount);
}
// Perform the actual upkeep flow
function performUpkeep() external onlyOwner {
require(checkUpkeep(), "Invalid upkeep state");
uint256 btrfly = IBTRFLY(BTRFLY).balanceOf(address(this));
uint256 ethAmount = calculateAmountRequiredForLP(btrfly, true);
uint256 ethCap = IERC20(WETH).balanceOf(TREASURY);
uint256 ethLiquidity = ethCap > ethAmount ? ethAmount : ethCap;
// Use BTRFLY balance if remaining capacity is enough, otherwise, calculate BTRFLY amount
uint256 btrflyLiquidity = ethCap > ethAmount
? btrfly
: calculateAmountRequiredForLP(ethLiquidity, false);
IRedactedTreasury(TREASURY).manage(WETH, ethLiquidity);
// Only complete upkeep only on sufficient amounts
require(ethLiquidity > 0 && btrflyLiquidity > 0, "Insufficient amounts");
addLiquidity(ethLiquidity, btrflyLiquidity);
// Transfer out the pool token to treasury
address token = ICurveCryptoPool(CURVEPOOL).token();
uint256 tokenBalance = IERC20(token).balanceOf(address(this));
IERC20(token).transfer(TREASURY, tokenBalance);
uint256 unusedBTRFLY = IBTRFLY(BTRFLY).balanceOf(address(this));
if (unusedBTRFLY > 0) {
IBTRFLY(BTRFLY).burn(unusedBTRFLY);
}
emit AddLiquidity(ethLiquidity, btrflyLiquidity, unusedBTRFLY);
}
// Withdraw arbitrary token and amount owned by the contract
function withdraw(
address token,
uint256 amount,
address recipient
) external onlyOwner {
require(recipient != address(0), "Invalid recipient");
IERC20(token).transfer(recipient, amount);
}
}