Description:
Multi-signature wallet contract requiring multiple confirmations for transaction execution.
Blockchain: Ethereum
Source Code: View Code On The Blockchain
Solidity Source Code:
{{
"language": "Solidity",
"sources": {
"@openzeppelin/contracts/access/Ownable.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (access/Ownable.sol)
pragma solidity ^0.8.0;
import "../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.
*
* By default, the owner account will be the one that deploys the contract. 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;
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
/**
* @dev Initializes the contract setting the deployer as the initial owner.
*/
constructor() {
_transferOwnership(_msgSender());
}
/**
* @dev Returns the address of the current owner.
*/
function owner() public view virtual returns (address) {
return _owner;
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
require(owner() == _msgSender(), "Ownable: caller is not the owner");
_;
}
/**
* @dev Leaves the contract without owner. It will not be possible to call
* `onlyOwner` functions anymore. Can only be called by the current owner.
*
* NOTE: Renouncing ownership will leave the contract without an owner,
* thereby removing 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 {
require(newOwner != address(0), "Ownable: new owner is the zero address");
_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);
}
}
"
},
"@openzeppelin/contracts/security/ReentrancyGuard.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (security/ReentrancyGuard.sol)
pragma solidity ^0.8.0;
/**
* @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;
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() {
// On the first call to nonReentrant, _notEntered will be true
require(_status != _ENTERED, "ReentrancyGuard: reentrant call");
// Any calls to nonReentrant after this point will fail
_status = _ENTERED;
_;
// By storing the original value once again, a refund is triggered (see
// https://eips.ethereum.org/EIPS/eip-2200)
_status = _NOT_ENTERED;
}
}
"
},
"@openzeppelin/contracts/utils/Context.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/Context.sol)
pragma solidity ^0.8.0;
/**
* @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;
}
}
"
},
"hardhat-contracts/IERC20.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.6.0) (token/ERC20/IERC20.sol)
pragma solidity ^0.8.0;
/**
* @dev Interface of the ERC20 standard as defined in the EIP.
*/
interface IERC20 {
/**
* @dev Emitted when `value` tokens are moved from one account (`from`) to
* another (`to`).
*
* Note that `value` may be zero.
*/
event Transfer(address indexed from, address indexed to, uint256 value);
/**
* @dev Emitted when the allowance of a `spender` for an `owner` is set by
* a call to {approve}. `value` is the new allowance.
*/
event Approval(address indexed owner, address indexed spender, uint256 value);
/**
* @dev Returns the amount of tokens in existence.
*/
function totalSupply() external view returns (uint256);
/**
* @dev Returns the symbol of the token
*/
function symbol() external view returns (string memory);
/**
* @dev Returns the amount of decimals of the token
*/
function decimals() external view returns (uint256);
/**
* @dev Returns the amount of tokens owned by `account`.
*/
function balanceOf(address account) external view returns (uint256);
/**
* @dev Moves `amount` tokens from the caller's account to `to`.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transfer(address to, uint256 amount) external returns (bool);
/**
* @dev Returns the remaining number of tokens that `spender` will be
* allowed to spend on behalf of `owner` through {transferFrom}. This is
* zero by default.
*
* This value changes when {approve} or {transferFrom} are called.
*/
function allowance(address owner, address spender) external view returns (uint256);
/**
* @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* IMPORTANT: Beware that changing an allowance with this method brings the risk
* that someone may use both the old and the new allowance by unfortunate
* transaction ordering. One possible solution to mitigate this race
* condition is to first reduce the spender's allowance to 0 and set the
* desired value afterwards:
* https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
*
* Emits an {Approval} event.
*/
function approve(address spender, uint256 amount) external returns (bool);
/**
* @dev Moves `amount` tokens from `from` to `to` using the
* allowance mechanism. `amount` is then deducted from the caller's
* allowance.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transferFrom(
address from,
address to,
uint256 amount
) external returns (bool);
}"
},
"hardhat-contracts/RewardStaked.sol": {
"content": "// contracts/Rain.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "./IERC20.sol";
import "./TransferHelper.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
/// @title RewardStaked
/// @notice A contract that allows users to stake a specific ERC20 token and earn rewards in other ERC20 tokens.
contract RewardStaked is Context, Ownable, ReentrancyGuard {
IERC20 public immutable Stakeable;
address[] public rewardTokens;
uint256 public totalStaked;
mapping(address => uint256) public deposits;
mapping(address => uint256) public lockedUntil;
mapping(address => mapping(address => uint256)) public nullifiedRewards;
mapping(address => uint256) public rewardsPerDeposit;
mapping(address => uint256) public totalRewarded;
uint256 public precision = 10 ** 18;
uint256 public lockTime;
/// @notice Initializes the contract with the stakeable token, lock time, and initial reward tokens.
/// @param _stakeable The token that users can stake.
/// @param _lockTime The time that users must wait after unlocking their stake before they can withdraw it.
/// @param _rewardTokens An array of tokens that users can earn as rewards.
constructor(
address _stakeable,
uint256 _lockTime,
address[] memory _rewardTokens
) {
Stakeable = IERC20(_stakeable);
lockTime = _lockTime;
for (uint16 i = 0; i < _rewardTokens.length; i++)
addRewardToken(_rewardTokens[i]);
}
/// @notice Returns the array of reward tokens.
function getRewardTokens() public view virtual returns (address[] memory) {
return rewardTokens;
}
/// @notice Allows a user to deposit the stakeable token.
/// @param amount The amount of the stakeable token to deposit.
function deposit(uint256 amount) external nonReentrant {
require(
!unlockInitiated(msg.sender),
"you must wait until you withdraw your tokens"
);
TransferHelper.safeTransferFrom(
address(Stakeable),
msg.sender,
address(this),
amount
);
// Claims all rewards before updating the user's stake.
claimAll(getRewardTokens());
deposits[msg.sender] += amount;
totalStaked += amount;
}
/// @notice Allows a user to unlock their stake.
function unlock() external nonReentrant {
require(!unlockInitiated(msg.sender), "Unlock is already pending");
// Claims all rewards before updating the user's stake.
claimAll(getRewardTokens());
totalStaked -= deposits[msg.sender];
lockedUntil[msg.sender] = block.timestamp + lockTime;
}
/// @notice Allows a user to withdraw their stake after it has been unlocked.
function withdraw() external nonReentrant {
require(
unlockInitiated(msg.sender),
"you must unlock your staked tokens first"
);
require(isUnlocked(msg.sender), "your staked tokens are still locked");
uint256 withdrawable = deposits[msg.sender];
deposits[msg.sender] = 0;
TransferHelper.safeTransfer(
address(Stakeable),
msg.sender,
withdrawable
);
lockedUntil[msg.sender] = 0;
}
/// @notice Nullifies the user's claimable rewards for the given token.
/// @param token The token for which to nullify the user's rewards.
function nullifyRewardsForToken(address token) internal {
nullifiedRewards[token][msg.sender] = rewardsPerDeposit[token];
}
/// @notice Allows anyone to add rewards for all stakers.
/// @param token The token in which the rewards are being added.
/// @param amount The amount of rewards to add.
function addRewards(address token, uint256 amount) external {
require(totalStaked > 0, "can not add rewards if there are no stakers");
TransferHelper.safeTransferFrom(
token,
msg.sender,
address(this),
amount
);
rewardsPerDeposit[token] += (precision * amount) / totalStaked;
totalRewarded[token] += amount;
}
/// @notice Returns various information about the current state of the contract and the user's stake.
/// @param user The user to retrieve information for.
function getInfo(
address user
)
external
view
returns (uint256, uint256, uint256, uint256, uint256, uint256)
{
return (
totalStaked,
deposits[user],
lockedUntil[user],
Stakeable.decimals(),
Stakeable.balanceOf(user),
Stakeable.allowance(user, address(this))
);
}
/// @notice Returns various information about all of the reward tokens and the user's claimable rewards.
/// @param user The user to retrieve information for.
function allClaimable(
address user
)
external
view
returns (
address[] memory,
string[] memory,
uint256[] memory,
uint256[] memory,
uint256[] memory
)
{
address[] memory tokens = getRewardTokens();
uint256[] memory totalRewards = new uint256[](tokens.length);
uint256[] memory canClaim = new uint256[](tokens.length);
uint256[] memory decimals = new uint256[](tokens.length);
string[] memory symbols = new string[](tokens.length);
for (uint16 i = 0; i < tokens.length; i++) {
IERC20 token = IERC20(tokens[i]);
symbols[i] = token.symbol();
decimals[i] = token.decimals();
canClaim[i] = claimable(tokens[i], user);
totalRewards[i] = totalRewarded[tokens[i]];
}
return (tokens, symbols, decimals, canClaim, totalRewards);
}
/// @notice Returns the amount of the given token that the user can claim as rewards.
/// @param token The token to check.
/// @param user The user to check.
function claimable(
address token,
address user
) public view returns (uint256) {
if (unlockInitiated(user)) return 0;
return
(deposits[user] *
(rewardsPerDeposit[token] - nullifiedRewards[token][user])) /
precision;
}
/// @notice Allows the user to claim their rewards in the given token.
/// @param token The token to claim rewards in.
function claim(address token) internal {
uint256 willClaim = claimable(token, msg.sender);
nullifyRewardsForToken(token);
if (willClaim > 0) IERC20(token).transfer(msg.sender, willClaim);
}
/// @notice Allows the user to claim their rewards in all tokens.
/// @param rewards An array of tokens to claim rewards in.
function claimAll(address[] memory rewards) public {
for (uint16 i = 0; i < rewards.length; i++) claim(rewards[i]);
}
/// @notice Returns whether the user has initiated an unlock.
/// @param user The user to check.
function unlockInitiated(address user) public view returns (bool) {
return lockedUntil[user] > 0;
}
/// @notice Returns whether the user's stake is currently locked.
/// @param user The user to check.
function isUnlocked(address user) public view returns (bool) {
return unlockInitiated(user) && lockedUntil[user] < block.timestamp;
}
/// @notice Removes a reward token.
/// @param index The index of the token to remove.
function removeRewardToken(uint16 index) public onlyOwner {
require(rewardTokens.length > index, "invalid index");
address lastToken = rewardTokens[rewardTokens.length - 1];
rewardTokens.pop();
if (rewardTokens.length > index) rewardTokens[index] = lastToken;
}
/// @notice Adds a new reward token.
/// @param token The token to add.
function addRewardToken(address token) public onlyOwner {
for (uint16 i = 0; i < rewardTokens.length; i++)
require(
rewardTokens[i] != token,
"this token is already a reward token"
);
rewardTokens.push(token);
}
}
"
},
"hardhat-contracts/TransferHelper.sol": {
"content": "// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity >=0.6.0;
// helper methods for interacting with ERC20 tokens and sending ETH that do not consistently return true/false
library TransferHelper {
function safeApprove(
address token,
address to,
uint256 value
) internal {
// bytes4(keccak256(bytes('approve(address,uint256)')));
(bool success, bytes memory data) = token.call(abi.encodeWithSelector(0x095ea7b3, to, value));
require(
success && (data.length == 0 || abi.decode(data, (bool))),
'TransferHelper::safeApprove: approve failed'
);
}
function safeTransfer(
address token,
address to,
uint256 value
) internal {
// bytes4(keccak256(bytes('transfer(address,uint256)')));
(bool success, bytes memory data) = token.call(abi.encodeWithSelector(0xa9059cbb, to, value));
require(
success && (data.length == 0 || abi.decode(data, (bool))),
'TransferHelper::safeTransfer: transfer failed'
);
}
function safeTransferFrom(
address token,
address from,
address to,
uint256 value
) internal {
// bytes4(keccak256(bytes('transferFrom(address,address,uint256)')));
(bool success, bytes memory data) = token.call(abi.encodeWithSelector(0x23b872dd, from, to, value));
require(
success && (data.length == 0 || abi.decode(data, (bool))),
'TransferHelper::transferFrom: transferFrom failed'
);
}
function safeTransferETH(address to, uint256 value) internal {
(bool success, ) = to.call{value: value}(new bytes(0));
require(success, 'TransferHelper::safeTransferETH: ETH transfer failed');
}
}"
}
},
"settings": {
"optimizer": {
"enabled": true,
"runs": 200
},
"evmVersion": "paris",
"outputSelection": {
"*": {
"*": [
"evm.bytecode",
"evm.deployedBytecode",
"devdoc",
"userdoc",
"metadata",
"abi"
]
}
}
}
}}
Submitted on: 2025-10-13 12:02:50
Comments
Log in to comment.
No comments yet.