Skip to content

Commit 4f238f9

Browse files
authored
feat: add updateFarmVest init function (#38)
* feat: add lsSKY -> Sky farm deployment script * refactor: add full init library for LSSKY -> SKY farm * test: add coverage for LsskySkyFarmingInit * refactor: generalize farm initialization library * refactor: rename parameters in TreasuryFundedFarmingInit test * feat: add regular farming init * refactor: improve testing for initializatino of farms * refactor: remove admin from parameters * chore: improve formatting * chore: simplify formatting and linting * chore: remove TODO * test: add integration tests for farms after initialization * chore: remove unused structs * feat: add updateFarmVest init function * fix: wrong chainlog key in deployment script * refactor: explicitly name and check for `DssVestTransferrable` * chore: clean up interfaces * fix: typo in function name * refactor: add comments to FarmingInitParams fields * fix: add ETH_RPC_URL to CI env * chore: fix typos * test: add coverage for updating `cap` * test: add fuzzing for e2e tests * fix: make CI use a stable Foundry version * refactor: make dependency on `DssVestTransferrable` explicit * fix: wrong interface name after merge * fix: missing interface method * fix: missing interface method (again) * refactor: remove noise from implementation * refactor: add test coverage for `updateVestFarm` * refactor: address review comments * refactor: add extended sanity checks to updateFarmVest - Validate vestTot > 0 to prevent zero total vesting - Validate vestTau > 0 to prevent division by zero - Check that distribution vestId is already set (not zero) - Verify vest gem matches distribution gem - Fix error message prefix from initFarm to updateFarmVest - Add comprehensive test coverage for invalid parameters * refactor: add cap adjustment handling for updateFarmVest - Check if vest cap needs adjustment when required rate exceeds current cap - Set cap to rate with 10% buffer when adjustment is needed - Refactor allowance calculation into scoped block for clarity - Add comprehensive test coverage for cap adjustment scenarios - Add tests for allowance calculation and vest creation parameters * chore: remove extra whitespace * fix: move vest parameter assertions to correct test function * refactor: separate integration tests into dedicated file * test: improve assertion error messages in integration tests Replace generic error messages with descriptive ones that clearly explain what operation failed and what the expected behavior should be. --------- Signed-off-by: amusingaxl <[email protected]>
1 parent 1f28462 commit 4f238f9

File tree

3 files changed

+805
-176
lines changed

3 files changed

+805
-176
lines changed

script/dependencies/treasury-funded-farms/TreasuryFundedFarmingInit.sol

Lines changed: 106 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,7 @@ pragma solidity ^0.8.16;
1717

1818
import {StakingRewardsInit, StakingRewardsInitParams} from "../StakingRewardsInit.sol";
1919
import {VestInit, VestCreateParams} from "../VestInit.sol";
20-
import {
21-
VestedRewardsDistributionInit, VestedRewardsDistributionInitParams
22-
} from "../VestedRewardsDistributionInit.sol";
20+
import {VestedRewardsDistributionInit, VestedRewardsDistributionInitParams} from "../VestedRewardsDistributionInit.sol";
2321

2422
struct FarmingInitParams {
2523
address stakingToken;
@@ -41,48 +39,56 @@ struct FarmingInitResult {
4139
uint256 distributedAmount;
4240
}
4341

42+
struct FarmingUpdateVestParams {
43+
address dist;
44+
uint256 vestTot;
45+
uint256 vestBgn;
46+
uint256 vestTau;
47+
}
48+
49+
struct FarmingUpdateVestResult {
50+
uint256 prevVestId;
51+
uint256 prevDistributedAmount;
52+
uint256 vestId;
53+
uint256 distributedAmount;
54+
}
55+
4456
library TreasuryFundedFarmingInit {
4557
ChainlogLike internal constant chainlog = ChainlogLike(0xdA0Ab1e0017DEbCd72Be8599041a2aa3bA7e740F);
4658

4759
function initFarm(FarmingInitParams memory p) internal returns (FarmingInitResult memory r) {
60+
DssVestTransferrableLike vest = DssVestTransferrableLike(p.vest);
4861
// Note: `p.vest` is expected to be of type `DssVestTransferrable`
49-
require(DssVestTransferrableLike(p.vest).czar() == address(this), "initFarm/vest-czar-mismatch");
50-
require(DssVestTransferrableLike(p.vest).gem() == p.rewardsToken, "initFarm/vest-gem-mismatch");
51-
52-
require(
53-
StakingRewardsLike(p.rewards).stakingToken() == p.stakingToken, "initFarm/rewards-staking-token-mismatch"
54-
);
55-
require(
56-
StakingRewardsLike(p.rewards).rewardsToken() == p.rewardsToken, "initFarm/rewards-rewards-token-mismatch"
57-
);
58-
require(StakingRewardsLike(p.rewards).rewardRate() == 0, "initFarm/reward-rate-not-zero");
59-
require(
60-
StakingRewardsLike(p.rewards).rewardsDistribution() == address(0),
61-
"initFarm/rewards-distribution-already-set"
62-
);
63-
require(StakingRewardsLike(p.rewards).owner() == address(this), "initFarm/invalid-owner");
64-
65-
require(VestedRewardsDistributionLike(p.dist).gem() == p.rewardsToken, "initFarm/dist-gem-mismatch");
66-
require(VestedRewardsDistributionLike(p.dist).dssVest() == p.vest, "initFarm/dist-dss-vest-mismatch");
67-
require(VestedRewardsDistributionLike(p.dist).vestId() == 0, "initFarm/dist-vest-id-already-set");
68-
require(
69-
VestedRewardsDistributionLike(p.dist).stakingRewards() == p.rewards,
70-
"initFarm/dist-staking-rewards-mismatch"
71-
);
72-
73-
// Set `dist` with `rewardsDistribution` role in `rewards`.
62+
require(vest.czar() == address(this), "initFarm/vest-czar-mismatch");
63+
require(vest.gem() == p.rewardsToken, "initFarm/vest-gem-mismatch");
64+
65+
StakingRewardsLike rewards = StakingRewardsLike(p.rewards);
66+
require(rewards.stakingToken() == p.stakingToken, "initFarm/rewards-staking-token-mismatch");
67+
require(rewards.rewardsToken() == p.rewardsToken, "initFarm/rewards-rewards-token-mismatch");
68+
require(rewards.rewardRate() == 0, "initFarm/reward-rate-not-zero");
69+
require(rewards.rewardsDistribution() == address(0), "initFarm/rewards-distribution-already-set");
70+
require(rewards.owner() == address(this), "initFarm/invalid-owner");
71+
72+
VestedRewardsDistributionLike dist = VestedRewardsDistributionLike(p.dist);
73+
require(dist.gem() == p.rewardsToken, "initFarm/dist-gem-mismatch");
74+
require(dist.dssVest() == p.vest, "initFarm/dist-dss-vest-mismatch");
75+
require(dist.vestId() == 0, "initFarm/dist-vest-id-already-set");
76+
require(dist.stakingRewards() == p.rewards, "initFarm/dist-staking-rewards-mismatch");
77+
78+
// Set `dist` with `rewardsDistribution` role in `rewards`.
7479
StakingRewardsInit.init(p.rewards, StakingRewardsInitParams({dist: p.dist}));
7580

81+
ERC20Like rewardsToken = ERC20Like(p.rewardsToken);
7682
// Increase `rewardsToken` `p.vest` allowance from the treasury for `p.vestTot`.
77-
uint256 allowance = ERC20Like(p.rewardsToken).allowance(address(this), p.vest);
78-
ERC20Like(p.rewardsToken).approve(p.vest, allowance + p.vestTot);
83+
uint256 allowance = rewardsToken.allowance(address(this), p.vest);
84+
rewardsToken.approve(p.vest, allowance + p.vestTot);
7985

8086
// Check if `p.vest.cap` needs to be adjusted based on the new vest rate.
8187
// Note: adds 10% buffer to the rate, as usual for this parameter.
82-
uint256 cap = DssVestTransferrableLike(p.vest).cap();
88+
uint256 cap = vest.cap();
8389
uint256 rateWithBuffer = (110 * p.vestTot) / (100 * p.vestTau);
8490
if (rateWithBuffer > cap) {
85-
DssVestTransferrableLike(p.vest).file("cap", rateWithBuffer);
91+
vest.file("cap", rateWithBuffer);
8692
}
8793

8894
// Create the proper vesting stream for rewards distribution.
@@ -94,12 +100,13 @@ library TreasuryFundedFarmingInit {
94100
VestedRewardsDistributionInit.init(p.dist, VestedRewardsDistributionInitParams({vestId: vestId}));
95101

96102
// Check if the first distribution is already available and then distribute.
97-
uint256 unpaid = DssVestTransferrableLike(p.vest).unpaid(vestId);
103+
uint256 unpaid = vest.unpaid(vestId);
98104
if (unpaid > 0) {
99-
VestedRewardsDistributionLike(p.dist).distribute();
105+
dist.distribute();
100106
}
101107

102-
VestedRewardsDistributionJobLike(p.distJob).set(p.dist, p.distJobInterval);
108+
VestedRewardsDistributionJobLike distJob = VestedRewardsDistributionJobLike(p.distJob);
109+
distJob.set(p.dist, p.distJobInterval);
103110

104111
r.vestId = vestId;
105112
r.distributedAmount = unpaid;
@@ -117,14 +124,78 @@ library TreasuryFundedFarmingInit {
117124
r = initFarm(p);
118125
LockstakeEngineLike(lockstakeEngine).addFarm(p.rewards);
119126
}
127+
128+
function updateFarmVest(FarmingUpdateVestParams memory p) internal returns (FarmingUpdateVestResult memory r) {
129+
require(p.vestTot > 0, "updateFarmVest/vest-tot-zero");
130+
require(p.vestTau > 0, "updateFarmVest/vest-tau-zero");
131+
132+
VestedRewardsDistributionLike dist = VestedRewardsDistributionLike(p.dist);
133+
require(dist.vestId() != 0, "updateFarmVest/dist-vest-id-not-set");
134+
135+
DssVestTransferrableLike vest = DssVestTransferrableLike(dist.dssVest());
136+
// Note: `vest` is expected to be of type `DssVestTransferrable`
137+
require(vest.czar() == address(this), "updateFarmVest/vest-czar-mismatch");
138+
require(vest.gem() == dist.gem(), "updateFarmVest/vest-gem-mismatch");
139+
140+
ERC20Like rewardsToken = ERC20Like(dist.gem());
141+
uint256 prevVestId = dist.vestId();
142+
143+
// Check if there is a distribution to be done in the previous vesting stream.
144+
uint256 prevUnpaid = vest.unpaid(prevVestId);
145+
if (prevUnpaid > 0) {
146+
dist.distribute();
147+
}
148+
149+
// Adjust allowance for the new vest
150+
{
151+
uint256 currAllowance = rewardsToken.allowance(address(this), address(vest));
152+
uint256 prevVestTot = vest.tot(prevVestId);
153+
uint256 prevVestRxd = vest.rxd(prevVestId);
154+
rewardsToken.approve(address(vest), currAllowance + p.vestTot - (prevVestTot - prevVestRxd));
155+
}
156+
157+
// Yank the previous vesting stream.
158+
vest.yank(prevVestId);
159+
160+
// Check if vest cap needs adjustment
161+
{
162+
uint256 cap = vest.cap();
163+
uint256 rateWithBuffer = (110 * p.vestTot) / (100 * p.vestTau);
164+
if (rateWithBuffer > cap) {
165+
vest.file("cap", rateWithBuffer);
166+
}
167+
}
168+
169+
// Create a new vesting stream for rewards distribution.
170+
uint256 vestId = VestInit.create(
171+
address(vest), VestCreateParams({usr: p.dist, tot: p.vestTot, bgn: p.vestBgn, tau: p.vestTau, eta: 0})
172+
);
173+
174+
// Set the `vestId` in `dist`
175+
VestedRewardsDistributionInit.init(p.dist, VestedRewardsDistributionInitParams({vestId: vestId}));
176+
177+
// Check if the first distribution is already available and then distribute.
178+
uint256 unpaid = vest.unpaid(vestId);
179+
if (unpaid > 0) {
180+
dist.distribute();
181+
}
182+
183+
r.prevVestId = prevVestId;
184+
r.prevDistributedAmount = prevUnpaid;
185+
r.vestId = vestId;
186+
r.distributedAmount = unpaid;
187+
}
120188
}
121189

122190
interface DssVestTransferrableLike {
123191
function cap() external view returns (uint256);
124192
function czar() external view returns (address);
125193
function gem() external view returns (address);
126194
function file(bytes32 key, uint256 value) external;
195+
function rxd(uint256 vestId) external view returns (uint256);
196+
function tot(uint256 vestId) external view returns (uint256);
127197
function unpaid(uint256 vestId) external view returns (uint256);
198+
function yank(uint256 vestId) external;
128199
}
129200

130201
interface StakingRewardsLike {

0 commit comments

Comments
 (0)