ElementStaking
0x904353dfedf884b474ab1f474cf88ee43bc7aa40
Verification
Verified
v0.8.24+commit.e11b9ed9
Type
Contract
4,376 bytes
ABI entries
44
15 read · 8 write
License
none
Contract information
- Address
- 0x904353dfedf884b474ab1f474cf88ee43bc7aa40
- Chain
- Robinhood Chain (4663)
- Compiler
- v0.8.24+commit.e11b9ed9
- Optimization
- Enabled
- Creator
- 0x847BC24350…bae092c4FA
- Creation tx
- 0x41afed14ed…f4f2a26ed2
Token
Not a token
This contract does not expose ERC-20 metadata.
Read contract (15)
element() → address
lastUpdate() → uint64
owner() → address
pendingOwner() → address
pendingReward(address) → uint256
positions(address) → uint256, uint8, uint64, uint256, uint256, uint256
rewardIndex() → uint256
stakedBalanceOf(address) → uint256
tierDurations(uint256) → uint64
tierMultipliers(uint256) → uint256
totalClaimed() → uint256
totalDistributed() → uint256
totalStaked() → uint256
totalWeightedStake() → uint256
usdg() → address
Events (7)
OwnershipTransferStartedOwnershipTransferredRewardClaimedRewardIndexUpdatedStakedTiersSetUnstaked
ABI
[
{
"inputs": [
{
"internalType": "address",
"name": "element_",
"type": "address"
},
{
"internalType": "address",
"name": "usdg_",
"type": "address"
},
{
"internalType": "uint64[3]",
"name": "durations_",
"type": "uint64[3]"
},
{
"internalType": "uint256[3]",
"name": "multipliers_",
"type": "uint256[3]"
}
],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"inputs": [],
"name": "AmountMismatch",
"type": "error"
},
{
"inputs": [],
"name": "BadTierConfig",
"type": "error"
},
{
"inputs": [],
"name": "InvalidTier",
"type": "error"
},
{
"inputs": [],
"name": "LockupNotExpired",
"type": "error"
},
{
"inputs": [],
"name": "NoRewards",
"type": "error"
},
{
"inputs": [],
"name": "NoStake",
"type": "error"
},
{
"inputs": [
{
"internalType": "address",
"name": "owner",
"type": "address"
}
],
"name": "OwnableInvalidOwner",
"type": "error"
},
{
"inputs": [
{
"internalType": "address",
"name": "account",
"type": "address"
}
],
"name": "OwnableUnauthorizedAccount",
"type": "error"
},
{
"inputs": [
{
"internalType": "address",
"name": "token",
"type": "address"
}
],
"name": "SafeERC20FailedOperation",
"type": "error"
},
{
"inputs": [],
"name": "TierDowngrade",
"type": "error"
},
{
"inputs": [],
"name": "TierMultiplierTooHigh",
"type": "error"
},
{
"inputs": [],
"name": "ZeroAddress",
"type": "error"
},
{
"inputs": [],
"name": "ZeroAmount",
"type": "error"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "previousOwner",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "newOwner",
"type": "address"
}
],
"name": "OwnershipTransferStarted",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "previousOwner",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "newOwner",
"type": "address"
}
],
"name": "OwnershipTransferred",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "owner",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "amountUsdg",
"type": "uint256"
}
],
"name": "RewardClaimed",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "uint256",
"name": "rewardIndex",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "newRewards",
"type": "uint256"
}
],
"name": "RewardIndexUpdated",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "owner",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "amount",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint8",
"name": "tier",
"type": "uint8"
},
{
"indexed": false,
"internalType": "uint64",
"name": "lockupExpiry",
"type": "uint64"
},
{
"indexed": false,
"internalType": "uint256",
"name": "weightedAmount",
"type": "uint256"
}
],
"name": "Staked",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "uint64[3]",
"name": "durations",
"type": "uint64[3]"
},
{
"indexed": false,
"internalType": "uint256[3]",
"name": "multipliers",
"type": "uint256[3]"
}
],
"name": "TiersSet",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "owner",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "Unstaked",
"type": "event"
},
{
"inputs": [],
"name": "acceptOwnership",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "claimReward",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "element",
"outputs": [
{
"internalType": "contract IERC20",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "lastUpdate",
"outputs": [
{
"internalType": "uint64",
"name": "",
"type": "uint64"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "owner",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "pendingOwner",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "owner",
"type": "address"
}
],
"name": "pendingReward",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"name": "positions",
"outputs": [
{
"internalType": "uint256",
"name": "amount",
"type": "uint256"
},
{
"internalType": "uint8",
"name": "tier",
"type": "uint8"
},
{
"internalType": "uint64",
"name": "lockupExpiry",
"type": "uint64"
},
{
"internalType": "uint256",
"name": "weightedAmount",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "rewardIndexSnap",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "accruedReward",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "renounceOwnership",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "rewardIndex",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint64[3]",
"name": "durations_",
"type": "uint64[3]"
},
{
"internalType": "uint256[3]",
"name": "multipliers_",
"type": "uint256[3]"
}
],
"name": "setTiers",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "amount",
"type": "uint256"
},
{
"internalType": "uint8",
"name": "tier",
"type": "uint8"
}
],
"name": "stake",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "owner",
"type": "address"
}
],
"name": "stakedBalanceOf",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"name": "tierDurations",
"outputs": [
{
"internalType": "uint64",
"name": "",
"type": "uint64"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"name": "tierMultipliers",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "totalClaimed",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "totalDistributed",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "totalStaked",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "totalWeightedStake",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "newOwner",
"type": "address"
}
],
"name": "transferOwnership",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "unstake",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "updateRewardIndex",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "usdg",
"outputs": [
{
"internalType": "contract IERC20",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
}
]Source code
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {Ownable2Step, Ownable} from "@openzeppelin/contracts/access/Ownable2Step.sol";
import {IStakingPool} from "./interfaces/IStakingPool.sol";
contract ElementStaking is IStakingPool, Ownable2Step {
using SafeERC20 for IERC20;
uint256 internal constant PRECISION = 1e18;
uint256 internal constant NUM_TIERS = 3;
uint256 internal constant MAX_TIER_MULTIPLIER = 1000;
IERC20 public immutable element;
IERC20 public immutable usdg;
uint64[NUM_TIERS] public tierDurations;
uint256[NUM_TIERS] public tierMultipliers;
uint256 public rewardIndex;
uint256 public totalWeightedStake;
uint256 public totalStaked;
uint256 public totalDistributed;
uint256 public totalClaimed;
uint64 public lastUpdate;
struct Position {
uint256 amount;
uint8 tier;
uint64 lockupExpiry;
uint256 weightedAmount;
uint256 rewardIndexSnap;
uint256 accruedReward;
}
mapping(address => Position) public positions;
error ZeroAddress();
error ZeroAmount();
error InvalidTier();
error TierDowngrade();
error LockupNotExpired();
error NoStake();
error NoRewards();
error AmountMismatch();
error BadTierConfig();
error TierMultiplierTooHigh();
event Staked(address indexed owner, uint256 amount, uint8 tier, uint64 lockupExpiry, uint256 weightedAmount);
event Unstaked(address indexed owner, uint256 amount);
event RewardClaimed(address indexed owner, uint256 amountUsdg);
event RewardIndexUpdated(uint256 rewardIndex, uint256 newRewards);
event TiersSet(uint64[3] durations, uint256[3] multipliers);
constructor(address element_, address usdg_, uint64[3] memory durations_, uint256[3] memory multipliers_)
Ownable(msg.sender)
{
if (element_ == address(0) || usdg_ == address(0)) revert ZeroAddress();
element = IERC20(element_);
usdg = IERC20(usdg_);
_setTiers(durations_, multipliers_);
lastUpdate = uint64(block.timestamp);
}
function _cumulativeReceived() internal view returns (uint256) {
return usdg.balanceOf(address(this)) + totalClaimed;
}
function updateRewardIndex() public {
uint256 cumulative = _cumulativeReceived();
uint256 newRewards = cumulative > totalDistributed ? cumulative - totalDistributed : 0;
if (totalWeightedStake != 0 && newRewards != 0) {
rewardIndex += (newRewards * PRECISION) / totalWeightedStake;
totalDistributed += newRewards;
emit RewardIndexUpdated(rewardIndex, newRewards);
}
lastUpdate = uint64(block.timestamp);
}
function _pending(Position storage p) internal view returns (uint256) {
return (p.weightedAmount * (rewardIndex - p.rewardIndexSnap)) / PRECISION + p.accruedReward;
}
function pendingReward(address owner) external view returns (uint256) {
Position storage p = positions[owner];
uint256 idx = rewardIndex;
if (totalWeightedStake != 0) {
uint256 cumulative = _cumulativeReceived();
uint256 newRewards = cumulative > totalDistributed ? cumulative - totalDistributed : 0;
if (newRewards != 0) idx += (newRewards * PRECISION) / totalWeightedStake;
}
return (p.weightedAmount * (idx - p.rewardIndexSnap)) / PRECISION + p.accruedReward;
}
function stakedBalanceOf(address owner) external view returns (uint256) {
return positions[owner].amount;
}
function stake(uint256 amount, uint8 tier) external {
if (tier >= NUM_TIERS) revert InvalidTier();
Position storage p = positions[msg.sender];
bool existing = p.amount > 0;
if (amount == 0 && !existing) revert ZeroAmount();
updateRewardIndex();
if (existing) {
if (tier < p.tier) revert TierDowngrade();
p.accruedReward = _pending(p);
}
p.rewardIndexSnap = rewardIndex;
if (amount > 0) {
uint256 balBefore = element.balanceOf(address(this));
element.safeTransferFrom(msg.sender, address(this), amount);
uint256 received = element.balanceOf(address(this)) - balBefore;
if (received != amount) revert AmountMismatch();
}
uint256 newAmount = p.amount + amount;
uint256 newWeighted = newAmount * tierMultipliers[tier];
totalWeightedStake = totalWeightedStake - p.weightedAmount + newWeighted;
totalStaked = totalStaked - p.amount + newAmount;
p.amount = newAmount;
p.tier = tier;
uint64 newExpiry = uint64(block.timestamp) + tierDurations[tier];
if (newExpiry > p.lockupExpiry) p.lockupExpiry = newExpiry;
p.weightedAmount = newWeighted;
emit Staked(msg.sender, amount, tier, p.lockupExpiry, newWeighted);
}
function claimReward() external {
updateRewardIndex();
Position storage p = positions[msg.sender];
uint256 pending = _pending(p);
if (pending == 0) revert NoRewards();
p.accruedReward = 0;
p.rewardIndexSnap = rewardIndex;
totalClaimed += pending;
usdg.safeTransfer(msg.sender, pending);
emit RewardClaimed(msg.sender, pending);
}
function unstake() external {
Position storage p = positions[msg.sender];
if (p.amount == 0) revert NoStake();
if (block.timestamp < p.lockupExpiry) revert LockupNotExpired();
updateRewardIndex();
uint256 pending = _pending(p);
uint256 amount = p.amount;
totalWeightedStake -= p.weightedAmount;
totalStaked -= amount;
delete positions[msg.sender];
if (pending > 0) {
totalClaimed += pending;
usdg.safeTransfer(msg.sender, pending);
emit RewardClaimed(msg.sender, pending);
}
element.safeTransfer(msg.sender, amount);
emit Unstaked(msg.sender, amount);
}
function setTiers(uint64[3] calldata durations_, uint256[3] calldata multipliers_) external onlyOwner {
_setTiers(durations_, multipliers_);
}
function _setTiers(uint64[3] memory durations_, uint256[3] memory multipliers_) internal {
for (uint256 i = 0; i < NUM_TIERS; i++) {
if (durations_[i] == 0 || multipliers_[i] == 0) revert BadTierConfig();
if (multipliers_[i] > MAX_TIER_MULTIPLIER) revert TierMultiplierTooHigh();
tierDurations[i] = durations_[i];
tierMultipliers[i] = multipliers_[i];
}
emit TiersSet(durations_, multipliers_);
}
}
Chain explorer1315msChain node83ms