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

Update EIP-5725: Move to Last Call #7180

Merged
merged 53 commits into from
Aug 29, 2023
Merged
Show file tree
Hide file tree
Changes from 52 commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
5188f6f
Merge pull request #1 from ethereum/master
Apegurus Sep 26, 2022
89934f8
Merge branch 'ethereum:master' into master
Apegurus Sep 28, 2022
70bb638
chore: Commit draft EIP
Apegurus Sep 28, 2022
bfc1e1f
chore: ERC to EIP and add EIP number
Apegurus Sep 28, 2022
1d9961a
refactor: EIP Validator changes
Apegurus Sep 28, 2022
81feac0
refactor: Relative EIP linking
Apegurus Sep 28, 2022
e051390
chore: updated author, discussions-to and removed various links
JorgeAtPaladin Sep 30, 2022
11e64dc
chore: Moved reference implementation to assets
JorgeAtPaladin Sep 30, 2022
3006271
chore: removed invalid EIP sections
JorgeAtPaladin Sep 30, 2022
48415ba
Merge pull request #3 from JorgeAtPaladin/draft
Apegurus Oct 2, 2022
068abab
refactor: Remove comments from EIP-5725
DeFiFoFum Oct 4, 2022
3a8ee84
refactor: Update SPDX license to CC0-1.0
DeFiFoFum Oct 4, 2022
761abc6
refactor: Rename IVestingNFT to IERC5725
DeFiFoFum Oct 4, 2022
f15a541
feat: Add EIP-5725 assets README
DeFiFoFum Oct 4, 2022
4cb049c
Merge branch 'draft' into rebrand/ERC5725
DeFiFoFum Oct 4, 2022
1118b8d
fix: Links
DeFiFoFum Oct 4, 2022
f89cd06
fix: Remove EIP-5725 header
DeFiFoFum Oct 4, 2022
3781d99
Apply suggestions from code review
Apegurus Oct 5, 2022
c62aebe
Merge pull request #4 from ApeSwapFinance/rebrand/ERC5725
DeFiFoFum Oct 5, 2022
f84d07c
refactor: Remove return value from claim()
DeFiFoFum Oct 7, 2022
6b60f17
fix: EIP-N reference
DeFiFoFum Oct 17, 2022
a491393
Update EIPS/eip-5725.md
Apegurus Nov 16, 2022
3b5e30a
refactor: Consistent style and event description
Apegurus Nov 16, 2022
23246ed
refactor: Implement new formatting lint recommendations
Apegurus Nov 16, 2022
6331c89
refactor: Improve Rationale section
DeFiFoFum Nov 16, 2022
aa09fae
Merge pull request #5 from ApeSwapFinance/dev
Apegurus Nov 16, 2022
1596f2e
fix: MD linter errors
Apegurus Nov 16, 2022
b1c8c4e
fix: Return value formatting
DeFiFoFum Jan 13, 2023
4a2437a
Merge branch 'dev' into draft
DeFiFoFum Jan 20, 2023
6ec61de
Merge pull request #2 from ApeSwapFinance/draft
DeFiFoFum Jan 23, 2023
ad6abb8
doc: Fix formatting
DeFiFoFum Feb 2, 2023
e619b63
review: ERC5725
DeFiFoFum Feb 15, 2023
8970226
Merge branch 'master' into upstream/master
DeFiFoFum Feb 22, 2023
c4867ce
Merge branch 'ethereum:master' into upstream/master
DeFiFoFum Feb 23, 2023
3618311
Merge branch 'ethereum:master' into master
DeFiFoFum Feb 23, 2023
61f7f7f
fix: lint
DeFiFoFum Feb 23, 2023
12d7411
Merge branch 'master' of github.com:ApeSwapFinance/EIPs
DeFiFoFum Feb 23, 2023
e07bf4f
fix: EIP -> ERC
DeFiFoFum Feb 23, 2023
e6f7a25
Merge branch 'ethereum:master' into master
DeFiFoFum Apr 11, 2023
041ccfc
refactor: Update 5725 with proper token vesting support
DeFiFoFum Apr 20, 2023
dc91010
Draft -> Review
DeFiFoFum Apr 20, 2023
83f0356
Merge branch 'ethereum:master' into master
DeFiFoFum Apr 20, 2023
badb449
Merge branch 'ethereum:master' into master
DeFiFoFum Jun 14, 2023
f547d05
Update EIP-5725: Move to Last Call
DeFiFoFum Jun 14, 2023
14e2d4c
Update EIP-5725: Add last-call-deadline
DeFiFoFum Jun 14, 2023
9f8b190
Merge branch 'ethereum:master' into master
DeFiFoFum Jun 19, 2023
8429c6b
Merge branch 'master' into master
DeFiFoFum Jul 7, 2023
617f6fc
[feat] Add allowance feature to EIP-5725
Elliott-Green Aug 22, 2023
a5544a3
[chore] change ERC5725.sol license to CC0-1.0
Elliott-Green Aug 22, 2023
14cf3aa
[chore] resolve walidator linter
Elliott-Green Aug 23, 2023
b4dcce3
Merge pull request #6 from Elliott-Green/master
DeFiFoFum Aug 24, 2023
0326e57
refactor: Last call to 2023-09-05
DeFiFoFum Aug 24, 2023
1e41272
chore: Resolve review comments
DeFiFoFum Aug 28, 2023
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
172 changes: 109 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