Contract Name:
DisposablePooledStaking
Contract Source Code:
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.0) (token/ERC721/IERC721.sol)
pragma solidity ^0.8.0;
import "../../utils/introspection/IERC165.sol";
/**
* @dev Required interface of an ERC721 compliant contract.
*/
interface IERC721 is IERC165 {
/**
* @dev Emitted when `tokenId` token is transferred from `from` to `to`.
*/
event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);
/**
* @dev Emitted when `owner` enables `approved` to manage the `tokenId` token.
*/
event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);
/**
* @dev Emitted when `owner` enables or disables (`approved`) `operator` to manage all of its assets.
*/
event ApprovalForAll(address indexed owner, address indexed operator, bool approved);
/**
* @dev Returns the number of tokens in ``owner``'s account.
*/
function balanceOf(address owner) external view returns (uint256 balance);
/**
* @dev Returns the owner of the `tokenId` token.
*
* Requirements:
*
* - `tokenId` must exist.
*/
function ownerOf(uint256 tokenId) external view returns (address owner);
/**
* @dev Safely transfers `tokenId` token from `from` to `to`.
*
* Requirements:
*
* - `from` cannot be the zero address.
* - `to` cannot be the zero address.
* - `tokenId` token must exist and be owned by `from`.
* - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
* - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
*
* Emits a {Transfer} event.
*/
function safeTransferFrom(
address from,
address to,
uint256 tokenId,
bytes calldata data
) external;
/**
* @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients
* are aware of the ERC721 protocol to prevent tokens from being forever locked.
*
* Requirements:
*
* - `from` cannot be the zero address.
* - `to` cannot be the zero address.
* - `tokenId` token must exist and be owned by `from`.
* - If the caller is not `from`, it must have been allowed to move this token by either {approve} or {setApprovalForAll}.
* - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
*
* Emits a {Transfer} event.
*/
function safeTransferFrom(
address from,
address to,
uint256 tokenId
) external;
/**
* @dev Transfers `tokenId` token from `from` to `to`.
*
* WARNING: Usage of this method is discouraged, use {safeTransferFrom} whenever possible.
*
* Requirements:
*
* - `from` cannot be the zero address.
* - `to` cannot be the zero address.
* - `tokenId` token must be owned by `from`.
* - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
*
* Emits a {Transfer} event.
*/
function transferFrom(
address from,
address to,
uint256 tokenId
) external;
/**
* @dev Gives permission to `to` to transfer `tokenId` token to another account.
* The approval is cleared when the token is transferred.
*
* Only a single account can be approved at a time, so approving the zero address clears previous approvals.
*
* Requirements:
*
* - The caller must own the token or be an approved operator.
* - `tokenId` must exist.
*
* Emits an {Approval} event.
*/
function approve(address to, uint256 tokenId) external;
/**
* @dev Approve or remove `operator` as an operator for the caller.
* Operators can call {transferFrom} or {safeTransferFrom} for any token owned by the caller.
*
* Requirements:
*
* - The `operator` cannot be the caller.
*
* Emits an {ApprovalForAll} event.
*/
function setApprovalForAll(address operator, bool _approved) external;
/**
* @dev Returns the account approved for `tokenId` token.
*
* Requirements:
*
* - `tokenId` must exist.
*/
function getApproved(uint256 tokenId) external view returns (address operator);
/**
* @dev Returns if the `operator` is allowed to manage all of the assets of `owner`.
*
* See {setApprovalForAll}
*/
function isApprovedForAll(address owner, address operator) external view returns (bool);
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/introspection/IERC165.sol)
pragma solidity ^0.8.0;
/**
* @dev Interface of the ERC165 standard, as defined in the
* https://eips.ethereum.org/EIPS/eip-165[EIP].
*
* Implementers can declare support of contract interfaces, which can then be
* queried by others ({ERC165Checker}).
*
* For an implementation, see {ERC165}.
*/
interface IERC165 {
/**
* @dev Returns true if this contract implements the interface defined by
* `interfaceId`. See the corresponding
* https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]
* to learn more about how these ids are created.
*
* This function call must use less than 30 000 gas.
*/
function supportsInterface(bytes4 interfaceId) external view returns (bool);
}
// SPDX-License-Identifier: GPL-3.0-only
pragma solidity ^0.8.18;
import "../interfaces/ISAFURAMaster.sol";
import "../interfaces/IMasterAwareV2.sol";
import "../interfaces/IMemberRoles.sol";
abstract contract MasterAwareV2 is IMasterAwareV2 {
ISAFURAMaster public master;
mapping(uint => address payable) public internalContracts;
modifier onlyMember {
require(
IMemberRoles(internalContracts[uint(ID.MR)]).checkRole(
msg.sender,
uint(IMemberRoles.Role.Member)
),
"Caller is not a member"
);
_;
}
modifier onlyAdvisoryBoard {
require(
IMemberRoles(internalContracts[uint(ID.MR)]).checkRole(
msg.sender,
uint(IMemberRoles.Role.AdvisoryBoard)
),
"Caller is not an advisory board member"
);
_;
}
modifier onlyInternal {
require(master.isInternal(msg.sender), "Caller is not an internal contract");
_;
}
modifier onlyMaster {
if (address(master) != address(0)) {
require(address(master) == msg.sender, "Not master");
}
_;
}
modifier onlyGovernance {
require(
master.checkIsAuthToGoverned(msg.sender),
"Caller is not authorized to govern"
);
_;
}
modifier onlyEmergencyAdmin {
require(
msg.sender == master.emergencyAdmin(),
"Caller is not emergency admin"
);
_;
}
modifier whenPaused {
require(master.isPause(), "System is not paused");
_;
}
modifier whenNotPaused {
require(!master.isPause(), "System is paused");
_;
}
function getInternalContractAddress(ID id) internal view returns (address payable) {
return internalContracts[uint(id)];
}
function changeMasterAddress(address masterAddress) public onlyMaster {
master = ISAFURAMaster(masterAddress);
}
}
// SPDX-License-Identifier: GPL-3.0-only
pragma solidity >=0.5.0;
import "./IStakingPoolFactory.sol";
/**
* @dev IStakingPoolFactory is missing the changeOperator() and operator() functions.
* @dev Any change to the original interface will affect staking pool addresses
* @dev This interface is created to add the missing functions so it can be used in other contracts.
*/
interface ICompleteStakingPoolFactory is IStakingPoolFactory {
function operator() external view returns (address);
function changeOperator(address newOperator) external;
}
// SPDX-License-Identifier: GPL-3.0-only
pragma solidity >=0.5.0;
import "./ICoverNFT.sol";
import "./IStakingNFT.sol";
import "./IStakingPool.sol";
import "./ICompleteStakingPoolFactory.sol";
/* io structs */
enum ClaimMethod {
IndividualClaims,
YieldTokenIncidents
}
struct PoolAllocationRequest {
uint40 poolId;
bool skip;
uint coverAmountInAsset;
}
struct BuyCoverParams {
uint coverId;
address owner;
uint24 productId;
uint8 coverAsset;
uint96 amount;
uint32 period;
uint maxPremiumInAsset;
uint8 paymentAsset;
uint16 commissionRatio;
address commissionDestination;
string ipfsData;
}
/* storage structs */
struct PoolAllocation {
uint40 poolId;
uint96 coverAmountInNXM;
uint96 premiumInNXM;
uint24 allocationId;
}
struct CoverData {
uint24 productId;
uint8 coverAsset;
uint96 amountPaidOut;
}
struct CoverSegment {
uint96 amount;
uint32 start;
uint32 period; // seconds
uint32 gracePeriod; // seconds
uint24 globalRewardsRatio;
uint24 globalCapacityRatio;
}
interface ICover {
/* ========== DATA STRUCTURES ========== */
/* internal structs */
struct RequestAllocationVariables {
uint previousPoolAllocationsLength;
uint previousPremiumInNXM;
uint refund;
uint coverAmountInNXM;
}
/* storage structs */
struct ActiveCover {
// Global active cover amount per asset.
uint192 totalActiveCoverInAsset;
// The last time activeCoverExpirationBuckets was updated
uint64 lastBucketUpdateId;
}
/* ========== VIEWS ========== */
function coverData(uint coverId) external view returns (CoverData memory);
function coverDataCount() external view returns (uint);
function coverSegmentsCount(uint coverId) external view returns (uint);
function coverSegments(uint coverId) external view returns (CoverSegment[] memory);
function coverSegmentWithRemainingAmount(
uint coverId,
uint segmentId
) external view returns (CoverSegment memory);
function recalculateActiveCoverInAsset(uint coverAsset) external;
function totalActiveCoverInAsset(uint coverAsset) external view returns (uint);
function getGlobalCapacityRatio() external view returns (uint);
function getGlobalRewardsRatio() external view returns (uint);
function getGlobalMinPriceRatio() external pure returns (uint);
function getGlobalCapacityAndPriceRatios() external view returns (
uint _globalCapacityRatio,
uint _globalMinPriceRatio
);
function GLOBAL_MIN_PRICE_RATIO() external view returns (uint);
/* === MUTATIVE FUNCTIONS ==== */
function buyCover(
BuyCoverParams calldata params,
PoolAllocationRequest[] calldata coverChunkRequests
) external payable returns (uint coverId);
function burnStake(
uint coverId,
uint segmentId,
uint amount
) external returns (address coverOwner);
function changeStakingPoolFactoryOperator() external;
function coverNFT() external returns (ICoverNFT);
function stakingNFT() external returns (IStakingNFT);
function stakingPoolFactory() external returns (ICompleteStakingPoolFactory);
/* ========== EVENTS ========== */
event CoverEdited(uint indexed coverId, uint indexed productId, uint indexed segmentId, address buyer, string ipfsMetadata);
// Auth
error OnlyOwnerOrApproved();
// Cover details
error CoverPeriodTooShort();
error CoverPeriodTooLong();
error CoverOutsideOfTheGracePeriod();
error CoverAmountIsZero();
// Products
error ProductNotFound();
error ProductDeprecated();
error UnexpectedProductId();
// Cover and payment assets
error CoverAssetNotSupported();
error InvalidPaymentAsset();
error UnexpectedCoverAsset();
error UnexpectedEthSent();
error EditNotSupported();
// Price & Commission
error PriceExceedsMaxPremiumInAsset();
error CommissionRateTooHigh();
// ETH transfers
error InsufficientEthSent();
error SendingEthToPoolFailed();
error SendingEthToCommissionDestinationFailed();
error ReturningEthRemainderToSenderFailed();
// Misc
error ExpiredCoversCannotBeEdited();
error CoverNotYetExpired(uint coverId);
error InsufficientCoverAmountAllocated();
error UnexpectedPoolId();
}
// SPDX-License-Identifier: GPL-3.0-only
pragma solidity >=0.5.0;
import "@openzeppelin/contracts-v4/token/ERC721/IERC721.sol";
interface ICoverNFT is IERC721 {
function isApprovedOrOwner(address spender, uint tokenId) external returns (bool);
function mint(address to) external returns (uint tokenId);
function changeOperator(address newOperator) external;
function changeNFTDescriptor(address newNFTDescriptor) external;
function totalSupply() external view returns (uint);
function name() external view returns (string memory);
error NotOperator();
error NotMinted();
error WrongFrom();
error InvalidRecipient();
error InvalidNewOperatorAddress();
error InvalidNewNFTDescriptorAddress();
error NotAuthorized();
error UnsafeRecipient();
error AlreadyMinted();
}
// SPDX-License-Identifier: GPL-3.0-only
pragma solidity >=0.5.0;
interface IMasterAwareV2 {
// TODO: if you update this enum, update lib/constants.js as well
enum ID {
TC, // TokenController.sol
P1, // Pool.sol
MR, // MemberRoles.sol
MC, // MCR.sol
CO, // Cover.sol
SP, // StakingProducts.sol
PS, // LegacyPooledStaking.sol
GV, // Governance.sol
GW, // LegacyGateway.sol - removed
CL, // CoverMigrator.sol - removed
AS, // Assessment.sol
CI, // IndividualClaims.sol - Claims for Individuals
CG, // YieldTokenIncidents.sol - Claims for Groups
RA, // Ramm.sol
ST, // SafeTracker.sol
CP // CoverProducts.sol
}
function changeMasterAddress(address masterAddress) external;
function changeDependentContractAddress() external;
function internalContracts(uint) external view returns (address payable);
}
// SPDX-License-Identifier: GPL-3.0-only
pragma solidity >=0.5.0;
interface IMemberRoles {
enum Role {Unassigned, AdvisoryBoard, Member, Owner, Auditor}
function join(address _userAddress, uint nonce, bytes calldata signature) external payable;
function switchMembership(address _newAddress) external;
function switchMembershipAndAssets(
address newAddress,
uint[] calldata coverIds,
uint[] calldata stakingTokenIds
) external;
function switchMembershipOf(address member, address _newAddress) external;
function totalRoles() external view returns (uint256);
function changeAuthorized(uint _roleId, address _newAuthorized) external;
function setKycAuthAddress(address _add) external;
function members(uint _memberRoleId) external view returns (uint, address[] memory memberArray);
function numberOfMembers(uint _memberRoleId) external view returns (uint);
function authorized(uint _memberRoleId) external view returns (address);
function roles(address _memberAddress) external view returns (uint[] memory);
function checkRole(address _memberAddress, uint _roleId) external view returns (bool);
function getMemberLengthForAllRoles() external view returns (uint[] memory totalMembers);
function memberAtIndex(uint _memberRoleId, uint index) external view returns (address, bool);
function membersLength(uint _memberRoleId) external view returns (uint);
event MemberRole(uint256 indexed roleId, bytes32 roleName, string roleDescription);
event MemberJoined(address indexed newMember, uint indexed nonce);
event switchedMembership(address indexed previousMember, address indexed newMember, uint timeStamp);
event MembershipWithdrawn(address indexed member, uint timestamp);
}
// SPDX-License-Identifier: GPL-3.0-only
pragma solidity >=0.5.0;
interface IPooledStaking {
struct Staker {
uint deposit; // total amount of deposit nxm
uint reward; // total amount that is ready to be claimed
address[] contracts; // list of contracts the staker has staked on
// staked amounts for each contract
mapping(address => uint) stakes;
// amount pending to be subtracted after all unstake requests will be processed
mapping(address => uint) pendingUnstakeRequestsTotal;
// flag to indicate the presence of this staker in the array of stakers of each contract
mapping(address => bool) isInContractStakers;
}
struct Burn {
uint amount;
uint burnedAt;
address contractAddress;
}
struct Reward {
uint amount;
uint rewardedAt;
address contractAddress;
}
struct UnstakeRequest {
uint amount;
uint unstakeAt;
address contractAddress;
address stakerAddress;
uint next; // id of the next unstake request in the linked list
}
struct ContractReward {
uint amount;
uint lastDistributionRound;
}
function accumulateReward(address contractAddress, uint amount) external;
function pushBurn(address contractAddress, uint amount) external;
function hasPendingActions() external view returns (bool);
function processPendingActions(uint maxIterations) external returns (bool finished);
function contractStake(address contractAddress) external view returns (uint);
function stakerReward(address staker) external view returns (uint);
function stakerDeposit(address staker) external view returns (uint);
function stakerContractStake(address staker, address contractAddress) external view returns (uint);
function withdraw(uint amount) external;
function withdrawForUser(address user) external;
function stakerMaxWithdrawable(address stakerAddress) external view returns (uint);
function withdrawReward(address stakerAddress) external;
}
// SPDX-License-Identifier: GPL-3.0-only
pragma solidity >=0.5.0;
interface ISAFURAMaster {
function tokenAddress() external view returns (address);
function owner() external view returns (address);
function emergencyAdmin() external view returns (address);
function masterInitialized() external view returns (bool);
function isInternal(address _add) external view returns (bool);
function isPause() external view returns (bool check);
function isMember(address _add) external view returns (bool);
function checkIsAuthToGoverned(address _add) external view returns (bool);
function getLatestAddress(bytes2 _contractName) external view returns (address payable contractAddress);
function contractAddresses(bytes2 code) external view returns (address payable);
function upgradeMultipleContracts(
bytes2[] calldata _contractCodes,
address payable[] calldata newAddresses
) external;
function removeContracts(bytes2[] calldata contractCodesToRemove) external;
function addNewInternalContracts(
bytes2[] calldata _contractCodes,
address payable[] calldata newAddresses,
uint[] calldata _types
) external;
function updateOwnerParameters(bytes8 code, address payable val) external;
}
// SPDX-License-Identifier: GPL-3.0-only
pragma solidity >=0.5.0;
interface ISAFURAToken {
function burn(uint256 amount) external returns (bool);
function burnFrom(address from, uint256 value) external returns (bool);
function operatorTransfer(address from, uint256 value) external returns (bool);
function mint(address account, uint256 amount) external;
function isLockedForMV(address member) external view returns (uint);
function whiteListed(address member) external view returns (bool);
function addToWhiteList(address _member) external returns (bool);
function removeFromWhiteList(address _member) external returns (bool);
function changeOperator(address _newOperator) external returns (bool);
function lockForMemberVote(address _of, uint _days) external;
/**
* @dev Returns the amount of tokens in existence.
*/
function totalSupply() 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 `recipient`.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transfer(address recipient, 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 `sender` to `recipient` 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 sender, address recipient, uint256 amount) external returns (bool);
/**
* @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);
}
// SPDX-License-Identifier: GPL-3.0-only
pragma solidity >=0.5.0;
import "@openzeppelin/contracts-v4/token/ERC721/IERC721.sol";
interface IStakingNFT is IERC721 {
function isApprovedOrOwner(address spender, uint tokenId) external returns (bool);
function mint(uint poolId, address to) external returns (uint tokenId);
function changeOperator(address newOperator) external;
function changeNFTDescriptor(address newNFTDescriptor) external;
function totalSupply() external returns (uint);
function tokenInfo(uint tokenId) external view returns (uint poolId, address owner);
function stakingPoolOf(uint tokenId) external view returns (uint poolId);
function stakingPoolFactory() external view returns (address);
function name() external view returns (string memory);
error NotOperator();
error NotMinted();
error WrongFrom();
error InvalidRecipient();
error InvalidNewOperatorAddress();
error InvalidNewNFTDescriptorAddress();
error NotAuthorized();
error UnsafeRecipient();
error AlreadyMinted();
error NotStakingPool();
}
// SPDX-License-Identifier: GPL-3.0-only
pragma solidity >=0.5.0;
/* structs for io */
struct AllocationRequest {
uint productId;
uint coverId;
uint allocationId;
uint period;
uint gracePeriod;
bool useFixedPrice;
uint previousStart;
uint previousExpiration;
uint previousRewardsRatio;
uint globalCapacityRatio;
uint capacityReductionRatio;
uint rewardRatio;
uint globalMinPrice;
}
struct BurnStakeParams {
uint allocationId;
uint productId;
uint start;
uint period;
uint deallocationAmount;
}
interface IStakingPool {
/* structs for storage */
// stakers are grouped in tranches based on the timelock expiration
// tranche index is calculated based on the expiration date
// the initial proposal is to have 4 tranches per year (1 tranche per quarter)
struct Tranche {
uint128 stakeShares;
uint128 rewardsShares;
}
struct ExpiredTranche {
uint96 accNxmPerRewardShareAtExpiry;
uint96 stakeAmountAtExpiry; // nxm total supply is 6.7e24 and uint96.max is 7.9e28
uint128 stakeSharesSupplyAtExpiry;
}
struct Deposit {
uint96 lastAccNxmPerRewardShare;
uint96 pendingRewards;
uint128 stakeShares;
uint128 rewardsShares;
}
function initialize(
bool isPrivatePool,
uint initialPoolFee,
uint maxPoolFee,
uint _poolId,
string memory ipfsDescriptionHash
) external;
function processExpirations(bool updateUntilCurrentTimestamp) external;
function requestAllocation(
uint amount,
uint previousPremium,
AllocationRequest calldata request
) external returns (uint premium, uint allocationId);
function burnStake(uint amount, BurnStakeParams calldata params) external;
function depositTo(
uint amount,
uint trancheId,
uint requestTokenId,
address destination
) external returns (uint tokenId);
function withdraw(
uint tokenId,
bool withdrawStake,
bool withdrawRewards,
uint[] memory trancheIds
) external returns (uint withdrawnStake, uint withdrawnRewards);
function isPrivatePool() external view returns (bool);
function isHalted() external view returns (bool);
function manager() external view returns (address);
function getPoolId() external view returns (uint);
function getPoolFee() external view returns (uint);
function getMaxPoolFee() external view returns (uint);
function getActiveStake() external view returns (uint);
function getStakeSharesSupply() external view returns (uint);
function getRewardsSharesSupply() external view returns (uint);
function getRewardPerSecond() external view returns (uint);
function getAccNxmPerRewardsShare() external view returns (uint);
function getLastAccNxmUpdate() external view returns (uint);
function getFirstActiveTrancheId() external view returns (uint);
function getFirstActiveBucketId() external view returns (uint);
function getNextAllocationId() external view returns (uint);
function getDeposit(uint tokenId, uint trancheId) external view returns (
uint lastAccNxmPerRewardShare,
uint pendingRewards,
uint stakeShares,
uint rewardsShares
);
function getTranche(uint trancheId) external view returns (
uint stakeShares,
uint rewardsShares
);
function getExpiredTranche(uint trancheId) external view returns (
uint accNxmPerRewardShareAtExpiry,
uint stakeAmountAtExpiry,
uint stakeShareSupplyAtExpiry
);
function setPoolFee(uint newFee) external;
function setPoolPrivacy(bool isPrivatePool) external;
function getActiveAllocations(
uint productId
) external view returns (uint[] memory trancheAllocations);
function getTrancheCapacities(
uint productId,
uint firstTrancheId,
uint trancheCount,
uint capacityRatio,
uint reductionRatio
) external view returns (uint[] memory trancheCapacities);
/* ========== EVENTS ========== */
event StakeDeposited(address indexed user, uint256 amount, uint256 trancheId, uint256 tokenId);
event DepositExtended(address indexed user, uint256 tokenId, uint256 initialTrancheId, uint256 newTrancheId, uint256 topUpAmount);
event PoolPrivacyChanged(address indexed manager, bool isPrivate);
event PoolFeeChanged(address indexed manager, uint newFee);
event PoolDescriptionSet(string ipfsDescriptionHash);
event Withdraw(address indexed user, uint indexed tokenId, uint tranche, uint amountStakeWithdrawn, uint amountRewardsWithdrawn);
event StakeBurned(uint amount);
event Deallocated(uint productId);
event BucketExpired(uint bucketId);
event TrancheExpired(uint trancheId);
// Auth
error OnlyCoverContract();
error OnlyStakingProductsContract();
error OnlyManager();
error PrivatePool();
error SystemPaused();
error PoolHalted();
// Fees
error PoolFeeExceedsMax();
error MaxPoolFeeAbove100();
// Voting
error NxmIsLockedForGovernanceVote();
error ManagerNxmIsLockedForGovernanceVote();
// Deposit
error InsufficientDepositAmount();
error RewardRatioTooHigh();
// Staking NFTs
error InvalidTokenId();
error NotTokenOwnerOrApproved();
error InvalidStakingPoolForToken();
// Tranche & capacity
error NewTrancheEndsBeforeInitialTranche();
error RequestedTrancheIsNotYetActive();
error RequestedTrancheIsExpired();
error InsufficientCapacity();
// Allocation
error AlreadyDeallocated(uint allocationId);
}
// SPDX-License-Identifier: GPL-3.0-only
pragma solidity >=0.5.0;
interface IStakingPoolFactory {
function stakingPoolCount() external view returns (uint);
function beacon() external view returns (address);
function create(address beacon) external returns (uint poolId, address stakingPoolAddress);
event StakingPoolCreated(uint indexed poolId, address indexed stakingPoolAddress);
}
// SPDX-License-Identifier: GPL-3.0-only
pragma solidity >=0.5.0;
import "./ISAFURAToken.sol";
interface ITokenController {
struct StakingPoolNXMBalances {
uint128 rewards;
uint128 deposits;
}
struct CoverInfo {
uint16 claimCount;
bool hasOpenClaim;
bool hasAcceptedClaim;
uint96 requestedPayoutAmount;
// note: still 128 bits available here, can be used later
}
struct StakingPoolOwnershipOffer {
address proposedManager;
uint96 deadline;
}
function coverInfo(uint id) external view returns (
uint16 claimCount,
bool hasOpenClaim,
bool hasAcceptedClaim,
uint96 requestedPayoutAmount
);
function withdrawCoverNote(
address _of,
uint[] calldata _coverIds,
uint[] calldata _indexes
) external;
function changeOperator(address _newOperator) external;
function operatorTransfer(address _from, address _to, uint _value) external returns (bool);
function burnFrom(address _of, uint amount) external returns (bool);
function addToWhitelist(address _member) external;
function removeFromWhitelist(address _member) external;
function mint(address _member, uint _amount) external;
function lockForMemberVote(address _of, uint _days) external;
function withdrawClaimAssessmentTokens(address[] calldata users) external;
function getLockReasons(address _of) external view returns (bytes32[] memory reasons);
function totalSupply() external view returns (uint);
function totalBalanceOf(address _of) external view returns (uint amount);
function totalBalanceOfWithoutDelegations(address _of) external view returns (uint amount);
function getTokenPrice() external view returns (uint tokenPrice);
function token() external view returns (ISAFURAToken);
function getStakingPoolManager(uint poolId) external view returns (address manager);
function getManagerStakingPools(address manager) external view returns (uint[] memory poolIds);
function isStakingPoolManager(address member) external view returns (bool);
function getStakingPoolOwnershipOffer(uint poolId) external view returns (address proposedManager, uint deadline);
function transferStakingPoolsOwnership(address from, address to) external;
function assignStakingPoolManager(uint poolId, address manager) external;
function createStakingPoolOwnershipOffer(uint poolId, address proposedManager, uint deadline) external;
function acceptStakingPoolOwnershipOffer(uint poolId) external;
function cancelStakingPoolOwnershipOffer(uint poolId) external;
function mintStakingPoolNXMRewards(uint amount, uint poolId) external;
function burnStakingPoolNXMRewards(uint amount, uint poolId) external;
function depositStakedNXM(address from, uint amount, uint poolId) external;
function withdrawNXMStakeAndRewards(address to, uint stakeToWithdraw, uint rewardsToWithdraw, uint poolId) external;
function burnStakedNXM(uint amount, uint poolId) external;
function stakingPoolNXMBalances(uint poolId) external view returns(uint128 rewards, uint128 deposits);
function tokensLocked(address _of, bytes32 _reason) external view returns (uint256 amount);
function getWithdrawableCoverNotes(
address coverOwner
) external view returns (
uint[] memory coverIds,
bytes32[] memory lockReasons,
uint withdrawableAmount
);
function getPendingRewards(address member) external view returns (uint);
}
// SPDX-License-Identifier: GPL-3.0-only
pragma solidity ^0.8.18;
import "../../interfaces/ITokenController.sol";
import "../../modules/legacy/LegacyPooledStaking.sol";
contract DisposablePooledStaking is LegacyPooledStaking {
function initialize(
address payable _tokenControllerAddress,
uint minStake,
uint minUnstake,
uint maxExposure,
uint unstakeLockTime
) external {
internalContracts[uint(ID.TC)] = _tokenControllerAddress;
ITokenController(_tokenControllerAddress).addToWhitelist(address(this));
MIN_STAKE = minStake;
MIN_UNSTAKE = minUnstake;
MAX_EXPOSURE = maxExposure;
UNSTAKE_LOCK_TIME = unstakeLockTime;
REWARD_ROUND_DURATION = 7 days;
REWARD_ROUNDS_START = block.timestamp;
}
constructor(address _tokenAddress) LegacyPooledStaking(
0x0000000000000000000000000000000000000000,
0x0000000000000000000000000000000000000000,
_tokenAddress
) {
// noop
}
}
// SPDX-License-Identifier: GPL-3.0-only
pragma solidity ^0.8.18;
import "../../abstract/MasterAwareV2.sol";
import "../../interfaces/ISAFURAToken.sol";
import "../../interfaces/IPooledStaking.sol";
import "../../interfaces/ITokenController.sol";
import "../../interfaces/ICover.sol";
import "../../interfaces/IStakingPool.sol";
import "../../interfaces/IStakingNFT.sol";
contract LegacyPooledStaking is IPooledStaking, MasterAwareV2 {
/* Constants */
address constant ARMOR_STAKER = 0x1337DEF1FC06783D4b03CB8C1Bf3EBf7D0593FC4;
address constant ARMOR_MANAGER = 0xFa760444A229e78A50Ca9b3779f4ce4CcE10E170;
address constant HUGH = 0x87B2a7559d85f4653f13E6546A14189cd5455d45;
address constant NM_FOUNDATION = 0x963Df0066ff8345922dF88eebeb1095BE4e4e12E;
uint constant MAX_ACTIVE_TRANCHES = 8;
ICover public immutable cover;
IStakingNFT public immutable stakingNFT;
/* Structs */
struct StakingPoolMigrationData {
address stakerAddress;
address managerAddress;
string ipfsDescriptionHash;
bool isPrivatePool;
uint initialPoolFee;
uint maxPoolFee;
uint deposit;
uint[MAX_ACTIVE_TRANCHES] trancheStakeRatio;
}
/* Events */
// deposits
event Deposited(address indexed staker, uint amount);
event Withdrawn(address indexed staker, uint amount);
// stakes
event Staked(address indexed contractAddress, address indexed staker, uint amount);
event UnstakeRequested(address indexed contractAddress, address indexed staker, uint amount, uint unstakeAt);
event Unstaked(address indexed contractAddress, address indexed staker, uint amount);
// burns
event BurnRequested(address indexed contractAddress, uint amount);
event Burned(address indexed contractAddress, uint amount, uint contractStakeBeforeBurn);
// rewards
event RewardAdded(address indexed contractAddress, uint amount);
event RewardRequested(address indexed contractAddress, uint amount);
event Rewarded(address indexed contractAddress, uint amount, uint contractStake);
event RewardWithdrawn(address indexed staker, uint amount);
// pending actions processing
event PendingActionsProcessed(bool finished);
/* Storage variables */
/*
Deleted storage variables
bool public initialized;
ISAFURAToken public token;
These 2 variables occupied 1 slot. The MasterAwareV2 interface has 1 extra slot more
compared to MasterAware. MasterAware.master storage variable is now overwriting initialized and token.
*/
// was tokenController
address internal _unused0;
uint public MIN_STAKE; // Minimum allowed stake per contract
uint public MAX_EXPOSURE; // Stakes sum must be less than the deposit amount times this
uint public MIN_UNSTAKE; // Forbid unstake of small amounts to prevent spam
uint public UNSTAKE_LOCK_TIME; // Lock period in seconds before unstaking takes place
mapping(address => Staker) public stakers; // stakerAddress => Staker
// temporary variables
uint public contractStaked; // used when processing burns and rewards
uint public contractBurned; // used when processing burns
uint public contractRewarded; // used when processing rewards
// list of stakers for all contracts
mapping(address => address[]) public contractStakers;
// there can be only one pending burn
Burn public burn;
mapping(uint => Reward) public rewards; // reward id => Reward
uint public firstReward;
uint public lastRewardId;
mapping(uint => UnstakeRequest) public unstakeRequests; // unstake id => UnstakeRequest
// firstUnstakeRequest is stored at unstakeRequests[0].next
uint public lastUnstakeRequestId;
uint public processedToStakerIndex; // we processed the action up this staker
bool public isContractStakeCalculated; // flag to indicate whether staked amount is up to date or not
/* state vars for rewards groupping upgrade */
// rewards to be distributed at the end of the current round
// contract address => ContractRewards
mapping(address => ContractReward) public accumulatedRewards;
uint public REWARD_ROUND_DURATION;
uint public REWARD_ROUNDS_START;
ISAFURAToken internal immutable token;
/* Modifiers */
modifier noPendingActions {
require(!hasPendingActions(), "Unable to execute request with unprocessed actions");
_;
}
modifier noPendingBurns {
require(!hasPendingBurns(), "Unable to execute request with unprocessed burns");
_;
}
modifier noPendingUnstakeRequests {
require(!hasPendingUnstakeRequests(), "Unable to execute request with unprocessed unstake requests");
_;
}
modifier noPendingRewards {
require(!hasPendingRewards(), "Unable to execute request with unprocessed rewards");
_;
}
constructor(address coverAddress, address stakingNFTAddress, address _tokenAddress) {
cover = ICover(coverAddress);
stakingNFT = IStakingNFT(stakingNFTAddress);
token = ISAFURAToken(_tokenAddress);
}
function min(uint x, uint y) pure internal returns (uint) {
return x < y ? x : y;
}
/* Getters and view functions */
function contractStakerCount(address contractAddress) external view returns (uint) {
return contractStakers[contractAddress].length;
}
function contractStakerAtIndex(address contractAddress, uint stakerIndex) external view returns (address) {
return contractStakers[contractAddress][stakerIndex];
}
function contractStakersArray(address contractAddress) external view returns (address[] memory _stakers) {
return contractStakers[contractAddress];
}
function contractStake(address contractAddress) public override view returns (uint) {
address[] storage _stakers = contractStakers[contractAddress];
uint stakerCount = _stakers.length;
uint stakedOnContract;
for (uint i = 0; i < stakerCount; i++) {
Staker storage staker = stakers[_stakers[i]];
uint deposit = staker.deposit;
uint stake = staker.stakes[contractAddress];
// add the minimum of the two
stake = deposit < stake ? deposit : stake;
stakedOnContract = stakedOnContract + stake;
}
return stakedOnContract;
}
function stakerContractCount(address staker) external view returns (uint) {
return stakers[staker].contracts.length;
}
function stakerContractAtIndex(address staker, uint contractIndex) external view returns (address) {
return stakers[staker].contracts[contractIndex];
}
function stakerContractsArray(address staker) external view returns (address[] memory) {
return stakers[staker].contracts;
}
function stakerContractStake(address staker, address contractAddress) public override view returns (uint) {
uint stake = stakers[staker].stakes[contractAddress];
uint deposit = stakers[staker].deposit;
return stake < deposit ? stake : deposit;
}
function stakerContractPendingUnstakeTotal(address staker, address contractAddress) external view returns (uint) {
return stakers[staker].pendingUnstakeRequestsTotal[contractAddress];
}
function stakerReward(address staker) external override view returns (uint) {
return stakers[staker].reward;
}
function stakerDeposit(address staker) external override view returns (uint) {
return stakers[staker].deposit;
}
function stakerMaxWithdrawable(address stakerAddress) public override view returns (uint) {
Staker storage staker = stakers[stakerAddress];
uint deposit = staker.deposit;
uint totalStaked;
uint maxStake;
for (uint i = 0; i < staker.contracts.length; i++) {
address contractAddress = staker.contracts[i];
uint initialStake = staker.stakes[contractAddress];
uint stake = deposit < initialStake ? deposit : initialStake;
totalStaked = totalStaked + stake;
if (stake > maxStake) {
maxStake = stake;
}
}
uint minRequired = totalStaked / MAX_EXPOSURE;
uint locked = maxStake > minRequired ? maxStake : minRequired;
return deposit - locked;
}
function unstakeRequestAtIndex(uint unstakeRequestId) external view returns (
uint amount, uint unstakeAt, address contractAddress, address stakerAddress, uint next
) {
UnstakeRequest storage unstakeRequest = unstakeRequests[unstakeRequestId];
amount = unstakeRequest.amount;
unstakeAt = unstakeRequest.unstakeAt;
contractAddress = unstakeRequest.contractAddress;
stakerAddress = unstakeRequest.stakerAddress;
next = unstakeRequest.next;
}
function hasPendingActions() public override view returns (bool) {
return hasPendingBurns() || hasPendingUnstakeRequests() || hasPendingRewards();
}
function hasPendingBurns() public view returns (bool) {
return burn.burnedAt != 0;
}
function hasPendingUnstakeRequests() public view returns (bool){
uint nextRequestIndex = unstakeRequests[0].next;
if (nextRequestIndex == 0) {
return false;
}
return unstakeRequests[nextRequestIndex].unstakeAt <= block.timestamp;
}
function hasPendingRewards() public view returns (bool){
return rewards[firstReward].rewardedAt != 0;
}
/* State-changing functions */
function depositAndStake(
uint /*amount*/,
address[] calldata /*_contracts*/,
uint[] calldata /*_stakes*/
) external pure {
revert("Migrate to v2");
}
function withdraw(uint /*ignoredParam*/) external override whenNotPaused onlyMember noPendingBurns {
_withdrawForUser(msg.sender);
}
function withdrawForUser(address user) external override whenNotPaused onlyMember noPendingBurns {
_withdrawForUser(user);
}
function _withdrawForUser(address user) internal {
// Stakers scheduled for automatic migration are not allowed to withdraw
require(
user != ARMOR_STAKER &&
user != HUGH &&
user != NM_FOUNDATION,
"Not allowed to withdraw"
);
uint amount = stakers[user].deposit;
stakers[user].deposit = 0;
token.transfer(user, amount);
emit Withdrawn(user, amount);
}
function requestUnstake(
address[] calldata /*_contracts*/,
uint[] calldata /*_amounts*/,
uint /*_insertAfter*/ // unstake request id after which the new unstake request will be inserted
) external pure {
revert("Migrate to v2");
}
function withdrawReward(address stakerAddress) external override whenNotPaused {
uint amount = stakers[stakerAddress].reward;
stakers[stakerAddress].reward = 0;
token.transfer(stakerAddress, amount);
emit RewardWithdrawn(stakerAddress, amount);
}
function pushBurn(address /*contractAddress*/, uint /*amount*/) external pure override {
revert("Migrate to v2");
}
function _getCurrentRewardsRound() internal view returns (uint) {
uint roundDuration = REWARD_ROUND_DURATION;
uint startTime = REWARD_ROUNDS_START;
require(startTime != 0, "REWARD_ROUNDS_START is not initialized");
return block.timestamp <= startTime ? 0 : (block.timestamp - startTime) / roundDuration;
}
function getCurrentRewardsRound() external view returns (uint) {
return _getCurrentRewardsRound();
}
/**
* @dev Pushes accumulated rewards to the processing queue.
*/
function _pushRewards(address[] memory contractAddresses, bool skipRoundCheck) internal {
uint currentRound = _getCurrentRewardsRound();
uint lastRewardIdCounter = lastRewardId;
uint pushedRewards = 0;
for (uint i = 0; i < contractAddresses.length; i++) {
address contractAddress = contractAddresses[i];
ContractReward storage contractRewards = accumulatedRewards[contractAddress];
uint lastRound = contractRewards.lastDistributionRound;
uint amount = contractRewards.amount;
bool shouldPush = amount > 0 && (skipRoundCheck || currentRound > lastRound);
if (!shouldPush) {
// prevent unintended distribution of the first reward in round
if (lastRound != currentRound) {
contractRewards.lastDistributionRound = currentRound;
}
continue;
}
rewards[++lastRewardIdCounter] = Reward(amount, block.timestamp, contractAddress);
emit RewardRequested(contractAddress, amount);
contractRewards.amount = 0;
contractRewards.lastDistributionRound = currentRound;
++pushedRewards;
if (pushedRewards == 1 && firstReward == 0) {
firstReward = lastRewardIdCounter;
}
}
if (pushedRewards != 0) {
lastRewardId = lastRewardIdCounter;
}
}
/**
* @dev External function for pushing accumulated rewards in the processing queue.
* @dev `_pushRewards` checks the current round and will only push if rewards can be distributed.
*/
function pushRewards(address[] calldata contractAddresses) external whenNotPaused {
_pushRewards(contractAddresses, true);
}
/**
* @dev Add reward for contract. Automatically triggers distribution if enough time has passed.
*/
function accumulateReward(address /*contractAddress*/, uint /*amount*/) external override pure {
revert("Migrate to v2");
}
function processPendingActions(uint maxIterations) public override whenNotPaused returns (bool finished) {
(finished,) = _processPendingActions(maxIterations);
}
function processPendingActionsReturnLeft(uint maxIterations) public whenNotPaused returns (bool finished, uint iterationsLeft) {
(finished, iterationsLeft) = _processPendingActions(maxIterations);
}
function _processPendingActions(uint maxIterations) public whenNotPaused returns (bool finished, uint iterationsLeft) {
iterationsLeft = maxIterations;
while (true) {
uint firstUnstakeRequestIndex = unstakeRequests[0].next;
UnstakeRequest storage unstakeRequest = unstakeRequests[firstUnstakeRequestIndex];
Reward storage reward = rewards[firstReward];
// read storage and cache in memory
uint burnedAt = burn.burnedAt;
uint rewardedAt = reward.rewardedAt;
uint unstakeAt = unstakeRequest.unstakeAt;
bool canUnstake = firstUnstakeRequestIndex > 0 && unstakeAt <= block.timestamp;
bool canBurn = burnedAt != 0;
bool canReward = firstReward != 0;
if (!canBurn && !canUnstake && !canReward) {
// everything is processed
break;
}
if (
canBurn &&
(!canUnstake || burnedAt < unstakeAt) &&
(!canReward || burnedAt < rewardedAt)
) {
(finished, iterationsLeft) = _processBurn(iterationsLeft);
if (!finished) {
emit PendingActionsProcessed(false);
return (false, iterationsLeft);
}
continue;
}
if (
canUnstake &&
(!canReward || unstakeAt < rewardedAt)
) {
// _processFirstUnstakeRequest is O(1) so we'll handle the iteration checks here
if (iterationsLeft == 0) {
emit PendingActionsProcessed(false);
return (false, iterationsLeft);
}
_processFirstUnstakeRequest();
--iterationsLeft;
continue;
}
(finished, iterationsLeft) = _processFirstReward(iterationsLeft);
if (!finished) {
emit PendingActionsProcessed(false);
return (false, iterationsLeft);
}
}
// everything is processed!
emit PendingActionsProcessed(true);
return (true, iterationsLeft);
}
function _processBurn(uint maxIterations) internal returns (bool finished, uint iterationsLeft) {
iterationsLeft = maxIterations;
address _contractAddress = burn.contractAddress;
uint _stakedOnContract;
(_stakedOnContract, finished, iterationsLeft) = _calculateContractStake(_contractAddress, iterationsLeft);
if (!finished) {
return (false, iterationsLeft);
}
address[] storage _contractStakers = contractStakers[_contractAddress];
uint _stakerCount = _contractStakers.length;
uint _totalBurnAmount = burn.amount;
uint _actualBurnAmount = contractBurned;
if (_totalBurnAmount > _stakedOnContract) {
_totalBurnAmount = _stakedOnContract;
}
for (uint i = processedToStakerIndex; i < _stakerCount; ) {
if (iterationsLeft == 0) {
contractBurned = _actualBurnAmount;
processedToStakerIndex = i;
return (false, iterationsLeft);
}
--iterationsLeft;
Staker storage staker = stakers[_contractStakers[i]];
uint _stakerBurnAmount;
uint _newStake;
(_stakerBurnAmount, _newStake) = _burnStaker(staker, _contractAddress, _stakedOnContract, _totalBurnAmount);
_actualBurnAmount = _actualBurnAmount + _stakerBurnAmount;
if (_newStake != 0) {
i++;
continue;
}
// if we got here, the stake is explicitly set to 0
// the staker is removed from the contract stakers array
// and we will add the staker back if he stakes again
staker.isInContractStakers[_contractAddress] = false;
_contractStakers[i] = _contractStakers[_stakerCount - 1];
_contractStakers.pop();
_stakerCount--;
}
delete burn;
contractBurned = 0;
processedToStakerIndex = 0;
isContractStakeCalculated = false;
token.burn(_actualBurnAmount);
emit Burned(_contractAddress, _actualBurnAmount, _stakedOnContract);
return (true, iterationsLeft);
}
function _burnStaker(
Staker storage staker, address _contractAddress, uint _stakedOnContract, uint _totalBurnAmount
) internal returns (
uint _stakerBurnAmount, uint _newStake
) {
uint _currentDeposit;
uint _currentStake;
// silence compiler warning
_newStake = 0;
// do we need a storage read?
if (_stakedOnContract != 0) {
_currentDeposit = staker.deposit;
_currentStake = staker.stakes[_contractAddress];
if (_currentStake > _currentDeposit) {
_currentStake = _currentDeposit;
}
}
if (_stakedOnContract != _totalBurnAmount) {
// formula: staker_burn = staker_stake / total_contract_stake * contract_burn
// reordered for precision loss prevention
_stakerBurnAmount = _currentStake * _totalBurnAmount / _stakedOnContract;
_newStake = _currentStake - _stakerBurnAmount;
} else {
// it's the whole stake
_stakerBurnAmount = _currentStake;
}
if (_stakerBurnAmount != 0) {
staker.deposit = _currentDeposit - _stakerBurnAmount;
}
staker.stakes[_contractAddress] = _newStake;
}
function _calculateContractStake(
address _contractAddress, uint maxIterations
) internal returns (
uint _stakedOnContract, bool finished, uint iterationsLeft
) {
iterationsLeft = maxIterations;
if (isContractStakeCalculated) {
// use previously calculated staked amount
return (contractStaked, true, iterationsLeft);
}
address[] storage _contractStakers = contractStakers[_contractAddress];
uint _stakerCount = _contractStakers.length;
uint startIndex = processedToStakerIndex;
if (startIndex != 0) {
_stakedOnContract = contractStaked;
}
// calculate amount staked on contract
for (uint i = startIndex; i < _stakerCount; i++) {
if (iterationsLeft == 0) {
processedToStakerIndex = i;
contractStaked = _stakedOnContract;
return (_stakedOnContract, false, iterationsLeft);
}
--iterationsLeft;
Staker storage staker = stakers[_contractStakers[i]];
uint deposit = staker.deposit;
uint stake = staker.stakes[_contractAddress];
stake = deposit < stake ? deposit : stake;
_stakedOnContract = _stakedOnContract + stake;
}
contractStaked = _stakedOnContract;
isContractStakeCalculated = true;
processedToStakerIndex = 0;
return (_stakedOnContract, true, iterationsLeft);
}
function _processFirstUnstakeRequest() internal {
uint firstRequest = unstakeRequests[0].next;
UnstakeRequest storage unstakeRequest = unstakeRequests[firstRequest];
address stakerAddress = unstakeRequest.stakerAddress;
Staker storage staker = stakers[stakerAddress];
address contractAddress = unstakeRequest.contractAddress;
uint deposit = staker.deposit;
uint initialStake = staker.stakes[contractAddress];
uint stake = deposit < initialStake ? deposit : initialStake;
uint requestedAmount = unstakeRequest.amount;
uint actualUnstakedAmount = stake < requestedAmount ? stake : requestedAmount;
staker.stakes[contractAddress] = stake - actualUnstakedAmount;
uint pendingUnstakeRequestsTotal = staker.pendingUnstakeRequestsTotal[contractAddress];
staker.pendingUnstakeRequestsTotal[contractAddress] = pendingUnstakeRequestsTotal - requestedAmount;
// update pointer to first unstake request
unstakeRequests[0].next = unstakeRequest.next;
delete unstakeRequests[firstRequest];
emit Unstaked(contractAddress, stakerAddress, requestedAmount);
}
function _processFirstReward(uint maxIterations) internal returns (bool finished, uint iterationsLeft) {
iterationsLeft = maxIterations;
Reward storage reward = rewards[firstReward];
address _contractAddress = reward.contractAddress;
uint _totalRewardAmount = reward.amount;
uint _stakedOnContract;
(_stakedOnContract, finished, iterationsLeft) = _calculateContractStake(_contractAddress, iterationsLeft);
if (!finished) {
return (false, iterationsLeft);
}
address[] storage _contractStakers = contractStakers[_contractAddress];
uint _stakerCount = _contractStakers.length;
uint _actualRewardAmount = contractRewarded;
for (uint i = processedToStakerIndex; i < _stakerCount;) {
if (iterationsLeft == 0) {
contractRewarded = _actualRewardAmount;
processedToStakerIndex = i;
return (false, iterationsLeft);
}
--iterationsLeft;
address _stakerAddress = _contractStakers[i];
(uint _stakerRewardAmount, uint _stake) = _rewardStaker(
_stakerAddress, _contractAddress, _totalRewardAmount, _stakedOnContract
);
// remove 0-amount stakers, similar to what we're doing when processing burns
if (_stake == 0) {
// mark the user as not present in contract stakers array
Staker storage staker = stakers[_stakerAddress];
staker.isInContractStakers[_contractAddress] = false;
// remove the staker from the contract stakers array
_contractStakers[i] = _contractStakers[_stakerCount - 1];
_contractStakers.pop();
_stakerCount--;
// since the stake is 0, there's no reward to give
continue;
}
_actualRewardAmount = _actualRewardAmount + _stakerRewardAmount;
i++;
}
delete rewards[firstReward];
contractRewarded = 0;
processedToStakerIndex = 0;
isContractStakeCalculated = false;
if (++firstReward > lastRewardId) {
firstReward = 0;
}
tokenController().mint(address(this), _actualRewardAmount);
emit Rewarded(_contractAddress, _actualRewardAmount, _stakedOnContract);
return (true, iterationsLeft);
}
function _rewardStaker(
address stakerAddress, address contractAddress, uint totalRewardAmount, uint totalStakedOnContract
) internal returns (uint rewardedAmount, uint stake) {
Staker storage staker = stakers[stakerAddress];
uint deposit = staker.deposit;
stake = staker.stakes[contractAddress];
if (stake > deposit) {
stake = deposit;
}
// prevent division by zero and set stake to zero
if (totalStakedOnContract == 0 || stake == 0) {
staker.stakes[contractAddress] = 0;
return (0, 0);
}
// reward = staker_stake / total_contract_stake * total_reward
rewardedAmount = totalRewardAmount * stake / totalStakedOnContract;
staker.reward = staker.reward + rewardedAmount;
}
function updateUintParameters(bytes8 code, uint value) external onlyGovernance {
if (code == "MIN_STAK") {
MIN_STAKE = value;
return;
}
if (code == "MAX_EXPO") {
MAX_EXPOSURE = value;
return;
}
if (code == "MIN_UNST") {
MIN_UNSTAKE = value;
return;
}
if (code == "UNST_LKT") {
UNSTAKE_LOCK_TIME = value;
return;
}
}
function tokenController() internal view returns (ITokenController) {
return ITokenController(internalContracts[uint(ID.TC)]);
}
function memberRoles() internal view returns (IMemberRoles) {
return IMemberRoles(internalContracts[uint(ID.MR)]);
}
function changeDependentContractAddress() public {
internalContracts[uint(ID.TC)] = master.getLatestAddress("TC");
internalContracts[uint(ID.MR)] = master.getLatestAddress("MR");
}
}