Contract detail
RoboSalaryPool
RoboSalaryPool is a verified Robinhood Chain smart contract at 0x2eaf5736…964da360 with 1 indexed transactions.
- Address type
- Smart contract
- Transactions
- 1
- First indexed block
- #28275052
- Latest indexed block
- #28275052
- Emitted events
- 7
- Verification
- Verified
- Compiler
- v0.8.28+commit.7893614a
- Verification match
- full
- Creation transaction
- 0xda39efb6…8a4898aa
- Created at block
- #28274551
- Language
- solidity
- EVM version
- paris
- License
- none
- Compiler optimization
- Enabled · 200 runs
Related activity
Transactions
| Transaction | Block | From | To | Value (wei) |
|---|---|---|---|---|
| 0xf476485d…0a61e5d6 | #28275052 | 0x5c69ee4c…7a920452 | 0x2eaf5736…964da360 | 0 |
Verified contract
Contract source code
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {Ownable2Step} from "@openzeppelin/contracts/access/Ownable2Step.sol";
import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import {Math} from "@openzeppelin/contracts/utils/math/Math.sol";
import {IActivationTransferHook, IRewardPool} from "./interfaces/IRoboSalaryPool.sol";
interface IERC20BurnFrom is IERC20 {
function burnFrom(address account, uint256 amount) external;
}
/// @title RoboBrokers Salary Pool
/// @notice Splits ROBO activation fees 50/50 between burn and an immutable
/// protocol treasury, then distributes the immutable NVDA/INTC/TSLA
/// robotics basket by activation weight.
contract RoboSalaryPool is IActivationTransferHook, IRewardPool, Ownable2Step, ReentrancyGuard {
using SafeERC20 for IERC20;
uint256 private constant ACCURACY = 1e24;
uint8 public constant REWARD_COUNT = 3;
uint8 public constant NVDA_REWARD = 0;
uint8 public constant INTC_REWARD = 1;
uint8 public constant TSLA_REWARD = 2;
uint8 public constant TIER_COUNT = 5;
uint8 public constant JUNIOR_BROKER = 0;
uint8 public constant MARKET_ANALYST = 1;
uint8 public constant PORTFOLIO_MANAGER = 2;
uint8 public constant MANAGING_DIRECTOR = 3;
uint8 public constant PARTNER = 4;
struct Tier {
uint128 activationCost;
uint32 weight100;
}
struct Activation {
address owner;
uint32 weight100;
uint8 tier;
bool active;
}
IERC20BurnFrom public immutable robo;
IERC721 public immutable nft;
address public immutable activationTreasury;
IERC20[3] public rewardTokens;
bool public tierConfigurationFrozen;
uint256 public activeRobotCount;
uint256 public override totalActiveWeight100;
mapping(uint8 rewardIndex => uint256 amount) public accumulatedRewardPerWeight;
mapping(uint8 tier => Tier config) public tiers;
mapping(uint256 tokenId => Activation activation) public activations;
mapping(uint256 tokenId => mapping(uint8 rewardIndex => uint256 amount)) public rewardDebt;
mapping(address account => mapping(uint8 rewardIndex => uint256 amount)) public storedRewards;
error ZeroAddress();
error InvalidContract();
error IdenticalTokens();
error NotNFTOwner();
error AlreadyActive();
error NotNFTContract();
error InvalidTransferOwner();
error InvalidReward();
error InvalidTier();
error InvalidTierConfiguration();
error InvalidTierOrder();
error InvalidUpgrade();
error TierConfigurationIsFrozen();
error TierConfigurationNotFrozen();
error NoActiveRobots();
error NoRewardsProvided();
event RobotActivated(
uint256 indexed tokenId,
address indexed owner,
uint8 indexed tier,
uint256 roboBurned,
uint256 roboToTreasury,
uint32 weight100
);
event RobotDeactivated(
uint256 indexed tokenId,
address indexed previousOwner,
uint256 nvdaStored,
uint256 intcStored,
uint256 tslaStored
);
event RobotUpgraded(
uint256 indexed tokenId,
address indexed owner,
uint8 indexed previousTier,
uint8 newTier,
uint256 roboBurned,
uint256 roboToTreasury,
uint32 newWeight100,
uint256 nvdaStored,
uint256 intcStored,
uint256 tslaStored
);
event RewardsFunded(
uint8 indexed rewardIndex,
address indexed rewardToken,
address indexed funder,
uint256 amount
);
event RewardsClaimed(
uint8 indexed rewardIndex,
address indexed rewardToken,
address indexed account,
uint256 amount
);
event TierUpdated(uint8 indexed tier, uint256 activationCost, uint32 weight100);
event TierConfigurationFrozen();
constructor(
address initialOwner,
address roboToken,
address nvdaToken,
address intcToken,
address tslaToken,
address nftContract,
address protocolTreasury
) Ownable(initialOwner) {
if (
initialOwner == address(0) ||
roboToken == address(0) ||
nvdaToken == address(0) ||
intcToken == address(0) ||
tslaToken == address(0) ||
nftContract == address(0) ||
protocolTreasury == address(0)
) revert ZeroAddress();
if (
roboToken == nvdaToken || roboToken == intcToken || roboToken == tslaToken ||
nvdaToken == intcToken || nvdaToken == tslaToken || intcToken == tslaToken
) {
revert IdenticalTokens();
}
if (
roboToken.code.length == 0 ||
nvdaToken.code.length == 0 ||
intcToken.code.length == 0 ||
tslaToken.code.length == 0 ||
nftContract.code.length == 0
) revert InvalidContract();
robo = IERC20BurnFrom(roboToken);
rewardTokens[NVDA_REWARD] = IERC20(nvdaToken);
rewardTokens[INTC_REWARD] = IERC20(intcToken);
rewardTokens[TSLA_REWARD] = IERC20(tslaToken);
nft = IERC721(nftContract);
activationTreasury = protocolTreasury;
_setTier(JUNIOR_BROKER, 55_555 ether, 100);
_setTier(MARKET_ANALYST, 144_444 ether, 120);
_setTier(PORTFOLIO_MANAGER, 333_333 ether, 150);
_setTier(MANAGING_DIRECTOR, 555_555 ether, 200);
_setTier(PARTNER, 1_444_444 ether, 300);
}
function activate(uint256 tokenId, uint8 tier) external nonReentrant {
if (!tierConfigurationFrozen) revert TierConfigurationNotFrozen();
if (tier >= TIER_COUNT) revert InvalidTier();
if (nft.ownerOf(tokenId) != msg.sender) revert NotNFTOwner();
if (activations[tokenId].active) revert AlreadyActive();
Tier memory config = tiers[tier];
(uint256 burnAmount, uint256 treasuryAmount) =
_collectActivationFee(msg.sender, config.activationCost);
activations[tokenId] = Activation({
owner: msg.sender,
weight100: config.weight100,
tier: tier,
active: true
});
for (uint8 rewardIndex; rewardIndex < REWARD_COUNT; ++rewardIndex) {
rewardDebt[tokenId][rewardIndex] = _accumulated(config.weight100, rewardIndex);
}
++activeRobotCount;
totalActiveWeight100 += config.weight100;
emit RobotActivated(
tokenId,
msg.sender,
tier,
burnAmount,
treasuryAmount,
config.weight100
);
}
function upgrade(uint256 tokenId, uint8 newTier) external nonReentrant {
if (!tierConfigurationFrozen) revert TierConfigurationNotFrozen();
if (newTier >= TIER_COUNT) revert InvalidTier();
Activation storage activation = activations[tokenId];
if (!activation.active || activation.owner != msg.sender || nft.ownerOf(tokenId) != msg.sender) {
revert NotNFTOwner();
}
uint8 previousTier = activation.tier;
if (newTier <= previousTier) revert InvalidUpgrade();
Tier memory previousConfig = tiers[previousTier];
Tier memory newConfig = tiers[newTier];
uint256 upgradeCost = uint256(newConfig.activationCost) - previousConfig.activationCost;
uint256[3] memory amountsStored;
for (uint8 rewardIndex; rewardIndex < REWARD_COUNT; ++rewardIndex) {
amountsStored[rewardIndex] = _storePending(tokenId, activation, rewardIndex);
}
(uint256 burnAmount, uint256 treasuryAmount) =
_collectActivationFee(msg.sender, upgradeCost);
totalActiveWeight100 = totalActiveWeight100 - activation.weight100 + newConfig.weight100;
activation.weight100 = newConfig.weight100;
activation.tier = newTier;
for (uint8 rewardIndex; rewardIndex < REWARD_COUNT; ++rewardIndex) {
rewardDebt[tokenId][rewardIndex] = _accumulated(newConfig.weight100, rewardIndex);
}
emit RobotUpgraded(
tokenId,
msg.sender,
previousTier,
newTier,
burnAmount,
treasuryAmount,
newConfig.weight100,
amountsStored[NVDA_REWARD],
amountsStored[INTC_REWARD],
amountsStored[TSLA_REWARD]
);
}
function _collectActivationFee(address payer, uint256 amount)
private
returns (uint256 burnAmount, uint256 treasuryAmount)
{
burnAmount = amount / 2;
treasuryAmount = amount - burnAmount;
robo.burnFrom(payer, burnAmount);
IERC20(address(robo)).safeTransferFrom(payer, activationTreasury, treasuryAmount);
}
function onNFTTransfer(uint256 tokenId, address previousOwner, address) external override nonReentrant {
if (msg.sender != address(nft)) revert NotNFTContract();
Activation memory activation = activations[tokenId];
if (!activation.active) return;
if (activation.owner != previousOwner) revert InvalidTransferOwner();
uint256[3] memory amountsStored;
for (uint8 rewardIndex; rewardIndex < REWARD_COUNT; ++rewardIndex) {
amountsStored[rewardIndex] = _storePending(tokenId, activation, rewardIndex);
delete rewardDebt[tokenId][rewardIndex];
}
delete activations[tokenId];
--activeRobotCount;
totalActiveWeight100 -= activation.weight100;
emit RobotDeactivated(
tokenId,
previousOwner,
amountsStored[NVDA_REWARD],
amountsStored[INTC_REWARD],
amountsStored[TSLA_REWARD]
);
}
function fundRewards(uint8 rewardIndex, uint256 amount)
external
override
nonReentrant
returns (uint256 received)
{
IERC20 rewardToken = _rewardToken(rewardIndex);
if (totalActiveWeight100 == 0) revert NoActiveRobots();
uint256 balanceBefore = rewardToken.balanceOf(address(this));
rewardToken.safeTransferFrom(msg.sender, address(this), amount);
received = rewardToken.balanceOf(address(this)) - balanceBefore;
if (received == 0) revert NoRewardsProvided();
accumulatedRewardPerWeight[rewardIndex] += Math.mulDiv(
received,
ACCURACY,
totalActiveWeight100
);
emit RewardsFunded(rewardIndex, address(rewardToken), msg.sender, received);
}
/// @notice Claims one reward asset at a time so a problem with one stock
/// token cannot block claims of the other components.
function claim(uint8 rewardIndex, uint256[] calldata activeTokenIds)
external
nonReentrant
returns (uint256 amount)
{
amount = _claim(rewardIndex, msg.sender, activeTokenIds);
}
/// @notice Convenience path for the UI. This is atomic; users can fall
/// back to claim(index, ids) if any individual stock token pauses.
function claimAll(uint256[] calldata activeTokenIds)
external
nonReentrant
returns (uint256[3] memory amounts)
{
for (uint8 rewardIndex; rewardIndex < REWARD_COUNT; ++rewardIndex) {
amounts[rewardIndex] = _claim(rewardIndex, msg.sender, activeTokenIds);
}
}
function pendingRewards(uint256 tokenId, uint8 rewardIndex) external view returns (uint256) {
if (rewardIndex >= REWARD_COUNT) revert InvalidReward();
Activation memory activation = activations[tokenId];
return activation.active ? _pending(tokenId, activation, rewardIndex) : 0;
}
function setTier(uint8 tier, uint128 activationCost, uint32 weight100) external onlyOwner {
if (tierConfigurationFrozen) revert TierConfigurationIsFrozen();
_setTier(tier, activationCost, weight100);
}
function freezeTierConfiguration() external onlyOwner {
for (uint8 tier = 1; tier < TIER_COUNT; ++tier) {
Tier memory previous = tiers[tier - 1];
Tier memory current = tiers[tier];
if (
current.activationCost <= previous.activationCost ||
current.weight100 <= previous.weight100
) revert InvalidTierOrder();
}
tierConfigurationFrozen = true;
emit TierConfigurationFrozen();
}
function _setTier(uint8 tier, uint128 activationCost, uint32 weight100) private {
if (tier >= TIER_COUNT) revert InvalidTier();
if (activationCost == 0 || weight100 == 0) revert InvalidTierConfiguration();
tiers[tier] = Tier(activationCost, weight100);
emit TierUpdated(tier, activationCost, weight100);
}
function _rewardToken(uint8 rewardIndex) private view returns (IERC20) {
if (rewardIndex >= REWARD_COUNT) revert InvalidReward();
return rewardTokens[rewardIndex];
}
function _claim(uint8 rewardIndex, address account, uint256[] calldata activeTokenIds)
private
returns (uint256 amount)
{
IERC20 rewardToken = _rewardToken(rewardIndex);
amount = storedRewards[account][rewardIndex];
storedRewards[account][rewardIndex] = 0;
for (uint256 i; i < activeTokenIds.length; ++i) {
uint256 tokenId = activeTokenIds[i];
Activation storage activation = activations[tokenId];
if (!activation.active || activation.owner != account) revert NotNFTOwner();
amount += _pending(tokenId, activation, rewardIndex);
rewardDebt[tokenId][rewardIndex] = _accumulated(activation.weight100, rewardIndex);
}
if (amount != 0) rewardToken.safeTransfer(account, amount);
emit RewardsClaimed(rewardIndex, address(rewardToken), account, amount);
}
function _accumulated(uint32 weight100, uint8 rewardIndex) private view returns (uint256) {
return Math.mulDiv(weight100, accumulatedRewardPerWeight[rewardIndex], ACCURACY);
}
function _pending(uint256 tokenId, Activation memory activation, uint8 rewardIndex)
private
view
returns (uint256)
{
return _accumulated(activation.weight100, rewardIndex) - rewardDebt[tokenId][rewardIndex];
}
function _storePending(uint256 tokenId, Activation memory activation, uint8 rewardIndex)
private
returns (uint256 amount)
{
amount = _pending(tokenId, activation, rewardIndex);
if (amount != 0) storedRewards[activation.owner][rewardIndex] += amount;
}
}