diff --git a/README.md b/README.md index 2c1484c..5ab48d6 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,15 @@ -## Foundry +# Breadchain +Breadchain smart contracts power [Breadchain's governance application](https://app.breadchain.xyz/governance). -**Foundry is a blazing fast, portable and modular toolkit for Ethereum application development written in Rust.** +To learn more check out the [Breadchain wiki](https://breadchain.notion.site/4d496b311b984bd9841ef9c192b9c1c7). -Foundry consists of: +## Contributing +Join in on the conversation in our [Discord](https://discord.com/invite/zmNqsHRHDa). -- **Forge**: Ethereum testing framework (like Truffle, Hardhat and DappTools). -- **Cast**: Swiss army knife for interacting with EVM smart contracts, sending transactions and getting chain data. -- **Anvil**: Local Ethereum node, akin to Ganache, Hardhat Network. -- **Chisel**: Fast, utilitarian, and verbose solidity REPL. +If you have skills (both technical and non-technical) that you believe would benefit our mission, you can fill out [this Google Form](https://forms.gle/UU4FmHq4CZbiEKPc6). Expect to hear from a member of our team shortly regarding any potential opportunities for collaboration. -## Documentation - -https://book.getfoundry.sh/ +### Style Guide +Contributions to this repo are expected to adhere to the [Biconomy Solidity Style Guide](https://github.com/bcnmy/biconomy-solidity-style-guide). ## Usage @@ -21,12 +19,6 @@ https://book.getfoundry.sh/ $ forge build ``` -### Test - -```shell -$ forge test -``` - ### Format ```shell @@ -39,12 +31,6 @@ $ forge fmt $ forge snapshot ``` -### Anvil - -```shell -$ anvil -``` - ### Test ```shell @@ -56,21 +42,8 @@ $ forge test --fork-url "https://rpc.gnosis.gateway.fm" -vvvv forge script script/deploy/DeployYieldDistributor.s.sol:DeployYieldDistributor --rpc-url "https://rpc.gnosis.gateway.fm" --broadcast --private-key ``` -### Cast - -```shell -$ cast -``` - -### Help - -```shell -$ forge --help -$ anvil --help -$ cast --help -``` - -## Validate Upgrade Safety +## Upgrading +### Validate Upgrade Safety 1. Checkout to the deployed implementation commit 2. Copy "YieldDistributor.sol" to `test/upgrades//YieldDistributor.sol` 3. Checkout to upgrade candidate version (A version that is strictly higher than the version in the previous step) @@ -78,7 +51,7 @@ $ cast --help 5. Run `forge clean && forge build && forge script script/upgrades/ValidateUpgrade.s.sol` 6. If script is runs successfully, proceed, otherwise address errors produced by the script until no errors are produced. -## Test Upgrade with Calldata Locally +### Test Upgrade with Calldata Locally 1. Amend the `data` variable in `script/upgrades/UpgradeYieldDistributor.s.sol` to match desired data 2. run `forge clean && forge build && forge script script/upgrades/UpgradeYieldDistributor.s.sol --sig "run(address)" --rpc-url $RPC_URL --sender ` diff --git a/src/ButteredBread.sol b/src/ButteredBread.sol index f609095..1900a63 100644 --- a/src/ButteredBread.sol +++ b/src/ButteredBread.sol @@ -5,6 +5,7 @@ import {OwnableUpgradeable} from "openzeppelin-contracts-upgradeable/contracts/a import {ERC20VotesUpgradeable} from "openzeppelin-contracts-upgradeable/contracts/token/ERC20/extensions/ERC20VotesUpgradeable.sol"; import {IERC20} from "openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; + import {IButteredBread} from "src/interfaces/IButteredBread.sol"; import {IERC20Votes} from "src/interfaces/IERC20Votes.sol"; @@ -16,11 +17,11 @@ import {IERC20Votes} from "src/interfaces/IERC20Votes.sol"; * @custom:coauthor @daopunk * @custom:coauthor @bagelface */ -contract ButteredBread is ERC20VotesUpgradeable, OwnableUpgradeable, IButteredBread { +contract ButteredBread is IButteredBread, ERC20VotesUpgradeable, OwnableUpgradeable { + /// @notice Value used for calculating the precision of scaling factors uint256 public constant FIXED_POINT_PERCENT = 100; - - IERC20Votes public BREAD; - + /// @notice `IERC20Votes` contract used for powering `ButteredBread` voting + IERC20Votes public bread; /// @notice Access control for Breadchain sanctioned liquidity pools mapping(address lp => bool allowed) public allowlistedLPs; /// @notice How much ButteredBread should be minted for a Liquidity Pool token (Butter) @@ -28,6 +29,7 @@ contract ButteredBread is ERC20VotesUpgradeable, OwnableUpgradeable, IButteredBr /// @notice Butter balance by account and Liquidity Pool token deposited mapping(address account => mapping(address lp => LPData)) internal _accountToLPData; + /// @dev Applied to functions to only allow access for sanctioned liquidity pools modifier onlyAllowed(address _lp) { if (!allowlistedLPs[_lp]) revert NotAllowListed(); _; @@ -38,10 +40,10 @@ contract ButteredBread is ERC20VotesUpgradeable, OwnableUpgradeable, IButteredBr _disableInitializers(); } - /// @param _initData See IButteredBread + /// @param _initData See `IButteredBread` function initialize(InitData calldata _initData) external initializer { if (_initData.liquidityPools.length != _initData.scalingFactors.length) revert InvalidValue(); - BREAD = IERC20Votes(_initData.breadToken); + bread = IERC20Votes(_initData.breadToken); __Ownable_init(msg.sender); __ERC20_init(_initData.name, _initData.symbol); @@ -53,15 +55,16 @@ contract ButteredBread is ERC20VotesUpgradeable, OwnableUpgradeable, IButteredBr } /** + * @notice Return token balance of account for a specified LP * @param _account Voting account * @param _lp Liquidity Pool token - * @return _lpBalance Balance of LP tokens for an account by LP type + * @return _lpBalance Balance of LP tokens for an account by LP address */ function accountToLPBalance(address _account, address _lp) external view returns (uint256 _lpBalance) { _lpBalance = _accountToLPData[_account][_lp].balance; } - /// @notice Sync this delegation with user delegate selection on BREAD + /// @notice Sync this delegation with user delegate selection on $BREAD function syncDelegation() external { _syncDelegation(msg.sender); } @@ -91,7 +94,7 @@ contract ButteredBread is ERC20VotesUpgradeable, OwnableUpgradeable, IButteredBr * @param _allowed Sanction status of LP token */ function modifyAllowList(address _lp, bool _allowed) external onlyOwner { - if (scalingFactors[_lp] < FIXED_POINT_PERCENT) revert Unset(); + if (scalingFactors[_lp] == 0) revert UnsetVariable(); allowlistedLPs[_lp] = _allowed; } @@ -105,17 +108,17 @@ contract ButteredBread is ERC20VotesUpgradeable, OwnableUpgradeable, IButteredBr _modifyScalingFactor(_lp, _factor, _holders); } - /// @notice ButteredBread tokens are non-transferable + /// @notice `ButteredBread` tokens are non-transferable function transfer(address, uint256) public virtual override returns (bool) { revert NonTransferable(); } - /// @notice ButteredBread tokens are non-transferable + /// @notice `ButteredBread` tokens are non-transferable function transferFrom(address, address, uint256) public virtual override returns (bool) { revert NonTransferable(); } - /// @notice ButteredBread delegation is determined by the BREAD token + /// @notice `ButteredBread` delegation is determined by `BreadToken` function delegate(address) public virtual override { revert NonDelegatable(); } @@ -131,7 +134,7 @@ contract ButteredBread is ERC20VotesUpgradeable, OwnableUpgradeable, IButteredBr _mint(_account, _amount * currentScalingFactor / FIXED_POINT_PERCENT); _syncDelegation(_account); - emit AddButter(_account, _lp, _amount); + emit ButterAdded(_account, _lp, _amount); } /// @notice Withdraw LP tokens and burn ButteredBread with corresponding LP scaling factor @@ -146,7 +149,7 @@ contract ButteredBread is ERC20VotesUpgradeable, OwnableUpgradeable, IButteredBr _burn(_account, _amount * scalingFactors[_lp] / FIXED_POINT_PERCENT); IERC20(_lp).transfer(_account, _amount); - emit RemoveButter(_account, _lp, _amount); + emit ButterRemoved(_account, _lp, _amount); } function _modifyScalingFactor(address _lp, uint256 _factor, address[] calldata _holders) internal { @@ -158,9 +161,9 @@ contract ButteredBread is ERC20VotesUpgradeable, OwnableUpgradeable, IButteredBr } } - /// @notice Sync this delegation with delegate selection on BREAD + /// @notice Sync this delegation with delegate selection on $BREAD function _syncDelegation(address _account) internal { - _delegate(_account, BREAD.delegates(_account)); + _delegate(_account, bread.delegates(_account)); if (this.delegates(_account) == address(0)) _delegate(_account, _account); } diff --git a/src/YieldDistributor.sol b/src/YieldDistributor.sol index 2094dd9..8e5a4f9 100644 --- a/src/YieldDistributor.sol +++ b/src/YieldDistributor.sol @@ -8,6 +8,8 @@ import {ERC20VotesUpgradeable} from "openzeppelin-contracts-upgradeable/contracts/token/ERC20/extensions/ERC20VotesUpgradeable.sol"; import {Bread} from "bread-token/src/Bread.sol"; +import {IYieldDistributor} from "src/interfaces/IYieldDistributor.sol"; + /** * @title Breadchain Yield Distributor * @notice Distribute $BREAD yield to eligible member projects based on a voted distribution @@ -18,66 +20,34 @@ import {Bread} from "bread-token/src/Bread.sol"; * @custom:coauthor kassandra.eth * @custom:coauthor theblockchainsocialist.eth */ -contract YieldDistributor is OwnableUpgradeable { - // @notice The error emitted when attempting to add a project that is already in the `projects` array - error AlreadyMemberProject(); - // @notice The error emitted when a user attempts to vote without the minimum required voting power - error BelowMinRequiredVotingPower(); - // @notice The error emitted when attempting to calculate voting power for a period that has not yet ended - error EndAfterCurrentBlock(); - // @notice The error emitted when attempting to vote with a point value greater than `pointsMax` - error ExceedsMaxPoints(); - // @notice The error emitted when attempting to vote with an incorrect number of projects - error IncorrectNumberOfProjects(); - // @notice The error emitted when attempting to instantiate a variable with a zero value - error MustBeGreaterThanZero(); - // @notice The error emitted when attempting to add or remove a project that is already queued for addition or removal - error ProjectAlreadyQueued(); - // @notice The error emitted when attempting to remove a project that is not in the `projects` array - error ProjectNotFound(); - // @notice The error emitted when attempting to calculate voting power for a period with a start block greater than the end block - error StartMustBeBeforeEnd(); - // @notice The error emitted when attempting to distribute yield when access conditions are not met - error YieldNotResolved(); - // @notice The error emitted if a user with zero points attempts to cast votes - error ZeroVotePoints(); - - // @notice The event emitted when an account casts a vote - event BreadHolderVoted(address indexed account, uint256[] points, address[] projects); - // @notice The event emitted when a project is added as eligibile for yield distribution - event ProjectAdded(address project); - // @notice The event emitted when a project is removed as eligibile for yield distribution - event ProjectRemoved(address project); - // @notice The event emitted when yield is distributed - event YieldDistributed(uint256 yield, uint256 totalVotes, uint256[] projectDistributions); - - // @notice The address of the $BREAD token contract +contract YieldDistributor is IYieldDistributor, OwnableUpgradeable { + /// @notice The address of the $BREAD token contract Bread public BREAD; - // @notice The precision to use for calculations + /// @notice The precision to use for calculations uint256 public PRECISION; - // @notice The minimum number of blocks between yield distributions + /// @notice The minimum number of blocks between yield distributions uint256 public cycleLength; - // @notice The maximum number of points a voter can allocate to a project + /// @notice The maximum number of points a voter can allocate to a project uint256 public maxPoints; - // @notice The minimum required voting power participants must have to cast a vote + /// @notice The minimum required voting power participants must have to cast a vote uint256 public minRequiredVotingPower; - // @notice The block number of the last yield distribution + /// @notice The block number of the last yield distribution uint256 public lastClaimedBlockNumber; - // @notice The total number of votes cast in the current cycle + /// @notice The total number of votes cast in the current cycle uint256 public currentVotes; - // @notice Array of projects eligible for yield distribution + /// @notice Array of projects eligible for yield distribution address[] public projects; - // @notice Array of projects queued for addition to the next cycle + /// @notice Array of projects queued for addition to the next cycle address[] public queuedProjectsForAddition; - // @notice Array of projects queued for removal from the next cycle + /// @notice Array of projects queued for removal from the next cycle address[] public queuedProjectsForRemoval; - // @notice The voting power allocated to projects by voters in the current cycle + /// @notice The voting power allocated to projects by voters in the current cycle uint256[] public projectDistributions; - // @notice The last block number in which a specified account cast a vote + /// @notice The last block number in which a specified account cast a vote mapping(address => uint256) public accountLastVoted; - // @notice The voting power allocated to projects by voters in the current cycle + /// @notice The voting power allocated to projects by voters in the current cycle mapping(address => uint256[]) voterDistributions; - // @notice How much of the yield is divided equally among projects + /// @notice How much of the yield is divided equally among projects uint256 public yieldFixedSplitDivisor; /// @custom:oz-upgrades-unsafe-allow constructor @@ -147,34 +117,34 @@ contract YieldDistributor is OwnableUpgradeable { if (_start >= _end) revert StartMustBeBeforeEnd(); if (_end > block.number) revert EndAfterCurrentBlock(); - // Initialized as the checkpoint count, but later used to track checkpoint index + /// Initialized as the checkpoint count, but later used to track checkpoint index uint32 _currentCheckpointIndex = _sourceContract.numCheckpoints(_account); if (_currentCheckpointIndex == 0) return 0; - // No voting power if the first checkpoint is after the end of the interval + /// No voting power if the first checkpoint is after the end of the interval Checkpoints.Checkpoint208 memory _currentCheckpoint = _sourceContract.checkpoints(_account, 0); if (_currentCheckpoint._key > _end) return 0; - // Find the latest checkpoint that is within the interval + /// Find the latest checkpoint that is within the interval do { --_currentCheckpointIndex; _currentCheckpoint = _sourceContract.checkpoints(_account, _currentCheckpointIndex); } while (_currentCheckpoint._key > _end); - // Initialize voting power with the latest checkpoint thats within the interval (or nearest to it) + /// Initialize voting power with the latest checkpoint thats within the interval (or nearest to it) uint48 _latestKey = _currentCheckpoint._key < _start ? uint48(_start) : _currentCheckpoint._key; uint256 _totalVotingPower = _currentCheckpoint._value * (_end - _latestKey); if (_latestKey == _start) return _totalVotingPower; for (uint32 i = _currentCheckpointIndex; i > 0;) { - // Latest checkpoint voting power is calculated when initializing `_totalVotingPower`, so we pre-decrement the index here + /// Latest checkpoint voting power is calculated when initializing `_totalVotingPower`, so we pre-decrement the index here _currentCheckpoint = _sourceContract.checkpoints(_account, --i); - // Add voting power for the sub-interval to the total + /// Add voting power for the sub-interval to the total _totalVotingPower += _currentCheckpoint._value * (_latestKey - _currentCheckpoint._key); - // At the start of the interval, deduct voting power accrued before the interval and return the total + /// At the start of the interval, deduct voting power accrued before the interval and return the total if (_currentCheckpoint._key <= _start) { _totalVotingPower -= _currentCheckpoint._value * (_start - _currentCheckpoint._key); break; @@ -196,10 +166,13 @@ contract YieldDistributor is OwnableUpgradeable { function resolveYieldDistribution() public view returns (bool, bytes memory) { uint256 _available_yield = BREAD.balanceOf(address(this)) + BREAD.yieldAccrued(); if ( - currentVotes == 0 // No votes were cast - || block.number < lastClaimedBlockNumber + cycleLength // Already claimed this cycle - || _available_yield / yieldFixedSplitDivisor < projects.length // Yield is insufficient + /// No votes were cast + /// Already claimed this cycle + currentVotes == 0 || block.number < lastClaimedBlockNumber + cycleLength + || _available_yield / yieldFixedSplitDivisor < projects.length ) { + /// Yield is insufficient + return (false, new bytes(0)); } else { return (true, abi.encodePacked(this.distributeYield.selector)); diff --git a/src/interfaces/IButteredBread.sol b/src/interfaces/IButteredBread.sol index 66b5d84..cc43cdc 100644 --- a/src/interfaces/IButteredBread.sol +++ b/src/interfaces/IButteredBread.sol @@ -2,32 +2,32 @@ pragma solidity ^0.8.25; /** - * @title Breadchain Buttered Bread interface + * @title `ButteredBread` interface */ interface IButteredBread { - /// @notice Occurs when user does not have sufficient Butter to mint ButteredBread + /// @notice Occurs when a user does not have sufficient Butter to mint `ButteredBread` error InsufficientFunds(); /// @notice Occurs when an invalid value is attempted to be used in setter functions error InvalidValue(); /// @notice Occurs when attempting a deposit with a non-sanctioned LP error NotAllowListed(); - /// @notice Occurs when attempting to delegate ButteredBread. Delegations are set via the BREAD contract + /// @notice Occurs when attempting to delegate `ButteredBrea`d tokens. Delegations are set via the $BREAD contract error NonDelegatable(); - /// @notice Occurs when attempting to transfer soulbound ButteredBread + /// @notice Occurs when attempting to transfer soulbound `ButteredBread` tokens error NonTransferable(); - /// @notice Occurs when dependent variable is not set - error Unset(); + /// @notice Occurs when a dependent variable is not set + error UnsetVariable(); - /// @notice Specifies how much LP Token (Butter) has been added - event AddButter(address _account, address _lp, uint256 _amount); - /// @notice Specifies how much LP Token (Butter) has been removed - event RemoveButter(address _account, address _lp, uint256 _amount); + /// @notice The event emitted when an LP Token (Butter) has been added + event ButterAdded(address _account, address _lp, uint256 _amount); + /// @notice The event emitted when an LP Token (Butter) has been removed + event ButterRemoved(address _account, address _lp, uint256 _amount); /** - * @param breadToken address of BREAD - * @param liquidityPools sanctioned LPs - * @param scalingFactors scaling factor on mint per sanctioned LP - * @dev each scaling factor is a fixed point percent (e.g. 100 = 1X, 150 = 1.5X, 1000 = 10X) + * @param breadToken Address of `BreadToken` + * @param liquidityPools Sanctioned LPs + * @param scalingFactors Scaling factor on mint per sanctioned LP + * @dev Each scaling factor is a fixed point percent (e.g. 100 = 1X, 150 = 1.5X, 1000 = 10X) * @param name ERC20 token name * @param symbol ERC20 token symbol */ @@ -48,27 +48,27 @@ interface IButteredBread { uint256 scalingFactor; } - /// @notice initialize contract as TransparentUpgradeableProxy + /// @notice Initialize contract as a `TransparentUpgradeableProxy` function initialize(InitData calldata _initData) external; /// @notice Returns whether a given liquidity pool is Breadchain sanctioned or not function allowlistedLPs(address _lp) external view returns (bool _allowed); - /// @notice Returns the factor that determines how much ButteredBread should be minted for a Liquidity Pool token (Butter) + /// @notice Returns the factor that determines how much `ButteredBread` should be minted for a Liquidity Pool token (Butter) function scalingFactors(address _lp) external view returns (uint256 _factor); - /// @notice The amount of LP tokens (Butter) deposited for an account + /// @notice Returns the amount of LP tokens (Butter) deposited for an account function accountToLPBalance(address _account, address _lp) external view returns (uint256 _balance); - /// @notice Deposits LP tokens (Butter) and mints ButteredBread according to the respective LP scaling factor + /// @notice Deposits LP tokens (Butter) and mints `ButteredBread` according to the respective LP scaling factor function deposit(address _lp, uint256 _amount) external; - /// @notice Withdraws some amount of Butter (LP token) and burns an amount of the user's ButteredBread according to the respective scaling factor + /// @notice Withdraws some amount of Butter (LP token) and burns an amount of the user's `ButteredBread` according to the respective scaling factor function withdraw(address _lp, uint256 _amount) external; /// @notice Defines a liquidity pool's status as sanctioned or unsanctioned by Breadchain function modifyAllowList(address _lp, bool _allowed) external; - /// @notice Modifies how much ButteredBread should be minted for a Liquidity Pool token (Butter) + /// @notice Modifies how much `ButteredBread` should be minted for a Liquidity Pool token (Butter) function modifyScalingFactor(address _lp, uint256 _factor, address[] calldata holders) external; } diff --git a/src/interfaces/ICurveStableSwap.sol b/src/interfaces/ICurveStableSwap.sol index 91ee9a0..bf53b0c 100644 --- a/src/interfaces/ICurveStableSwap.sol +++ b/src/interfaces/ICurveStableSwap.sol @@ -4,7 +4,18 @@ pragma solidity ^0.8.25; import {IERC20} from "openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; interface ICurveStableSwap is IERC20 { + /** + * @dev Returns the name of the token + */ function name() external returns (string memory); + + /** + * @dev Returns the symbol of the token + */ function symbol() external returns (string memory); + + /** + * @dev Add liquidity to a Curve pool + */ function add_liquidity(uint256[] memory _amounts, uint256 _min_mint_amount) external returns (uint256); } diff --git a/src/interfaces/IYieldDistributor.sol b/src/interfaces/IYieldDistributor.sol new file mode 100644 index 0000000..aa3b91a --- /dev/null +++ b/src/interfaces/IYieldDistributor.sol @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity ^0.8.22; + +/** + * @title `YieldDistributor` interface + */ +interface IYieldDistributor { + /// @notice The error emitted when attempting to add a project that is already in the `projects` array + error AlreadyMemberProject(); + /// @notice The error emitted when a user attempts to vote without the minimum required voting power + error BelowMinRequiredVotingPower(); + /// @notice The error emitted when attempting to calculate voting power for a period that has not yet ended + error EndAfterCurrentBlock(); + /// @notice The error emitted when attempting to vote with a point value greater than `pointsMax` + error ExceedsMaxPoints(); + /// @notice The error emitted when attempting to vote with an incorrect number of projects + error IncorrectNumberOfProjects(); + /// @notice The error emitted when attempting to instantiate a variable with a zero value + error MustBeGreaterThanZero(); + /// @notice The error emitted when attempting to add or remove a project that is already queued for addition or removal + error ProjectAlreadyQueued(); + /// @notice The error emitted when attempting to remove a project that is not in the `projects` array + error ProjectNotFound(); + /// @notice The error emitted when attempting to calculate voting power for a period with a start block greater than the end block + error StartMustBeBeforeEnd(); + /// @notice The error emitted when attempting to distribute yield when access conditions are not met + error YieldNotResolved(); + /// @notice The error emitted if a user with zero points attempts to cast votes + error ZeroVotePoints(); + + /// @notice The event emitted when an account casts a vote + event BreadHolderVoted(address indexed account, uint256[] points, address[] projects); + /// @notice The event emitted when a project is added as eligibile for yield distribution + event ProjectAdded(address project); + /// @notice The event emitted when a project is removed as eligibile for yield distribution + event ProjectRemoved(address project); + /// @notice The event emitted when yield is distributed + event YieldDistributed(uint256 yield, uint256 totalVotes, uint256[] projectDistributions); +} diff --git a/src/test/YieldDistributorTestWrapper.sol b/src/test/YieldDistributorTestWrapper.sol index eb24910..cf7a875 100644 --- a/src/test/YieldDistributorTestWrapper.sol +++ b/src/test/YieldDistributorTestWrapper.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.22; -import {YieldDistributor} from "../YieldDistributor.sol"; +import {YieldDistributor} from "src/YieldDistributor.sol"; contract YieldDistributorTestWrapper is YieldDistributor { constructor() {} diff --git a/test/ButteredBread.t.sol b/test/ButteredBread.t.sol index a97264d..9cf9491 100644 --- a/test/ButteredBread.t.sol +++ b/test/ButteredBread.t.sol @@ -8,8 +8,9 @@ import {Test} from "forge-std/Test.sol"; import {TransparentUpgradeableProxy} from "openzeppelin-contracts/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; import {IERC20} from "openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; -import {ICurveStableSwap} from "src/interfaces/ICurveStableSwap.sol"; + import {ButteredBread, IButteredBread} from "src/ButteredBread.sol"; +import {ICurveStableSwap} from "src/interfaces/ICurveStableSwap.sol"; import {IERC20Votes} from "src/interfaces/IERC20Votes.sol"; uint256 constant XDAI_FACTOR = 700; // 700% scaling factor; 7X diff --git a/test/YieldDistributor.t.sol b/test/YieldDistributor.t.sol index 036ebcd..b04d0e2 100644 --- a/test/YieldDistributor.t.sol +++ b/test/YieldDistributor.t.sol @@ -10,8 +10,8 @@ import {OwnableUpgradeable} from "openzeppelin-contracts-upgradeable/contracts/a import {TransparentUpgradeableProxy} from "openzeppelin-contracts/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; -import {YieldDistributor} from "../src/YieldDistributor.sol"; -import {YieldDistributorTestWrapper} from "../src/test/YieldDistributorTestWrapper.sol"; +import {YieldDistributor, IYieldDistributor} from "src/YieldDistributor.sol"; +import {YieldDistributorTestWrapper} from "src/test/YieldDistributorTestWrapper.sol"; abstract contract Bread is ERC20VotesUpgradeable, OwnableUpgradeable { function claimYield(uint256 amount, address receiver) public virtual; @@ -21,7 +21,7 @@ abstract contract Bread is ERC20VotesUpgradeable, OwnableUpgradeable { } contract YieldDistributorTest is Test { - uint256 constant START = 32323232323; + uint256 constant START = 32_323_232_323; uint256 marginOfError = 3; YieldDistributorTestWrapper public yieldDistributor; YieldDistributorTestWrapper public yieldDistributor2; @@ -53,7 +53,7 @@ contract YieldDistributorTest is Test { uint256 _minRequiredVotingPower = stdJson.readUint(config_data, "._minRequiredVotingPower"); function setUp() public { - vm.createSelectFork(vm.rpcUrl('gnosis')); + vm.createSelectFork(vm.rpcUrl("gnosis")); YieldDistributorTestWrapper yieldDistributorImplementation = new YieldDistributorTestWrapper(); address[] memory projects1 = new address[](1); @@ -216,7 +216,7 @@ contract YieldDistributorTest is Test { // Generating random values for the test vm.assume(seed > 10); uint256 accounts = 3; - seed = uint256(bound(seed, 1, 100000000000)); + seed = uint256(bound(seed, 1, 100_000_000_000)); setUpForCycle(yieldDistributor2); for (uint256 i = 0; i < accounts; i++) { @@ -235,7 +235,7 @@ contract YieldDistributorTest is Test { // Casting vote with random distribution vm.roll(START); votes.push(vote); - votes.push(10000 - vote); + votes.push(10_000 - vote); vm.prank(holder); yieldDistributor2.castVote(votes); votes.pop(); @@ -259,7 +259,7 @@ contract YieldDistributorTest is Test { // Generating random values for the test vm.assume(seed > 10); uint256 accounts = 3; - seed = uint256(bound(seed, 1, 100000000000)); + seed = uint256(bound(seed, 1, 100_000_000_000)); setUpForCycle(yieldDistributor2); for (uint256 i = 0; i < accounts; i++) { @@ -279,13 +279,13 @@ contract YieldDistributorTest is Test { // Casting vote with random distribution vm.roll(START); votes.push(vote); - votes.push(10000 - vote); + votes.push(10_000 - vote); vm.prank(holder); yieldDistributor2.castVote(votes); votes.pop(); votes.pop(); votes.push(vote2); - votes.push(10000 - vote2); + votes.push(10_000 - vote2); vm.roll(START + 10); vm.prank(holder); yieldDistributor2.castVote(votes); @@ -310,41 +310,46 @@ contract YieldDistributorTest is Test { } function test_voting_power() public { - vm.roll(32323232323); + vm.roll(32_323_232_323); uint256 votingPowerBefore; vm.expectRevert(); - votingPowerBefore = yieldDistributor.getVotingPowerForPeriod(bread, 32323232323, 32323232324, address(this)); - vm.deal(address(this), 1000000000000); - vm.roll(42424242424); - bread.mint{value: 1000000}(address(this)); - vm.roll(42424242425); + votingPowerBefore = + yieldDistributor.getVotingPowerForPeriod(bread, 32_323_232_323, 32_323_232_324, address(this)); + vm.deal(address(this), 1_000_000_000_000); + vm.roll(42_424_242_424); + bread.mint{value: 1_000_000}(address(this)); + vm.roll(42_424_242_425); uint256 votingPowerAfter = - yieldDistributor.getVotingPowerForPeriod(bread, 42424242424, 42424242425, address(this)); - assertEq(votingPowerAfter, 1000000); - vm.roll(42424242426); - votingPowerAfter = yieldDistributor.getVotingPowerForPeriod(bread, 42424242424, 42424242426, address(this)); - assertEq(votingPowerAfter, 2000000); - vm.roll(42424242427); - bread.mint{value: 1000000}(address(this)); - vm.roll(42424242428); - votingPowerAfter = yieldDistributor.getVotingPowerForPeriod(bread, 42424242424, 42424242428, address(this)); - assertEq(votingPowerAfter, 5000000); - vm.roll(42424242430); - votingPowerAfter = yieldDistributor.getVotingPowerForPeriod(bread, 42424242424, 42424242430, address(this)); - assertEq(votingPowerAfter, 9000000); + yieldDistributor.getVotingPowerForPeriod(bread, 42_424_242_424, 42_424_242_425, address(this)); + assertEq(votingPowerAfter, 1_000_000); + vm.roll(42_424_242_426); + votingPowerAfter = + yieldDistributor.getVotingPowerForPeriod(bread, 42_424_242_424, 42_424_242_426, address(this)); + assertEq(votingPowerAfter, 2_000_000); + vm.roll(42_424_242_427); + bread.mint{value: 1_000_000}(address(this)); + vm.roll(42_424_242_428); + votingPowerAfter = + yieldDistributor.getVotingPowerForPeriod(bread, 42_424_242_424, 42_424_242_428, address(this)); + assertEq(votingPowerAfter, 5_000_000); + vm.roll(42_424_242_430); + votingPowerAfter = + yieldDistributor.getVotingPowerForPeriod(bread, 42_424_242_424, 42_424_242_430, address(this)); + assertEq(votingPowerAfter, 9_000_000); vm.expectRevert(); - votingPowerAfter = yieldDistributor.getVotingPowerForPeriod(bread, 42424242424, 42424242431, address(this)); + votingPowerAfter = + yieldDistributor.getVotingPowerForPeriod(bread, 42_424_242_424, 42_424_242_431, address(this)); } function testFuzzy_voting_power(uint256 seed, uint256 mints) public { mints = uint256(bound(mints, 1, 100)); - vm.assume(seed < 100000000000 / mints); + vm.assume(seed < 100_000_000_000 / mints); vm.assume(seed > 0); vm.assume(mints > 2); - uint256 start = 32323232323; + uint256 start = 32_323_232_323; vm.roll(start); address holder = address(0x1234567840123456789012345678701234567890); - vm.deal(holder, 1000000000000000000); + vm.deal(holder, 1_000_000_000_000_000_000); uint256 prevblocknum = vm.getBlockNumber(); uint256 mintblocknum = prevblocknum; uint256 expectedVotingPower = 0; @@ -432,7 +437,7 @@ contract YieldDistributorTest is Test { percentages.push(vote); vm.prank(account); - vm.expectRevert(abi.encodeWithSelector(YieldDistributor.BelowMinRequiredVotingPower.selector)); + vm.expectRevert(abi.encodeWithSelector(IYieldDistributor.BelowMinRequiredVotingPower.selector)); yieldDistributor.castVote(percentages); } }