From 44708a1e1497ddd4a217aed359e28e7d3b2bf7d8 Mon Sep 17 00:00:00 2001 From: James Save Chives Date: Sun, 9 Feb 2025 11:43:24 +0800 Subject: [PATCH] Add ERC: Diffusive Tokens Merged by EIP-Bot. --- ERCS/erc-7837.md | 407 +++++++++++++++++++++++++++++ assets/erc-7837/DiffusiveToken.sol | 220 ++++++++++++++++ 2 files changed, 627 insertions(+) create mode 100755 ERCS/erc-7837.md create mode 100644 assets/erc-7837/DiffusiveToken.sol diff --git a/ERCS/erc-7837.md b/ERCS/erc-7837.md new file mode 100755 index 0000000000..533257c023 --- /dev/null +++ b/ERCS/erc-7837.md @@ -0,0 +1,407 @@ +--- +eip: 7837 +title: Diffusive Tokens +description: A fungible token that mints new tokens on transfer, charges a per-token native fee, and enforces a capped supply. +author: James Savechives (@jamesavechives) +discussions-to: https://ethereum-magicians.org/t/erc-7837-diffusive-tokens/21989 +status: Draft +type: Standards Track +category: ERC +created: 2024-12-07 +--- + +## Abstract + +This ERC proposes a standard for a new type of fungible token, called **Diffusive Tokens (DIFF)**. Unlike traditional [ERC-20](./eip-20.md) tokens, transferring DIFF tokens does not decrease the sender’s balance. Instead, it *mints* new tokens directly to the recipient, increasing the total supply on every transfer action. A fixed native currency fee is charged per token transferred, and this fee is paid by the sender to the contract owner. The supply growth is limited by a maximum supply set by the owner. Token holders can also burn their tokens to reduce the total supply. These features enable a controlled, incentivized token distribution model that merges fungibility with a built-in economic mechanism. + +## Motivation + +Traditional [ERC-20](./eip-20.md) tokens maintain a constant total supply and simply redistribute balances on transfers. While this model is widespread, certain use cases benefit from a token design that continuously expands supply during transfers, simulating a controlled "diffusion" of value. The Diffusive Token model may be suitable for representing claims on real-world goods (e.g., a product batch like iPhone 15 units), digital goods, or controlled asset distributions where initial token distribution and ongoing availability need to be managed differently. + +This model also includes a native currency fee per token transferred, incentivizing careful, value-driven transfers and providing a revenue stream for the token’s issuer. The maximum supply cap prevents unbounded inflation, ensuring long-term scarcity. The ability for owners to burn tokens to redeem underlying goods or services directly maps on-chain assets to real-world redemptions. + +**Use Cases**: + +- **Real-World Asset Backing**: A manufacturer can issue DIFF tokens representing a batch of products (e.g., iPhones). Each token can be redeemed (burned) for one physical item. + +- **Fee-Driven Incentives**: The transfer fee ensures that infinite minting by constant transferring is economically disincentivized. The fee also supports the token issuer or provides a funding mechanism. + + +## Specification + +### Terminology + +- **Diffusive Token**: A fungible token unit that is minted on transfers. +- **Max Supply**: The maximum total supply the token can reach. +- **Transfer Fee**: A fee in native blockchain currency (e.g., ETH) that must be paid by the sender for each token transferred. The total fee = `transferFee * amount`. +- **Burn**: The action of destroying tokens, reducing both the holder’s balance and the total supply. + +### Data Structures + +- **Total Supply and Max Supply**: + + ```solidity + uint256 public totalSupply; + uint256 public maxSupply; + ``` + +- **Transfer Fee**: + + ```solidity + uint256 public transferFee; // fee per token transferred in wei + address public owner; + ``` + + The `owner` sets and updates `transferFee` and `maxSupply`. + +### Token Semantics + +1. **Minting on Transfer** + When a transfer occurs from `A` to `B`: + - `A` does not lose any tokens. + - `B` receives newly minted tokens (increasing their balance and totalSupply). + - The `totalSupply` increases by the transferred amount, but must not exceed `maxSupply`. + +2. **Fixed Transfer Fee in Native Currency** + Each transfer requires the sender to pay `transferFee * amount` in the native currency. If `msg.value` is insufficient, the transaction reverts. + +3. **Maximum Supply** + If a transfer would cause `totalSupply + amount > maxSupply`, it must revert. + +4. **Burning Tokens** + Token holders can burn tokens to: + - Reduce their balance by the burned amount. + - Decrease `totalSupply` by the burned amount. + + This can map to redeeming underlying goods or simply deflating the token. + +### Interface + +The DIFF standard aligns partially with [ERC-20](./eip-20.md), but redefines certain behaviors: + +**Core Functions:** + +- `function balanceOf(address account) external view returns (uint256);` + +- `function transfer(address to, uint256 amount) external payable returns (bool);` + + - **Modified behavior**: Mints `amount` tokens to `to`, requires `msg.value >= transferFee * amount`. + +- `function burn(uint256 amount) external;` + + - Reduces sender’s balance and `totalSupply`. + +**Administration Functions (Owner Only):** + +- `function setMaxSupply(uint256 newMax) external;` + +- `function setTransferFee(uint256 newFee) external;` + +- `function withdrawFees(address payable recipient) external;` + + - Withdraws accumulated native currency fees. + +**Optional Approval Interface (For Compatibility):** + +- `function approve(address spender, uint256 amount) external returns (bool);` +- `function transferFrom(address from, address to, uint256 amount) external payable returns (bool);` + + - **Modified behavior**: Similar to `transfer`, but uses allowance and still mints tokens to `to` rather than redistributing from `from`. + +### Events + +- `event Transfer(address indexed from, address indexed to, uint256 amount);` + + Emitted when tokens are minted to `to` via a transfer call. + +- `event Burn(address indexed burner, uint256 amount);` + + Emitted when `amount` of tokens are burned from an address. + +- `event FeeUpdated(uint256 newFee);` + + Emitted when the owner updates the `transferFee`. + +- `event MaxSupplyUpdated(uint256 newMaxSupply);` + + Emitted when the owner updates `maxSupply`. + +### Compliance with ERC-20 + +The DIFF standard implements the ERC-20 interface but significantly alters the `transfer` and `transferFrom` semantics: + +- **Fungibility**: Each token unit is identical and divisible as in ERC-20. +- **Balances and Transfers**: The `balanceOf` function works as normal. However, `transfer` and `transferFrom` no longer redistribute tokens. Instead, they mint new tokens (up to `maxSupply`). +- **Approvals**: The `approve` and `transferFrom` functions remain, but their logic is unconventional since the sender’s balance is never reduced by transfers. + +While the DIFF standard can be seen as ERC-20 compatible at the interface level, the underlying economics differ substantially. + +## Rationale + +**Design Decisions**: + +- **Unlimited Minting vs. Max Supply**: Allowing minting on every transfer provides a “diffusive” spread of tokens. The `maxSupply` prevents uncontrolled inflation. + +- **Burn Mechanism**: Enables redemption or deflation as tokens are taken out of circulation. + +- **Owner Controls**: The owner (e.g., issuer) can adjust fees and max supply, maintaining flexibility as market conditions change. + +## Backwards Compatibility + +The DIFF standard is interface-compatible with ERC-20 but not behaviorally identical. Any system integrating DIFF tokens should understand the difference in minting on transfer. + +- **Wallets and Exchanges**: Most ERC-20 compatible tools can display balances and initiate transfers. However, the unusual economics (mint on transfer) may confuse users and pricing mechanisms. +- **Allowances and TransferFrom**: Still implemented for interoperability, but the expected logic (debiting `from` balance) does not apply. + +## Test Cases + +1. **Initial Conditions**: + - Deploy contract with `maxSupply = 1,000,000 DIFF`, `transferFee = 0.001 ETH`. + - `totalSupply = 0`. + - Owner sets parameters and verifies via `maxSupply()` and `transferFee()` getters. + +2. **Minting on Transfer**: + - User A calls `transfer(B, 100)` with `msg.value = 0.1 ETH` (assuming `transferFee = 0.001 ETH`). + - Check `balances[B] == 100`, `totalSupply == 100`. + - Check that the contract now holds 0.1 ETH from the fee. + +3. **Exceeding Max Supply**: + - If `totalSupply = 999,950` and someone tries to transfer 100 tokens, causing `totalSupply` to exceed `1,000,000`, the transaction reverts. + +4. **Burning Tokens**: + - User B calls `burn(50)`. + - Check `balances[B] == 50`, `totalSupply == 50` less than before. + - `Burn` event emitted. + +5. **Updating Fee and Withdrawing Funds**: + - Owner calls `setTransferFee(0.002 ETH)`. + - `FeeUpdated` event emitted. + - Owner calls `withdrawFees(ownerAddress)`. + - Check that `ownerAddress` receives accumulated fees. + +## Reference Implementation + +A reference implementation is provided under the asset folder in the EIPs repository. The implementation includes: + +- A basic contract implementing the DIFF standard. +```solidity +contract DiffusiveToken { + // ----------------------------------------- + // State Variables + // ----------------------------------------- + + string public name; + string public symbol; + uint8 public decimals; + + uint256 public totalSupply; + uint256 public maxSupply; + uint256 public transferFee; // Fee per token transferred in wei + + address public owner; + + // ----------------------------------------- + // Events + // ----------------------------------------- + + event Transfer(address indexed from, address indexed to, uint256 amount); + event Burn(address indexed burner, uint256 amount); + event FeeUpdated(uint256 newFee); + event MaxSupplyUpdated(uint256 newMaxSupply); + event Approval(address indexed owner, address indexed spender, uint256 value); + + // ----------------------------------------- + // Modifiers + // ----------------------------------------- + + modifier onlyOwner() { + require(msg.sender == owner, "DiffusiveToken: caller is not the owner"); + _; + } + + // ----------------------------------------- + // Constructor + // ----------------------------------------- + + /** + * @dev Constructor sets the initial parameters for the Diffusive Token. + * @param _name Token name + * @param _symbol Token symbol + * @param _decimals Decimal places + * @param _maxSupply The max supply of tokens that can ever exist + * @param _transferFee Initial fee per token transferred in wei + */ + constructor( + string memory _name, + string memory _symbol, + uint8 _decimals, + uint256 _maxSupply, + uint256 _transferFee + ) { + name = _name; + symbol = _symbol; + decimals = _decimals; + maxSupply = _maxSupply; + transferFee = _transferFee; + owner = msg.sender; + totalSupply = 0; // Initially, no tokens are minted + } + + // ----------------------------------------- + // External and Public Functions + // ----------------------------------------- + + /** + * @notice Returns the token balance of the given address. + * @param account The address to query + */ + function balanceOf(address account) external view returns (uint256) { + return balances[account]; + } + + /** + * @notice Transfers `amount` tokens to address `to`, minting new tokens in the process. + * @dev Requires payment of native currency: transferFee * amount. + * @param to Recipient address + * @param amount Number of tokens to transfer + * @return True if successful + */ + function transfer(address to, uint256 amount) external payable returns (bool) { + require(to != address(0), "DiffusiveToken: transfer to zero address"); + require(amount > 0, "DiffusiveToken: amount must be greater than zero"); + + uint256 requiredFee = transferFee * amount; + require(msg.value >= requiredFee, "DiffusiveToken: insufficient fee"); + + // Check max supply limit + require(totalSupply + amount <= maxSupply, "DiffusiveToken: would exceed max supply"); + + // Mint new tokens to `to` + balances[to] += amount; + totalSupply += amount; + + emit Transfer(msg.sender, to, amount); + return true; + } + + /** + * @notice Burns `amount` tokens from the caller's balance, decreasing total supply. + * @param amount The number of tokens to burn + */ + function burn(uint256 amount) external { + require(amount > 0, "DiffusiveToken: burn amount must be greater than zero"); + require(balances[msg.sender] >= amount, "DiffusiveToken: insufficient balance"); + + balances[msg.sender] -= amount; + totalSupply -= amount; + + emit Burn(msg.sender, amount); + } + + /** + * @notice Approves `spender` to transfer up to `amount` tokens on behalf of `msg.sender`. + * @param spender The address authorized to spend + * @param amount The max amount they can spend + */ + function approve(address spender, uint256 amount) external returns (bool) { + require(spender != address(0), "DiffusiveToken: approve to zero address"); + allowances[msg.sender][spender] = amount; + emit Approval(msg.sender, spender, amount); + return true; + } + + /** + * @notice Returns the current allowance of `spender` for `owner`. + * @param _owner The owner of the tokens + * @param _spender The address allowed to spend the tokens + */ + function allowance(address _owner, address _spender) external view returns (uint256) { + return allowances[_owner][_spender]; + } + + /** + * @notice Transfers `amount` tokens from `from` to `to` using the allowance mechanism. + * @dev The `from` account does not lose tokens; this still mints to `to`. + * @param from The address from which the allowance has been given + * @param to The recipient address + * @param amount The number of tokens to transfer (mint) + */ + function transferFrom(address from, address to, uint256 amount) external payable returns (bool) { + require(to != address(0), "DiffusiveToken: transfer to zero address"); + require(amount > 0, "DiffusiveToken: amount must be greater than zero"); + + uint256 allowed = allowances[from][msg.sender]; + require(allowed >= amount, "DiffusiveToken: allowance exceeded"); + + // Deduct from allowance + allowances[from][msg.sender] = allowed - amount; + + uint256 requiredFee = transferFee * amount; + require(msg.value >= requiredFee, "DiffusiveToken: insufficient fee"); + + // Check max supply + require(totalSupply + amount <= maxSupply, "DiffusiveToken: would exceed max supply"); + + // Mint tokens to `to` + balances[to] += amount; + totalSupply += amount; + + emit Transfer(from, to, amount); + return true; + } + + // ----------------------------------------- + // Owner Functions + // ----------------------------------------- + + /** + * @notice Updates the maximum supply of tokens. Must be >= current totalSupply. + * @param newMaxSupply The new maximum supply + */ + function setMaxSupply(uint256 newMaxSupply) external onlyOwner { + require(newMaxSupply >= totalSupply, "DiffusiveToken: new max < current supply"); + maxSupply = newMaxSupply; + emit MaxSupplyUpdated(newMaxSupply); + } + + /** + * @notice Updates the per-token transfer fee. + * @param newFee The new fee in wei per token transferred + */ + function setTransferFee(uint256 newFee) external onlyOwner { + transferFee = newFee; + emit FeeUpdated(newFee); + } + + /** + * @notice Allows the owner to withdraw accumulated native currency fees. + * @param recipient The address that will receive the withdrawn fees + */ + function withdrawFees(address payable recipient) external onlyOwner { + require(recipient != address(0), "DiffusiveToken: withdraw to zero address"); + uint256 balance = address(this).balance; + (bool success, ) = recipient.call{value: balance}(""); + require(success, "DiffusiveToken: withdrawal failed"); + } + + // ----------------------------------------- + // Fallback and Receive + // ----------------------------------------- + + // Allows the contract to receive Ether. + receive() external payable {} +} +``` + +- Interfaces and helper contracts for testing and demonstration purposes. + +## Security Considerations + +- **Reentrancy**: Handle fee transfers using the Checks-Effects-Interactions pattern. Consider `ReentrancyGuard` from OpenZeppelin to prevent reentrant calls. +- **Overflow/Underflow**: Solidity 0.8.x guards against this by default. +- **Contract Balance Management**: Ensure enough native currency is sent to cover fees. Revert on insufficient fees. +- **Access Control**: Only the owner can update `transferFee` and `maxSupply`. Use proper `onlyOwner` modifiers. + +## Copyright + +Copyright and related rights waived via [CC0](../LICENSE.md). diff --git a/assets/erc-7837/DiffusiveToken.sol b/assets/erc-7837/DiffusiveToken.sol new file mode 100644 index 0000000000..7661ea8eee --- /dev/null +++ b/assets/erc-7837/DiffusiveToken.sol @@ -0,0 +1,220 @@ +// SPDX-License-Identifier: CC0-1.0 +pragma solidity ^0.8.0; + +/** + * @title DiffusiveToken + * @author + * @notice An ERC-20-like token that mints new tokens to the recipient on each transfer, + * does not reduce the sender's balance, requires a native fee per token transferred, + * and caps the total supply at a maximum value. Holders can burn tokens to reduce supply. + */ + +contract DiffusiveToken { + // ----------------------------------------- + // State Variables + // ----------------------------------------- + + string public name; + string public symbol; + uint8 public decimals; + + uint256 public totalSupply; + uint256 public maxSupply; + uint256 public transferFee; // Fee per token transferred in wei + + address public owner; + + mapping(address => uint256) private balances; + mapping(address => mapping(address => uint256)) private allowances; + + // ----------------------------------------- + // Events + // ----------------------------------------- + + event Transfer(address indexed from, address indexed to, uint256 amount); + event Burn(address indexed burner, uint256 amount); + event FeeUpdated(uint256 newFee); + event MaxSupplyUpdated(uint256 newMaxSupply); + event Approval(address indexed owner, address indexed spender, uint256 value); + + // ----------------------------------------- + // Modifiers + // ----------------------------------------- + + modifier onlyOwner() { + require(msg.sender == owner, "DiffusiveToken: caller is not the owner"); + _; + } + + // ----------------------------------------- + // Constructor + // ----------------------------------------- + + /** + * @dev Constructor sets the initial parameters for the Diffusive Token. + * @param _name Token name + * @param _symbol Token symbol + * @param _decimals Decimal places + * @param _maxSupply The max supply of tokens that can ever exist + * @param _transferFee Initial fee per token transferred in wei + */ + constructor( + string memory _name, + string memory _symbol, + uint8 _decimals, + uint256 _maxSupply, + uint256 _transferFee + ) { + name = _name; + symbol = _symbol; + decimals = _decimals; + maxSupply = _maxSupply; + transferFee = _transferFee; + owner = msg.sender; + totalSupply = 0; // Initially, no tokens are minted + } + + // ----------------------------------------- + // External and Public Functions + // ----------------------------------------- + + /** + * @notice Returns the token balance of the given address. + * @param account The address to query + */ + function balanceOf(address account) external view returns (uint256) { + return balances[account]; + } + + /** + * @notice Transfers `amount` tokens to address `to`, minting new tokens in the process. + * @dev Requires payment of native currency: transferFee * amount. + * @param to Recipient address + * @param amount Number of tokens to transfer + * @return True if successful + */ + function transfer(address to, uint256 amount) external payable returns (bool) { + require(to != address(0), "DiffusiveToken: transfer to zero address"); + require(amount > 0, "DiffusiveToken: amount must be greater than zero"); + + uint256 requiredFee = transferFee * amount; + require(msg.value >= requiredFee, "DiffusiveToken: insufficient fee"); + + // Check max supply limit + require(totalSupply + amount <= maxSupply, "DiffusiveToken: would exceed max supply"); + + // Mint new tokens to `to` + balances[to] += amount; + totalSupply += amount; + + emit Transfer(msg.sender, to, amount); + return true; + } + + /** + * @notice Burns `amount` tokens from the caller's balance, decreasing total supply. + * @param amount The number of tokens to burn + */ + function burn(uint256 amount) external { + require(amount > 0, "DiffusiveToken: burn amount must be greater than zero"); + require(balances[msg.sender] >= amount, "DiffusiveToken: insufficient balance"); + + balances[msg.sender] -= amount; + totalSupply -= amount; + + emit Burn(msg.sender, amount); + } + + /** + * @notice Approves `spender` to transfer up to `amount` tokens on behalf of `msg.sender`. + * @param spender The address authorized to spend + * @param amount The max amount they can spend + */ + function approve(address spender, uint256 amount) external returns (bool) { + require(spender != address(0), "DiffusiveToken: approve to zero address"); + allowances[msg.sender][spender] = amount; + emit Approval(msg.sender, spender, amount); + return true; + } + + /** + * @notice Returns the current allowance of `spender` for `owner`. + * @param _owner The owner of the tokens + * @param _spender The address allowed to spend the tokens + */ + function allowance(address _owner, address _spender) external view returns (uint256) { + return allowances[_owner][_spender]; + } + + /** + * @notice Transfers `amount` tokens from `from` to `to` using the allowance mechanism. + * @dev The `from` account does not lose tokens; this still mints to `to`. + * @param from The address from which the allowance has been given + * @param to The recipient address + * @param amount The number of tokens to transfer (mint) + */ + function transferFrom(address from, address to, uint256 amount) external payable returns (bool) { + require(to != address(0), "DiffusiveToken: transfer to zero address"); + require(amount > 0, "DiffusiveToken: amount must be greater than zero"); + + uint256 allowed = allowances[from][msg.sender]; + require(allowed >= amount, "DiffusiveToken: allowance exceeded"); + + // Deduct from allowance + allowances[from][msg.sender] = allowed - amount; + + uint256 requiredFee = transferFee * amount; + require(msg.value >= requiredFee, "DiffusiveToken: insufficient fee"); + + // Check max supply + require(totalSupply + amount <= maxSupply, "DiffusiveToken: would exceed max supply"); + + // Mint tokens to `to` + balances[to] += amount; + totalSupply += amount; + + emit Transfer(from, to, amount); + return true; + } + + // ----------------------------------------- + // Owner Functions + // ----------------------------------------- + + /** + * @notice Updates the maximum supply of tokens. Must be >= current totalSupply. + * @param newMaxSupply The new maximum supply + */ + function setMaxSupply(uint256 newMaxSupply) external onlyOwner { + require(newMaxSupply >= totalSupply, "DiffusiveToken: new max < current supply"); + maxSupply = newMaxSupply; + emit MaxSupplyUpdated(newMaxSupply); + } + + /** + * @notice Updates the per-token transfer fee. + * @param newFee The new fee in wei per token transferred + */ + function setTransferFee(uint256 newFee) external onlyOwner { + transferFee = newFee; + emit FeeUpdated(newFee); + } + + /** + * @notice Allows the owner to withdraw accumulated native currency fees. + * @param recipient The address that will receive the withdrawn fees + */ + function withdrawFees(address payable recipient) external onlyOwner { + require(recipient != address(0), "DiffusiveToken: withdraw to zero address"); + uint256 balance = address(this).balance; + (bool success, ) = recipient.call{value: balance}(""); + require(success, "DiffusiveToken: withdrawal failed"); + } + + // ----------------------------------------- + // Fallback and Receive + // ----------------------------------------- + + // Allows the contract to receive Ether. + receive() external payable {} +} \ No newline at end of file