From 1989246acde89544eec77f17ac55344e57e6747f Mon Sep 17 00:00:00 2001 From: RonTuretzky <74178515+RonTuretzky@users.noreply.github.com> Date: Sun, 22 Sep 2024 13:27:51 +0300 Subject: [PATCH] Integrate $BB in YieldDistributor (#94) * chore: adding voting power pulling * fix: adding buttered bread init to tests * fix: adding buttered bread init to tests * chore: adding buttered bread to initalizer in yd * fix: reordering variable for upgrade safety * fix: adding validation of init variables * fix: amending variable name to conform with style guide * fix: line break after conditional" * fix: fixing implication of token symbol * fix: amend init values to align with init validations * chore: adding integration test --------- Co-authored-by: Ron Turetzky --- script/deploy/DeployYieldDistributor.s.sol | 2 + script/deploy/config/deployBB.json | 8 ++- script/deploy/config/deployYD.json | 3 +- src/YieldDistributor.sol | 27 +++++++- test/ButteredBread.t.sol | 77 ++++++++++++++++++++++ test/YieldDistributor.t.sol | 5 ++ test/test_deploy.json | 3 +- 7 files changed, 119 insertions(+), 6 deletions(-) diff --git a/script/deploy/DeployYieldDistributor.s.sol b/script/deploy/DeployYieldDistributor.s.sol index f8b1e64..28bca42 100644 --- a/script/deploy/DeployYieldDistributor.s.sol +++ b/script/deploy/DeployYieldDistributor.s.sol @@ -12,6 +12,7 @@ contract DeployYieldDistributor is Script { string public deployConfigPath = string(bytes("./script/deploy/config/deployYD.json")); string config_data = vm.readFile(deployConfigPath); address _bread = stdJson.readAddress(config_data, "._bread"); + address _butteredBread = stdJson.readAddress(config_data, "._butteredBread"); uint256 _minRequiredVotingPower = stdJson.readUint(config_data, "._minRequiredVotingPower"); uint256 _cycleLength = stdJson.readUint(config_data, "._cycleLength"); uint256 _maxPoints = stdJson.readUint(config_data, "._maxPoints"); @@ -24,6 +25,7 @@ contract DeployYieldDistributor is Script { bytes initData = abi.encodeWithSelector( YieldDistributor.initialize.selector, _bread, + _butteredBread, _precision, _minRequiredVotingPower, _maxPoints, diff --git a/script/deploy/config/deployBB.json b/script/deploy/config/deployBB.json index 0fe3c70..0e93773 100644 --- a/script/deploy/config/deployBB.json +++ b/script/deploy/config/deployBB.json @@ -1,7 +1,11 @@ { "_owner": "0x918dEf5d593F46735f74F9E2B280Fe51AF3A99ad", - "_liquidityPools": ["0xa555d5344f6FB6c65da19e403Cb4c1eC4a1a5Ee3"], - "_scalingFactors": ["100"], + "_liquidityPools": [ + "0xf3d8f3de71657d342db60dd714c8a2ae37eac6b4" + ], + "_scalingFactors": [ + "100" + ], "_name": "ButteredBread", "_symbol": "BB" } \ No newline at end of file diff --git a/script/deploy/config/deployYD.json b/script/deploy/config/deployYD.json index 7ed5332..41b195f 100644 --- a/script/deploy/config/deployYD.json +++ b/script/deploy/config/deployYD.json @@ -1,5 +1,6 @@ { "_bread": "0xa555d5344f6FB6c65da19e403Cb4c1eC4a1a5Ee3", + "_butteredbread": "0x123456789abcdef123456789abcdef123456789a", "_owner": "0x918dEf5d593F46735f74F9E2B280Fe51AF3A99ad", "_projectNames": [ "laborDao", @@ -19,7 +20,7 @@ "_maxPoints": 10000, "_cycleLength": 518400, "_minRequiredVotingPower": 1728000000000000000000000, - "_lastClaimedBlockNumber": 0, + "_lastClaimedBlockNumber": 1, "_precision": 1000000000000000000, "_yieldFixedSplitDivisor": 2 } \ No newline at end of file diff --git a/src/YieldDistributor.sol b/src/YieldDistributor.sol index 8e5a4f9..8128b57 100644 --- a/src/YieldDistributor.sol +++ b/src/YieldDistributor.sol @@ -49,6 +49,8 @@ contract YieldDistributor is IYieldDistributor, OwnableUpgradeable { mapping(address => uint256[]) voterDistributions; /// @notice How much of the yield is divided equally among projects uint256 public yieldFixedSplitDivisor; + /// @notice The address of the `ButteredBread` token contract + ERC20VotesUpgradeable public BUTTERED_BREAD; /// @custom:oz-upgrades-unsafe-allow constructor constructor() { @@ -57,6 +59,7 @@ contract YieldDistributor is IYieldDistributor, OwnableUpgradeable { function initialize( address _bread, + address _butteredBread, uint256 _precision, uint256 _minRequiredVotingPower, uint256 _maxPoints, @@ -66,8 +69,16 @@ contract YieldDistributor is IYieldDistributor, OwnableUpgradeable { address[] memory _projects ) public initializer { __Ownable_init(msg.sender); + if ( + _bread == address(0) || _butteredBread == address(0) || _precision == 0 || _minRequiredVotingPower == 0 + || _maxPoints == 0 || _cycleLength == 0 || _yieldFixedSplitDivisor == 0 || _lastClaimedBlockNumber == 0 + || _projects.length == 0 + ) { + revert MustBeGreaterThanZero(); + } BREAD = Bread(_bread); + BUTTERED_BREAD = ERC20VotesUpgradeable(_butteredBread); PRECISION = _precision; minRequiredVotingPower = _minRequiredVotingPower; maxPoints = _maxPoints; @@ -97,8 +108,12 @@ contract YieldDistributor is IYieldDistributor, OwnableUpgradeable { * @return uint256 The voting power of the user */ function getCurrentVotingPower(address _account) public view returns (uint256) { - return - this.getVotingPowerForPeriod(BREAD, lastClaimedBlockNumber - cycleLength, lastClaimedBlockNumber, _account); + return this.getVotingPowerForPeriod( + BREAD, lastClaimedBlockNumber - cycleLength, lastClaimedBlockNumber, _account + ) + + this.getVotingPowerForPeriod( + BUTTERED_BREAD, lastClaimedBlockNumber - cycleLength, lastClaimedBlockNumber, _account + ); } /** @@ -373,4 +388,12 @@ contract YieldDistributor is IYieldDistributor, OwnableUpgradeable { yieldFixedSplitDivisor = _yieldFixedSplitDivisor; } + + /** + * @notice Set the ButteredBread token contract + * @param _butteredBread Address of the ButteredBread token contract + */ + function setButteredBread(address _butteredBread) public onlyOwner { + BUTTERED_BREAD = ERC20VotesUpgradeable(_butteredBread); + } } diff --git a/test/ButteredBread.t.sol b/test/ButteredBread.t.sol index 9cf9491..38dd287 100644 --- a/test/ButteredBread.t.sol +++ b/test/ButteredBread.t.sol @@ -12,6 +12,11 @@ import {IERC20} from "openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; import {ButteredBread, IButteredBread} from "src/ButteredBread.sol"; import {ICurveStableSwap} from "src/interfaces/ICurveStableSwap.sol"; import {IERC20Votes} from "src/interfaces/IERC20Votes.sol"; +import {YieldDistributorTestWrapper} from "src/test/YieldDistributorTestWrapper.sol"; +import {YieldDistributor, IYieldDistributor} from "src/YieldDistributor.sol"; +import {IBread} from "bread-token/src/interfaces/IBread.sol"; +import {OwnableUpgradeable} from "openzeppelin-contracts-upgradeable/contracts/access/OwnableUpgradeable.sol"; +import {ERC20Mock} from "openzeppelin-contracts/contracts/mocks/token/ERC20Mock.sol"; uint256 constant XDAI_FACTOR = 700; // 700% scaling factor; 7X uint256 constant TOKEN_AMOUNT = 1000 ether; @@ -23,6 +28,21 @@ contract ButteredBreadTest is Test { uint256 public fixedPointPercent; address[] public userList; + YieldDistributorTestWrapper public yieldDistributor; + string public deployConfigPath = string(bytes("./test/test_deploy.json")); + string config_data = vm.readFile(deployConfigPath); + bytes projectsRaw = stdJson.parseRaw(config_data, "._projects"); + address[] projects = abi.decode(projectsRaw, (address[])); + uint256 _blocktime = stdJson.readUint(config_data, "._blocktime"); + uint256 _maxPoints = stdJson.readUint(config_data, "._maxPoints"); + uint256 _precision = stdJson.readUint(config_data, "._precision"); + uint256 _minVotingAmount = stdJson.readUint(config_data, "._minVotingAmount"); + uint256 _cycleLength = stdJson.readUint(config_data, "._cycleLength"); + uint256 _minHoldingDuration = stdJson.readUint(config_data, "._minHoldingDuration"); + uint256 _lastClaimedBlockNumber = stdJson.readUint(config_data, "._lastClaimedBlockNumber"); + uint256 _yieldFixedSplitDivisor = stdJson.readUint(config_data, "._yieldFixedSplitDivisor"); + // See test/YieldDistributor.t.sol for explanation of these values + uint256 _minRequiredVotingPower = stdJson.readUint(config_data, "._minRequiredVotingPower"); function setUp() public virtual { vm.createSelectFork(vm.rpcUrl("gnosis")); @@ -54,6 +74,25 @@ contract ButteredBreadTest is Test { vm.label(GNOSIS_CURVE_POOL_XDAI_BREAD, "CurveLP_XDAI_BREAD"); userList.push(ALICE); + + YieldDistributorTestWrapper yieldDistributorImplementation = new YieldDistributorTestWrapper(); + address[] memory projects1 = new address[](1); + projects1[0] = address(this); + bytes memory ydinitData = abi.encodeWithSelector( + YieldDistributor.initialize.selector, + address(GNOSIS_BREAD), + address(bb), + _precision, + _minRequiredVotingPower, + _maxPoints, + _cycleLength, + _yieldFixedSplitDivisor, + _lastClaimedBlockNumber, + projects1 + ); + yieldDistributor = YieldDistributorTestWrapper( + address(new TransparentUpgradeableProxy(address(yieldDistributorImplementation), address(this), ydinitData)) + ); } function _helperAddLiquidity(address _account, uint256 _amountT0, uint256 _amountT1) internal { @@ -490,3 +529,41 @@ contract ButteredBreadTest_Delegation is ButteredBreadTest { assertEq(bb.delegates(ALICE), DELEGATEE); } } + +interface Mintable { + function mint(address account) external payable; +} + +contract ButteredBreadTest_Integration is ButteredBreadTest { + uint256 public start = 32_323_232_323; + + function setUp() public virtual override { + super.setUp(); + _helperAddLiquidity(ALICE, TOKEN_AMOUNT, TOKEN_AMOUNT); + } + + function setUpForCycle(YieldDistributorTestWrapper _yieldDistributor) public { + vm.roll(start - (_cycleLength)); + address yieldDistributorOwner = OwnableUpgradeable(_yieldDistributor).owner(); + vm.prank(yieldDistributorOwner); + _yieldDistributor.setLastClaimedBlockNumber(vm.getBlockNumber()); + address breadOwner = OwnableUpgradeable(GNOSIS_BREAD).owner(); + vm.prank(breadOwner); + IBread(GNOSIS_BREAD).setYieldClaimer(address(_yieldDistributor)); + vm.roll(start - (_cycleLength + 1)); + vm.deal(ALICE, _minVotingAmount); + vm.startPrank(ALICE); + bb.deposit(GNOSIS_CURVE_POOL_XDAI_BREAD, TOKEN_AMOUNT); + Mintable(GNOSIS_BREAD).mint{value: _minVotingAmount}(ALICE); + vm.roll(start); + } + + function testIntegration() public { + setUpForCycle(yieldDistributor); + assertEq(bb.balanceOf(ALICE), TOKEN_AMOUNT * XDAI_FACTOR / fixedPointPercent); + assertEq(bb.accountToLPBalance(ALICE, GNOSIS_CURVE_POOL_XDAI_BREAD), TOKEN_AMOUNT); + uint256 bbBalance = bb.balanceOf(ALICE); + uint256 bBalance = IERC20(GNOSIS_BREAD).balanceOf(ALICE); + assertEq(yieldDistributor.getCurrentVotingPower(ALICE), bbBalance + bBalance); + } +} diff --git a/test/YieldDistributor.t.sol b/test/YieldDistributor.t.sol index b04d0e2..32caac9 100644 --- a/test/YieldDistributor.t.sol +++ b/test/YieldDistributor.t.sol @@ -13,6 +13,8 @@ import {TransparentUpgradeableProxy} from import {YieldDistributor, IYieldDistributor} from "src/YieldDistributor.sol"; import {YieldDistributorTestWrapper} from "src/test/YieldDistributorTestWrapper.sol"; +import {ButteredBread} from "src/ButteredBread.sol"; + abstract contract Bread is ERC20VotesUpgradeable, OwnableUpgradeable { function claimYield(uint256 amount, address receiver) public virtual; function yieldAccrued() external view virtual returns (uint256); @@ -43,6 +45,7 @@ contract YieldDistributorTest is Test { uint256 _lastClaimedBlockNumber = stdJson.readUint(config_data, "._lastClaimedBlockNumber"); uint256 _yieldFixedSplitDivisor = stdJson.readUint(config_data, "._yieldFixedSplitDivisor"); Bread public bread = Bread(address(_bread)); + ButteredBread public butteredBread = ButteredBread(address(_bread)); uint256 minHoldingDurationInBlocks = _minHoldingDuration / _blocktime; // For testing purposes, these values were used in the following way to configure _minRequiredVotingPower @@ -61,6 +64,7 @@ contract YieldDistributorTest is Test { bytes memory initData = abi.encodeWithSelector( YieldDistributor.initialize.selector, address(bread), + address(butteredBread), _precision, _minRequiredVotingPower, _maxPoints, @@ -79,6 +83,7 @@ contract YieldDistributorTest is Test { initData = abi.encodeWithSelector( YieldDistributor.initialize.selector, address(bread), + address(butteredBread), _precision, _minRequiredVotingPower, _maxPoints, diff --git a/test/test_deploy.json b/test/test_deploy.json index 9a3bf22..6b3a77a 100644 --- a/test/test_deploy.json +++ b/test/test_deploy.json @@ -1,5 +1,6 @@ { "_bread": "0xa555d5344f6FB6c65da19e403Cb4c1eC4a1a5Ee3", + "_butteredbread": "0x123456789abcdef123456789abcdef123456789a", "_projectNames": [ "laborDao", "Dandelion", @@ -17,7 +18,7 @@ "_blocktime": 5, "_cycleLength": 10, "_maxPoints": 10000, - "_lastClaimedBlockNumber": 0, + "_lastClaimedBlockNumber": 1, "_minRequiredVotingPower": 10000000000000000000, "_minVotingAmount": 10000000000000000000, "_minHoldingDuration": 864000,