Skip to content

Commit

Permalink
Merge a5544a3 into 8429c6b
Browse files Browse the repository at this point in the history
  • Loading branch information
Elliott-Green authored Aug 22, 2023
2 parents 8429c6b + a5544a3 commit b896b21
Show file tree
Hide file tree
Showing 9 changed files with 548 additions and 248 deletions.
170 changes: 107 additions & 63 deletions EIPS/eip-5725.md

Large diffs are not rendered by default.

163 changes: 117 additions & 46 deletions assets/eip-5725/contracts/ERC5725.sol
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,13 @@ abstract contract ERC5725 is IERC5725, ERC721 {
using SafeERC20 for IERC20;

/// @dev mapping for claimed payouts
mapping(uint256 => uint256) /*tokenId*/ /*claimed*/
internal _payoutClaimed;
mapping(uint256 => uint256) /*tokenId*/ /*claimed*/ internal _payoutClaimed;

/// @dev Mapping from token ID to approved tokenId operator
mapping(uint256 => address) private _tokenIdApprovals;

/// @dev Mapping from owner to operator approvals
mapping(address => mapping(address => bool)) /* owner */ /*(operator, isApproved)*/ internal _operatorApprovals;

/**
* @notice Checks if the tokenId exists and its valid
Expand All @@ -28,7 +33,8 @@ abstract contract ERC5725 is IERC5725, ERC721 {
* @dev See {IERC5725}.
*/
function claim(uint256 tokenId) external override(IERC5725) validToken(tokenId) {
require(ownerOf(tokenId) == msg.sender, "Not owner of NFT");
require(isApprovedClaimOrOwner(msg.sender, tokenId), "ERC5725: not owner or operator");

uint256 amountClaimed = claimablePayout(tokenId);
require(amountClaimed > 0, "ERC5725: No pending payout");

Expand All @@ -38,6 +44,26 @@ abstract contract ERC5725 is IERC5725, ERC721 {
IERC20(payoutToken(tokenId)).safeTransfer(msg.sender, amountClaimed);
}

/**
* @dev See {IERC5725}.
*/
function setClaimApprovalForAll(address operator, bool approved) external override(IERC5725) {
_setClaimApprovalForAll(operator, approved);
emit ClaimApprovalForAll(msg.sender, operator, approved);
}

/**
* @dev See {IERC5725}.
*/
function setClaimApproval(
address operator,
bool approved,
uint256 tokenId
) external override(IERC5725) validToken(tokenId) {
_setClaimApproval(operator, tokenId);
emit ClaimApproval(msg.sender, operator, tokenId, approved);
}

/**
* @dev See {IERC5725}.
*/
Expand All @@ -48,62 +74,44 @@ abstract contract ERC5725 is IERC5725, ERC721 {
/**
* @dev See {IERC5725}.
*/
function vestedPayoutAtTime(uint256 tokenId, uint256 timestamp)
public
view
virtual
override(IERC5725)
returns (uint256 payout);
function vestedPayoutAtTime(
uint256 tokenId,
uint256 timestamp
) public view virtual override(IERC5725) returns (uint256 payout);

/**
* @dev See {IERC5725}.
*/
function vestingPayout(uint256 tokenId)
public
view
override(IERC5725)
validToken(tokenId)
returns (uint256 payout)
{
function vestingPayout(
uint256 tokenId
) public view override(IERC5725) validToken(tokenId) returns (uint256 payout) {
return _payout(tokenId) - vestedPayout(tokenId);
}

/**
* @dev See {IERC5725}.
*/
function claimablePayout(uint256 tokenId)
public
view
override(IERC5725)
validToken(tokenId)
returns (uint256 payout)
{
function claimablePayout(
uint256 tokenId
) public view override(IERC5725) validToken(tokenId) returns (uint256 payout) {
return vestedPayout(tokenId) - _payoutClaimed[tokenId];
}

/**
* @dev See {IERC5725}.
*/
function claimedPayout(uint256 tokenId)
public
view
override(IERC5725)
validToken(tokenId)
returns (uint256 payout)
{
function claimedPayout(
uint256 tokenId
) public view override(IERC5725) validToken(tokenId) returns (uint256 payout) {
return _payoutClaimed[tokenId];
}

/**
* @dev See {IERC5725}.
*/
function vestingPeriod(uint256 tokenId)
public
view
override(IERC5725)
validToken(tokenId)
returns (uint256 vestingStart, uint256 vestingEnd)
{
function vestingPeriod(
uint256 tokenId
) public view override(IERC5725) validToken(tokenId) returns (uint256 vestingStart, uint256 vestingEnd) {
return (_startTime(tokenId), _endTime(tokenId));
}

Expand All @@ -116,18 +124,81 @@ abstract contract ERC5725 is IERC5725, ERC721 {

/**
* @dev See {IERC165-supportsInterface}.
* IERC5725 interfaceId = 0x7c89676d
*/
function supportsInterface(bytes4 interfaceId)
public
view
virtual
override(ERC721, IERC165)
returns (bool supported)
{
* IERC5725 interfaceId = 0xbd3a202b
*/
function supportsInterface(
bytes4 interfaceId
) public view virtual override(ERC721, IERC165) returns (bool supported) {
return interfaceId == type(IERC5725).interfaceId || super.supportsInterface(interfaceId);
}

/**
* @dev See {IERC5725}.
*/
function getClaimApproved(uint256 tokenId) public view returns (address operator) {
return _tokenIdApprovals[tokenId];
}

/**
* @dev Returns true if `owner` has set `operator` to manage all `tokenId`s.
* @param owner The owner allowing `operator` to manage all `tokenId`s.
* @param operator The address who is given permission to spend tokens on behalf of the `owner`.
*/
function isClaimApprovedForAll(address owner, address operator) public view returns (bool isClaimApproved) {
return _operatorApprovals[owner][operator];
}

/**
* @dev Public view which returns true if the operator has permission to claim for `tokenId`
* @notice To remove permissions, set operator to zero address.
*
* @param operator The address that has permission for a `tokenId`.
* @param tokenId The NFT `tokenId`.
*/
function isApprovedClaimOrOwner(address operator, uint256 tokenId) public view virtual returns (bool) {
address owner = ownerOf(tokenId);
return (operator == owner || isClaimApprovedForAll(owner, operator) || getClaimApproved(tokenId) == operator);
}

/**
* @dev Internal function to set the operator status for a given owner to manage all `tokenId`s.
* @notice To remove permissions, set approved to false.
*
* @param operator The address who is given permission to spend vested tokens.
* @param approved The approved status.
*/
function _setClaimApprovalForAll(address operator, bool approved) internal virtual {
_operatorApprovals[msg.sender][operator] = approved;
}

/**
* @dev Internal function to set the operator status for a given tokenId.
* @notice To remove permissions, set operator to zero address.
*
* @param operator The address who is given permission to spend vested tokens.
* @param tokenId The NFT `tokenId`.
*/
function _setClaimApproval(address operator, uint256 tokenId) internal virtual {
require(ownerOf(tokenId) == msg.sender, "ERC5725: not owner of tokenId");
_tokenIdApprovals[tokenId] = operator;
}

/**
* @dev Internal function to hook into {IERC721-_afterTokenTransfer}, when a token is being transferred.
* Removes permissions to _tokenIdApprovals[tokenId] when the tokenId is transferred, burnt, but not on mint.
*
* @param from The address from which the tokens are being transferred.
* @param to The address to which the tokens are being transferred.
* @param firstTokenId The first tokenId in the batch that is being transferred.
* @param batchSize The number of tokens being transferred in this batch.
*/
function _beforeTokenTransfer(address from, address to, uint256 firstTokenId, uint256 batchSize) internal override {
super._beforeTokenTransfer(from, to, firstTokenId, batchSize);
if (from != address(0)) {
delete _tokenIdApprovals[firstTokenId];
}
}

/**
* @dev Internal function to get the payout token of a given vesting NFT
*
Expand Down
113 changes: 80 additions & 33 deletions assets/eip-5725/contracts/IERC5725.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,88 +3,135 @@ pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC721/IERC721.sol";

/**
* @title Non-Fungible Vesting Token Standard
* @title Non-Fungible Vesting Token Standard.
* @notice A non-fungible token standard used to vest ERC-20 tokens over a vesting release curve
* scheduled using timestamps.
* @dev Because this standard relies on timestamps for the vesting schedule, it's important to keep track of the
* tokens claimed per Vesting NFT so that a user cannot withdraw more tokens than alloted for a specific Vesting NFT.
* tokens claimed per Vesting NFT so that a user cannot withdraw more tokens than allotted for a specific Vesting NFT.
* @custom:interface-id 0xbd3a202b
*/
interface IERC5725 is IERC721 {
/**
* This event is emitted when the payout is claimed through the claim function
* This event is emitted when the payout is claimed through the claim function.
* @param tokenId the NFT tokenId of the assets being claimed.
* @param recipient The address which is receiving the payout.
* @param claimAmount The amount of tokens being claimed.
*/
event PayoutClaimed(uint256 indexed tokenId, address indexed recipient, uint256 claimAmount);

/**
* @notice Claim the pending payout for the NFT
* @dev MUST grant the claimablePayout value at the time of claim being called
* MUST revert if not called by the token owner or approved users
* MUST emit PayoutClaimed
* SHOULD revert if there is nothing to claim
* @param tokenId The NFT token id
* This event is emitted when an `owner` sets an address to manage token claims for all tokens.
* @param owner The address setting a manager to manage all tokens.
* @param spender The address being permitted to manage all tokens.
* @param approved A boolean indicating whether the spender is approved to claim for all tokens.
*/
event ClaimApprovalForAll(address indexed owner, address indexed spender, bool approved);

/**
* This event is emitted when an `owner` sets an address to manage token claims for a `tokenId`.
* @param owner The `owner` of `tokenId`.
* @param spender The address being permitted to manage a tokenId.
* @param tokenId The unique identifier of the token being managed.
* @param approved A boolean indicating whether the spender is approved to claim for `tokenId`.
*/
event ClaimApproval(address indexed owner, address indexed spender, uint256 indexed tokenId, bool approved);

/**
* @notice Claim the pending payout for the NFT.
* @dev MUST grant the claimablePayout value at the time of claim being called to `msg.sender`.
* MUST revert if not called by the token owner or approved users.
* MUST emit PayoutClaimed.
* SHOULD revert if there is nothing to claim.
* @param tokenId The NFT token id.
*/
function claim(uint256 tokenId) external;

/**
* @notice Number of tokens for the NFT which have been claimed at the current timestamp
* @param tokenId The NFT token id
* @return payout The total amount of payout tokens claimed for this NFT
* @notice Number of tokens for the NFT which have been claimed at the current timestamp.
* @param tokenId The NFT token id.
* @return payout The total amount of payout tokens claimed for this NFT.
*/
function claimedPayout(uint256 tokenId) external view returns (uint256 payout);

/**
* @notice Number of tokens for the NFT which can be claimed at the current timestamp
* @notice Number of tokens for the NFT which can be claimed at the current timestamp.
* @dev It is RECOMMENDED that this is calculated as the `vestedPayout()` subtracted from `payoutClaimed()`.
* @param tokenId The NFT token id
* @return payout The amount of unlocked payout tokens for the NFT which have not yet been claimed
* @param tokenId The NFT token id.
* @return payout The amount of unlocked payout tokens for the NFT which have not yet been claimed.
*/
function claimablePayout(uint256 tokenId) external view returns (uint256 payout);

/**
* @notice Total amount of tokens which have been vested at the current timestamp.
* This number also includes vested tokens which have been claimed.
* @dev It is RECOMMENDED that this function calls `vestedPayoutAtTime` with
* `block.timestamp` as the `timestamp` parameter.
* @param tokenId The NFT token id
* This number also includes vested tokens which have been claimed.
* @dev It is RECOMMENDED that this function calls `vestedPayoutAtTime`
* with `block.timestamp` as the `timestamp` parameter.
* @param tokenId The NFT token id.
* @return payout Total amount of tokens which have been vested at the current timestamp.
*/
function vestedPayout(uint256 tokenId) external view returns (uint256 payout);

/**
* @notice Total amount of vested tokens at the provided timestamp.
* This number also includes vested tokens which have been claimed.
* This number also includes vested tokens which have been claimed.
* @dev `timestamp` MAY be both in the future and in the past.
* Zero MUST be returned if the timestamp is before the token was minted.
* @param tokenId The NFT token id
* @param timestamp The timestamp to check on, can be both in the past and the future
* @return payout Total amount of tokens which have been vested at the provided timestamp
* Zero MUST be returned if the timestamp is before the token was minted.
* @param tokenId The NFT token id.
* @param timestamp The timestamp to check on, can be both in the past and the future.
* @return payout Total amount of tokens which have been vested at the provided timestamp.
*/
function vestedPayoutAtTime(uint256 tokenId, uint256 timestamp) external view returns (uint256 payout);

/**
* @notice Number of tokens for an NFT which are currently vesting.
* @dev The sum of vestedPayout and vestingPayout SHOULD always be the total payout.
* @param tokenId The NFT token id
* @param tokenId The NFT token id.
* @return payout The number of tokens for the NFT which are vesting until a future date.
*/
function vestingPayout(uint256 tokenId) external view returns (uint256 payout);

/**
* @notice The start and end timestamps for the vesting of the provided NFT
* MUST return the timestamp where no further increase in vestedPayout occurs for `vestingEnd`.
* @param tokenId The NFT token id
* @return vestingStart The beginning of the vesting as a unix timestamp
* @return vestingEnd The ending of the vesting as a unix timestamp
* @notice The start and end timestamps for the vesting of the provided NFT.
* MUST return the timestamp where no further increase in vestedPayout occurs for `vestingEnd`.
* @param tokenId The NFT token id.
* @return vestingStart The beginning of the vesting as a unix timestamp.
* @return vestingEnd The ending of the vesting as a unix timestamp.
*/
function vestingPeriod(uint256 tokenId) external view returns (uint256 vestingStart, uint256 vestingEnd);

/**
* @notice Token which is used to pay out the vesting claims
* @param tokenId The NFT token id
* @return token The token which is used to pay out the vesting claims
* @notice Token which is used to pay out the vesting claims.
* @param tokenId The NFT token id.
* @return token The token which is used to pay out the vesting claims.
*/
function payoutToken(uint256 tokenId) external view returns (address token);

/**
* @notice Sets a global `operator` with permission to manage all tokens owned by the current `msg.sender`.
* @param operator The address to let manage all tokens.
* @param approved A boolean indicating whether the spender is approved to claim for all tokens.
*/
function setClaimApprovalForAll(address operator, bool approved) external;

/**
* @notice Sets a tokenId `operator` with permission to manage a single `tokenId` owned by the `msg.sender`.
* @param operator The address to let manage a single `tokenId`.
* @param tokenId the `tokenId` to be managed.
* @param approved A boolean indicating whether the spender is approved to claim for all tokens.
*/
function setClaimApproval(address operator, bool approved, uint256 tokenId) external;

/**
* @notice Returns true if `owner` has set `operator` to manage all `tokenId`s.
* @param owner The owner allowing `operator` to manage all `tokenId`s.
* @param operator The address who is given permission to spend tokens on behalf of the `owner`.
*/
function isClaimApprovedForAll(address owner, address operator) external view returns (bool isClaimApproved);

/**
* @notice Returns the operating address for a `tokenId`.
* If `tokenId` is not managed, then returns the zero address.
* @param tokenId The NFT `tokenId` to query for a `tokenId` manager.
*/
function getClaimApproved(uint256 tokenId) external view returns (address operator);
}
Loading

0 comments on commit b896b21

Please sign in to comment.