Skip to content

Commit

Permalink
feat(Karma): allocate external rewards using the Karma contract
Browse files Browse the repository at this point in the history
  • Loading branch information
gravityblast committed Feb 27, 2025
1 parent a51d519 commit d5c9f37
Show file tree
Hide file tree
Showing 9 changed files with 160 additions and 68 deletions.
1 change: 0 additions & 1 deletion docs/xp-token.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ providers. XP tokens are not transferrable, but they can be used as voting power

- `XPToken__MintAllowanceExceeded`: Raised when minting exceeds the allowed threshold.
- `XPToken__TransfersNotAllowed`: Raised when a transfer, approval, or transferFrom is attempted.
- `RewardProvider__IndexOutOfBounds`: Raised when an invalid index is used for removing a reward provider.

## Supply and Balance Calculation

Expand Down
1 change: 1 addition & 0 deletions script/DeploymentConfig.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ pragma solidity >=0.8.26 <=0.9.0;

import { Script } from "forge-std/Script.sol";
import { MockToken } from "../test/mocks/MockToken.sol";
import { Karma } from "../src/Karma.sol";

Check warning on line 7 in script/DeploymentConfig.s.sol

View workflow job for this annotation

GitHub Actions / lint

imported name Karma is not used

contract DeploymentConfig is Script {
error DeploymentConfig_InvalidDeployerAddress();
Expand Down
62 changes: 45 additions & 17 deletions src/Karma.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,35 +4,57 @@ pragma solidity ^0.8.26;
import { Ownable, Ownable2Step } from "@openzeppelin/contracts/access/Ownable2Step.sol";
import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import { IRewardProvider } from "./interfaces/IRewardProvider.sol";
import { EnumerableSet } from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";

contract Karma is ERC20, Ownable2Step {
error Karma__MintAllowanceExceeded();
using EnumerableSet for EnumerableSet.AddressSet;

string public constant NAME = "Karma";
string public constant SYMBOL = "KARMA";

IRewardProvider[] public rewardProviders;
uint256 totalProvidersAllocation;

Check warning on line 15 in src/Karma.sol

View workflow job for this annotation

GitHub Actions / lint

Explicitly mark visibility of state

EnumerableSet.AddressSet private rewardProviders;
mapping(address => uint256) public rewardProviderAllocations;

Check warning on line 18 in src/Karma.sol

View workflow job for this annotation

GitHub Actions / lint

Main key parameter in mapping rewardProviderAllocations is not named

Check warning on line 18 in src/Karma.sol

View workflow job for this annotation

GitHub Actions / lint

Value parameter in mapping rewardProviderAllocations is not named

error Karma__TransfersNotAllowed();
error RewardProvider__IndexOutOfBounds();
error Karma__MintAllowanceExceeded();
error Karma__ProviderAlreadyAdded();
error Karma__UnknownProvider();

event RewardProviderAdded(address provider);

constructor() ERC20(NAME, SYMBOL) Ownable(msg.sender) { }

function addRewardProvider(IRewardProvider provider) external onlyOwner {
rewardProviders.push(provider);
function addRewardProvider(address provider) external onlyOwner {
if (rewardProviders.contains(provider)) {
revert Karma__ProviderAlreadyAdded();
}

rewardProviders.add(address(provider));
emit RewardProviderAdded(provider);
}

function removeRewardProvider(uint256 index) external onlyOwner {
if (index >= rewardProviders.length) {
revert RewardProvider__IndexOutOfBounds();
function removeRewardProvider(address provider) external onlyOwner {
if (!rewardProviders.contains(provider)) {
revert Karma__UnknownProvider();
}

rewardProviders[index] = rewardProviders[rewardProviders.length - 1];
rewardProviders.pop();
rewardProviders.remove(provider);
}

function getRewardProviders() external view returns (IRewardProvider[] memory) {
return rewardProviders;
function setReward(address rewardsProvider, uint256 amount, uint256 duration) external onlyOwner {
if (!rewardProviders.contains(rewardsProvider)) {
revert Karma__UnknownProvider();
}

rewardProviderAllocations[rewardsProvider] = amount;
totalProvidersAllocation += amount;
IRewardProvider(rewardsProvider).setReward(amount, duration);
}

function getRewardProviders() external view returns (address[] memory) {
return rewardProviders.values();
}

function _totalSupply() public view returns (uint256) {
Expand Down Expand Up @@ -68,8 +90,14 @@ contract Karma is ERC20, Ownable2Step {
function _externalSupply() internal view returns (uint256) {
uint256 externalSupply;

for (uint256 i = 0; i < rewardProviders.length; i++) {
externalSupply += rewardProviders[i].totalRewardsSupply();
for (uint256 i = 0; i < rewardProviders.length(); i++) {
IRewardProvider provider = IRewardProvider(rewardProviders.at(i));
uint256 supply = provider.totalRewardsSupply();
if (supply > rewardProviderAllocations[address(provider)]) {
supply = rewardProviderAllocations[address(provider)];
}

externalSupply += supply;
}

return externalSupply;
Expand All @@ -78,9 +106,9 @@ contract Karma is ERC20, Ownable2Step {
function balanceOf(address account) public view override returns (uint256) {
uint256 externalBalance;

for (uint256 i = 0; i < rewardProviders.length; i++) {
IRewardProvider provider = rewardProviders[i];
externalBalance += provider.rewardsBalanceOfAccount(account);
for (uint256 i = 0; i < rewardProviders.length(); i++) {
address provider = rewardProviders.at(i);
externalBalance += IRewardProvider(provider).rewardsBalanceOfAccount(account);
}

return super.balanceOf(account) + externalBalance;
Expand Down
20 changes: 19 additions & 1 deletion src/RewardsStreamerMP.sol
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,9 @@ contract RewardsStreamerMP is
/// @notice Total amount of staked multiplier points
uint256 public totalMPStaked;

/// @notice The address that can set rewards
address public rewardsSupplier;

modifier onlyRegisteredVault() {
if (vaultOwners[msg.sender] == address(0)) {
revert StakingManager__VaultNotRegistered();
Expand All @@ -88,6 +91,13 @@ contract RewardsStreamerMP is
_;
}

modifier onlyRewardsSupplier() {
if (msg.sender != rewardsSupplier) {
revert StakingManager__Unauthorized();
}
_;
}

/**
* @notice Initializes the contract.
* @dev Disables initializers to prevent reinitialization.
Expand All @@ -111,6 +121,10 @@ contract RewardsStreamerMP is
lastMPUpdatedTime = block.timestamp;
}

function setRewardsSupplier(address _rewardsSupplier) external onlyOwner {
rewardsSupplier = _rewardsSupplier;
}

/**
* @notice Authorizes contract upgrades via UUPS.
* @dev This function is only callable by the owner.
Expand Down Expand Up @@ -417,7 +431,11 @@ contract RewardsStreamerMP is
* @param amount The amount of rewards to distribute.
* @param duration The duration of the reward period.
*/
function setReward(uint256 amount, uint256 duration) external onlyOwner {
function setReward(uint256 amount, uint256 duration) external onlyRewardsSupplier {
if (rewardEndTime > block.timestamp) {
revert StakingManager__RewardPeriodNotEnded();
}

if (duration == 0) {
revert StakingManager__DurationCannotBeZero();
}
Expand Down
1 change: 1 addition & 0 deletions src/interfaces/IRewardProvider.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@ interface IRewardProvider {
function totalRewardsSupply() external view returns (uint256);
function rewardsBalanceOf(address account) external view returns (uint256);
function rewardsBalanceOfAccount(address user) external view returns (uint256);
function setReward(uint256 amount, uint256 duration) external;
}
1 change: 1 addition & 0 deletions src/interfaces/IStakeManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ interface IStakeManager is ITrustedCodehashAccess, IStakeConstants {
error StakingManager__Unauthorized();
error StakingManager__DurationCannotBeZero();
error StakingManager__InsufficientBalance();
error StakingManager__RewardPeriodNotEnded();

event VaultRegistered(address indexed vault, address indexed owner);
event VaultMigrated(address indexed from, address indexed to);
Expand Down
Loading

0 comments on commit d5c9f37

Please sign in to comment.