Skip to content

Commit e5f02bc

Browse files
vittominacoriAmxxernestognw
authored
Add ERC1363 implementation (#4631)
Co-authored-by: Hadrien Croubois <hadrien.croubois@gmail.com> Co-authored-by: ernestognw <ernestognw@gmail.com>
1 parent a51f1e1 commit e5f02bc

21 files changed

+1227
-215
lines changed

.changeset/friendly-nails-push.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'openzeppelin-solidity': minor
3+
---
4+
5+
`ERC1363`: Add implementation of the token payable standard allowing execution of contract code after transfers and approvals.

.changeset/nice-paws-pull.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'openzeppelin-solidity': minor
3+
---
4+
5+
`SafeERC20`: Add "relaxed" function for interacting with ERC-1363 functions in a way that is compatible with EOAs.

contracts/interfaces/IERC1363.sol

Lines changed: 46 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,13 @@ import {IERC20} from "./IERC20.sol";
77
import {IERC165} from "./IERC165.sol";
88

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

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

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

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

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

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

7277
/**
73-
* @dev Approve the passed address to spend the specified amount of tokens on behalf of msg.sender
74-
* and then call `onApprovalReceived` on spender.
75-
* @param spender address The address which will spend the funds
76-
* @param amount uint256 The amount of tokens to be spent
77-
* @param data bytes Additional data with no specified format, sent in call to `spender`
78+
* @dev Sets a `value` amount of tokens as the allowance of `spender` over the
79+
* caller's tokens and then calls {IERC1363Spender-onApprovalReceived} on `spender`.
80+
* @param spender The address which will spend the funds.
81+
* @param value The amount of tokens to be spent.
82+
* @param data Additional data with no specified format, sent in call to `spender`.
83+
* @return A boolean value indicating whether the operation succeeded unless throwing.
7884
*/
79-
function approveAndCall(address spender, uint256 amount, bytes memory data) external returns (bool);
85+
function approveAndCall(address spender, uint256 value, bytes calldata data) external returns (bool);
8086
}

contracts/interfaces/IERC1363Receiver.sol

Lines changed: 17 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -4,32 +4,29 @@
44
pragma solidity ^0.8.20;
55

66
/**
7-
* @dev Interface for any contract that wants to support {IERC1363-transferAndCall}
8-
* or {IERC1363-transferFromAndCall} from {ERC1363} token contracts.
7+
* @title IERC1363Receiver
8+
* @dev Interface for any contract that wants to support `transferAndCall` or `transferFromAndCall`
9+
* from ERC-1363 token contracts.
910
*/
1011
interface IERC1363Receiver {
11-
/*
12-
* Note: the ERC-165 identifier for this interface is 0x88a7ca5c.
13-
* 0x88a7ca5c === bytes4(keccak256("onTransferReceived(address,address,uint256,bytes)"))
14-
*/
15-
1612
/**
17-
* @notice Handle the receipt of ERC-1363 tokens
18-
* @dev Any ERC-1363 smart contract calls this function on the recipient
19-
* after a `transfer` or a `transferFrom`. This function MAY throw to revert and reject the
20-
* transfer. Return of other than the magic value MUST result in the
21-
* transaction being reverted.
22-
* Note: the token contract address is always the message sender.
23-
* @param operator address The address which called `transferAndCall` or `transferFromAndCall` function
24-
* @param from address The address which are token transferred from
25-
* @param amount uint256 The amount of tokens transferred
26-
* @param data bytes Additional data with no specified format
27-
* @return `bytes4(keccak256("onTransferReceived(address,address,uint256,bytes)"))` unless throwing
13+
* @dev Whenever ERC-1363 tokens are transferred to this contract via `transferAndCall` or `transferFromAndCall`
14+
* by `operator` from `from`, this function is called.
15+
*
16+
* NOTE: To accept the transfer, this must return
17+
* `bytes4(keccak256("onTransferReceived(address,address,uint256,bytes)"))`
18+
* (i.e. 0x88a7ca5c, or its own function selector).
19+
*
20+
* @param operator The address which called `transferAndCall` or `transferFromAndCall` function.
21+
* @param from The address which are tokens transferred from.
22+
* @param value The amount of tokens transferred.
23+
* @param data Additional data with no specified format.
24+
* @return `bytes4(keccak256("onTransferReceived(address,address,uint256,bytes)"))` if transfer is allowed unless throwing.
2825
*/
2926
function onTransferReceived(
3027
address operator,
3128
address from,
32-
uint256 amount,
33-
bytes memory data
29+
uint256 value,
30+
bytes calldata data
3431
) external returns (bytes4);
3532
}

contracts/interfaces/IERC1363Spender.sol

Lines changed: 15 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -4,26 +4,23 @@
44
pragma solidity ^0.8.20;
55

66
/**
7-
* @dev Interface for any contract that wants to support {IERC1363-approveAndCall}
8-
* from {ERC1363} token contracts.
7+
* @title ERC1363Spender
8+
* @dev Interface for any contract that wants to support `approveAndCall`
9+
* from ERC-1363 token contracts.
910
*/
1011
interface IERC1363Spender {
11-
/*
12-
* Note: the ERC-165 identifier for this interface is 0x7b04a2d0.
13-
* 0x7b04a2d0 === bytes4(keccak256("onApprovalReceived(address,uint256,bytes)"))
14-
*/
15-
1612
/**
17-
* @notice Handle the approval of ERC-1363 tokens
18-
* @dev Any ERC-1363 smart contract calls this function on the recipient
19-
* after an `approve`. This function MAY throw to revert and reject the
20-
* approval. Return of other than the magic value MUST result in the
21-
* transaction being reverted.
22-
* Note: the token contract address is always the message sender.
23-
* @param owner address The address which called `approveAndCall` function
24-
* @param amount uint256 The amount of tokens to be spent
25-
* @param data bytes Additional data with no specified format
26-
* @return `bytes4(keccak256("onApprovalReceived(address,uint256,bytes)"))`unless throwing
13+
* @dev Whenever an ERC-1363 token `owner` approves this contract via `approveAndCall`
14+
* to spend their tokens, this function is called.
15+
*
16+
* NOTE: To accept the approval, this must return
17+
* `bytes4(keccak256("onApprovalReceived(address,uint256,bytes)"))`
18+
* (i.e. 0x7b04a2d0, or its own function selector).
19+
*
20+
* @param owner The address which called `approveAndCall` function and previously owned the tokens.
21+
* @param value The amount of tokens to be spent.
22+
* @param data Additional data with no specified format.
23+
* @return `bytes4(keccak256("onApprovalReceived(address,uint256,bytes)"))` if approval is allowed unless throwing.
2724
*/
28-
function onApprovalReceived(address owner, uint256 amount, bytes memory data) external returns (bytes4);
25+
function onApprovalReceived(address owner, uint256 value, bytes calldata data) external returns (bytes4);
2926
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// SPDX-License-Identifier: MIT
2+
3+
pragma solidity ^0.8.20;
4+
5+
import {IERC20} from "../../interfaces/IERC20.sol";
6+
import {ERC20, ERC1363} from "../../token/ERC20/extensions/ERC1363.sol";
7+
8+
// contract that replicate USDT approval behavior in approveAndCall
9+
abstract contract ERC1363ForceApproveMock is ERC1363 {
10+
function approveAndCall(address spender, uint256 amount, bytes memory data) public virtual override returns (bool) {
11+
require(amount == 0 || allowance(msg.sender, spender) == 0, "USDT approval failure");
12+
return super.approveAndCall(spender, amount, data);
13+
}
14+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// SPDX-License-Identifier: MIT
2+
3+
pragma solidity ^0.8.20;
4+
5+
import {IERC20, ERC20} from "../../token/ERC20/ERC20.sol";
6+
import {ERC1363} from "../../token/ERC20/extensions/ERC1363.sol";
7+
8+
abstract contract ERC1363NoReturnMock is ERC1363 {
9+
function transferAndCall(address to, uint256 value, bytes memory data) public override returns (bool) {
10+
super.transferAndCall(to, value, data);
11+
assembly {
12+
return(0, 0)
13+
}
14+
}
15+
16+
function transferFromAndCall(
17+
address from,
18+
address to,
19+
uint256 value,
20+
bytes memory data
21+
) public override returns (bool) {
22+
super.transferFromAndCall(from, to, value, data);
23+
assembly {
24+
return(0, 0)
25+
}
26+
}
27+
28+
function approveAndCall(address spender, uint256 value, bytes memory data) public override returns (bool) {
29+
super.approveAndCall(spender, value, data);
30+
assembly {
31+
return(0, 0)
32+
}
33+
}
34+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
// SPDX-License-Identifier: MIT
2+
3+
pragma solidity ^0.8.20;
4+
5+
import {IERC1363Receiver} from "../../interfaces/IERC1363Receiver.sol";
6+
7+
contract ERC1363ReceiverMock is IERC1363Receiver {
8+
enum RevertType {
9+
None,
10+
RevertWithoutMessage,
11+
RevertWithMessage,
12+
RevertWithCustomError,
13+
Panic
14+
}
15+
16+
bytes4 private _retval;
17+
RevertType private _error;
18+
19+
event Received(address operator, address from, uint256 value, bytes data);
20+
error CustomError(bytes4);
21+
22+
constructor() {
23+
_retval = IERC1363Receiver.onTransferReceived.selector;
24+
_error = RevertType.None;
25+
}
26+
27+
function setUp(bytes4 retval, RevertType error) public {
28+
_retval = retval;
29+
_error = error;
30+
}
31+
32+
function onTransferReceived(
33+
address operator,
34+
address from,
35+
uint256 value,
36+
bytes calldata data
37+
) external override returns (bytes4) {
38+
if (_error == RevertType.RevertWithoutMessage) {
39+
revert();
40+
} else if (_error == RevertType.RevertWithMessage) {
41+
revert("ERC1363ReceiverMock: reverting");
42+
} else if (_error == RevertType.RevertWithCustomError) {
43+
revert CustomError(_retval);
44+
} else if (_error == RevertType.Panic) {
45+
uint256 a = uint256(0) / uint256(0);
46+
a;
47+
}
48+
49+
emit Received(operator, from, value, data);
50+
return _retval;
51+
}
52+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// SPDX-License-Identifier: MIT
2+
3+
pragma solidity ^0.8.20;
4+
5+
import {IERC20, ERC20} from "../../token/ERC20/ERC20.sol";
6+
import {ERC1363} from "../../token/ERC20/extensions/ERC1363.sol";
7+
8+
abstract contract ERC1363ReturnFalseOnERC20Mock is ERC1363 {
9+
function transfer(address, uint256) public pure override(IERC20, ERC20) returns (bool) {
10+
return false;
11+
}
12+
13+
function transferFrom(address, address, uint256) public pure override(IERC20, ERC20) returns (bool) {
14+
return false;
15+
}
16+
17+
function approve(address, uint256) public pure override(IERC20, ERC20) returns (bool) {
18+
return false;
19+
}
20+
}
21+
22+
abstract contract ERC1363ReturnFalseMock is ERC1363 {
23+
function transferAndCall(address, uint256, bytes memory) public pure override returns (bool) {
24+
return false;
25+
}
26+
27+
function transferFromAndCall(address, address, uint256, bytes memory) public pure override returns (bool) {
28+
return false;
29+
}
30+
31+
function approveAndCall(address, uint256, bytes memory) public pure override returns (bool) {
32+
return false;
33+
}
34+
}

0 commit comments

Comments
 (0)