Skip to content

Implement VotingStreakMultiplier #129

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 22 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ cache
out
broadcast
.env
.DS_Store
3 changes: 2 additions & 1 deletion src/VotingMultipliers.sol
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ contract VotingMultipliers is Ownable2StepUpgradeable, IVotingMultipliers {
/// @param _user The address of the user
/// @param _multiplierIndexes Array of multiplier indexes to use
/// @return The total multiplier value for the user
function getTotalMultipliers(address _user, uint256[] calldata _multiplierIndexes) public view returns (uint256) {
function getTotalMultipliers(address _user, uint256[] calldata _multiplierIndexes) public returns (uint256) {
uint256 _totalMultiplier = 0;

for (uint256 i = 0; i < _multiplierIndexes.length; i++) {
Expand All @@ -81,6 +81,7 @@ contract VotingMultipliers is Ownable2StepUpgradeable, IVotingMultipliers {
}

IMultiplier multiplier = allowlistedMultipliers[index];
multiplier.updateMultiplyingFactor(_user);
if (block.number <= multiplier.validUntil(_user)) {
_totalMultiplier += multiplier.getMultiplyingFactor(_user);
}
Expand Down
4 changes: 2 additions & 2 deletions src/YieldDistributor.sol
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,11 @@ contract YieldDistributor is IYieldDistributor, Ownable2StepUpgradeable, VotingM
address[] public queuedProjectsForAddition;
/// @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 each project by voters in the current cycle
uint256[] public projectDistributions;
/// @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 each project by a specific voter in the current cycle
mapping(address => uint256[]) voterDistributions;
/// @notice How much of the yield is divided equally among projects
uint256 public yieldFixedSplitDivisor;
Expand Down
2 changes: 2 additions & 0 deletions src/interfaces/IVotingMultipliers.sol
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,15 @@ interface IVotingMultipliers {
error InvalidMultiplierIndex();
/// @notice Emitted when a new multiplier is added to the allowlist
/// @param multiplier The address of the added multiplier

event MultiplierAdded(IMultiplier indexed multiplier);
/// @notice Emitted when a multiplier is removed from the allowlist
/// @param multiplier The address of the removed multiplier
event MultiplierRemoved(IMultiplier indexed multiplier);
/// @notice Returns the multiplier at the specified index in the allowlist
/// @param index The index of the multiplier in the allowlist
/// @return The multiplier contract at the specified index

function allowlistedMultipliers(uint256 index) external view returns (IMultiplier);
/// @notice Calculates the total multiplier for a given _user
/// @param __user The address of the _user
Expand Down
6 changes: 5 additions & 1 deletion src/interfaces/multipliers/IMultiplier.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@
pragma solidity ^0.8.0;

interface IMultiplier {
/// @notice Returns the voting multiplier for `_user`.
/// @notice Updates the multiplying factor for a specific user
/// @param _user The address of the user to update the multiplying factor for
function updateMultiplyingFactor(address _user) external;

/// @notice Returns the multiplying factor for `_user`.
function getMultiplyingFactor(address _user) external view returns (uint256);

/// @notice Returns the validity period of the multiplier for `_user`.
Expand Down
4 changes: 4 additions & 0 deletions src/interfaces/multipliers/INFTMultiplier.sol
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,8 @@ interface INFTMultiplier is IMultiplier {
/// @param _user The address of the _user to check
/// @return True if the _user owns at least one NFT, false otherwise
function hasNFT(address _user) external view returns (bool);

/// @notice Updates the multiplying factor for a specific user
/// @param _user The address of the user to update the multiplying factor for
function updateMultiplyingFactor(address _user) external;
}
8 changes: 4 additions & 4 deletions src/multipliers/NFTMultiplier.sol
Original file line number Diff line number Diff line change
Expand Up @@ -60,10 +60,10 @@ contract NFTMultiplier is INFTMultiplier, Initializable, Ownable2StepUpgradeable
return validUntilBlock;
}

/// @notice Update the multiplying factor
/// @param _newMultiplyingFactor New multiplying factor (in basis points)
function updateMultiplyingFactor(uint256 _newMultiplyingFactor) external onlyOwner {
multiplyingFactor = _newMultiplyingFactor;
/// @notice Updates the multiplying factor for a specific user
/// @param _user The address of the user to update the multiplying factor for
function updateMultiplyingFactor(address _user) external view onlyOwner {
return;
}

/// @notice Update the valid until block
Expand Down
7 changes: 7 additions & 0 deletions src/multipliers/PermanentNFTMultiplier.sol
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ contract PermanentNFTMultiplier is INFTMultiplier {
/// @notice Get the multiplying factor for a _user
/// @param _user The address of the _user
/// @return The multiplying factor if the _user has an NFT, 0 otherwise

function getMultiplyingFactor(address _user) external view override returns (uint256) {
return hasNFT(_user) ? FACTOR : 0;
}
Expand All @@ -40,4 +41,10 @@ contract PermanentNFTMultiplier is INFTMultiplier {
function hasNFT(address _user) public view override returns (bool) {
return NFT_ADDRESS.balanceOf(_user) > 0;
}

/// @notice Updates the multiplying factor for a specific user
/// @param _user The address of the user to update the multiplying factor for
function updateMultiplyingFactor(address _user) external pure override {
return;
}
}
124 changes: 124 additions & 0 deletions src/multipliers/VotingStreakMultiplier.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.22;

import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import {IMultiplier} from "src/interfaces/multipliers/IMultiplier.sol";
import {YieldDistributor} from "src/YieldDistributor.sol";
import {Math} from "@openzeppelin/contracts/utils/math/Math.sol";

/// @title VotingStreakMultiplier
/// @notice A contract for managing voting streak multipliers
/// @dev Implements IMultiplier and IVotingStreakMultiplier interfaces
contract VotingStreakMultiplier is Initializable, OwnableUpgradeable, IMultiplier {
/// @notice The maximum multiplier incrementation
uint256 public maxMultiplier;

/// @notice The increment value for the multiplier
uint256 public multiplierIncrement;

/// @notice The YieldDistributor contract
YieldDistributor public yieldDistributor;

/// @notice Mapping of user addresses to their current multiplier factor
mapping(address => uint256) public userToMultiplier;

/// @notice Mapping of user addresses to their multiplier validity period
mapping(address => uint256) public userToValidity;

/// @notice Emitted when a user's multiplier is updated
/// @param user The address of the user
/// @param newFactor The new multiplier factor
/// @param validity The new validity period
event MultiplierUpdated(address indexed user, uint256 newFactor, uint256 validity);

/// @custom:oz-upgrades-unsafe-allow constructor
constructor() {
_disableInitializers();
}

/// @notice Initializes the contract
/// @param _yieldDistributor The address of the YieldDistributor contract
/// @param _multiplierIncrement The initial multiplier increment value
/// @param _maxMultiplier The maximum multiplier value
function initialize(address _yieldDistributor, uint256 _multiplierIncrement, uint256 _maxMultiplier)
public
initializer
{
__Ownable_init(msg.sender);

yieldDistributor = YieldDistributor(_yieldDistributor);
multiplierIncrement = _multiplierIncrement;
maxMultiplier = _maxMultiplier;
}

/// @notice Gets the current multiplying factor for a user
/// @param user The address of the user
/// @return The current multiplying factor
function getMultiplyingFactor(address user) public view override returns (uint256) {
if (
yieldDistributor.accountLastVoted(user)
> yieldDistributor.lastClaimedBlockNumber() - yieldDistributor.cycleLength()
&& block.number <= userToValidity[user]
) {
return userToMultiplier[user];
}

return 0;
}

/// @notice Gets the current multiplying factor for a user
/// @param user The address of the user
/// @return The current multiplying factor
function _getMultiplyingFactor(address user) external view returns (uint256) {
return getMultiplyingFactor(user);
}

/// @notice Updates the multiplying factor for a user
/// @param _user The address of the user to update the multiplying factor for
function updateMultiplyingFactor(address _user) external override {
// Check if user has already voted in current cycle
uint256 lastVotedBlock = yieldDistributor.accountLastVoted(_user);
uint256 lastClaimedBlock = yieldDistributor.lastClaimedBlockNumber();
uint256 cycleLength = yieldDistributor.cycleLength();

// If user has already voted in current cycle, do nothing
if (lastVotedBlock > lastClaimedBlock) {
return;
}

uint256 currentMultiplier = getMultiplyingFactor(_user);
uint256 newMultiplier = (currentMultiplier == 0)
? multiplierIncrement
: Math.min(currentMultiplier + multiplierIncrement, maxMultiplier * multiplierIncrement);

userToMultiplier[_user] = newMultiplier;
userToValidity[_user] = lastClaimedBlock + 2 * cycleLength;
emit MultiplierUpdated(_user, newMultiplier, userToValidity[_user]);
}

/// @notice Gets the validity period for a user's multiplier
/// @param user The address of the user
/// @return The block number until which the multiplier is valid
function validUntil(address user) external view override returns (uint256) {
return userToValidity[user];
}

/// @notice Sets the YieldDistributor contract address
/// @param _yieldDistributor The new YieldDistributor contract address
function setYieldDistributor(address _yieldDistributor) external onlyOwner {
yieldDistributor = YieldDistributor(_yieldDistributor);
}

/// @notice Sets the multiplier increment value
/// @param _multiplierIncrement The new multiplier increment value
function setMultiplierIncrement(uint256 _multiplierIncrement) external onlyOwner {
multiplierIncrement = _multiplierIncrement;
}

/// @notice Sets the maximum multiplier value
/// @param _maxMultiplier The new maximum multiplier value
function setMaxMultiplier(uint256 _maxMultiplier) external onlyOwner {
maxMultiplier = _maxMultiplier;
}
}
10 changes: 8 additions & 2 deletions src/test/MockMultiplier.sol
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ contract MockMultiplier is IMultiplier {
/// @notice Sets the multiplying factor and valid until block for testing
/// @param _factor The multiplying factor to set
/// @param _validUntilBlock The block number until which the multiplier is valid
function setMultiplier(uint256 _factor , uint256 _validUntilBlock) external {
_multiplyingFactor = _factor ;
function setMultiplier(uint256 _factor, uint256 _validUntilBlock) external {
_multiplyingFactor = _factor;
_validUntil = _validUntilBlock;
}

Expand All @@ -28,4 +28,10 @@ contract MockMultiplier is IMultiplier {
function validUntil(address /* _user */ ) external view returns (uint256) {
return _validUntil;
}

/// @notice Updates the multiplying factor for a specific user
/// @param _user The address of the user to update the multiplying factor for
function updateMultiplyingFactor(address _user) external pure {
return;
}
}
3 changes: 2 additions & 1 deletion test/YieldDistributor.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import {ERC20VotesUpgradeable} from
import {Ownable2StepUpgradeable} from "openzeppelin-contracts-upgradeable/contracts/access/Ownable2StepUpgradeable.sol";
import {TransparentUpgradeableProxy} from
"openzeppelin-contracts/contracts/proxy/transparent/TransparentUpgradeableProxy.sol";
import {IERC721} from "openzeppelin-contracts/contracts/token/ERC721/IERC721.sol";

import {YieldDistributor, IYieldDistributor} from "src/YieldDistributor.sol";
import {YieldDistributorTestWrapper} from "src/test/YieldDistributorTestWrapper.sol";
import {ButteredBread} from "src/ButteredBread.sol";
Expand All @@ -17,7 +19,6 @@ import {MockMultiplier} from "src/test/MockMultiplier.sol";
import {IMultiplier} from "src/interfaces/IVotingMultipliers.sol";
import {NFTMultiplier} from "src/multipliers/NFTMultiplier.sol";
import {DeployNFTMultiplier} from "script/deploy/DeployNFTMultiplier.s.sol";
import {IERC721} from "openzeppelin-contracts/contracts/token/ERC721/IERC721.sol";

abstract contract Bread is ERC20VotesUpgradeable, Ownable2StepUpgradeable {
function claimYield(uint256 amount, address receiver) public virtual;
Expand Down
Loading