Add ERC7674 (draft) (#5071)

Co-authored-by: Ernesto García <ernestognw@gmail.com>
Co-authored-by: cairo <cairoeth@protonmail.com>
This commit is contained in:
Hadrien Croubois
2024-07-22 17:23:08 +02:00
committed by GitHub
parent 19a657bef8
commit e30b390d84
9 changed files with 356 additions and 1 deletions

View File

@ -40,6 +40,7 @@ are useful to interact with third party contracts that implement them.
- {IERC5313}
- {IERC5805}
- {IERC6372}
- {IERC7674}
== Detailed ABI
@ -80,3 +81,5 @@ are useful to interact with third party contracts that implement them.
{{IERC5805}}
{{IERC6372}}
{{IERC7674}}

View File

@ -0,0 +1,16 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import {IERC20} from "./IERC20.sol";
/**
* @dev Temporary Approval Extension for ERC-20 (https://github.com/ethereum/ERCs/pull/358[ERC-7674])
*/
interface IERC7674 is IERC20 {
/**
* @dev Set the temporary allowance, allowing `spender` to withdraw (within the same transaction) assets
* held by the caller.
*/
function temporaryApprove(address spender, uint256 value) external returns (bool success);
}

View File

@ -0,0 +1,20 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import {Address} from "../utils/Address.sol";
contract BatchCaller {
struct Call {
address target;
uint256 value;
bytes data;
}
function execute(Call[] calldata calls) external returns (bytes[] memory) {
bytes[] memory returndata = new bytes[](calls.length);
for (uint256 i = 0; i < calls.length; ++i) {
returndata[i] = Address.functionCallWithValue(calls[i].target, calls[i].data, calls[i].value);
}
return returndata;
}
}

View File

@ -0,0 +1,38 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import {IERC20} from "../../token/ERC20/IERC20.sol";
import {IERC20Metadata} from "../../token/ERC20/extensions/IERC20Metadata.sol";
contract ERC20GetterHelper {
event ERC20TotalSupply(IERC20 token, uint256 totalSupply);
event ERC20BalanceOf(IERC20 token, address account, uint256 balanceOf);
event ERC20Allowance(IERC20 token, address owner, address spender, uint256 allowance);
event ERC20Name(IERC20Metadata token, string name);
event ERC20Symbol(IERC20Metadata token, string symbol);
event ERC20Decimals(IERC20Metadata token, uint8 decimals);
function totalSupply(IERC20 token) external {
emit ERC20TotalSupply(token, token.totalSupply());
}
function balanceOf(IERC20 token, address account) external {
emit ERC20BalanceOf(token, account, token.balanceOf(account));
}
function allowance(IERC20 token, address owner, address spender) external {
emit ERC20Allowance(token, owner, spender, token.allowance(owner, spender));
}
function name(IERC20Metadata token) external {
emit ERC20Name(token, token.name());
}
function symbol(IERC20Metadata token) external {
emit ERC20Symbol(token, token.symbol());
}
function decimals(IERC20Metadata token) external {
emit ERC20Decimals(token, token.decimals());
}
}

View File

@ -22,6 +22,7 @@ Additionally there are multiple custom extensions, including:
* {ERC20FlashMint}: token level support for flash loans through the minting and burning of ephemeral tokens (standardized as ERC-3156).
* {ERC20Votes}: support for voting and vote delegation.
* {ERC20Wrapper}: wrapper to create an ERC-20 backed by another ERC-20, with deposit and withdraw methods. Useful in conjunction with {ERC20Votes}.
* {ERC20TemporaryApproval}: support for approvals lasting for only one transaction, as defined in ERC-7674.
* {ERC1363}: support for calling the target of a transfer or approval, enabling code execution on the receiver within a single transaction.
* {ERC4626}: tokenized vault that manages shares (represented as ERC-20) that are backed by assets (another ERC-20).
@ -61,6 +62,8 @@ NOTE: This core set of contracts is designed to be unopinionated, allowing devel
{{ERC20FlashMint}}
{{ERC20TemporaryApproval}}
{{ERC1363}}
{{ERC4626}}

View File

@ -0,0 +1,119 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import {IERC20, ERC20} from "../ERC20.sol";
import {IERC7674} from "../../../interfaces/draft-IERC7674.sol";
import {Math} from "../../../utils/math/Math.sol";
import {SlotDerivation} from "../../../utils/SlotDerivation.sol";
import {StorageSlot} from "../../../utils/StorageSlot.sol";
/**
* @dev Extension of {ERC20} that adds support for temporary allowances following ERC-7674.
*
* WARNING: This is a draft contract. The corresponding ERC is still subject to changes.
*/
abstract contract ERC20TemporaryApproval is ERC20, IERC7674 {
using SlotDerivation for bytes32;
using StorageSlot for bytes32;
using StorageSlot for StorageSlot.Uint256SlotType;
// keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.ERC20_TEMPORARY_APPROVAL_STORAGE")) - 1)) & ~bytes32(uint256(0xff))
bytes32 private constant ERC20_TEMPORARY_APPROVAL_STORAGE =
0xea2d0e77a01400d0111492b1321103eed560d8fe44b9a7c2410407714583c400;
/**
* @dev {allowance} override that includes the temporary allowance when looking up the current allowance. If
* adding up the persistent and the temporary allowances result in an overflow, type(uint256).max is returned.
*/
function allowance(address owner, address spender) public view virtual override(IERC20, ERC20) returns (uint256) {
(bool success, uint256 amount) = Math.tryAdd(
super.allowance(owner, spender),
_temporaryAllowance(owner, spender)
);
return success ? amount : type(uint256).max;
}
/**
* @dev Internal getter for the current temporary allowance that `spender` has over `owner` tokens.
*/
function _temporaryAllowance(address owner, address spender) internal view virtual returns (uint256) {
return _temporaryAllowanceSlot(owner, spender).tload();
}
/**
* @dev Alternative to {approve} that sets a `value` amount of tokens as the temporary allowance of `spender` over
* the caller's tokens.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Requirements:
* - `spender` cannot be the zero address.
*
* Does NOT emit an {Approval} event.
*/
function temporaryApprove(address spender, uint256 value) public virtual returns (bool) {
_temporaryApprove(_msgSender(), spender, value);
return true;
}
/**
* @dev Sets `value` as the temporary allowance of `spender` over the `owner` s tokens.
*
* This internal function is equivalent to `temporaryApprove`, and can be used to e.g. set automatic allowances
* for certain subsystems, etc.
*
* Requirements:
* - `owner` cannot be the zero address.
* - `spender` cannot be the zero address.
*
* Does NOT emit an {Approval} event.
*/
function _temporaryApprove(address owner, address spender, uint256 value) internal virtual {
if (owner == address(0)) {
revert ERC20InvalidApprover(address(0));
}
if (spender == address(0)) {
revert ERC20InvalidSpender(address(0));
}
_temporaryAllowanceSlot(owner, spender).tstore(value);
}
/**
* @dev {_spendAllowance} override that consumes the temporary allowance (if any) before eventually falling back
* to consuming the persistent allowance.
* NOTE: This function skips calling `super._spendAllowance` if the temporary allowance
* is enough to cover the spending.
*/
function _spendAllowance(address owner, address spender, uint256 value) internal virtual override {
// load transient allowance
uint256 currentTemporaryAllowance = _temporaryAllowance(owner, spender);
// Check and update (if needed) the temporary allowance + set remaining value
if (currentTemporaryAllowance > 0) {
// All value is covered by the infinite allowance. nothing left to spend, we can return early
if (currentTemporaryAllowance == type(uint256).max) {
return;
}
// check how much of the value is covered by the transient allowance
uint256 spendTemporaryAllowance = Math.min(currentTemporaryAllowance, value);
unchecked {
// decrease transient allowance accordingly
_temporaryApprove(owner, spender, currentTemporaryAllowance - spendTemporaryAllowance);
// update value necessary
value -= spendTemporaryAllowance;
}
}
// reduce any remaining value from the persistent allowance
if (value > 0) {
super._spendAllowance(owner, spender, value);
}
}
function _temporaryAllowanceSlot(
address owner,
address spender
) private pure returns (StorageSlot.Uint256SlotType) {
return ERC20_TEMPORARY_APPROVAL_STORAGE.deriveMapping(owner).deriveMapping(spender).asUint256();
}
}