Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

collab/staking points #50

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 99 additions & 0 deletions contracts/manifold/staking/ERC721StakingPoints.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

/// @author: manifold.xyz

import "@openzeppelin/contracts/token/ERC721/IERC721.sol";
import "@manifoldxyz/creator-core-solidity/contracts/core/IERC721CreatorCore.sol";
import "../../libraries/IERC721CreatorCoreVersion.sol";
import "./IERC721StakingPoints.sol";
import "./StakingPointsCore.sol";
import "./IStakingPointsCore.sol";

/**
* @title ERC721 Staking Points
* @author manifold.xyz
* @notice logic for Staking Points for ERC721 extension.
*/
contract ERC721StakingPoints is StakingPointsCore, IERC721StakingPoints {
using SafeMath for uint256;
// { creatorContractAddress => {instanceId => uint256 } }
mapping(address => mapping(uint256 => uint256)) public totalPointsClaimed;

function supportsInterface(bytes4 interfaceId) public view virtual override(StakingPointsCore, IERC165) returns (bool) {
return interfaceId == type(IERC721StakingPoints).interfaceId || super.supportsInterface(interfaceId);
}

function initializeStakingPoints(
address creatorContractAddress,
uint256 instanceId,
StakingPointsParams calldata stakingPointsParams
) external creatorAdminRequired(creatorContractAddress) nonReentrant {
// Max uint56 for instanceId
require(instanceId > 0 && instanceId <= MAX_UINT_56, "Invalid instanceId");
require(stakingPointsParams.paymentReceiver != address(0), "Cannot initialize without payment receiver");

uint8 creatorContractVersion;
try IERC721CreatorCoreVersion(creatorContractAddress).VERSION() returns (uint256 version) {
require(version <= 255, "Unsupported contract version");
creatorContractVersion = uint8(version);
} catch {}
StakingPoints storage instance = _stakingPointsInstances[creatorContractAddress][instanceId];
require(instance.paymentReceiver == address(0), "StakingPoints already initialized");
_initialize(creatorContractAddress, creatorContractVersion, instanceId, stakingPointsParams);

emit StakingPointsInitialized(creatorContractAddress, instanceId, msg.sender);
}

function updateStakingPoints(
address creatorContractAddress,
uint256 instanceId,
StakingPointsParams calldata stakingPointsParams
) external creatorAdminRequired(creatorContractAddress) nonReentrant {
// Max uint56 for instanceId
require(instanceId > 0 && instanceId <= MAX_UINT_56, "Invalid instanceId");
require(stakingPointsParams.paymentReceiver != address(0), "Cannot update without payment receiver");

uint8 creatorContractVersion;
try IERC721CreatorCoreVersion(creatorContractAddress).VERSION() returns (uint256 version) {
require(version <= 255, "Unsupported contract version");
creatorContractVersion = uint8(version);
} catch {}
StakingPoints storage instance = _stakingPointsInstances[creatorContractAddress][instanceId];
require(instance.stakers.length == (0), "StakingPoints cannot be updated when 1 or more wallets have staked");
_update(creatorContractAddress, creatorContractVersion, instanceId, stakingPointsParams);

emit StakingPointsUpdated(creatorContractAddress, instanceId, msg.sender);
}

/**
* @dev was originally using safeTransferFrom but was getting a reentrancy error
*/
function _transfer(address contractAddress, uint256 tokenId, address from, address to) internal override {
require(
IERC721(contractAddress).ownerOf(tokenId) == from &&
(IERC721(contractAddress).getApproved(tokenId) == address(this) ||
IERC721(contractAddress).isApprovedForAll(from, address(this))),
"Token not owned or not approved"
);
require(IERC721(contractAddress).ownerOf(tokenId) == from, "Token not in sender possesion");
IERC721(contractAddress).transferFrom(from, to, tokenId);
}

/**
* @dev was originally using safeTransferFrom but was getting a reentrancy error
*/
function _transferBack(address contractAddress, uint256 tokenId, address from, address to) internal override {
require(IERC721(contractAddress).ownerOf(tokenId) == from, "Token not in sender possesion");
IERC721(contractAddress).transferFrom(from, to, tokenId);
}

/**
* @dev
*/
function _redeem(address creatorContractAddress, uint256 instanceId, uint256 pointsAmount) internal override {
uint256 currTotal = totalPointsClaimed[creatorContractAddress][instanceId];
totalPointsClaimed[creatorContractAddress][instanceId] = currTotal.add(pointsAmount);
}
}
34 changes: 34 additions & 0 deletions contracts/manifold/staking/IERC721StakingPoints.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

/// @author: manifold.xyz

import "./IStakingPointsCore.sol";
import "./StakingPointsCore.sol";

interface IERC721StakingPoints is IStakingPointsCore {
/**
* @notice initialize a new staking points, emit initialize event
* @param creatorContractAddress the address of the creator contract
* @param instanceId the instanceId of the staking points for the creator contract
* @param stakingPointsParams the stakingPointsParams object
*/
function initializeStakingPoints(
address creatorContractAddress,
uint256 instanceId,
StakingPointsParams calldata stakingPointsParams
) external;

/**
* @notice update existing staking points, emit update event
* @param creatorContractAddress the address of the creator contract
* @param instanceId the instanceId of the staking points for the creator contract
* @param stakingPointsParams the stakingPointsParams object
*/
function updateStakingPoints(
address creatorContractAddress,
uint256 instanceId,
StakingPointsParams calldata stakingPointsParams
) external;
}
118 changes: 118 additions & 0 deletions contracts/manifold/staking/IStakingPointsCore.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

/// @author: manifold.xyz

import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "@openzeppelin/contracts/utils/introspection/IERC165.sol";
import "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol";

// import "@openzeppelin/contracts/token/ERC1155/IERC1155Receiver.sol";

/**
* StakingPointsCore interface
*/
interface IStakingPointsCore is IERC165, IERC721Receiver {
/** TODO: CONFIRM STRUCTS */

struct StakedToken {
uint256 tokenId;
address contractAddress;
address stakerAddress;
uint256 timeStaked;
uint256 timeUnstaked;
uint256 tokenIdx;
}

struct StakedTokenIdx {
uint256 tokenIdx;
address stakerAddress;
}

struct StakedTokenParams {
address tokenAddress;
uint256 tokenId;
}

struct Staker {
uint256 pointsRedeemed;
uint256 stakerIdx;
StakedToken[] stakersTokens;
address stakerAddress;
}

struct StakingRule {
address tokenAddress;
uint256 pointsRatePerDay;
uint256 startTime;
uint256 endTime;
}

struct StakingPoints {
address payable paymentReceiver;
uint8 contractVersion;
StakingRule[] stakingRules;
Staker[] stakers;
}

struct StakingPointsParams {
address payable paymentReceiver;
StakingRule[] stakingRules;
}

event StakingPointsInitialized(address indexed creatorContract, uint256 indexed instanceId, address initializer);
event StakingPointsUpdated(address indexed creatorContract, uint256 indexed instanceId, address updater);
event TokensStaked(address indexed creatorContract, uint256 indexed instanceId, StakedToken[] stakedTokens, address owner);
event TokensUnstaked(address indexed creatorContract, uint256 indexed instanceId, StakedToken[] stakedTokens, address owner);
event PointsDistributed(address indexed creatorContract, uint256 indexed instanceId, address user, uint256 amount);

/**
* @notice stake tokens
* @param creatorContractAddress the address of the creator contract
* @param instanceId the staking points instanceId for the creator contract
* @param stakingTokens a list of tokenIds with token contract addresses
*/
function stakeTokens(
address creatorContractAddress,
uint256 instanceId,
StakedTokenParams[] calldata stakingTokens
) external;

/**
* @notice unstake tokens
* @param creatorContractAddress the address of the creator contract
* @param instanceId the staking points instanceId for the creator contract
* @param unstakingTokens a list of tokenIds with token contract addresses
*/
function unstakeTokens(
address creatorContractAddress,
uint256 instanceId,
StakedTokenParams[] calldata unstakingTokens
) external;

/**
* @notice get a staking points instance corresponding to a creator contract and instanceId
* @param creatorContractAddress the address of the creator contract
* @param instanceId the staking points instanceId for the creator contract
* @return StakingPoints the staking points object
*/
function getStakingPointsInstance(
address creatorContractAddress,
uint256 instanceId
) external view returns (StakingPoints memory);

/**
* @notice recover a token that was sent to the contract without safeTransferFrom
* @param tokenAddress the address of the token contract
* @param tokenId the id of the token
* @param destination the address to send the token to
*/
function recoverERC721(address tokenAddress, uint256 tokenId, address destination) external;

/**
* @notice set the Manifold Membership contract address
* @param addr the address of the Manifold Membership contract
*/
function setMembershipAddress(address addr) external;
}
Loading