Skip to content

Commit 0459aba

Browse files
authored
Merge e5e4a60 into 441f79c
2 parents 441f79c + e5e4a60 commit 0459aba

24 files changed

+9896
-0
lines changed

ERCS/erc-7818.md

+162
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
---
2+
eip: 7818
3+
title: Expirable ERC-20
4+
description: An ERC-20 extension for creating fungible tokens with expiration, supporting time-limited use cases.
5+
author: sirawt (@MASDXI), ADISAKBOONMARK (@ADISAKBOONMARK)
6+
discussions-to: https://ethereum-magicians.org/t/erc-7818-expirable-erc20/21655
7+
status: Draft
8+
type: Standards Track
9+
category: ERC
10+
created: 2024-11-13
11+
requires: 20
12+
---
13+
14+
## Abstract
15+
16+
Introduces an extension for [ERC-20](./eip-20.md) tokens, which facilitates the implementation of an expiration mechanism. Through this extension, tokens have a predetermined validity period, after which they become invalid and can no longer be transferred or used. This functionality proves beneficial in scenarios such as time-limited bonds, loyalty rewards, or game tokens necessitating automatic invalidation after a specific duration. The extension is crafted to seamlessly align with the existing [ERC-20](./eip-20.md) standard, ensuring smooth integration with the prevailing token smart contract while introducing the capability to govern and enforce token expiration at the contract level.
17+
18+
## Motivation
19+
20+
This extension facilitates the development of [ERC-20](./eip-20.md) standard compatible tokens featuring expiration dates. This capability broadens the scope of potential applications, particularly those involving time-sensitive assets. Expirable tokens are well-suited for scenarios necessitating temporary validity, including
21+
22+
- Bonds or financial instruments with defined maturity dates
23+
- Time-constrained assets within gaming ecosystems
24+
- Next-gen loyalty programs incorporating expiring rewards or points
25+
- Prepaid credits for utilities or services (e.g., cashback, data packages, fuel, computing resources) that expire if not used within a specified time frame
26+
- Postpaid telecom data package allocations that expire at the end of the billing cycle, motivating users to utilize their data before it resets
27+
- Tokenized e-Money for a closed-loop ecosystem, such as transportation, food court, and retail payments
28+
29+
## Specification
30+
31+
The keywords “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119.
32+
33+
Compatible implementations MUST inherit from [ERC-20](./eip-20.md)'s interface and **MUST** have all the following functions and all function behavior **MUST** meet the specification.
34+
35+
```solidity
36+
// SPDX-License-Identifier: CC0-1.0
37+
pragma solidity >=0.8.0 <0.9.0;
38+
39+
/**
40+
* @title ERC-7818: Expirable ERC20
41+
* @dev Interface for creating expirable ERC20 tokens.
42+
*/
43+
44+
import "./IERC20.sol";
45+
46+
interface IERC7818 is IERC20 {
47+
48+
/**
49+
* @dev Retrieves the balance of a specific `epoch` owned by an account.
50+
* @param account The address of the account.
51+
* @param epoch "MAY" represents an epoch, round, or period.
52+
* @return uint256 The balance of the specified `epoch`.
53+
* @notice `epoch` "MUST" represent a unique identifier, and its meaning "SHOULD"
54+
* align with how contract maintain the `epoch` in the implementing contract.
55+
*/
56+
function balanceOf(
57+
address account,
58+
uint256 epoch
59+
) external view returns (uint256);
60+
61+
/**
62+
* @dev Retrieves the current epoch of the contract.
63+
* @return uint256 The current epoch of the token contract,
64+
* often used for determining active/expired states.
65+
*/
66+
function epoch() external view returns (uint256);
67+
68+
/**
69+
* @dev Retrieves the duration a token remains valid.
70+
* @return uint256 The validity duration.
71+
* @notice `duration` "MUST" specify the token's validity period.
72+
* The implementing contract "SHOULD" clearly document,
73+
* whether the unit is blocks or time in seconds.
74+
*/
75+
function duration() external view returns (uint256);
76+
77+
/**
78+
* @dev Checks whether a specific `epoch` is expired.
79+
* @param epoch "MAY" represents an epoch, round, or period.
80+
* @return bool True if the token is expired, false otherwise.
81+
* @notice Implementing contracts "MUST" define the logic for determining expiration,
82+
* typically by comparing the current `epoch()` with the given `epoch`.
83+
*/
84+
function expired(uint256 epoch) external view returns (bool);
85+
86+
/**
87+
* @dev Transfers a specific `epoch` and value to a recipient.
88+
* @param to The recipient address.
89+
* @param epoch "MAY" represents an epoch, round, or period.
90+
* @param value The amount to transfer.
91+
* @return bool True if the transfer succeeded, false or reverted if give `epoch` it's expired.
92+
* @notice The transfer "MUST" revert if the token `epoch` is expired.
93+
*/
94+
function transfer(
95+
address to,
96+
uint256 epoch,
97+
uint256 value
98+
) external returns (bool);
99+
100+
/**
101+
* @dev Transfers a specific `epoch` and value from one account to another.
102+
* @param from The sender's address.
103+
* @param to The recipient's address.
104+
* @param epoch "MAY" represents an epoch, round, or period.
105+
* @param value The amount to transfer.
106+
* @return bool True if the transfer succeeded, false or reverted if give `epoch` it's expired.
107+
* @notice The transfer "MUST" revert if the token `epoch` is expired.
108+
*/
109+
function transferFrom(
110+
address from,
111+
address to,
112+
uint256 epoch,
113+
uint256 value
114+
) external returns (bool);
115+
}
116+
```
117+
118+
### Behavior specification
119+
120+
- `balanceOf` **MUST** return the total balance of tokens held by an account that are still valid (i.e., have not expired). This includes any tokens associated with specific periods, epochs, or other identifiers, provided they remain within their validity duration. Expired tokens **MUST NOT** be included in the returned balance, ensuring that only actively usable tokens are reflected in the result.
121+
- `transfer` and `transferFrom` **MUST** exclusively transfer tokens that remain non-expired at the time of the transaction. Attempting to transfer expired tokens **MUST** revert the transaction or return false. Additionally, implementations **MAY** include logic to prioritize the automatic transfer of tokens closest to expiration, ensuring that the earliest expiring tokens are used first, provided they meet the non-expired condition.
122+
- `totalSupply` **SHOULD** be set to `0` or `type(uint256).max` due to the challenges of tracking only valid (non-expired) tokens.
123+
- The implementation **MAY** use a standardized custom error revert message, such as `ERC7818TransferredExpiredToken` or `ERC7818TransferredExpiredToken(address sender, uint256 epoch)`, to clearly indicate that the operation failed due to attempting to transfer expired tokens.
124+
125+
## Rationale
126+
127+
The rationale for developing an expirable [ERC-20](./eip-20.md) token extension is based on several key requirements that ensure its practicality and adaptability for various applications to
128+
129+
### Compatibility with the existing [ERC-20](./eip-20.md) standard.
130+
The extension should integrate smoothly with the [ERC-20](./eip-20.md) interface, This ensures compatibility with existing token ecosystems and third-party tools like wallets and blockchain explorers.
131+
132+
### Flexible interface for various implementation.
133+
The smart contract should be extensible, allowing businesses to tailor the expiration functionality to their specific needs like expiry in bulk or each token independent expire, whether it’s dynamic reward systems or time-sensitive applications.
134+
135+
## Backwards Compatibility
136+
137+
This standard is fully [ERC-20](./eip-20.md) compatible.
138+
139+
## Reference Implementation
140+
141+
For reference implementation can be found [here](../assets/eip-7818/README.md), But in the reference implementation, we employ a sorted list to automatically select the token that nearest expires first with a First-In-First-Out (`FIFO`) and sliding window algorithm that operates based on the `block.number` as opposed to relying on `block.timestamp`, which has been criticized for its lack of security and resilience, particularly given the increasing usage of Layer 2 (L2) networks over Layer 1 (L1) networks. Many L2 networks exhibit centralization and instability, which directly impacts asset integrity, rendering them potentially unusable during periods of network halting, as they are still reliant on the timestamp.
142+
143+
## Security Considerations
144+
145+
### Denial Of Service
146+
Run out of gas problem due to the operation consuming higher gas if transferring multiple groups of small tokens or loop transfer.
147+
148+
### Gas Limit Vulnerabilities
149+
Exceeds block gas limit if the blockchain has a block gas limit lower than the gas used in the transaction.
150+
151+
### Block values as a proxy for time
152+
if using `block.timestamp` for calculating `epoch()` and In rare network halts, block production stops, freezing `block.timestamp` and disrupting time-based logic. This risks asset integrity and inconsistent states.
153+
154+
### Fairness Concerns
155+
In a straightforward implementation, where all tokens within the same epoch share the same expiration (e.g., at `epoch`:`x`), bulk expiration occurs.
156+
157+
### Risks in Liquidity Pools
158+
When tokens with expiration dates are deposited into liquidity pools (e.g., in DEXs), they may expire while still in the pool.
159+
160+
## Copyright
161+
162+
Copyright and related rights waived via [CC0](../LICENSE.md).

assets/erc-7818/.gitignore

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
node_modules
2+
.env
3+
4+
# Hardhat files
5+
/cache
6+
/artifacts
7+
8+
# TypeChain files
9+
/typechain
10+
/typechain-types
11+
12+
# solidity-coverage files
13+
/coverage
14+
/coverage.json
15+
16+
# Hardhat Ignition default folder for deployments against a local node
17+
ignition
18+
ignition/deployments/chain-31337

assets/erc-7818/README.md

+93
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
# ERC-7818
2+
3+
This is reference implementation of [ERC-7818](../../ERCS/erc-7818.md)
4+
5+
## Implementation Describe
6+
7+
#### Sliding Window Algorithm to look for expiration balance
8+
9+
This contract creates an abstract implementation that adopts the `Sliding Window Algorithm` to maintain a window over a period of time (block height). This efficient approach allows for the look back and calculation of `usable balances` for each account within that window period. With this approach, the contract does not require a variable acting as a `counter` or a `state` to keep updating the latest state, nor does it need any interaction calls to keep updating the current period, which is an effortful and costly design.
10+
11+
<p align="center">
12+
<img src="implementation.svg">
13+
</p>
14+
15+
#### Era and Slot for storing data in vertical and horizontal way
16+
17+
```solidity
18+
// skipping
19+
20+
struct Slot {
21+
uint256 slotBalance;
22+
mapping(uint256 => uint256) blockBalances;
23+
SortedList.List list;
24+
}
25+
26+
// skipping
27+
28+
// O(n→) fot traversal each slot in era.
29+
// O(n↓) for traversal each element in list.
30+
mapping(address => mapping(uint256 => mapping(uint8 => Slot))) private _balances;
31+
mapping(uint256 => uint256) private _worldBlockBalance;
32+
```
33+
34+
With this struct `Slot` it provides an abstract loop in a horizontal way more efficient for calculating the usable balance of the account because it provides `slotBalance` which acts as suffix balance so you don't need to get to iterate or traversal over the `list` for each `Slot` to calculate the entire slot balance if the slot can presume not to expire. otherwise struct `Slot` also provides vertical in a sorted list.
35+
The `_worldBlockBalance` mapping tracks the total token balance across all accounts that minted tokens within a particular block. This structure allows the contract to trace expired balances easily. By consolidating balance data for each block.
36+
37+
#### Buffering 1 slot rule for ensuring safety
38+
39+
In this design, the buffering slot is the critical element that requires careful calculation to ensure accurate handling of balances nearing expiration. By incorporating this buffer, the contract guarantees that any expiring balance is correctly accounted for within the sliding window mechanism, ensuring reliability and preventing premature expiration or missed balances.
40+
41+
#### First-In-First-Out (FIFO) priority to enforce token expiration rules
42+
43+
Enforcing `FIFO` priority ensures that tokens nearing expiration are processed before newer ones, aligning with the token lifecycle and expiration rules. This method eliminates the need for additional off-chain computation and ensures that all token processing occurs efficiently on-chain, fully compliant with the ERC20 interface.
44+
A **sorted** list is integral to this approach. Each slot maintains its own list, sorted by token creation which is can be `block.timestamp` or `blocknumber`, preventing any overlap with other slots. This separation ensures that tokens in one slot do not interfere with the balance handling in another. The contract can then independently manage token expirations within each slot, minimizing computation while maintaining accuracy and predictability in processing balances.
45+
46+
---
47+
48+
#### Token Receipt and Transaction Likelihood across various blocktime
49+
50+
Assuming each `Era` contains 4 `slots`, which aligns with familiar time-based divisions like a year being divided into four quarters, the following table presents various scenarios based on block time and token receipt intervals. It illustrates the potential transaction frequency and likelihood of receiving tokens within a given period.
51+
52+
| Block Time (ms) | Receive Token Every (ms) | Index/Slot | Transactions per Day | Likelihood |
53+
| --------------- | ------------------------ | ---------- | -------------------- | ------------- |
54+
| 100 | 100 | 78,892,315 | 864,000 | Very Unlikely |
55+
| 500 | 500 | 15,778,463 | 172,800 | Very Unlikely |
56+
| 1000 | 1000 | 7,889,231 | 86,400 | Very Unlikely |
57+
| 1000 | 28,800,000 | 273 | 3 | Unlikely |
58+
| 1000 | 86,400,000 | 91 | 1 | Possible |
59+
| 5000 | 86,400,000 | 18 | 1 | Very Likely |
60+
| 10000 | 86,400,000 | 9 | 1 | Very Likely |
61+
62+
> [!IMPORTANT]
63+
> - Transactions per day are assumed based on loyalty point earnings.
64+
> - Likelihood varies depending on the use case; for instance, gaming use cases may have higher transaction volumes than the given estimates.
65+
66+
## Security Considerations in The Reference Implementation
67+
68+
- Solidity Division Rounding Down This implementation contract may encounter scenarios where the calculated expiration block is shorter than the actual expiration block. This discrepancy can arise from the outputs of `blockPerYear` and `blockPerSlot * slotPerEra`, which may differ. Additionally, Solidity's division operation only returns integers, rounding down to the nearest whole number. However, by enforcing valid block times within the defined limits of `MINIMUM_BLOCK_TIME_IN_MILLISECONDS` and `MAXIMUM_BLOCK_TIME_IN_MILLISECONDS`, the contract mitigates this risk effectively.
69+
70+
## Usage
71+
72+
#### Install Dependencies
73+
```bash
74+
yarn install
75+
```
76+
77+
#### Compile the Contract
78+
Compile the reference implementation
79+
```bash
80+
yarn compile
81+
```
82+
83+
#### Run Tests
84+
Execute the provided test suite to verify the contract's functionality and integrity
85+
```bash
86+
yarn test
87+
```
88+
89+
### Cleaning Build Artifacts
90+
To clean up compiled files and artifacts generated during testing or deployment
91+
```bash
92+
yarn clean
93+
```
+79
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity >=0.8.0 <0.9.0;
3+
4+
/**
5+
* @title ERC-7818: Expirable ERC20
6+
* @dev Interface for creating expirable ERC20 tokens.
7+
*/
8+
9+
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
10+
11+
interface IERC7818 is IERC20 {
12+
/**
13+
* @dev Retrieves the balance of a specific `epoch` owned by an account.
14+
* @param account The address of the account.
15+
* @param epoch "MAY" represents an epoch, round, or period.
16+
* @return uint256 The balance of the specified `epoch`.
17+
* @notice `epoch` "MUST" represent a unique identifier, and its meaning "SHOULD"
18+
* align with how contract maintain the `epoch` in the implementing contract.
19+
*/
20+
function balanceOf(
21+
address account,
22+
uint256 epoch
23+
) external view returns (uint256);
24+
25+
/**
26+
* @dev Retrieves the current epoch of the contract.
27+
* @return uint256 The current epoch of the token contract,
28+
* often used for determining active/expired states.
29+
*/
30+
function epoch() external view returns (uint256);
31+
32+
/**
33+
* @dev Retrieves the duration a token remains valid.
34+
* @return uint256 The validity duration.
35+
* @notice `duration` "MUST" specify the token's validity period.
36+
* The implementing contract "SHOULD" clearly document,
37+
* whether the unit is blocks or time in seconds.
38+
*/
39+
function duration() external view returns (uint256);
40+
41+
/**
42+
* @dev Checks whether a specific `epoch` is expired.
43+
* @param epoch "MAY" represents an epoch, round, or period.
44+
* @return bool True if the token is expired, false otherwise.
45+
* @notice Implementing contracts "MUST" define the logic for determining expiration,
46+
* typically by comparing the current `epoch()` with the given `epoch`.
47+
*/
48+
function expired(uint256 epoch) external view returns (bool);
49+
50+
/**
51+
* @dev Transfers a specific `epoch` and value to a recipient.
52+
* @param to The recipient address.
53+
* @param epoch "MAY" represents an epoch, round, or period.
54+
* @param value The amount to transfer.
55+
* @return bool True if the transfer succeeded, false or reverted if give `epoch` it's expired.
56+
* @notice The transfer "MUST" revert if the token `epoch` is expired.
57+
*/
58+
function transfer(
59+
address to,
60+
uint256 epoch,
61+
uint256 value
62+
) external returns (bool);
63+
64+
/**
65+
* @dev Transfers a specific `epoch` and value from one account to another.
66+
* @param from The sender's address.
67+
* @param to The recipient's address.
68+
* @param epoch "MAY" represents an epoch, round, or period.
69+
* @param value The amount to transfer.
70+
* @return bool True if the transfer succeeded, false or reverted if give `epoch` it's expired.
71+
* @notice The transfer "MUST" revert if the token `epoch` is expired.
72+
*/
73+
function transferFrom(
74+
address from,
75+
address to,
76+
uint256 epoch,
77+
uint256 value
78+
) external returns (bool);
79+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity >=0.5.0 <0.9.0;
3+
4+
import "../contracts/abstracts/ERC20Expirable.sol";
5+
6+
contract MockERC20Expirable is ERC20Expirable {
7+
constructor(
8+
string memory _name,
9+
string memory _symbol,
10+
uint16 blockTime_,
11+
uint8 frameSize_,
12+
uint8 slotSize_
13+
) ERC20Expirable(_name, _symbol, block.number, blockTime_, frameSize_, slotSize_) {}
14+
15+
function mint(address to, uint256 value) public {
16+
_mint(to, value);
17+
}
18+
19+
function burn(address from, uint256 value) public {
20+
_burn(from, value);
21+
}
22+
23+
function badApprove(address owner, address spender, uint256 value) public returns (bool) {
24+
_approve(owner, spender, value);
25+
return true;
26+
}
27+
28+
function badTransfer(address from, address to, uint256 value) public returns (bool) {
29+
_transfer(from, to, value);
30+
return true;
31+
}
32+
}

0 commit comments

Comments
 (0)