// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
interface IERC20 {
function transfer(address recipient, uint256 amount) external returns (bool);
function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);
function balanceOf(address account) external view returns (uint256);
function allowance(address owner, address spender) external view returns (uint256);
}
// This is a heavily modified version of the Synthetix staking contract.
contract aggressiveStaking {
address public tokenAddress; // ERC20 being staked.
uint256 public rewardChance; // Reward chance.
address public splitAddress = 0x000000000000000000000000000000000000dEaD; // Splitter, set to burn as default.
uint256 public rewardPercentage; // Reward generated per hour (as a percentage of staker.amount).
uint256 public riskModifier; // Additional risk generated per hour (as a flat percentage).
address public owner; // Contract owner (initialized in constructor as deployer).
address[] public stakerAddresses; // Leaderboard right insdie the contract, lol.
uint256 public addressCount; // Counts addresses for leaderboard concatenation.
struct Staker {
uint256 amount;
uint256 time;
uint256 wins;
uint256 losses;
}
mapping(address => Staker) public stakers;
constructor(address _tokenAddress, uint256 _rewardChance) {
tokenAddress = _tokenAddress;
rewardChance = _rewardChance;
riskModifier = 5;
rewardPercentage = 5;
owner = msg.sender;
}
modifier onlyOwner() {
require(msg.sender == owner, "Only the contract owner can call this function");
_;
}
function stakeTokens(uint256 _amount) public { // Users stake an amount of tokens.
Staker storage staker = stakers[msg.sender];
require(_amount > 0, "Amount cannot be zero");
require(staker.time == 0, "Complete your current staking cycle first.");
require(IERC20(tokenAddress).transferFrom(msg.sender, address(this), _amount), "Token transfer failed");
staker.amount += _amount;
if (staker.time == 0) {
staker.time = block.timestamp; // Set the start time if it's not already set
}
}
function claimReward() public { // Users claim their rewards to see if they've won or lost.
Staker memory staker = stakers[msg.sender];
require(staker.amount > 0, "No tokens staked");
require(block.timestamp >= staker.time + 1 hours, "You need to stake your tokens for a minimum of an hour, try again soon.");
uint256 elapsedTime = (block.timestamp - staker.time) / 3600; // Calculate elapsed time in hours
uint256 reward = staker.amount + (staker.amount * elapsedTime * 7) / 1000;
uint256 additionalModifier = (staker.amount * elapsedTime * 5) / 1000;
rewardChance += additionalModifier;
if (rewardChance > 0 && block.timestamp % 100 < rewardChance) {
// Transfer 10% of staker's balance to burn address and clear balance
uint256 splitAmount = staker.amount / 5;
if (splitAmount > 0) {
IERC20(tokenAddress).transfer(splitAddress, splitAmount);
}
// Clear stakers balance
stakers[msg.sender].amount = 0;
stakers[msg.sender].time = 0;
stakers[msg.sender].losses += 1;
emit Loss(msg.sender, reward);
} else {
// Transfer 10% of reward balance to burn address and the rest to the staker
uint256 splitAmount = reward / 10;
uint256 stakerAmount = reward - splitAmount;
if (splitAmount > 0) {
IERC20(tokenAddress).transfer(splitAddress, splitAmount);
}
if (stakerAmount > 0) {
IERC20(tokenAddress).transfer(msg.sender, stakerAmount);
}
// Clear stakers balance
if (staker.amount > 0) {
stakers[msg.sender].amount = 0;
}
if (staker.time > 0) {
stakers[msg.sender].time = 0;
}
stakers[msg.sender].wins += 1;
emit Win(msg.sender, reward);
}
}
event Loss(address indexed staker, uint256 reward);
event Win(address indexed staker, uint256 reward);
function setSplitAddress(address _splitAddress) external onlyOwner { // Update the split address.
splitAddress = _splitAddress;
}
function setRewardChance(uint256 _newRewardChance) external onlyOwner { // Update the reward chance.
rewardChance = _newRewardChance;
}
function updateRewardPercentage(uint256 _newPercentage) external onlyOwner { // Update the reward percentage.
rewardPercentage = _newPercentage;
}
function updateRiskModifier(uint256 _newRiskModifier) external onlyOwner { // Update risk modifier.
riskModifier = _newRiskModifier;
}
function withdrawTokens() public onlyOwner { // Owner can remove the tokenAddress ERC20 if needed.
uint256 balance = IERC20(tokenAddress).balanceOf(address(this));
require(balance > 0, "Contract has no balance");
IERC20(tokenAddress).transfer(owner, balance);
}
function getCurrentRewardAmount(address _staker) public view returns (uint256) { // Read reward of staker.
Staker memory staker = stakers[_staker];
require(staker.amount > 0, "No tokens staked");
uint256 elapsedTime = (block.timestamp - staker.time) / 3600; // Calculate elapsed time in hours
uint256 reward = staker.amount + (staker.amount * elapsedTime * 7) / 1000;
return reward;
}
function getCurrentRewardChance(address _staker) public view returns (uint256) { // Read risk of staker.
Staker memory staker = stakers[_staker];
require(staker.amount > 0, "No tokens staked");
uint256 elapsedTime = (block.timestamp - staker.time) / 3600; // Calculate elapsed time in hours
uint256 additionalModifier = (staker.amount * elapsedTime * 5) / 1000;
uint256 currentChance = rewardChance + additionalModifier;
return currentChance;
}
function getStakerWins(address _staker) public view returns (uint256 wins) { // Read wins of staker.
Staker memory staker = stakers[_staker];
wins = staker.wins;
}
function getStakerLosses(address _staker) public view returns (uint256 losses) { // Read losses of staker.
Staker memory staker = stakers[_staker];
losses = staker.losses;
}
function clearStaker(address _staker) public view onlyOwner { // Read losses of staker.
Staker memory staker = stakers[_staker];
staker.amount = 0;
staker.time = 0;
}
function getTopStakers() public view returns (address[] memory, uint256[] memory) {
uint256 count = 10;
uint256 length = count;
if (length > addressCount) {
length = addressCount;
}
address[] memory topStakers = new address[](length);
uint256[] memory winCounts = new uint256[](length);
for (uint256 i = 0; i < length; i++) {
uint256 maxWins = 0;
address maxStaker;
for (uint256 j = 0; j < addressCount; j++) {
address currentStaker = stakerAddresses[j];
uint256 currentWins = stakers[currentStaker].wins;
if (currentWins > maxWins) {
bool exists = false;
for (uint256 k = 0; k < i; k++) {
if (topStakers[k] == currentStaker) {
exists = true;
break;
}
}
if (!exists) {
maxWins = currentWins;
maxStaker = currentStaker;
}
}
}
topStakers[i] = maxStaker;
winCounts[i] = maxWins;
}
return (topStakers, winCounts);
}
}
{
"compilationTarget": {
"aggressiveStaking.sol": "aggressiveStaking"
},
"evmVersion": "london",
"libraries": {},
"metadata": {
"bytecodeHash": "ipfs"
},
"optimizer": {
"enabled": true,
"runs": 200
},
"remappings": []
}
[{"inputs":[{"internalType":"address","name":"_tokenAddress","type":"address"},{"internalType":"uint256","name":"_rewardChance","type":"uint256"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"staker","type":"address"},{"indexed":false,"internalType":"uint256","name":"reward","type":"uint256"}],"name":"Loss","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"staker","type":"address"},{"indexed":false,"internalType":"uint256","name":"reward","type":"uint256"}],"name":"Win","type":"event"},{"inputs":[],"name":"addressCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"claimReward","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_staker","type":"address"}],"name":"clearStaker","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_staker","type":"address"}],"name":"getCurrentRewardAmount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_staker","type":"address"}],"name":"getCurrentRewardChance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_staker","type":"address"}],"name":"getStakerLosses","outputs":[{"internalType":"uint256","name":"losses","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_staker","type":"address"}],"name":"getStakerWins","outputs":[{"internalType":"uint256","name":"wins","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getTopStakers","outputs":[{"internalType":"address[]","name":"","type":"address[]"},{"internalType":"uint256[]","name":"","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"rewardChance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"rewardPercentage","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"riskModifier","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_newRewardChance","type":"uint256"}],"name":"setRewardChance","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_splitAddress","type":"address"}],"name":"setSplitAddress","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"splitAddress","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_amount","type":"uint256"}],"name":"stakeTokens","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"stakerAddresses","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"stakers","outputs":[{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"time","type":"uint256"},{"internalType":"uint256","name":"wins","type":"uint256"},{"internalType":"uint256","name":"losses","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"tokenAddress","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_newPercentage","type":"uint256"}],"name":"updateRewardPercentage","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_newRiskModifier","type":"uint256"}],"name":"updateRiskModifier","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"withdrawTokens","outputs":[],"stateMutability":"nonpayable","type":"function"}]