Add ERC1363 implementation (#4631)
Co-authored-by: Hadrien Croubois <hadrien.croubois@gmail.com> Co-authored-by: ernestognw <ernestognw@gmail.com>
This commit is contained in:
committed by
GitHub
parent
a51f1e1354
commit
e5f02bc608
@ -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}.
|
||||
* {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).
|
||||
|
||||
Finally, there are some utilities to interact with ERC-20 contracts in various ways:
|
||||
@ -60,6 +61,8 @@ NOTE: This core set of contracts is designed to be unopinionated, allowing devel
|
||||
|
||||
{{ERC20FlashMint}}
|
||||
|
||||
{{ERC1363}}
|
||||
|
||||
{{ERC4626}}
|
||||
|
||||
== Utilities
|
||||
|
||||
198
contracts/token/ERC20/extensions/ERC1363.sol
Normal file
198
contracts/token/ERC20/extensions/ERC1363.sol
Normal file
@ -0,0 +1,198 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
pragma solidity ^0.8.20;
|
||||
|
||||
import {ERC20} from "../ERC20.sol";
|
||||
import {IERC165, ERC165} from "../../../utils/introspection/ERC165.sol";
|
||||
|
||||
import {IERC1363} from "../../../interfaces/IERC1363.sol";
|
||||
import {IERC1363Receiver} from "../../../interfaces/IERC1363Receiver.sol";
|
||||
import {IERC1363Spender} from "../../../interfaces/IERC1363Spender.sol";
|
||||
|
||||
/**
|
||||
* @title ERC1363
|
||||
* @dev Extension of {ERC20} tokens that adds support for code execution after transfers and approvals
|
||||
* on recipient contracts. Calls after transfers are enabled through the {ERC1363-transferAndCall} and
|
||||
* {ERC1363-transferFromAndCall} methods while calls after approvals can be made with {ERC1363-approveAndCall}
|
||||
*/
|
||||
abstract contract ERC1363 is ERC20, ERC165, IERC1363 {
|
||||
/**
|
||||
* @dev Indicates a failure with the token `receiver`. Used in transfers.
|
||||
* @param receiver Address to which tokens are being transferred.
|
||||
*/
|
||||
error ERC1363InvalidReceiver(address receiver);
|
||||
|
||||
/**
|
||||
* @dev Indicates a failure with the token `spender`. Used in approvals.
|
||||
* @param spender Address that may be allowed to operate on tokens without being their owner.
|
||||
*/
|
||||
error ERC1363InvalidSpender(address spender);
|
||||
|
||||
/**
|
||||
* @dev Indicates a failure within the {transfer} part of a transferAndCall operation.
|
||||
*/
|
||||
error ERC1363TransferFailed(address to, uint256 value);
|
||||
|
||||
/**
|
||||
* @dev Indicates a failure within the {transferFrom} part of a transferFromAndCall operation.
|
||||
*/
|
||||
error ERC1363TransferFromFailed(address from, address to, uint256 value);
|
||||
|
||||
/**
|
||||
* @dev Indicates a failure within the {approve} part of a approveAndCall operation.
|
||||
*/
|
||||
error ERC1363ApproveFailed(address spender, uint256 value);
|
||||
|
||||
/**
|
||||
* @inheritdoc IERC165
|
||||
*/
|
||||
function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165, IERC165) returns (bool) {
|
||||
return interfaceId == type(IERC1363).interfaceId || super.supportsInterface(interfaceId);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Moves a `value` amount of tokens from the caller's account to `to`
|
||||
* and then calls {IERC1363Receiver-onTransferReceived} on `to`.
|
||||
*
|
||||
* Requirements:
|
||||
*
|
||||
* - The target has code (i.e. is a contract).
|
||||
* - The target `to` must implement the {IERC1363Receiver} interface.
|
||||
* - The target should return the {IERC1363Receiver} interface id.
|
||||
* - The internal {transfer} must succeed (returned `true`).
|
||||
*/
|
||||
function transferAndCall(address to, uint256 value) public returns (bool) {
|
||||
return transferAndCall(to, value, "");
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Variant of {transferAndCall} that accepts an additional `data` parameter with
|
||||
* no specified format.
|
||||
*/
|
||||
function transferAndCall(address to, uint256 value, bytes memory data) public virtual returns (bool) {
|
||||
if (!transfer(to, value)) {
|
||||
revert ERC1363TransferFailed(to, value);
|
||||
}
|
||||
_checkOnTransferReceived(_msgSender(), to, value, data);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Moves a `value` amount of tokens from `from` to `to` using the allowance mechanism
|
||||
* and then calls {IERC1363Receiver-onTransferReceived} on `to`.
|
||||
*
|
||||
* Requirements:
|
||||
*
|
||||
* - The target has code (i.e. is a contract).
|
||||
* - The target `to` must implement the {IERC1363Receiver} interface.
|
||||
* - The target should return the {IERC1363Receiver} interface id.
|
||||
* - The internal {transferFrom} must succeed (returned `true`).
|
||||
*/
|
||||
function transferFromAndCall(address from, address to, uint256 value) public returns (bool) {
|
||||
return transferFromAndCall(from, to, value, "");
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Variant of {transferFromAndCall} that accepts an additional `data` parameter with
|
||||
* no specified format.
|
||||
*/
|
||||
function transferFromAndCall(
|
||||
address from,
|
||||
address to,
|
||||
uint256 value,
|
||||
bytes memory data
|
||||
) public virtual returns (bool) {
|
||||
if (!transferFrom(from, to, value)) {
|
||||
revert ERC1363TransferFromFailed(from, to, value);
|
||||
}
|
||||
_checkOnTransferReceived(from, to, value, data);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Sets a `value` amount of tokens as the allowance of `spender` over the
|
||||
* caller's tokens and then calls {IERC1363Spender-onApprovalReceived} on `spender`.
|
||||
*
|
||||
* Requirements:
|
||||
*
|
||||
* - The target has code (i.e. is a contract).
|
||||
* - The target `to` must implement the {IERC1363Spender} interface.
|
||||
* - The target should return the {IERC1363Spender} interface id.
|
||||
* - The internal {approve} must succeed (returned `true`).
|
||||
*/
|
||||
function approveAndCall(address spender, uint256 value) public returns (bool) {
|
||||
return approveAndCall(spender, value, "");
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Variant of {approveAndCall} that accepts an additional `data` parameter with
|
||||
* no specified format.
|
||||
*/
|
||||
function approveAndCall(address spender, uint256 value, bytes memory data) public virtual returns (bool) {
|
||||
if (!approve(spender, value)) {
|
||||
revert ERC1363ApproveFailed(spender, value);
|
||||
}
|
||||
_checkOnApprovalReceived(spender, value, data);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Performs a call to {IERC1363Receiver-onTransferReceived} on a target address.
|
||||
*
|
||||
* Requirements:
|
||||
*
|
||||
* - The target has code (i.e. is a contract).
|
||||
* - The target `to` must implement the {IERC1363Receiver} interface.
|
||||
* - The target should return the {IERC1363Receiver} interface id.
|
||||
*/
|
||||
function _checkOnTransferReceived(address from, address to, uint256 value, bytes memory data) private {
|
||||
if (to.code.length == 0) {
|
||||
revert ERC1363InvalidReceiver(to);
|
||||
}
|
||||
|
||||
try IERC1363Receiver(to).onTransferReceived(_msgSender(), from, value, data) returns (bytes4 retval) {
|
||||
if (retval != IERC1363Receiver.onTransferReceived.selector) {
|
||||
revert ERC1363InvalidReceiver(to);
|
||||
}
|
||||
} catch (bytes memory reason) {
|
||||
if (reason.length == 0) {
|
||||
revert ERC1363InvalidReceiver(to);
|
||||
} else {
|
||||
/// @solidity memory-safe-assembly
|
||||
assembly {
|
||||
revert(add(32, reason), mload(reason))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Performs a call to {IERC1363Spender-onApprovalReceived} on a target address.
|
||||
*
|
||||
* Requirements:
|
||||
*
|
||||
* - The target has code (i.e. is a contract).
|
||||
* - The target `to` must implement the {IERC1363Spender} interface.
|
||||
* - The target should return the {IERC1363Spender} interface id.
|
||||
*/
|
||||
function _checkOnApprovalReceived(address spender, uint256 value, bytes memory data) private {
|
||||
if (spender.code.length == 0) {
|
||||
revert ERC1363InvalidSpender(spender);
|
||||
}
|
||||
|
||||
try IERC1363Spender(spender).onApprovalReceived(_msgSender(), value, data) returns (bytes4 retval) {
|
||||
if (retval != IERC1363Spender.onApprovalReceived.selector) {
|
||||
revert ERC1363InvalidSpender(spender);
|
||||
}
|
||||
} catch (bytes memory reason) {
|
||||
if (reason.length == 0) {
|
||||
revert ERC1363InvalidSpender(spender);
|
||||
} else {
|
||||
/// @solidity memory-safe-assembly
|
||||
assembly {
|
||||
revert(add(32, reason), mload(reason))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -4,7 +4,7 @@
|
||||
pragma solidity ^0.8.20;
|
||||
|
||||
import {IERC20} from "../IERC20.sol";
|
||||
import {IERC20Permit} from "../extensions/IERC20Permit.sol";
|
||||
import {IERC1363} from "../../../interfaces/IERC1363.sol";
|
||||
import {Address} from "../../../utils/Address.sol";
|
||||
|
||||
/**
|
||||
@ -82,6 +82,61 @@ library SafeERC20 {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Performs an {ERC1363} transferAndCall, with a fallback to the simple {ERC20} transfer if the target has no
|
||||
* code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when
|
||||
* targeting contracts.
|
||||
*
|
||||
* Reverts if the returned value is other than `true`.
|
||||
*/
|
||||
function transferAndCallRelaxed(IERC1363 token, address to, uint256 value, bytes memory data) internal {
|
||||
if (to.code.length == 0) {
|
||||
safeTransfer(token, to, value);
|
||||
} else if (!token.transferAndCall(to, value, data)) {
|
||||
revert SafeERC20FailedOperation(address(token));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Performs an {ERC1363} transferFromAndCall, with a fallback to the simple {ERC20} transferFrom if the target
|
||||
* has no code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when
|
||||
* targeting contracts.
|
||||
*
|
||||
* Reverts if the returned value is other than `true`.
|
||||
*/
|
||||
function transferFromAndCallRelaxed(
|
||||
IERC1363 token,
|
||||
address from,
|
||||
address to,
|
||||
uint256 value,
|
||||
bytes memory data
|
||||
) internal {
|
||||
if (to.code.length == 0) {
|
||||
safeTransferFrom(token, from, to, value);
|
||||
} else if (!token.transferFromAndCall(from, to, value, data)) {
|
||||
revert SafeERC20FailedOperation(address(token));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Performs an {ERC1363} approveAndCall, with a fallback to the simple {ERC20} approve if the target has no
|
||||
* code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when
|
||||
* targeting contracts.
|
||||
*
|
||||
* NOTE: When the recipient address (`to`) has no code (i.e. is an EOA), this function behaves as {forceApprove}.
|
||||
* Opposedly, when the recipient address (`to`) has code, this function only attempts to call {ERC1363-approveAndCall}
|
||||
* once without retrying, and relies on the returned value to be true.
|
||||
*
|
||||
* Reverts if the returned value is other than `true`.
|
||||
*/
|
||||
function approveAndCallRelaxed(IERC1363 token, address to, uint256 value, bytes memory data) internal {
|
||||
if (to.code.length == 0) {
|
||||
forceApprove(token, to, value);
|
||||
} else if (!token.approveAndCall(to, value, data)) {
|
||||
revert SafeERC20FailedOperation(address(token));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
|
||||
* on the return value: the return value is optional (but if data is returned, it must not be false).
|
||||
|
||||
Reference in New Issue
Block a user