Skip to content

Commit

Permalink
Add ERC1363 implementation (#4631)
Browse files Browse the repository at this point in the history
Co-authored-by: Hadrien Croubois <hadrien.croubois@gmail.com>
Co-authored-by: ernestognw <ernestognw@gmail.com>
  • Loading branch information
3 people authored Jan 24, 2024
1 parent a51f1e1 commit e5f02bc
Show file tree
Hide file tree
Showing 21 changed files with 1,227 additions and 215 deletions.
5 changes: 5 additions & 0 deletions .changeset/friendly-nails-push.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'openzeppelin-solidity': minor
---

`ERC1363`: Add implementation of the token payable standard allowing execution of contract code after transfers and approvals.
5 changes: 5 additions & 0 deletions .changeset/nice-paws-pull.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'openzeppelin-solidity': minor
---

`SafeERC20`: Add "relaxed" function for interacting with ERC-1363 functions in a way that is compatible with EOAs.
86 changes: 46 additions & 40 deletions contracts/interfaces/IERC1363.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@ import {IERC20} from "./IERC20.sol";
import {IERC165} from "./IERC165.sol";

/**
* @dev Interface of an ERC-1363 compliant contract, as defined in the
* https://eips.ethereum.org/EIPS/eip-1363[ERC].
* @title IERC1363
* @dev Interface of the ERC-1363 standard as defined in the https://eips.ethereum.org/EIPS/eip-1363[ERC-1363].
*
* Defines a interface for ERC-20 tokens that supports executing recipient
* code after `transfer` or `transferFrom`, or spender code after `approve`.
* Defines an extension interface for ERC-20 tokens that supports executing code on a recipient contract
* after `transfer` or `transferFrom`, or code on a spender contract after `approve`, in a single transaction.
*/
interface IERC1363 is IERC165, IERC20 {
interface IERC1363 is IERC20, IERC165 {
/*
* Note: the ERC-165 identifier for this interface is 0xb0202a11.
* 0xb0202a11 ===
Expand All @@ -26,55 +26,61 @@ interface IERC1363 is IERC165, IERC20 {
*/

/**
* @dev Transfer tokens from `msg.sender` to another address and then call `onTransferReceived` on receiver
* @param to address The address which you want to transfer to
* @param amount uint256 The amount of tokens to be transferred
* @return true unless throwing
* @dev Moves a `value` amount of tokens from the caller's account to `to`
* and then calls {IERC1363Receiver-onTransferReceived} on `to`.
* @param to The address which you want to transfer to.
* @param value The amount of tokens to be transferred.
* @return A boolean value indicating whether the operation succeeded unless throwing.
*/
function transferAndCall(address to, uint256 amount) external returns (bool);
function transferAndCall(address to, uint256 value) external returns (bool);

/**
* @dev Transfer tokens from `msg.sender` to another address and then call `onTransferReceived` on receiver
* @param to address The address which you want to transfer to
* @param amount uint256 The amount of tokens to be transferred
* @param data bytes Additional data with no specified format, sent in call to `to`
* @return true unless throwing
* @dev Moves a `value` amount of tokens from the caller's account to `to`
* and then calls {IERC1363Receiver-onTransferReceived} on `to`.
* @param to The address which you want to transfer to.
* @param value The amount of tokens to be transferred.
* @param data Additional data with no specified format, sent in call to `to`.
* @return A boolean value indicating whether the operation succeeded unless throwing.
*/
function transferAndCall(address to, uint256 amount, bytes memory data) external returns (bool);
function transferAndCall(address to, uint256 value, bytes calldata data) external returns (bool);

/**
* @dev Transfer tokens from one address to another and then call `onTransferReceived` on receiver
* @param from address The address which you want to send tokens from
* @param to address The address which you want to transfer to
* @param amount uint256 The amount of tokens to be transferred
* @return true unless throwing
* @dev Moves a `value` amount of tokens from `from` to `to` using the allowance mechanism
* and then calls {IERC1363Receiver-onTransferReceived} on `to`.
* @param from The address which you want to send tokens from.
* @param to The address which you want to transfer to.
* @param value The amount of tokens to be transferred.
* @return A boolean value indicating whether the operation succeeded unless throwing.
*/
function transferFromAndCall(address from, address to, uint256 amount) external returns (bool);
function transferFromAndCall(address from, address to, uint256 value) external returns (bool);

/**
* @dev Transfer tokens from one address to another and then call `onTransferReceived` on receiver
* @param from address The address which you want to send tokens from
* @param to address The address which you want to transfer to
* @param amount uint256 The amount of tokens to be transferred
* @param data bytes Additional data with no specified format, sent in call to `to`
* @return true unless throwing
* @dev Moves a `value` amount of tokens from `from` to `to` using the allowance mechanism
* and then calls {IERC1363Receiver-onTransferReceived} on `to`.
* @param from The address which you want to send tokens from.
* @param to The address which you want to transfer to.
* @param value The amount of tokens to be transferred.
* @param data Additional data with no specified format, sent in call to `to`.
* @return A boolean value indicating whether the operation succeeded unless throwing.
*/
function transferFromAndCall(address from, address to, uint256 amount, bytes memory data) external returns (bool);
function transferFromAndCall(address from, address to, uint256 value, bytes calldata data) external returns (bool);

/**
* @dev Approve the passed address to spend the specified amount of tokens on behalf of msg.sender
* and then call `onApprovalReceived` on spender.
* @param spender address The address which will spend the funds
* @param amount uint256 The amount of tokens to be spent
* @dev Sets a `value` amount of tokens as the allowance of `spender` over the
* caller's tokens and then calls {IERC1363Spender-onApprovalReceived} on `spender`.
* @param spender The address which will spend the funds.
* @param value The amount of tokens to be spent.
* @return A boolean value indicating whether the operation succeeded unless throwing.
*/
function approveAndCall(address spender, uint256 amount) external returns (bool);
function approveAndCall(address spender, uint256 value) external returns (bool);

/**
* @dev Approve the passed address to spend the specified amount of tokens on behalf of msg.sender
* and then call `onApprovalReceived` on spender.
* @param spender address The address which will spend the funds
* @param amount uint256 The amount of tokens to be spent
* @param data bytes Additional data with no specified format, sent in call to `spender`
* @dev Sets a `value` amount of tokens as the allowance of `spender` over the
* caller's tokens and then calls {IERC1363Spender-onApprovalReceived} on `spender`.
* @param spender The address which will spend the funds.
* @param value The amount of tokens to be spent.
* @param data Additional data with no specified format, sent in call to `spender`.
* @return A boolean value indicating whether the operation succeeded unless throwing.
*/
function approveAndCall(address spender, uint256 amount, bytes memory data) external returns (bool);
function approveAndCall(address spender, uint256 value, bytes calldata data) external returns (bool);
}
37 changes: 17 additions & 20 deletions contracts/interfaces/IERC1363Receiver.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,32 +4,29 @@
pragma solidity ^0.8.20;

/**
* @dev Interface for any contract that wants to support {IERC1363-transferAndCall}
* or {IERC1363-transferFromAndCall} from {ERC1363} token contracts.
* @title IERC1363Receiver
* @dev Interface for any contract that wants to support `transferAndCall` or `transferFromAndCall`
* from ERC-1363 token contracts.
*/
interface IERC1363Receiver {
/*
* Note: the ERC-165 identifier for this interface is 0x88a7ca5c.
* 0x88a7ca5c === bytes4(keccak256("onTransferReceived(address,address,uint256,bytes)"))
*/

/**
* @notice Handle the receipt of ERC-1363 tokens
* @dev Any ERC-1363 smart contract calls this function on the recipient
* after a `transfer` or a `transferFrom`. This function MAY throw to revert and reject the
* transfer. Return of other than the magic value MUST result in the
* transaction being reverted.
* Note: the token contract address is always the message sender.
* @param operator address The address which called `transferAndCall` or `transferFromAndCall` function
* @param from address The address which are token transferred from
* @param amount uint256 The amount of tokens transferred
* @param data bytes Additional data with no specified format
* @return `bytes4(keccak256("onTransferReceived(address,address,uint256,bytes)"))` unless throwing
* @dev Whenever ERC-1363 tokens are transferred to this contract via `transferAndCall` or `transferFromAndCall`
* by `operator` from `from`, this function is called.
*
* NOTE: To accept the transfer, this must return
* `bytes4(keccak256("onTransferReceived(address,address,uint256,bytes)"))`
* (i.e. 0x88a7ca5c, or its own function selector).
*
* @param operator The address which called `transferAndCall` or `transferFromAndCall` function.
* @param from The address which are tokens transferred from.
* @param value The amount of tokens transferred.
* @param data Additional data with no specified format.
* @return `bytes4(keccak256("onTransferReceived(address,address,uint256,bytes)"))` if transfer is allowed unless throwing.
*/
function onTransferReceived(
address operator,
address from,
uint256 amount,
bytes memory data
uint256 value,
bytes calldata data
) external returns (bytes4);
}
33 changes: 15 additions & 18 deletions contracts/interfaces/IERC1363Spender.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,26 +4,23 @@
pragma solidity ^0.8.20;

/**
* @dev Interface for any contract that wants to support {IERC1363-approveAndCall}
* from {ERC1363} token contracts.
* @title ERC1363Spender
* @dev Interface for any contract that wants to support `approveAndCall`
* from ERC-1363 token contracts.
*/
interface IERC1363Spender {
/*
* Note: the ERC-165 identifier for this interface is 0x7b04a2d0.
* 0x7b04a2d0 === bytes4(keccak256("onApprovalReceived(address,uint256,bytes)"))
*/

/**
* @notice Handle the approval of ERC-1363 tokens
* @dev Any ERC-1363 smart contract calls this function on the recipient
* after an `approve`. This function MAY throw to revert and reject the
* approval. Return of other than the magic value MUST result in the
* transaction being reverted.
* Note: the token contract address is always the message sender.
* @param owner address The address which called `approveAndCall` function
* @param amount uint256 The amount of tokens to be spent
* @param data bytes Additional data with no specified format
* @return `bytes4(keccak256("onApprovalReceived(address,uint256,bytes)"))`unless throwing
* @dev Whenever an ERC-1363 token `owner` approves this contract via `approveAndCall`
* to spend their tokens, this function is called.
*
* NOTE: To accept the approval, this must return
* `bytes4(keccak256("onApprovalReceived(address,uint256,bytes)"))`
* (i.e. 0x7b04a2d0, or its own function selector).
*
* @param owner The address which called `approveAndCall` function and previously owned the tokens.
* @param value The amount of tokens to be spent.
* @param data Additional data with no specified format.
* @return `bytes4(keccak256("onApprovalReceived(address,uint256,bytes)"))` if approval is allowed unless throwing.
*/
function onApprovalReceived(address owner, uint256 amount, bytes memory data) external returns (bytes4);
function onApprovalReceived(address owner, uint256 value, bytes calldata data) external returns (bytes4);
}
14 changes: 14 additions & 0 deletions contracts/mocks/token/ERC1363ForceApproveMock.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.20;

import {IERC20} from "../../interfaces/IERC20.sol";
import {ERC20, ERC1363} from "../../token/ERC20/extensions/ERC1363.sol";

// contract that replicate USDT approval behavior in approveAndCall
abstract contract ERC1363ForceApproveMock is ERC1363 {
function approveAndCall(address spender, uint256 amount, bytes memory data) public virtual override returns (bool) {
require(amount == 0 || allowance(msg.sender, spender) == 0, "USDT approval failure");
return super.approveAndCall(spender, amount, data);
}
}
34 changes: 34 additions & 0 deletions contracts/mocks/token/ERC1363NoReturnMock.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.20;

import {IERC20, ERC20} from "../../token/ERC20/ERC20.sol";
import {ERC1363} from "../../token/ERC20/extensions/ERC1363.sol";

abstract contract ERC1363NoReturnMock is ERC1363 {
function transferAndCall(address to, uint256 value, bytes memory data) public override returns (bool) {
super.transferAndCall(to, value, data);
assembly {
return(0, 0)
}
}

function transferFromAndCall(
address from,
address to,
uint256 value,
bytes memory data
) public override returns (bool) {
super.transferFromAndCall(from, to, value, data);
assembly {
return(0, 0)
}
}

function approveAndCall(address spender, uint256 value, bytes memory data) public override returns (bool) {
super.approveAndCall(spender, value, data);
assembly {
return(0, 0)
}
}
}
52 changes: 52 additions & 0 deletions contracts/mocks/token/ERC1363ReceiverMock.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.20;

import {IERC1363Receiver} from "../../interfaces/IERC1363Receiver.sol";

contract ERC1363ReceiverMock is IERC1363Receiver {
enum RevertType {
None,
RevertWithoutMessage,
RevertWithMessage,
RevertWithCustomError,
Panic
}

bytes4 private _retval;
RevertType private _error;

event Received(address operator, address from, uint256 value, bytes data);
error CustomError(bytes4);

constructor() {
_retval = IERC1363Receiver.onTransferReceived.selector;
_error = RevertType.None;
}

function setUp(bytes4 retval, RevertType error) public {
_retval = retval;
_error = error;
}

function onTransferReceived(
address operator,
address from,
uint256 value,
bytes calldata data
) external override returns (bytes4) {
if (_error == RevertType.RevertWithoutMessage) {
revert();
} else if (_error == RevertType.RevertWithMessage) {
revert("ERC1363ReceiverMock: reverting");
} else if (_error == RevertType.RevertWithCustomError) {
revert CustomError(_retval);
} else if (_error == RevertType.Panic) {
uint256 a = uint256(0) / uint256(0);
a;
}

emit Received(operator, from, value, data);
return _retval;
}
}
34 changes: 34 additions & 0 deletions contracts/mocks/token/ERC1363ReturnFalseMock.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.20;

import {IERC20, ERC20} from "../../token/ERC20/ERC20.sol";
import {ERC1363} from "../../token/ERC20/extensions/ERC1363.sol";

abstract contract ERC1363ReturnFalseOnERC20Mock is ERC1363 {
function transfer(address, uint256) public pure override(IERC20, ERC20) returns (bool) {
return false;
}

function transferFrom(address, address, uint256) public pure override(IERC20, ERC20) returns (bool) {
return false;
}

function approve(address, uint256) public pure override(IERC20, ERC20) returns (bool) {
return false;
}
}

abstract contract ERC1363ReturnFalseMock is ERC1363 {
function transferAndCall(address, uint256, bytes memory) public pure override returns (bool) {
return false;
}

function transferFromAndCall(address, address, uint256, bytes memory) public pure override returns (bool) {
return false;
}

function approveAndCall(address, uint256, bytes memory) public pure override returns (bool) {
return false;
}
}
Loading

0 comments on commit e5f02bc

Please sign in to comment.