Skip to content

Commit 81fe0a9

Browse files
authored
Fix bug in convex yield collection (#842)
* Collect convex yield. * Use CRV as the official reward token * Unit tests test harvesting coins * Remove debugging * Better comment * More comment tweaks
1 parent 892d60a commit 81fe0a9

9 files changed

Lines changed: 153 additions & 61 deletions

File tree

contracts/contracts/mocks/curve/MockBooster.sol

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,18 +23,24 @@ contract MockBooster {
2323
}
2424

2525
address public minter; // this is CVx for the booster on live
26-
address public crv; // this is crv token for crv rewards
26+
address public crv; // Curve rewards token
27+
address public cvx; // Convex rewards token
2728
mapping(uint256 => PoolInfo) public poolInfo;
2829

29-
constructor(address _rewardsMinter, address _crv) public {
30+
constructor(
31+
address _rewardsMinter,
32+
address _crv,
33+
address _cvx
34+
) public {
3035
minter = _rewardsMinter;
3136
crv = _crv;
37+
cvx = _cvx;
3238
}
3339

3440
function setPool(uint256 pid, address _lpToken) external returns (bool) {
3541
address token = address(new MockDepositToken());
3642
address rewards = address(
37-
new MockRewardPool(pid, token, crv, address(this))
43+
new MockRewardPool(pid, token, crv, cvx, address(this))
3844
);
3945

4046
poolInfo[pid] = PoolInfo({

contracts/contracts/mocks/curve/MockRewardPool.sol

Lines changed: 21 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,8 @@ contract MockRewardPool {
3838

3939
uint256 public pid;
4040
address public stakingToken;
41-
address public rewardToken;
41+
address public rewardTokenA;
42+
address public rewardTokenB;
4243
address public operator;
4344

4445
uint256 private _totalSupply;
@@ -47,14 +48,16 @@ contract MockRewardPool {
4748
mapping(address => uint256) public rewards;
4849

4950
constructor(
50-
uint256 pid_,
51-
address stakingToken_,
52-
address rewardToken_,
53-
address operator_
51+
uint256 _pid,
52+
address _stakingToken,
53+
address _rewardTokenA,
54+
address _rewardTokenB,
55+
address _operator
5456
) public {
55-
pid = pid_;
56-
stakingToken = stakingToken_;
57-
rewardToken = rewardToken_;
57+
pid = _pid;
58+
stakingToken = _stakingToken;
59+
rewardTokenA = _rewardTokenA;
60+
rewardTokenB = _rewardTokenB;
5861
}
5962

6063
function totalSupply() public view returns (uint256) {
@@ -65,12 +68,6 @@ contract MockRewardPool {
6568
return _balances[account];
6669
}
6770

68-
// mock function to set the rewards per account
69-
function earnRewards(address account, uint256 amount) external {
70-
IMintableERC20(rewardToken).mint(amount);
71-
rewards[account] += amount;
72-
}
73-
7471
function stakeFor(address _for, uint256 _amount) public returns (bool) {
7572
require(_amount > 0, "RewardPool : Cannot stake 0");
7673

@@ -113,12 +110,16 @@ contract MockRewardPool {
113110
public
114111
returns (bool)
115112
{
116-
uint256 reward = rewards[_account];
117-
if (reward > 0) {
118-
rewards[_account] = 0;
119-
IERC20(rewardToken).safeTransfer(_account, reward);
120-
IDeposit(operator).rewardClaimed(pid, _account, reward);
121-
}
113+
IMintableERC20(rewardTokenA).mint(2 * 1e18);
114+
IERC20(rewardTokenA).transfer(_account, 2 * 1e18);
115+
116+
IMintableERC20(rewardTokenB).mint(3 * 1e18);
117+
IERC20(rewardTokenB).transfer(_account, 3 * 1e18);
118+
122119
return true;
123120
}
121+
122+
function getReward() public returns (bool) {
123+
getReward(msg.sender, true);
124+
}
124125
}

contracts/contracts/strategies/BaseCurveStrategy.sol

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,11 @@ abstract contract BaseCurveStrategy is InitializableAbstractStrategy {
147147
(, uint256 gaugePTokens, uint256 totalPTokens) = _getTotalPTokens();
148148
_lpWithdraw(gaugePTokens);
149149
// Withdraws are proportional to assets held by 3Pool
150-
uint256[3] memory minWithdrawAmounts = [uint256(0), uint256(0), uint256(0)];
150+
uint256[3] memory minWithdrawAmounts = [
151+
uint256(0),
152+
uint256(0),
153+
uint256(0)
154+
];
151155
// Remove liquidity
152156
ICurvePool threePool = ICurvePool(platformAddress);
153157
threePool.remove_liquidity(totalPTokens, minWithdrawAmounts);

contracts/contracts/strategies/ConvexStrategy.sol

Lines changed: 38 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,14 @@ contract ConvexStrategy is BaseCurveStrategy {
2424
uint256 amount
2525
);
2626

27+
event CvxRewardTokenAddressUpdated(
28+
address _oldAddress,
29+
address _newAddress
30+
);
31+
2732
address internal cvxDepositorAddress;
2833
address internal cvxRewardStakerAddress;
29-
address internal crvRewardTokenAddress;
34+
address internal cvxRewardTokenAddress;
3035
uint256 internal cvxDepositorPTokenId;
3136

3237
/**
@@ -35,21 +40,21 @@ contract ConvexStrategy is BaseCurveStrategy {
3540
* well within that abstraction.
3641
* @param _platformAddress Address of the Curve 3pool
3742
* @param _vaultAddress Address of the vault
38-
* @param _rewardTokenAddress Address of CRVX
39-
* @param _crvRewardTokenAddress Address of CRV *yes we get both*
43+
* @param _rewardTokenAddress Address of CRV
44+
* @param _cvxRewardTokenAddress Address of CVX *yes we get both*
4045
* @param _assets Addresses of supported assets. MUST be passed in the same
4146
* order as returned by coins on the pool contract, i.e.
4247
* DAI, USDC, USDT
4348
* @param _pTokens Platform Token corresponding addresses
4449
* @param _cvxDepositorAddress Address of the Convex depositor(AKA booster) for this pool
45-
* @param _cvxRewardStakerAddress Address of the CRVX rewards staker
50+
* @param _cvxRewardStakerAddress Address of the CVX rewards staker
4651
* @param _cvxDepositorPTokenId Pid of the pool referred to by Depositor and staker
4752
*/
4853
function initialize(
4954
address _platformAddress, // 3Pool address
5055
address _vaultAddress,
51-
address _rewardTokenAddress, // CRVX
52-
address _crvRewardTokenAddress,
56+
address _rewardTokenAddress, // CRV
57+
address _cvxRewardTokenAddress, // CVX
5358
address[] calldata _assets,
5459
address[] calldata _pTokens,
5560
address _cvxDepositorAddress,
@@ -62,7 +67,7 @@ contract ConvexStrategy is BaseCurveStrategy {
6267
cvxDepositorAddress = _cvxDepositorAddress;
6368
cvxRewardStakerAddress = _cvxRewardStakerAddress;
6469
cvxDepositorPTokenId = _cvxDepositorPTokenId;
65-
crvRewardTokenAddress = _crvRewardTokenAddress;
70+
cvxRewardTokenAddress = _cvxRewardTokenAddress;
6671
pTokenAddress = _pTokens[0];
6772
super._initialize(
6873
_platformAddress,
@@ -74,6 +79,21 @@ contract ConvexStrategy is BaseCurveStrategy {
7479
_approveBase();
7580
}
7681

82+
/**
83+
* @dev Set the CVX reward token address.
84+
* @param _cvxRewardTokenAddress Address of the reward token
85+
*/
86+
function setCvxRewardTokenAddress(address _cvxRewardTokenAddress)
87+
external
88+
onlyGovernor
89+
{
90+
emit CvxRewardTokenAddressUpdated(
91+
cvxRewardTokenAddress,
92+
_cvxRewardTokenAddress
93+
);
94+
cvxRewardTokenAddress = _cvxRewardTokenAddress;
95+
}
96+
7797
function _lpDepositAll() internal override {
7898
IERC20 pToken = IERC20(pTokenAddress);
7999
// Deposit with staking
@@ -129,19 +149,20 @@ contract ConvexStrategy is BaseCurveStrategy {
129149
}
130150

131151
/**
132-
* @dev Collect accumulated CRV and send to Vault.
152+
* @dev Collect accumulated CRV and CVX and send to Vault.
133153
*/
134154
function collectRewardToken() external override onlyVault nonReentrant {
135-
// Collect is done automatically with withdrawAndUnwrap
136-
// Send CVX
137-
IERC20 crvxToken = IERC20(rewardTokenAddress);
138-
uint256 balance = crvxToken.balanceOf(address(this));
139-
emit RewardTokenCollected(vaultAddress, rewardTokenAddress, balance);
140-
crvxToken.safeTransfer(vaultAddress, balance);
155+
// Collect CRV and CVX
156+
IRewardStaking(cvxRewardStakerAddress).getReward();
141157
// Send CRV
142-
IERC20 crvToken = IERC20(crvRewardTokenAddress);
143-
balance = crvToken.balanceOf(address(this));
144-
emit RewardTokenCollected(vaultAddress, crvRewardTokenAddress, balance);
158+
IERC20 crvToken = IERC20(rewardTokenAddress);
159+
uint256 balance = crvToken.balanceOf(address(this));
160+
emit RewardTokenCollected(vaultAddress, rewardTokenAddress, balance);
145161
crvToken.safeTransfer(vaultAddress, balance);
162+
// Send CVX
163+
IERC20 cvxToken = IERC20(cvxRewardTokenAddress);
164+
balance = cvxToken.balanceOf(address(this));
165+
emit RewardTokenCollected(vaultAddress, cvxRewardTokenAddress, balance);
166+
cvxToken.safeTransfer(vaultAddress, balance);
146167
}
147168
}

contracts/deploy/000_mock.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -230,7 +230,7 @@ const deployMocks = async ({ getNamedAccounts, deployments }) => {
230230

231231
await deploy("MockBooster", {
232232
from: deployerAddr,
233-
args: [mockCVX.address, mockCRV.address],
233+
args: [mockCVX.address, mockCRV.address, mockCVX.address],
234234
});
235235
const mockBooster = await ethers.getContract("MockBooster");
236236
await mockBooster.setPool(threeCRVPid, threePoolToken.address);
@@ -241,6 +241,7 @@ const deployMocks = async ({ getNamedAccounts, deployments }) => {
241241
threeCRVPid,
242242
threePoolToken.address,
243243
mockCRV.address,
244+
mockCVX.address,
244245
mockCRV.address,
245246
],
246247
});

contracts/deploy/001_core.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -264,8 +264,8 @@ const deployConvexStrategy = async () => {
264264
](
265265
assetAddresses.ThreePool,
266266
cVaultProxy.address,
267-
assetAddresses.CVX,
268267
assetAddresses.CRV,
268+
assetAddresses.CVX,
269269
[assetAddresses.DAI, assetAddresses.USDC, assetAddresses.USDT],
270270
[
271271
assetAddresses.ThreePoolToken,
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
const { deploymentWithProposal } = require("../utils/deploy");
2+
3+
module.exports = deploymentWithProposal(
4+
{ deployName: "032_convex_rewards", forceDeploy: true },
5+
async ({
6+
assetAddresses,
7+
deployWithConfirmation,
8+
ethers,
9+
getTxOpts,
10+
withConfirmation,
11+
}) => {
12+
const { deployerAddr, governorAddr } = await getNamedAccounts();
13+
const sDeployer = await ethers.provider.getSigner(deployerAddr);
14+
15+
// Current contracts
16+
const cVaultProxy = await ethers.getContract("VaultProxy");
17+
const cVaultAdmin = await ethers.getContractAt(
18+
"VaultAdmin",
19+
cVaultProxy.address
20+
);
21+
22+
// Deployer Actions
23+
// ----------------
24+
25+
// 1. Deploy new implementation
26+
const dConvexStrategyImpl = await deployWithConfirmation(
27+
"ConvexStrategy",
28+
undefined,
29+
undefined,
30+
true // Disable storage slot checking. We are intentionaly renaming a slot.
31+
);
32+
const cConvexStrategyProxy = await ethers.getContract(
33+
"ConvexStrategyProxy"
34+
);
35+
console.log(cConvexStrategyProxy.address);
36+
const cConvexStrategy = await ethers.getContractAt(
37+
"ConvexStrategy",
38+
cConvexStrategyProxy.address
39+
);
40+
console.log("ConvexStrategyProxy ", await cConvexStrategyProxy.governor());
41+
42+
// Governance Actions
43+
// ----------------
44+
return {
45+
name: "Switch to new Convex implementation",
46+
actions: [
47+
// 1. Upgrade implementation
48+
{
49+
contract: cConvexStrategyProxy,
50+
signature: "upgradeTo(address)",
51+
args: [dConvexStrategyImpl.address],
52+
},
53+
// 2. Use CRV as main rewards token
54+
{
55+
contract: cConvexStrategy,
56+
signature: "setRewardTokenAddress(address)",
57+
args: [assetAddresses.CRV],
58+
},
59+
// 2. Use correct CVX token addresss
60+
{
61+
contract: cConvexStrategy,
62+
signature: "setCvxRewardTokenAddress(address)",
63+
args: [assetAddresses.CVX],
64+
},
65+
],
66+
};
67+
}
68+
);

contracts/test/strategies/convex.js

Lines changed: 8 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ describe("Convex Strategy", function () {
2222
vault,
2323
governor,
2424
crv,
25-
crvMinter,
25+
cvx,
2626
threePoolToken,
2727
convexStrategy,
2828
cvxBooster,
@@ -45,7 +45,7 @@ describe("Convex Strategy", function () {
4545
ousd = fixture.ousd;
4646
governor = fixture.governor;
4747
crv = fixture.crv;
48-
crvMinter = fixture.crvMinter;
48+
cvx = fixture.cvx;
4949
threePoolToken = fixture.threePoolToken;
5050
convexStrategy = fixture.convexStrategy;
5151
cvxBooster = fixture.cvxBooster;
@@ -139,26 +139,26 @@ describe("Convex Strategy", function () {
139139

140140
it("Should collect reward tokens using collect rewards on all strategies", async () => {
141141
// Mint of MockCRVMinter mints a fixed 2e18
142-
await crvMinter.connect(governor).mint(convexStrategy.address);
143142
await vault.connect(governor)["harvest()"]();
144143
await expect(await crv.balanceOf(vault.address)).to.be.equal(
145144
utils.parseUnits("2", 18)
146145
);
146+
await expect(await cvx.balanceOf(vault.address)).to.be.equal(
147+
utils.parseUnits("3", 18)
148+
);
147149
});
148150

149151
it("Should collect reward tokens using collect rewards on a specific strategy", async () => {
150-
// Mint of MockCRVMinter mints a fixed 2e18
151-
await crvMinter.connect(governor).mint(convexStrategy.address);
152152
await vault.connect(governor)[
153153
// eslint-disable-next-line
154154
"harvest(address)"
155155
](convexStrategy.address);
156+
156157
await expect(await crv.balanceOf(vault.address)).to.be.equal(
157158
utils.parseUnits("2", 18)
158159
);
159-
await crvMinter.connect(governor).mint(convexStrategy.address);
160-
await expect(await crv.balanceOf(vault.address)).to.be.equal(
161-
utils.parseUnits("2", 18)
160+
await expect(await cvx.balanceOf(vault.address)).to.be.equal(
161+
utils.parseUnits("3", 18)
162162
);
163163
});
164164

@@ -174,15 +174,6 @@ describe("Convex Strategy", function () {
174174
// Make sure Vault has 0 USDT balance
175175
await expect(vault).has.a.balanceOf("0", usdt);
176176

177-
// Make sure the Strategy has CRV balance
178-
await crvMinter.connect(governor).mint(convexStrategy.address);
179-
await expect(
180-
await crv.balanceOf(await governor.getAddress())
181-
).to.be.equal("0");
182-
await expect(await crv.balanceOf(convexStrategy.address)).to.be.equal(
183-
utils.parseUnits("2", 18)
184-
);
185-
186177
// Give Uniswap mock some USDT so it can give it back in CRV liquidation
187178
await usdt
188179
.connect(anna)

contracts/utils/addresses.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ addresses.mainnet.ThreePool = "0xbEbc44782C7dB0a1A60Cb6fe97d0b483032FF1C7";
4747
addresses.mainnet.ThreePoolToken = "0x6c3f90f043a72fa612cbac8115ee7e52bde6e490";
4848
addresses.mainnet.ThreePoolGauge = "0xbFcF63294aD7105dEa65aA58F8AE5BE2D9d0952A";
4949
// CVX
50-
addresses.mainnet.CVX = "0x30D9410ED1D5DA1F6C8391af5338C93ab8d4035C";
50+
addresses.mainnet.CVX = "0x4e3fbd56cd56c3e72c1403e103b45db9da5b9d2b";
5151
addresses.mainnet.CRVRewardsPool = "0x689440f2ff927e1f24c72f1087e1faf471ece1c8";
5252
addresses.mainnet.CVXBooster = "0xF403C135812408BFbE8713b5A23a04b3D48AAE31";
5353
// Open Oracle

0 commit comments

Comments
 (0)