//SPDX-License-Identifier:MIT
pragma solidity ^0.8.17;
import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
/**
* @title BipzyStaking
* @notice This contract allows holders of Bipzy and BipzyPass NFTs to stake their tokens for predetermined durations.
* @dev The contract supports two types of NFTs: Bipzy and BipzyPass, and allows staking for either 180 or 360 days.
*/
contract BipzyStaking is Ownable {
/**
* @notice Custom errors for various error scenarios.
* @dev Using custom errors helps save gas compared to revert strings.
*/
error BPZ__InvalidPrevId();
error BPZ__StakePeriodNotOver();
error BPZ__MustOwn1NFTAtLeast();
error BPZ__InvalidStakeDuration();
error BPZ__ContractInstanceLocked();
error BPZ__NFTContractNotSupported();
error BPZ__ZeroStakePreferencesFound();
error BPZ__OnlyStakerCanWithdrawTheirStake();
/**
* @notice The Bipzy NFT contract instance.
*/
IERC721 public i_bipzy;
/**
* @notice The BipzyPass NFT contract instance.
*/
IERC721 public i_bipzyPass;
/**
* @notice This boolean flag indicates whether the contract instances (i_bipzy and i_bipzyPass) are locked.
* @dev If set to true, the contract instances cannot be updated by calling setNewBipzyInstance or setNewBipzyPassInstance.
*/
bool public contractInstancesLocked;
/**
* @notice The duration of minimum stake period days in seconds.
*/
uint64 public minDuration = 3 * 30 days;
/**
* @notice The duration of maximum stake period days in seconds.
*/
uint64 public maxDuration = 6 * 30 days;
/**
* @notice The booster value for a staked NFT with a 180-day duration.
*/
uint256 public boosterValueOne = 10000;
/**
* @notice The booster value for a staked NFT with a 360-day duration.
*/
uint256 public boosterValueTwo = 15000;
/**
* @notice The sentinel token ID used as a marker in the linked list.
* @dev This value is derived from the string "Bipzys" converted to binary and then decimal.
*/
uint256 public constant SENTINAL_TOKEN_ID = 73020626073971;
/**
* @notice Struct to store stake information for each staked token.
* @param staker The address of the staker.
* @param tokenId The ID of the staked token.
* @param tokenContract The contract instance of the staked token (Bipzy or BipzyPass).
* @param stakeStartedOn The timestamp when the stake started.
* @param stakeEndsOn The timestamp when the stake ends.
*/
struct StakeInfo {
address staker;
uint96 tokenId;
IERC721 tokenContract;
uint32 stakeStartedOn;
uint32 stakeEndsOn;
}
/**
* @notice Struct to store stake preference information for staking NFTs.
* @param tokenId The ID of the token to be staked.
* @param stakeDuration The duration of the stake (180 or 360 days).
* @param nftContract The contract instance of the NFT (Bipzy or BipzyPass).
*/
struct StakePreference {
uint96 tokenId;
uint32 stakeDuration;
IERC721 nftContract;
}
/**
* @notice Struct to store stake withdrawal preference information.
* @param tokenId The ID of the token to be withdrawn.
* @param prevTokenId The ID of the token preceding the token to be withdrawn in the linked list.
*/
struct StakeWithdrawalPreference {
uint96 tokenId;
uint96 prevTokenId;
}
/**
* @notice Mapping to store stake information for each token ID.
*/
mapping(uint256 => StakeInfo) public idBipzyToStakeInfo;
mapping(uint256 => StakeInfo) public idBipzyPassToStakeInfo;
/**
* @notice Mapping to store token IDs in a linked list for each staker.
* @dev The linked list is used to efficiently traverse the staker's staked tokens during booster calculation.
*/
mapping(address => mapping(uint256 => uint256)) public ownerToBipzyTokenIds;
mapping(address => mapping(uint256 => uint256))
public ownerToBipzyPassTokenIds;
/**
* @notice Mapping to store Bipzy NFT Token IDs that are rare
* @dev Only Owner can update the mapping
* only Bipzy NFT will have rare NFTs hence only Bipzy NFT are going to be stored here
*/
mapping(uint256 => bool) public rareTokenIds;
/**
* @notice Event emitted when a Bipzy token is staked.
* @param staker The address of the staker.
* @param tokenId The ID of the staked token.
* @param nftContractInstance The contract instance of the staked token (Bipzy or BipzyPass).
* @param stakingStartedOn The timestamp when the stake started.
* @param stakingEndTimeStamp The timestamp when the stake ends.
*/
event BipzyTokenStaked(
address indexed staker,
uint96 indexed tokenId,
IERC721 indexed nftContractInstance,
uint32 stakingStartedOn,
uint32 stakingEndTimeStamp
);
/**
* @notice Event emitted when a Bipzy Pass token is staked.
* @param staker The address of the staker.
* @param tokenId The ID of the staked token.
* @param nftContractInstance The contract instance of the staked token (Bipzy or BipzyPass).
* @param stakingStartedOn The timestamp when the stake started.
* @param stakingEndTimeStamp The timestamp when the stake ends.
*/
event BipzyPassTokenStaked(
address indexed staker,
uint96 indexed tokenId,
IERC721 indexed nftContractInstance,
uint32 stakingStartedOn,
uint32 stakingEndTimeStamp
);
/**
* @notice Event emitted when a staked token is withdrawn.
* @param staker The address of the staker.
* @param tokenId The ID of the withdrawn token.
* @param nftContractInstance The contract instance of the withdrawn token (Bipzy or BipzyPass).
*/
event BipzyStakeWithdrawn(
address indexed staker,
uint96 indexed tokenId,
IERC721 indexed nftContractInstance
);
/**
* @notice Event emitted when a staked token is withdrawn.
* @param staker The address of the staker.
* @param tokenId The ID of the withdrawn token.
* @param nftContractInstance The contract instance of the withdrawn token (Bipzy or BipzyPass).
*/
event BipzyPassStakeWithdrawn(
address indexed staker,
uint96 indexed tokenId,
IERC721 indexed nftContractInstance
);
/**
* @notice Event emitteed when owner force unstakes the token
* @param tokenId token ID that was force unstaked
*/
event ForceUnstaked(
uint96 indexed tokenId
);
/**
* @notice Constructor to set the Bipzy and BipzyPass NFT contract instances.
* @param bipzyNftAddr The address of the Bipzy NFT contract.
* @param bipzyPassNftAddr The address of the BipzyPass NFT contract.
*/
constructor(IERC721 bipzyNftAddr, IERC721 bipzyPassNftAddr) {
i_bipzy = bipzyNftAddr;
i_bipzyPass = bipzyPassNftAddr;
contractInstancesLocked = false;
}
/**
* @dev Allows the staking of BIPZY tokens.
* @param stakePreferences Array of `StakePreference` structs containing stake preferences.
*/
function stakeBipzy(StakePreference[] calldata stakePreferences) public {
// Ensure that the caller owns at least one Bipzy or BipzyPass NFT
if (i_bipzy.balanceOf(msg.sender) == 0)
revert BPZ__MustOwn1NFTAtLeast();
// Ensure that at least one stake preference is provided
if (stakePreferences.length == 0)
revert BPZ__ZeroStakePreferencesFound();
uint32 currentTimestamp = uint32(block.timestamp);
if(ownerToBipzyTokenIds[msg.sender][SENTINAL_TOKEN_ID] == 0) {
ownerToBipzyTokenIds[msg.sender][SENTINAL_TOKEN_ID] = SENTINAL_TOKEN_ID;
}
// Iterate through the stake preferences and process each one
for (uint i = 0; i < stakePreferences.length; i++) {
StakePreference memory preference = stakePreferences[i];
uint96 tokenId = preference.tokenId;
uint32 duration = preference.stakeDuration;
IERC721 nftContractInstance = preference.nftContract;
// Ensure that the stake duration is either minDuration or maxDuration days
if (duration != minDuration && duration != maxDuration)
revert BPZ__InvalidStakeDuration();
uint32 stakeEndsOn = currentTimestamp + duration;
// Ensure that the NFT contract is either Bipzy or BipzyPass
if (nftContractInstance != i_bipzy) {
revert BPZ__NFTContractNotSupported();
}
// Transfer the NFT from the staker to the contract
nftContractInstance.transferFrom(
msg.sender,
address(this),
tokenId
);
// Update the linked list of token IDs for the staker
ownerToBipzyTokenIds[msg.sender][tokenId] = ownerToBipzyTokenIds[msg.sender][SENTINAL_TOKEN_ID];
ownerToBipzyTokenIds[msg.sender][SENTINAL_TOKEN_ID] = tokenId;
// Store the stake information for the token ID
idBipzyToStakeInfo[tokenId] = StakeInfo(
msg.sender,
tokenId,
nftContractInstance,
currentTimestamp,
stakeEndsOn
);
// Emit the TokenStaked event
emit BipzyTokenStaked(
msg.sender,
tokenId,
nftContractInstance,
currentTimestamp,
stakeEndsOn
);
}
}
/**
* @dev Allows the staking of BIPZY PASS tokens.
* @param stakePreferences Array of `StakePreference` structs containing stake preferences.
*/
function stakeBipzyPass(
StakePreference[] calldata stakePreferences
) public {
// Ensure that the caller owns at least one Bipzy or BipzyPass NFT
if (i_bipzyPass.balanceOf(msg.sender) == 0)
revert BPZ__MustOwn1NFTAtLeast();
// Ensure that at least one stake preference is provided
if (stakePreferences.length == 0)
revert BPZ__ZeroStakePreferencesFound();
uint32 currentTimestamp = uint32(block.timestamp);
if(ownerToBipzyPassTokenIds[msg.sender][SENTINAL_TOKEN_ID] == 0) {
ownerToBipzyPassTokenIds[msg.sender][SENTINAL_TOKEN_ID] = SENTINAL_TOKEN_ID;
}
// Iterate through the stake preferences and process each one
for (uint i = 0; i < stakePreferences.length; i++) {
StakePreference memory preference = stakePreferences[i];
uint96 tokenId = preference.tokenId;
uint32 duration = preference.stakeDuration;
IERC721 nftContractInstance = preference.nftContract;
// Ensure that the stake duration is either minDuration or maxDuration days
if (duration != minDuration && duration != maxDuration)
revert BPZ__InvalidStakeDuration();
uint32 stakeEndsOn = currentTimestamp + duration;
// Ensure that the NFT contract is either Bipzy or BipzyPass
if (nftContractInstance != i_bipzyPass) {
revert BPZ__NFTContractNotSupported();
}
// Transfer the NFT from the staker to the contract
nftContractInstance.transferFrom(
msg.sender,
address(this),
tokenId
);
// Update the linked list of token IDs for the staker
ownerToBipzyPassTokenIds[msg.sender][tokenId] = ownerToBipzyPassTokenIds[msg.sender][SENTINAL_TOKEN_ID];
ownerToBipzyPassTokenIds[msg.sender][SENTINAL_TOKEN_ID] = tokenId;
// Store the stake information for the token ID
idBipzyPassToStakeInfo[tokenId] = StakeInfo(
msg.sender,
tokenId,
nftContractInstance,
currentTimestamp,
stakeEndsOn
);
// Emit the TokenStaked event
emit BipzyPassTokenStaked(
msg.sender,
tokenId,
nftContractInstance,
currentTimestamp,
stakeEndsOn
);
}
}
/**
* @dev Allows the withdrawal of stakes for BIPZY tokens.
* @param stakeWithdrawalPreferences Array of `StakeWithdrawalPreference` structs containing withdrawal preferences.
*/
function stakeBipzyWithdrawal(
StakeWithdrawalPreference[] calldata stakeWithdrawalPreferences
) public {
// Iterate through the withdrawal preferences
for (uint96 i = 0; i < stakeWithdrawalPreferences.length; i++) {
uint96 tokenId = stakeWithdrawalPreferences[i].tokenId;
uint96 prevId = stakeWithdrawalPreferences[i].prevTokenId;
StakeInfo memory stakeInfo = idBipzyToStakeInfo[tokenId];
IERC721 stakedContractInstance = stakeInfo.tokenContract;
// Ensure that the caller is the staker of the token
if (stakeInfo.staker != msg.sender)
revert BPZ__OnlyStakerCanWithdrawTheirStake();
if (ownerToBipzyTokenIds[msg.sender][prevId] != tokenId)
revert BPZ__InvalidPrevId();
// Ensure that the stake period is over
if (block.timestamp < stakeInfo.stakeEndsOn)
revert BPZ__StakePeriodNotOver();
// Update the linked list of token IDs for the staker
ownerToBipzyTokenIds[msg.sender][prevId] = ownerToBipzyTokenIds[
msg.sender
][tokenId];
// Delete this token ID from the linked list
delete ownerToBipzyTokenIds[msg.sender][tokenId];
// Delete the stake information for the token ID
delete idBipzyToStakeInfo[tokenId];
// Transfer the NFT back to the staker
stakedContractInstance.transferFrom(
address(this),
msg.sender,
tokenId
);
// Emit the StakeWithdrawn event
emit BipzyStakeWithdrawn(msg.sender, tokenId, stakedContractInstance);
}
}
/**
* @dev Allows the withdrawal of stakes for BIPZY PASS tokens.
* @param stakeWithdrawalPreferences Array of `StakeWithdrawalPreference` structs containing withdrawal preferences.
*/
function stakeBipzyPassWithdrawal(
StakeWithdrawalPreference[] calldata stakeWithdrawalPreferences
) public {
// Iterate through the withdrawal preferences
for (uint96 i = 0; i < stakeWithdrawalPreferences.length; i++) {
uint96 tokenId = stakeWithdrawalPreferences[i].tokenId;
uint96 prevId = stakeWithdrawalPreferences[i].prevTokenId;
StakeInfo memory stakeInfo = idBipzyPassToStakeInfo[tokenId];
IERC721 stakedContractInstance = stakeInfo.tokenContract;
// Ensure that the caller is the staker of the token
if (stakeInfo.staker != msg.sender)
revert BPZ__OnlyStakerCanWithdrawTheirStake();
if (ownerToBipzyPassTokenIds[msg.sender][prevId] != tokenId)
revert BPZ__InvalidPrevId();
// Ensure that the stake period is over
if (block.timestamp < stakeInfo.stakeEndsOn)
revert BPZ__StakePeriodNotOver();
// Update the linked list of token IDs for the staker
ownerToBipzyPassTokenIds[msg.sender][
prevId
] = ownerToBipzyPassTokenIds[msg.sender][tokenId];
// Delete this token ID from the linked list
delete ownerToBipzyPassTokenIds[msg.sender][tokenId];
// Delete the stake information for the token ID
delete idBipzyPassToStakeInfo[tokenId];
// Transfer the NFT back to the staker
stakedContractInstance.transferFrom(
address(this),
msg.sender,
tokenId
);
// Emit the StakeWithdrawn event
emit BipzyPassStakeWithdrawn(msg.sender, tokenId, stakedContractInstance);
}
}
/**
* @notice Retrieves the total booster amount for a given owner's BipzyPass stakes.
* @param owner The address of the BipzyPass owner.
* @return The total booster amount calculated based on the active BipzyPass stakes and their durations.
*/
function getBipzyPassBoosterInfo(
address owner
) internal view returns (uint256) {
if (ownerToBipzyPassTokenIds[owner][SENTINAL_TOKEN_ID] != 0) {
uint256 currentTokenId = ownerToBipzyPassTokenIds[owner][
SENTINAL_TOKEN_ID
];
uint256 prevId = SENTINAL_TOKEN_ID;
uint32 _numDurationOneStakes;
uint32 _numDurationTwoStakes;
// Traverse the linked list of token IDs for the staker
while (currentTokenId != SENTINAL_TOKEN_ID) {
prevId = currentTokenId;
uint64 stakeDuration = idBipzyPassToStakeInfo[prevId]
.stakeEndsOn -
idBipzyPassToStakeInfo[prevId].stakeStartedOn;
// If the stake is still active, increment the respective counter
if (
idBipzyPassToStakeInfo[prevId].stakeEndsOn > block.timestamp
)
if (stakeDuration == minDuration) {
_numDurationOneStakes += 1;
} else {
_numDurationTwoStakes += 1;
}
currentTokenId = ownerToBipzyPassTokenIds[owner][prevId];
}
// Calculate the total booster amount based on the stake durations
uint256 totalBoosterAmount = (_numDurationOneStakes *
boosterValueOne) + (_numDurationTwoStakes * boosterValueTwo);
return totalBoosterAmount;
} else {
return 0;
}
}
/**
* @notice Retrieves the total booster amount for a given owner's Bipzy stakes.
* @param owner The address of the Bipzy owner.
* @return The total booster amount calculated based on the active Bipzy stakes and their durations.
*/
function getBipzyBoosterInfo(
address owner
) internal view returns (uint256) {
if (ownerToBipzyTokenIds[owner][SENTINAL_TOKEN_ID] != 0) {
uint256 currentTokenId = ownerToBipzyTokenIds[owner][
SENTINAL_TOKEN_ID
];
uint256 prevId = SENTINAL_TOKEN_ID;
uint32 _numDurationOneStakes;
uint32 _numDurationTwoStakes;
// Traverse the linked list of token IDs for the staker
while (currentTokenId != SENTINAL_TOKEN_ID) {
prevId = currentTokenId;
uint64 stakeDuration = idBipzyToStakeInfo[prevId].stakeEndsOn -
idBipzyToStakeInfo[prevId].stakeStartedOn;
// If the stake is still active, increment the respective counter
if (idBipzyToStakeInfo[prevId].stakeEndsOn > block.timestamp)
if (stakeDuration == minDuration) {
_numDurationOneStakes += 1;
} else {
_numDurationTwoStakes += 1;
}
currentTokenId = ownerToBipzyTokenIds[owner][prevId];
}
// Calculate the total booster amount based on the stake durations
uint256 totalBoosterAmount = (_numDurationOneStakes *
boosterValueOne) + (_numDurationTwoStakes * boosterValueTwo);
return totalBoosterAmount;
} else {
return 0;
}
}
/* @notice Allows the contract owner to forcibly unstake one or more Bipzy NFTs.
* @dev This function sets the `stakeEndsOn` timestamp of each specified token ID to the current block timestamp, effectively ending the stake.
* It emits a `ForceUnstaked` event for each token ID. This function can only be called by the contract owner.
* @param _tokenIds An array of token IDs to be forcibly unstaked.
*/
function forceUnstakeBipzy(uint96[] calldata _tokenIds) external onlyOwner {
for(uint256 i; i<_tokenIds.length; i++) {
idBipzyToStakeInfo[_tokenIds[i]].stakeEndsOn = uint32(block.timestamp);
emit ForceUnstaked(_tokenIds[i]);
}
}
/* @notice Allows the contract owner to forcibly unstake one or more Bipzy Pass NFTs.
* @dev This function sets the `stakeEndsOn` timestamp of each specified token ID to the current block timestamp, effectively ending the stake.
* It emits a `ForceUnstaked` event for each token ID. This function can only be called by the contract owner.
* @param _tokenIds An array of token IDs to be forcibly unstaked.
*/
function forceUnstakeBipzyPass(uint96[] calldata _tokenIds) external onlyOwner {
for(uint256 i; i<_tokenIds.length; i++) {
idBipzyPassToStakeInfo[_tokenIds[i]].stakeEndsOn = uint32(block.timestamp);
emit ForceUnstaked(_tokenIds[i]);
}
}
/**
* @notice Calculates the booster amount for a given staker based on their currently staked NFTs.
* @dev The booster amount is calculated by traversing the linked list of token IDs and summing the booster values for each active stake.
* @param owner The address of the staker for whom the booster amount is being calculated.
* @return The total booster amount for the staker.
*/
function getBoosterInfo(address owner) public view returns (uint256) {
uint256 bipzyBoosterAmt = getBipzyBoosterInfo(owner);
uint256 bipzyPassBoosterAmt = getBipzyPassBoosterInfo(owner);
// Calculate the total booster amount based on the stake durations
return bipzyBoosterAmt + bipzyPassBoosterAmt;
}
/**
* @notice check if user has staked any rare token
* To get if a user is actively staking a rare NFT or not,
* check for each token ID in ownerToTokenIds linked list,
* if token ID is rare, then return true
* else return false
* @param _account the account that needs to be checked
*/
function isUserStakingRare(address _account) external view returns (bool) {
uint256 currentTokenId = ownerToBipzyTokenIds[_account][
SENTINAL_TOKEN_ID
];
uint256 prevId = SENTINAL_TOKEN_ID;
// Traverse the linked list of token IDs for the staker
while (currentTokenId != SENTINAL_TOKEN_ID) {
prevId = currentTokenId;
// If the stake is still active, increment the respective counter
if (
idBipzyToStakeInfo[prevId].stakeEndsOn > block.timestamp &&
idBipzyToStakeInfo[currentTokenId].tokenContract == i_bipzy &&
rareTokenIds[currentTokenId]
) return true;
currentTokenId = ownerToBipzyTokenIds[_account][prevId];
}
return false;
}
/**
* @notice Only owner can update the token IDs that are rare
* @dev by default rare token ID can only exist of Bipzy NFT and not for Bipzy Pass.
* @param tokenId the token ID, that will be rare
* @param isRare rare staus
*/
function updateRareTokenId(uint96 tokenId, bool isRare) external onlyOwner {
rareTokenIds[tokenId] = isRare;
}
/**
* @notice Allows the owner to lock the contract instances (i_bipzy and i_bipzyPass).
* @dev Once locked, the contract instances cannot be updated by calling setNewBipzyInstance or setNewBipzyPassInstance.
*/
function lockContractInstances() public onlyOwner {
contractInstancesLocked = true;
}
/**
* @notice Updates the Bipzy NFT contract instance.
* @param _newBipzyInstance The new Bipzy NFT contract instance.
* @dev This function can only be called by the contract owner and if the contract instances are not locked.
*/
function setNewBipzyInstance(IERC721 _newBipzyInstance) public onlyOwner {
if (contractInstancesLocked) revert BPZ__ContractInstanceLocked();
i_bipzy = _newBipzyInstance;
}
/**
* @notice Updates the BipzyPass NFT contract instance.
* @param _newBipzyPassInstance The new BipzyPass NFT contract instance.
* @dev This function can only be called by the contract owner and if the contract instances are not locked.
*/
function setNewBipzyPassInstance(
IERC721 _newBipzyPassInstance
) public onlyOwner {
if (contractInstancesLocked) revert BPZ__ContractInstanceLocked();
i_bipzyPass = _newBipzyPassInstance;
}
/**
* @notice Updates the minimum and maximum stake durations for the contract.
* @dev This function can only be called by the contract owner.
* @param _minDuration The new minimum stake duration in seconds.
* @param _maxDuration The new maximum stake duration in seconds.
*/
function setNewStakeDurations(
uint64 _minDuration,
uint64 _maxDuration
) public onlyOwner {
minDuration = _minDuration;
maxDuration = _maxDuration;
}
/**
* @notice Updates the booster values for the two different stake durations.
* @dev This function can only be called by the contract owner.
* @param _boosterAmtOneEightyDays The new booster value for the 180-day stake duration.
* @param _boosterAmtThreeSixtyDays The new booster value for the 360-day stake duration.
*/
function setBoosterAmt(
uint256 _boosterAmtOneEightyDays,
uint256 _boosterAmtThreeSixtyDays
) public onlyOwner {
boosterValueOne = _boosterAmtOneEightyDays;
boosterValueTwo = _boosterAmtThreeSixtyDays;
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.4) (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;
}
function _contextSuffixLength() internal view virtual returns (uint256) {
return 0;
}
}
// 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: MIT
// OpenZeppelin Contracts (last updated v4.9.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: Note that the caller is responsible to confirm that the recipient is capable of receiving ERC721
* or else they may be permanently lost. Usage of {safeTransferFrom} prevents loss, though the caller must
* understand this adds an external call which potentially creates a reentrancy vulnerability.
*
* 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 (last updated v4.9.0) (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 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 {
require(owner() == _msgSender(), "Ownable: caller is not the owner");
}
/**
* @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 {
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);
}
}
{
"compilationTarget": {
"contracts/Staking/BipzyStaking.sol": "BipzyStaking"
},
"evmVersion": "london",
"libraries": {},
"metadata": {
"bytecodeHash": "ipfs",
"useLiteralContent": true
},
"optimizer": {
"enabled": true,
"runs": 200
},
"remappings": []
}
[{"inputs":[{"internalType":"contract IERC721","name":"bipzyNftAddr","type":"address"},{"internalType":"contract IERC721","name":"bipzyPassNftAddr","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"BPZ__ContractInstanceLocked","type":"error"},{"inputs":[],"name":"BPZ__InvalidPrevId","type":"error"},{"inputs":[],"name":"BPZ__InvalidStakeDuration","type":"error"},{"inputs":[],"name":"BPZ__MustOwn1NFTAtLeast","type":"error"},{"inputs":[],"name":"BPZ__NFTContractNotSupported","type":"error"},{"inputs":[],"name":"BPZ__OnlyStakerCanWithdrawTheirStake","type":"error"},{"inputs":[],"name":"BPZ__StakePeriodNotOver","type":"error"},{"inputs":[],"name":"BPZ__ZeroStakePreferencesFound","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"staker","type":"address"},{"indexed":true,"internalType":"uint96","name":"tokenId","type":"uint96"},{"indexed":true,"internalType":"contract IERC721","name":"nftContractInstance","type":"address"}],"name":"BipzyPassStakeWithdrawn","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"staker","type":"address"},{"indexed":true,"internalType":"uint96","name":"tokenId","type":"uint96"},{"indexed":true,"internalType":"contract IERC721","name":"nftContractInstance","type":"address"},{"indexed":false,"internalType":"uint32","name":"stakingStartedOn","type":"uint32"},{"indexed":false,"internalType":"uint32","name":"stakingEndTimeStamp","type":"uint32"}],"name":"BipzyPassTokenStaked","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"staker","type":"address"},{"indexed":true,"internalType":"uint96","name":"tokenId","type":"uint96"},{"indexed":true,"internalType":"contract IERC721","name":"nftContractInstance","type":"address"}],"name":"BipzyStakeWithdrawn","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"staker","type":"address"},{"indexed":true,"internalType":"uint96","name":"tokenId","type":"uint96"},{"indexed":true,"internalType":"contract IERC721","name":"nftContractInstance","type":"address"},{"indexed":false,"internalType":"uint32","name":"stakingStartedOn","type":"uint32"},{"indexed":false,"internalType":"uint32","name":"stakingEndTimeStamp","type":"uint32"}],"name":"BipzyTokenStaked","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint96","name":"tokenId","type":"uint96"}],"name":"ForceUnstaked","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"inputs":[],"name":"SENTINAL_TOKEN_ID","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"boosterValueOne","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"boosterValueTwo","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"contractInstancesLocked","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint96[]","name":"_tokenIds","type":"uint96[]"}],"name":"forceUnstakeBipzy","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint96[]","name":"_tokenIds","type":"uint96[]"}],"name":"forceUnstakeBipzyPass","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"getBoosterInfo","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"i_bipzy","outputs":[{"internalType":"contract IERC721","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"i_bipzyPass","outputs":[{"internalType":"contract IERC721","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"idBipzyPassToStakeInfo","outputs":[{"internalType":"address","name":"staker","type":"address"},{"internalType":"uint96","name":"tokenId","type":"uint96"},{"internalType":"contract IERC721","name":"tokenContract","type":"address"},{"internalType":"uint32","name":"stakeStartedOn","type":"uint32"},{"internalType":"uint32","name":"stakeEndsOn","type":"uint32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"idBipzyToStakeInfo","outputs":[{"internalType":"address","name":"staker","type":"address"},{"internalType":"uint96","name":"tokenId","type":"uint96"},{"internalType":"contract IERC721","name":"tokenContract","type":"address"},{"internalType":"uint32","name":"stakeStartedOn","type":"uint32"},{"internalType":"uint32","name":"stakeEndsOn","type":"uint32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_account","type":"address"}],"name":"isUserStakingRare","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"lockContractInstances","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"maxDuration","outputs":[{"internalType":"uint64","name":"","type":"uint64"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"minDuration","outputs":[{"internalType":"uint64","name":"","type":"uint64"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"ownerToBipzyPassTokenIds","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"ownerToBipzyTokenIds","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"rareTokenIds","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_boosterAmtOneEightyDays","type":"uint256"},{"internalType":"uint256","name":"_boosterAmtThreeSixtyDays","type":"uint256"}],"name":"setBoosterAmt","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract IERC721","name":"_newBipzyInstance","type":"address"}],"name":"setNewBipzyInstance","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract IERC721","name":"_newBipzyPassInstance","type":"address"}],"name":"setNewBipzyPassInstance","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint64","name":"_minDuration","type":"uint64"},{"internalType":"uint64","name":"_maxDuration","type":"uint64"}],"name":"setNewStakeDurations","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"uint96","name":"tokenId","type":"uint96"},{"internalType":"uint32","name":"stakeDuration","type":"uint32"},{"internalType":"contract IERC721","name":"nftContract","type":"address"}],"internalType":"struct BipzyStaking.StakePreference[]","name":"stakePreferences","type":"tuple[]"}],"name":"stakeBipzy","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"uint96","name":"tokenId","type":"uint96"},{"internalType":"uint32","name":"stakeDuration","type":"uint32"},{"internalType":"contract IERC721","name":"nftContract","type":"address"}],"internalType":"struct BipzyStaking.StakePreference[]","name":"stakePreferences","type":"tuple[]"}],"name":"stakeBipzyPass","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"uint96","name":"tokenId","type":"uint96"},{"internalType":"uint96","name":"prevTokenId","type":"uint96"}],"internalType":"struct BipzyStaking.StakeWithdrawalPreference[]","name":"stakeWithdrawalPreferences","type":"tuple[]"}],"name":"stakeBipzyPassWithdrawal","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"uint96","name":"tokenId","type":"uint96"},{"internalType":"uint96","name":"prevTokenId","type":"uint96"}],"internalType":"struct BipzyStaking.StakeWithdrawalPreference[]","name":"stakeWithdrawalPreferences","type":"tuple[]"}],"name":"stakeBipzyWithdrawal","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint96","name":"tokenId","type":"uint96"},{"internalType":"bool","name":"isRare","type":"bool"}],"name":"updateRareTokenId","outputs":[],"stateMutability":"nonpayable","type":"function"}]