diff --git a/script/dependencies/phase-1d/LsmkrSpkFarmingInit.sol b/script/dependencies/phase-1d/LsmkrSpkFarmingInit.sol new file mode 100644 index 0000000..a39ec9f --- /dev/null +++ b/script/dependencies/phase-1d/LsmkrSpkFarmingInit.sol @@ -0,0 +1,127 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +pragma solidity ^0.8.16; + +import {StakingRewardsInit, StakingRewardsInitParams} from "../StakingRewardsInit.sol"; +import {VestedRewardsDistributionInit, VestedRewardsDistributionInitParams} from "../VestedRewardsDistributionInit.sol"; +import {VestInit, VestCreateParams} from "../VestInit.sol"; + +struct LsmkrSpkFarmingInitParams { + address lsmkr; + address spk; + address rewards; + bytes32 rewardsKey; // Chainlog key + address dist; + bytes32 distKey; // Chainlog key + address vest; + uint256 vestTot; + uint256 vestBgn; + uint256 vestTau; +} + +struct LsmkrSpkFarmingInitResult { + uint256 vestId; +} + +library LsmkrSpkFarmingInit { + ChainlogLike internal constant chainlog = ChainlogLike(0xdA0Ab1e0017DEbCd72Be8599041a2aa3bA7e740F); + + function init(LsmkrSpkFarmingInitParams memory p) internal returns (LsmkrSpkFarmingInitResult memory r) { + require(DssVestWithGemLike(p.vest).gem() == p.spk, "LsmkrSpkFarmingInit/vest-gem-mismatch"); + + require( + StakingRewardsLike(p.rewards).stakingToken() == p.lsmkr, + "LsmkrSpkFarmingInit/rewards-staking-token-mismatch" + ); + require( + StakingRewardsLike(p.rewards).rewardsToken() == p.spk, + "LsmkrSpkFarmingInit/rewards-rewards-token-mismatch" + ); + require(StakingRewardsLike(p.rewards).rewardRate() == 0, "LsmkrSpkFarmingInit/reward-rate-not-zero"); + require( + StakingRewardsLike(p.rewards).rewardsDistribution() == address(0), + "LsmkrSpkFarmingInit/rewards-distribution-already-set" + ); + require( + StakingRewardsLike(p.rewards).owner() == chainlog.getAddress("MCD_PAUSE_PROXY"), + "LsmkrSpkFarmingInit/invalid-owner" + ); + + require(VestedRewardsDistributionLike(p.dist).gem() == p.spk, "LsmkrSpkFarmingInit/dist-gem-mismatch"); + require( + VestedRewardsDistributionLike(p.dist).dssVest() == p.vest, + "LsmkrSpkFarmingInit/dist-dss-vest-mismatch" + ); + require( + VestedRewardsDistributionLike(p.dist).stakingRewards() == p.rewards, + "LsmkrSpkFarmingInit/dist-staking-rewards-mismatch" + ); + + // `vest` is expected to be an instance of `DssVestMintable`. + // Check if minting rights on `spk` were granted to `vest`. + require(WardsLike(p.spk).wards(p.vest) == 1, "LsmkrSpkFarmingInit/missing-spk-rely-vest"); + // Set `dist` with `rewardsDistribution` role in `rewards`. + StakingRewardsInit.init(p.rewards, StakingRewardsInitParams({dist: p.dist})); + + // Create the proper vesting stream for rewards distribution. + uint256 vestId = VestInit.create( + p.vest, + VestCreateParams({usr: p.dist, tot: p.vestTot, bgn: p.vestBgn, tau: p.vestTau, eta: 0}) + ); + + // Set the `vestId` in `dist` + VestedRewardsDistributionInit.init(p.dist, VestedRewardsDistributionInitParams({vestId: vestId})); + + r.vestId = vestId; + + chainlog.setAddress(p.rewardsKey, p.rewards); + chainlog.setAddress(p.distKey, p.dist); + } +} + +interface WardsLike { + function wards(address who) external view returns (uint256); +} + +interface DssVestWithGemLike { + function gem() external view returns (address); +} + +interface StakingRewardsLike { + function owner() external view returns (address); + + function rewardRate() external view returns (uint256); + + function rewardsDistribution() external view returns (address); + + function rewardsToken() external view returns (address); + + function stakingToken() external view returns (address); +} + +interface VestedRewardsDistributionLike { + function dssVest() external view returns (address); + + function gem() external view returns (address); + + function stakingRewards() external view returns (address); +} + +interface ChainlogLike { + function getAddress(bytes32 key) external view returns (address); + + function setAddress(bytes32 key, address addr) external; +} diff --git a/script/input/1/phase-1d/template-lsmkr-spk-farming-deploy.json b/script/input/1/phase-1d/template-lsmkr-spk-farming-deploy.json new file mode 100644 index 0000000..05d9fea --- /dev/null +++ b/script/input/1/phase-1d/template-lsmkr-spk-farming-deploy.json @@ -0,0 +1,7 @@ +{ + "lsmkr": "address: the address of LSMKR", + "spk": "address: the address of SPK", + "vest": "address: the address of a DssVestMintable contract for SPK", + "rewards": "[OPTIONAL] address: the address of the StakingRewards contract", + "dist": "[OPTIONAL] address: the address of the VestedRewardsDistrubution contract" +} diff --git a/script/input/314310/phase-1d/template-lsmkr-spk-farming-deploy.json b/script/input/314310/phase-1d/template-lsmkr-spk-farming-deploy.json new file mode 100644 index 0000000..05d9fea --- /dev/null +++ b/script/input/314310/phase-1d/template-lsmkr-spk-farming-deploy.json @@ -0,0 +1,7 @@ +{ + "lsmkr": "address: the address of LSMKR", + "spk": "address: the address of SPK", + "vest": "address: the address of a DssVestMintable contract for SPK", + "rewards": "[OPTIONAL] address: the address of the StakingRewards contract", + "dist": "[OPTIONAL] address: the address of the VestedRewardsDistrubution contract" +} diff --git a/script/phase-1d/31-LsmkrSpkFarmingDeploy.s.sol b/script/phase-1d/31-LsmkrSpkFarmingDeploy.s.sol new file mode 100644 index 0000000..84e3685 --- /dev/null +++ b/script/phase-1d/31-LsmkrSpkFarmingDeploy.s.sol @@ -0,0 +1,76 @@ +// SPDX-FileCopyrightText: © 2023 Dai Foundation +// SPDX-License-Identifier: AGPL-3.0-or-later +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . +pragma solidity ^0.8.16; + +import {Script} from "forge-std/Script.sol"; +import {ScriptTools} from "dss-test/ScriptTools.sol"; + +import {Reader} from "../helpers/Reader.sol"; +import {StakingRewardsDeploy, StakingRewardsDeployParams} from "../dependencies/StakingRewardsDeploy.sol"; +import { + VestedRewardsDistributionDeploy, + VestedRewardsDistributionDeployParams +} from "../dependencies/VestedRewardsDistributionDeploy.sol"; + +contract Phase1d_LsmkrSpkFarmingDeployScript is Script { + ChainlogLike internal constant chainlog = ChainlogLike(0xdA0Ab1e0017DEbCd72Be8599041a2aa3bA7e740F); + + string internal constant NAME = "phase-1d/lsmkr-spk-farming-deploy"; + + function run() external { + Reader reader = new Reader(ScriptTools.loadConfig()); + + address admin = chainlog.getAddress("MCD_PAUSE_PROXY"); + + address lsmkr = reader.envOrReadAddress("FOUNDRY_LSMKR", ".lsmkr"); + address spk = reader.envOrReadAddress("FOUNDRY_SPK", ".spk"); + address vest = reader.envOrReadAddress("FOUNDRY_VEST", ".vest"); + address dist = reader.readAddressOptional(".dist"); + address rewards = reader.readAddressOptional(".rewards"); + + vm.startBroadcast(); + + if (rewards == address(0)) { + rewards = StakingRewardsDeploy.deploy( + StakingRewardsDeployParams({owner: admin, stakingToken: lsmkr, rewardsToken: spk}) + ); + } + + if (dist == address(0)) { + dist = VestedRewardsDistributionDeploy.deploy( + VestedRewardsDistributionDeployParams({ + deployer: msg.sender, + owner: admin, + vest: vest, + rewards: rewards + }) + ); + } + + vm.stopBroadcast(); + + ScriptTools.exportContract(NAME, "admin", admin); + ScriptTools.exportContract(NAME, "spk", spk); + ScriptTools.exportContract(NAME, "lsmkr", lsmkr); + ScriptTools.exportContract(NAME, "dist", dist); + ScriptTools.exportContract(NAME, "rewards", rewards); + ScriptTools.exportContract(NAME, "vest", vest); + } +} + +interface ChainlogLike { + function getAddress(bytes32 _key) external view returns (address addr); +} diff --git a/script/phase-1d/32-LsmkrSpkFarmingInit.s.sol b/script/phase-1d/32-LsmkrSpkFarmingInit.s.sol new file mode 100644 index 0000000..a5dcb52 --- /dev/null +++ b/script/phase-1d/32-LsmkrSpkFarmingInit.s.sol @@ -0,0 +1,100 @@ +// SPDX-FileCopyrightText: © 2023 Dai Foundation +// SPDX-License-Identifier: AGPL-3.0-or-later +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . +pragma solidity ^0.8.16; + +import {Script} from "forge-std/Script.sol"; +import {ScriptTools} from "dss-test/ScriptTools.sol"; + +import {Reader} from "../helpers/Reader.sol"; +import { + LsmkrSpkFarmingInit, + LsmkrSpkFarmingInitParams, + LsmkrSpkFarmingInitResult +} from "../dependencies/phase-1d/LsmkrSpkFarmingInit.sol"; + +contract Phase1d_LsmkrSpkFarmingInitScript is Script { + using ScriptTools for string; + + ChainlogLike internal constant chainlog = ChainlogLike(0xdA0Ab1e0017DEbCd72Be8599041a2aa3bA7e740F); + + string internal constant NAME = "phase-1d/lsmkr-spk-farming-init"; + + function run() external { + Reader config = new Reader(ScriptTools.loadConfig()); + Reader deps = new Reader(ScriptTools.loadDependencies()); + + uint256 vestTot = config.envOrReadUint("FOUNDRY_VEST_TOT", ".vestTot"); + uint256 vestBgn = config.envOrReadUint("FOUNDRY_VEST_BGN", ".vestBgn"); + uint256 vestTau = config.envOrReadUint("FOUNDRY_VEST_TAU", ".vestTau"); + + address lsmkr = deps.envOrReadAddress("FOUNDRY_LSMKR", ".lsmkr"); + address spk = deps.envOrReadAddress("FOUNDRY_SPK", ".spk"); + address dist = deps.envOrReadAddress("FOUNDRY_DIST", ".dist"); + address rewards = deps.envOrReadAddress("FOUNDRY_FARM", ".rewards"); + address vest = deps.envOrReadAddress("FOUNDRY_VEST", ".vest"); + + LsmkrSpkFarmingInitParams memory farmingCfg = LsmkrSpkFarmingInitParams({ + spk: spk, + lsmkr: lsmkr, + dist: dist, + distKey: "REWARDS_DISTRIBUTION_LSMKR_SPK", + rewards: rewards, + rewardsKey: "FARM_LSMKR_SPK", + vest: vest, + vestTot: vestTot, + vestBgn: vestBgn, + vestTau: vestTau + }); + + address pauseProxy = chainlog.getAddress("MCD_PAUSE_PROXY"); + + vm.startBroadcast(); + + LsmkrSpkFarmingInitSpell spell = new LsmkrSpkFarmingInitSpell(); + bytes memory out = ProxyLike(pauseProxy).exec(address(spell), abi.encodeCall(spell.cast, (farmingCfg))); + + vm.stopBroadcast(); + + LsmkrSpkFarmingInitResult memory res = abi.decode(out, (LsmkrSpkFarmingInitResult)); + + ScriptTools.exportContract(NAME, "spk", spk); + ScriptTools.exportContract(NAME, "dist", dist); + ScriptTools.exportContract(NAME, "rewards", rewards); + ScriptTools.exportContract(NAME, "vest", vest); + ScriptTools.exportValue(NAME, "vestId", res.vestId); + } +} + +contract LsmkrSpkFarmingInitSpell { + uint256 internal constant CAP = type(uint256).max; + + function cast(LsmkrSpkFarmingInitParams memory farmingCfg) public returns (LsmkrSpkFarmingInitResult memory) { + DssVestLike(farmingCfg.vest).file("cap", CAP); + return LsmkrSpkFarmingInit.init(farmingCfg); + } +} + +interface ProxyLike { + function exec(address usr, bytes memory fax) external returns (bytes memory out); +} + +interface ChainlogLike { + function getAddress(bytes32 _key) external view returns (address addr); +} + +interface DssVestLike { + function file(bytes32 _what, uint256 _data) external; +} diff --git a/script/phase-1d/33-LsmkrSpkFarmingCheck.s.sol b/script/phase-1d/33-LsmkrSpkFarmingCheck.s.sol new file mode 100644 index 0000000..f76facb --- /dev/null +++ b/script/phase-1d/33-LsmkrSpkFarmingCheck.s.sol @@ -0,0 +1,109 @@ +// SPDX-FileCopyrightText: © 2023 Dai Foundation +// SPDX-License-Identifier: AGPL-3.0-or-later +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . +pragma solidity ^0.8.16; + +import {Script} from "forge-std/Script.sol"; +import {ScriptTools} from "dss-test/ScriptTools.sol"; +import {Reader} from "../helpers/Reader.sol"; + +contract Phase1d_LsmkrSpkFarmingCheckScript is Script { + function run() external returns (bool) { + Reader config = new Reader(ScriptTools.loadConfig()); + Reader deps = new Reader(ScriptTools.loadDependencies()); + + address admin = deps.envOrReadAddress("FOUNDRY_ADMIN", ".admin"); + address lsmkr = deps.envOrReadAddress("FOUNDRY_LSMKR", ".lsmkr"); + address spk = deps.envOrReadAddress("FOUNDRY_SPK", ".spk"); + address dist = deps.readAddress(".dist"); + address rewards = deps.readAddress(".rewards"); + address vest = deps.readAddress(".vest"); + uint256 vestId = deps.readUint(".vestId"); + uint256 vestBgn = config.readUint(".vestBgn"); + uint256 vestTau = config.readUint(".vestTau"); + uint256 vestTot = config.readUint(".vestTot"); + + require(VestedRewardsDistributionLike(dist).dssVest() == vest, "VestedRewardsDistribution/invalid-vest"); + require(VestedRewardsDistributionLike(dist).vestId() == vestId, "VestedRewardsDistribution/invalid-vest-id"); + require(VestedRewardsDistributionLike(dist).gem() == spk, "VestedRewardsDistribution/invalid-gem"); + require( + VestedRewardsDistributionLike(dist).stakingRewards() == rewards, + "VestedRewardsDistribution/invalid-staking-rewards" + ); + + require(StakingRewardsLike(rewards).owner() == admin, "StakingRewards/admin-not-owner"); + require(StakingRewardsLike(rewards).rewardsToken() == spk, "StakingRewards/invalid-rewards-token"); + require(StakingRewardsLike(rewards).stakingToken() == lsmkr, "StakingRewards/invalid-staking-token"); + require( + StakingRewardsLike(rewards).rewardsDistribution() == dist, + "StakingRewards/invalid-rewards-distribution" + ); + + require(WardsLike(spk).wards(vest) == 1, "Spk/dss-vest-not-ward"); + + require(DssVestWithGemLike(vest).gem() == spk, "DssVest/invalid-gem"); + require(DssVestWithGemLike(vest).valid(vestId), "DssVest/invalid-vest-id"); + require(DssVestWithGemLike(vest).res(vestId) == 1, "DssVest/invalid-vest-res"); + require(DssVestWithGemLike(vest).usr(vestId) == dist, "DssVest/wrong-dist"); + require(DssVestWithGemLike(vest).mgr(vestId) == address(0), "DssVest/mgr-should-not-be-set"); + require(DssVestWithGemLike(vest).bgn(vestId) == vestBgn, "DssVest/invalid-bgn"); + require(DssVestWithGemLike(vest).fin(vestId) == vestBgn + vestTau, "DssVest/invalid-tau"); + require(DssVestWithGemLike(vest).tot(vestId) == vestTot, "DssVest/invalid-tot"); + + return true; + } +} + +interface WardsLike { + function wards(address who) external view returns (uint256); +} + +interface VestedRewardsDistributionLike { + function dssVest() external view returns (address); + + function vestId() external view returns (uint256); + + function stakingRewards() external view returns (address); + + function gem() external view returns (address); +} + +interface StakingRewardsLike { + function owner() external view returns (address); + + function stakingToken() external view returns (address); + + function rewardsToken() external view returns (address); + + function rewardsDistribution() external view returns (address); +} + +interface DssVestWithGemLike { + function bgn(uint256 _id) external view returns (uint256); + + function fin(uint256 _id) external view returns (uint256); + + function gem() external view returns (address); + + function mgr(uint256 _id) external view returns (address); + + function res(uint256 _id) external view returns (uint256); + + function tot(uint256 _id) external view returns (uint256); + + function usr(uint256 _id) external view returns (address); + + function valid(uint256 _id) external view returns (bool); +}