Transaction Hash:
Block:
20624083 at Aug-28-2024 02:23:59 AM +UTC
Transaction Fee:
0.000138005971564608 ETH
$0.22
Gas Used:
106,848 Gas / 1.291610246 Gwei
Emitted Events:
145 |
NodeStaking.RewardPaid( user=[Sender] 0xf5b1973d916fc4c30cf88f2398a2d4d80a216eeb, reward=32577517493288820 )
|
Account State Difference:
Address | Before | After | State Difference | ||
---|---|---|---|---|---|
0x4838B106...B0BAD5f97
Miner
| (Titan Builder) | 5.318977045418995269 Eth | 5.319030469418995269 Eth | 0.000053424 | |
0xF4dA8857...e2eeA6C85 | 35.079402205953690591 Eth | 35.046824688460401771 Eth | 0.03257751749328882 | ||
0xf5b1973d...80A216eeB |
0.007612694131308086 Eth
Nonce: 37
|
0.040052205653032298 Eth
Nonce: 38
| 0.032439511521724212 |
Execution Trace
NodeStaking.CALL( )
- ETH 0.03257751749328882
0xf5b1973d916fc4c30cf88f2398a2d4d80a216eeb.CALL( )
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable.sol) pragma solidity ^0.8.20; import {Context} from "../utils/Context.sol"; /** * @dev Contract module which provides a basic access control mechanism, where * there is an account (an owner) that can be granted exclusive access to * specific functions. * * The initial owner is set to the address provided by the deployer. This can * later be changed with {transferOwnership}. * * This module is used through inheritance. It will make available the modifier * `onlyOwner`, which can be applied to your functions to restrict their use to * the owner. */ abstract contract Ownable is Context { address private _owner; /** * @dev The caller account is not authorized to perform an operation. */ error OwnableUnauthorizedAccount(address account); /** * @dev The owner is not a valid owner account. (eg. `address(0)`) */ error OwnableInvalidOwner(address owner); event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); /** * @dev Initializes the contract setting the address provided by the deployer as the initial owner. */ constructor(address initialOwner) { if (initialOwner == address(0)) { revert OwnableInvalidOwner(address(0)); } _transferOwnership(initialOwner); } /** * @dev Throws if called by any account other than the owner. */ modifier onlyOwner() { _checkOwner(); _; } /** * @dev Returns the address of the current owner. */ function owner() public view virtual returns (address) { return _owner; } /** * @dev Throws if the sender is not the owner. */ function _checkOwner() internal view virtual { if (owner() != _msgSender()) { revert OwnableUnauthorizedAccount(_msgSender()); } } /** * @dev Leaves the contract without owner. It will not be possible to call * `onlyOwner` functions. Can only be called by the current owner. * * NOTE: Renouncing ownership will leave the contract without an owner, * thereby disabling any functionality that is only available to the owner. */ function renounceOwnership() public virtual onlyOwner { _transferOwnership(address(0)); } /** * @dev Transfers ownership of the contract to a new account (`newOwner`). * Can only be called by the current owner. */ function transferOwnership(address newOwner) public virtual onlyOwner { if (newOwner == address(0)) { revert OwnableInvalidOwner(address(0)); } _transferOwnership(newOwner); } /** * @dev Transfers ownership of the contract to a new account (`newOwner`). * Internal function without access restriction. */ function _transferOwnership(address newOwner) internal virtual { address oldOwner = _owner; _owner = newOwner; emit OwnershipTransferred(oldOwner, newOwner); } } // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol) pragma solidity ^0.8.20; /** * @dev Provides information about the current execution context, including the * sender of the transaction and its data. While these are generally available * via msg.sender and msg.data, they should not be accessed in such a direct * manner, since when dealing with meta-transactions the account sending and * paying for execution may not be the actual sender (as far as an application * is concerned). * * This contract is only required for intermediate, library-like contracts. */ abstract contract Context { function _msgSender() internal view virtual returns (address) { return msg.sender; } function _msgData() internal view virtual returns (bytes calldata) { return msg.data; } function _contextSuffixLength() internal view virtual returns (uint256) { return 0; } } // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v5.0.0) (utils/Pausable.sol) pragma solidity ^0.8.20; import {Context} from "../utils/Context.sol"; /** * @dev Contract module which allows children to implement an emergency stop * mechanism that can be triggered by an authorized account. * * This module is used through inheritance. It will make available the * modifiers `whenNotPaused` and `whenPaused`, which can be applied to * the functions of your contract. Note that they will not be pausable by * simply including this module, only once the modifiers are put in place. */ abstract contract Pausable is Context { bool private _paused; /** * @dev Emitted when the pause is triggered by `account`. */ event Paused(address account); /** * @dev Emitted when the pause is lifted by `account`. */ event Unpaused(address account); /** * @dev The operation failed because the contract is paused. */ error EnforcedPause(); /** * @dev The operation failed because the contract is not paused. */ error ExpectedPause(); /** * @dev Initializes the contract in unpaused state. */ constructor() { _paused = false; } /** * @dev Modifier to make a function callable only when the contract is not paused. * * Requirements: * * - The contract must not be paused. */ modifier whenNotPaused() { _requireNotPaused(); _; } /** * @dev Modifier to make a function callable only when the contract is paused. * * Requirements: * * - The contract must be paused. */ modifier whenPaused() { _requirePaused(); _; } /** * @dev Returns true if the contract is paused, and false otherwise. */ function paused() public view virtual returns (bool) { return _paused; } /** * @dev Throws if the contract is paused. */ function _requireNotPaused() internal view virtual { if (paused()) { revert EnforcedPause(); } } /** * @dev Throws if the contract is not paused. */ function _requirePaused() internal view virtual { if (!paused()) { revert ExpectedPause(); } } /** * @dev Triggers stopped state. * * Requirements: * * - The contract must not be paused. */ function _pause() internal virtual whenNotPaused { _paused = true; emit Paused(_msgSender()); } /** * @dev Returns to normal state. * * Requirements: * * - The contract must be paused. */ function _unpause() internal virtual whenPaused { _paused = false; emit Unpaused(_msgSender()); } } // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v5.0.0) (utils/ReentrancyGuard.sol) pragma solidity ^0.8.20; /** * @dev Contract module that helps prevent reentrant calls to a function. * * Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier * available, which can be applied to functions to make sure there are no nested * (reentrant) calls to them. * * Note that because there is a single `nonReentrant` guard, functions marked as * `nonReentrant` may not call one another. This can be worked around by making * those functions `private`, and then adding `external` `nonReentrant` entry * points to them. * * TIP: If you would like to learn more about reentrancy and alternative ways * to protect against it, check out our blog post * https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul]. */ abstract contract ReentrancyGuard { // Booleans are more expensive than uint256 or any type that takes up a full // word because each write operation emits an extra SLOAD to first read the // slot's contents, replace the bits taken up by the boolean, and then write // back. This is the compiler's defense against contract upgrades and // pointer aliasing, and it cannot be disabled. // The values being non-zero value makes deployment a bit more expensive, // but in exchange the refund on every call to nonReentrant will be lower in // amount. Since refunds are capped to a percentage of the total // transaction's gas, it is best to keep them low in cases like this one, to // increase the likelihood of the full refund coming into effect. uint256 private constant NOT_ENTERED = 1; uint256 private constant ENTERED = 2; uint256 private _status; /** * @dev Unauthorized reentrant call. */ error ReentrancyGuardReentrantCall(); constructor() { _status = NOT_ENTERED; } /** * @dev Prevents a contract from calling itself, directly or indirectly. * Calling a `nonReentrant` function from another `nonReentrant` * function is not supported. It is possible to prevent this from happening * by making the `nonReentrant` function external, and making it call a * `private` function that does the actual work. */ modifier nonReentrant() { _nonReentrantBefore(); _; _nonReentrantAfter(); } function _nonReentrantBefore() private { // On the first call to nonReentrant, _status will be NOT_ENTERED if (_status == ENTERED) { revert ReentrancyGuardReentrantCall(); } // Any calls to nonReentrant after this point will fail _status = ENTERED; } function _nonReentrantAfter() private { // By storing the original value once again, a refund is triggered (see // https://eips.ethereum.org/EIPS/eip-2200) _status = NOT_ENTERED; } /** * @dev Returns true if the reentrancy guard is currently set to "entered", which indicates there is a * `nonReentrant` function in the call stack. */ function _reentrancyGuardEntered() internal view returns (bool) { return _status == ENTERED; } } // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.24; import '@openzeppelin/contracts/utils/ReentrancyGuard.sol'; import '@openzeppelin/contracts/access/Ownable.sol'; import '@openzeppelin/contracts/utils/Pausable.sol'; 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 approve(address spender, uint256 value) external returns (bool); } contract NodeStaking is ReentrancyGuard, Ownable, Pausable { IERC20 public stakingToken; struct Stake { uint256 amount; uint256 startTime; uint256 rewardPaid; } mapping(address => Stake[]) public stakes; mapping(address => uint256) public totalUserRewards; address[] public stakers; mapping(address => bool) public isStaker; uint256 public totalRewards = 0; uint256 public totalStaked; uint256 public earlyUnstakePenality = 30; uint256 public maxTotalStaked = 10_000_000 * (10 ** 18); // Example: 10 million tokens uint256 public maxPerWallet = 1_000 * (10 ** 18); // Example: 1,000 tokens per wallet uint256 public constant stakingPeriod = 30 days; address public constant deadWallet = 0x000000000000000000000000000000000000dEaD; event Staked(address indexed user, uint256 amount, uint256 index); event Unstaked(address indexed user, uint256 amount, uint256 index); event RewardPaid(address indexed user, uint256 reward); event Migrated( address indexed newStakingContract, uint256 tokenAmount, uint256 ethAmount ); /* Initializes the constructure with the staking token set and owner */ constructor(address _stakingToken) Ownable(_msgSender()) { stakingToken = IERC20(_stakingToken); } /* Sets the staking token */ function setStakingToken(address _stakingToken) external onlyOwner { require(_stakingToken != address(0), 'Invalid address'); stakingToken = IERC20(_stakingToken); } /* Sets the maximum total staked amount */ function setMaxTotalStaked(uint256 _maxTotalStaked) external onlyOwner { maxTotalStaked = _maxTotalStaked; } /* Sets the maximum staked amount per wallet */ function setMaxPerWallet(uint256 _maxPerWallet) external onlyOwner { maxPerWallet = _maxPerWallet; } /* Sets the pause state of the contract */ function setPaused(bool _paused) external onlyOwner { require(_paused != paused(), 'Already in the requested state'); if (_paused) { _pause(); } else { _unpause(); } } /* Migrates the staking contract to a new contract */ function migrate(address _newStakingContract) external onlyOwner { require(_newStakingContract != address(0), 'Invalid address'); uint256 contractTokenBalance = stakingToken.balanceOf(address(this)); require( stakingToken.transfer(_newStakingContract, contractTokenBalance), 'Failed to transfer tokens to owner' ); uint256 contractETHBalance = address(this).balance; (bool sent, ) = _newStakingContract.call{value: contractETHBalance}(''); require(sent, 'Failed to transfer ETH'); emit Migrated( _newStakingContract, contractTokenBalance, contractETHBalance ); } function stake(uint256 _amount) external nonReentrant whenNotPaused { require(_amount > 0, 'Amount must be greater than 0'); require(totalStaked + _amount <= maxTotalStaked, 'Staking limit exceeded'); uint256 walletStaked = getWalletStaked(msg.sender); require(walletStaked + _amount <= maxPerWallet, 'Staking limit exceeded'); if(!isStaker[msg.sender]) { stakers.push(msg.sender); isStaker[msg.sender] = true; } bool success = stakingToken.transferFrom(msg.sender, address(this), _amount); require(success, 'Failed to transfer tokens'); stakes[msg.sender].push(Stake(_amount, block.timestamp, 0)); totalStaked += _amount; emit Staked(msg.sender, _amount, stakes[msg.sender].length - 1); } function unstake(uint256 _index, uint256 _amount) external nonReentrant { require(_index < stakes[msg.sender].length, 'Invalid stake index'); Stake storage userStake = stakes[msg.sender][_index]; require( block.timestamp >= userStake.startTime + stakingPeriod, 'Stake is still locked' ); require(userStake.amount >= _amount, 'Insufficient staked amount'); uint256 reward = _claimRewards(msg.sender, _index); if(reward > 0) { (bool sent, ) = payable(msg.sender).call{value: reward}(''); require(sent, 'Failed to send Ether'); emit RewardPaid(msg.sender, reward); } userStake.amount -= _amount; totalStaked -= _amount; if (userStake.amount == 0) { removeStake(msg.sender, _index); } bool success = stakingToken.transfer(msg.sender, _amount); require(success, 'Failed to transfer tokens'); emit Unstaked(msg.sender, _amount, _index); } function earlyUnstake(uint256 _index, uint256 _amount) external nonReentrant { require(_index < stakes[msg.sender].length, 'Invalid stake index'); Stake storage userStake = stakes[msg.sender][_index]; require(userStake.amount >= _amount, 'Insufficient staked amount'); uint256 timeElapsed = block.timestamp - userStake.startTime; require( timeElapsed < stakingPeriod, 'Stake is not in early unstake period' ); uint256 reward = _claimRewards(msg.sender, _index); if(reward > 0) { (bool sent, ) = payable(msg.sender).call{value: reward}(''); require(sent, 'Failed to send Ether'); emit RewardPaid(msg.sender, reward); } // Calculate the penalty fee, which linearly decreases from 50% to 0% over the lock-up period uint256 penaltyPercentage = earlyUnstakePenality - ((timeElapsed * earlyUnstakePenality) / stakingPeriod); uint256 penaltyAmount = (_amount * penaltyPercentage) / 100; // Apply the penalty uint256 returnAmount = _amount - penaltyAmount; // Update the stake and total staked amount userStake.amount -= _amount; totalStaked -= _amount; // Burn the penalty amount bool successBurn = stakingToken.transfer(deadWallet, penaltyAmount); require(successBurn, 'Failed to burn tokens'); // Return the remaining tokens to the user bool sucessUnstake = stakingToken.transfer(msg.sender, returnAmount); require(sucessUnstake, 'Failed to transfer tokens'); // Remove the stake if it's fully unstaked if (userStake.amount == 0) { removeStake(msg.sender, _index); } emit Unstaked(msg.sender, returnAmount, _index); } function _claimRewards( address user, uint256 index ) private returns (uint256) { require(index < stakes[user].length, 'Invalid stake index'); Stake storage userStake = stakes[user][index]; uint256 reward = calculateReward(user, index); if (reward > 0) { userStake.rewardPaid += reward; totalUserRewards[user] += reward; totalRewards += reward; } return reward; } function claimRewards() external nonReentrant { uint256 stakeCount = stakes[msg.sender].length; require(stakeCount > 0, 'No stakes available'); uint256 totalReward = 0; for (uint256 i = 0; i < stakeCount; i++) { totalReward += _claimRewards(msg.sender, i); // Aggregate rewards } if(totalReward <= 0) { return; } (bool sent, ) = payable(msg.sender).call{value: totalReward}(''); require(sent, 'Failed to send Ether'); emit RewardPaid(msg.sender, totalReward); } function getWalletStaked(address _user) public view returns (uint256) { uint256 walletStaked = 0; for (uint256 i = 0; i < stakes[_user].length; i++) { walletStaked += stakes[_user][i].amount; } return walletStaked; } function getWalletReward(address _user) public view returns (uint256) { uint256 walletReward = 0; for (uint256 i = 0; i < stakes[_user].length; i++) { walletReward += stakes[_user][i].rewardPaid; } return walletReward; } function getWalletStakes(address _user) public view returns (Stake[] memory) { return stakes[_user]; } function getWalletClaimableRewards( address _user ) public view returns (uint256 totalClaimable) { totalClaimable = 0; for (uint256 i = 0; i < stakes[_user].length; i++) { uint256 reward = calculateReward(_user, i); totalClaimable += reward; } } function calculateAllPendingRewards() public view returns (uint256 totalClaimable) { totalClaimable = 0; for (uint256 i = 0; i < stakers.length; i++) { address staker = stakers[i]; totalClaimable += getWalletClaimableRewards(staker); } } function calculateReward( address _user, uint256 _index ) public view returns (uint256) { Stake storage userStake = stakes[_user][_index]; uint256 stakeDuration = block.timestamp - userStake.startTime; if (stakeDuration > stakingPeriod) { stakeDuration = stakingPeriod; // Cap the stake duration to the lock-up period for reward calculation } // Scale the numerator before dividing uint256 scaledReward = (address(this).balance * userStake.amount * stakeDuration) / stakingPeriod; uint256 reward = scaledReward / totalStaked; if(userStake.rewardPaid > reward) { return 0; } return reward - userStake.rewardPaid; // Assuming rewardPaid is correctly managed elsewhere } function getUserStakingDetails( address _user ) public view returns ( uint256 _totalStaked, uint256 totalRewardsInEth, uint256[] memory timeElapsedPerStake ) { _totalStaked = 0; totalRewardsInEth = 0; timeElapsedPerStake = new uint256[](stakes[_user].length); for (uint256 i = 0; i < stakes[_user].length; i++) { _totalStaked += stakes[_user][i].amount; totalRewardsInEth += calculateReward(_user, i); // This should return ETH rewards timeElapsedPerStake[i] = block.timestamp - stakes[_user][i].startTime; } } function removeStake(address _user, uint256 _index) private { require(_index < stakes[_user].length, 'Invalid stake index'); stakes[_user][_index] = stakes[_user][stakes[_user].length - 1]; stakes[_user].pop(); } receive() external payable {} }