Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ERC1363 #3017

Closed
wants to merge 18 commits into from
13 changes: 4 additions & 9 deletions contracts/interfaces/IERC1363.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,14 @@ pragma solidity ^0.8.0;
import "./IERC20.sol";
import "./IERC165.sol";

interface IERC1363 is IERC165, IERC20 {
interface IERC1363 is IERC20, IERC165 {
/*
* Note: the ERC-165 identifier for this interface is 0x4bbee2df.
* 0x4bbee2df ===
* Note: the ERC-165 identifier for this interface is 0xb0202a11.
* 0xb0202a11 ===
* bytes4(keccak256('transferAndCall(address,uint256)')) ^
* bytes4(keccak256('transferAndCall(address,uint256,bytes)')) ^
* bytes4(keccak256('transferFromAndCall(address,address,uint256)')) ^
* bytes4(keccak256('transferFromAndCall(address,address,uint256,bytes)'))
*/

/*
* Note: the ERC-165 identifier for this interface is 0xfb9ec8ce.
* 0xfb9ec8ce ===
* bytes4(keccak256('transferFromAndCall(address,address,uint256,bytes)')) ^
* bytes4(keccak256('approveAndCall(address,uint256)')) ^
* bytes4(keccak256('approveAndCall(address,uint256,bytes)'))
*/
Expand Down
60 changes: 60 additions & 0 deletions contracts/mocks/ERC1363Mock.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import "../token/ERC20/extensions/ERC1363.sol";

contract ERC1363Mock is ERC1363 {
constructor(
string memory name,
string memory symbol,
address initialAccount,
uint256 initialBalance
) payable ERC20(name, symbol) {
_mint(initialAccount, initialBalance);
}

function mint(address account, uint256 amount) public {
_mint(account, amount);
}

function burn(address account, uint256 amount) public {
_burn(account, amount);
}
}

contract ERC1363ReceiverMock is IERC1363Receiver, IERC1363Spender {
event TransferReceived(address operator, address from, uint256 value, bytes data);
event ApprovalReceived(address owner, uint256 value, bytes data);

function onTransferReceived(
address operator,
address from,
uint256 value,
bytes memory data
) external override returns (bytes4) {
if (data.length == 1) {
if (data[0] == 0x00) return bytes4(0);
if (data[0] == 0x01) revert("onTransferReceived revert");
if (data[0] == 0x02) revert();
if (data[0] == 0x03) assert(false);
}
emit TransferReceived(operator, from, value, data);
return this.onTransferReceived.selector;
}

function onApprovalReceived(
address owner,
uint256 value,
bytes memory data
) external override returns (bytes4) {
if (data.length == 1) {
if (data[0] == 0x00) return bytes4(0);
if (data[0] == 0x01) revert("onApprovalReceived revert");
if (data[0] == 0x02) revert();
if (data[0] == 0x03) assert(false);
}
emit ApprovalReceived(owner, value, data);
return this.onApprovalReceived.selector;
}
}
143 changes: 143 additions & 0 deletions contracts/token/ERC20/extensions/ERC1363.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "../ERC20.sol";
import "../../../interfaces/IERC1363.sol";
import "../../../interfaces/IERC1363Receiver.sol";
import "../../../interfaces/IERC1363Spender.sol";
import "../../../utils/introspection/ERC165.sol";
import "../../../utils/Address.sol";

abstract contract ERC1363 is IERC1363, ERC20, ERC165 {
using Address for address;

/**
* @dev See {IERC165-supportsInterface}.
*/
function supportsInterface(bytes4 interfaceId) public view virtual override(IERC165, ERC165) returns (bool) {
return interfaceId == type(IERC1363).interfaceId || super.supportsInterface(interfaceId);
Amxx marked this conversation as resolved.
Show resolved Hide resolved
}

/**
* @dev See {IERC1363-transferAndCall}.
*/
function transferAndCall(address to, uint256 value) public override returns (bool) {
return transferAndCall(to, value, bytes(""));
}

/**
* @dev See {IERC1363-transferAndCall}.
*/
function transferAndCall(
address to,
uint256 value,
bytes memory data
) public override returns (bool) {
require(transfer(to, value));
require(
_checkOnTransferReceived(_msgSender(), _msgSender(), to, value, data),
"ERC1363: transfer to non ERC1363Receiver implementer"
);
return true;
}

/**
* @dev See {IERC1363-transferFromAndCall}.
*/
function transferFromAndCall(
address from,
address to,
uint256 value
) public override returns (bool) {
return transferFromAndCall(from, to, value, bytes(""));
}

/**
* @dev See {IERC1363-transferFromAndCall}.
*/
function transferFromAndCall(
address from,
address to,
uint256 value,
bytes memory data
) public override returns (bool) {
require(transferFrom(from, to, value));
require(
_checkOnTransferReceived(_msgSender(), from, to, value, data),
"ERC1363: transfer to non ERC1363Receiver implementer"
);
return true;
}

/**
* @dev See {IERC1363-approveAndCall}.
*/
function approveAndCall(address spender, uint256 value) public override returns (bool) {
return approveAndCall(spender, value, bytes(""));
}

/**
* @dev See {IERC1363-approveAndCall}.
*/
function approveAndCall(
address spender,
uint256 value,
bytes memory data
) public override returns (bool) {
require(approve(spender, value));
require(
_checkOnApprovalReceived(_msgSender(), spender, value, data),
"ERC1363: transfer to non ERC1363Spender implementer"
);
return true;
}

/**
* @dev Internal function to invoke {IERC1363Receiver-onTransferReceived} on a target address.
* The call is not executed if the target address is not a contract.
*/
function _checkOnTransferReceived(
address operator,
address from,
address to,
uint256 value,
bytes memory data
) private returns (bool) {
try IERC1363Receiver(to).onTransferReceived(operator, from, value, data) returns (bytes4 retval) {
return retval == IERC1363Receiver.onTransferReceived.selector;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now that we always revert we could just revert in here instead of returning boolean no?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I copied the approach we use in the ERC721 checks, for consistency through the code base, but I guess putting the require here would be ok

} catch (bytes memory reason) {
if (reason.length == 0) {
revert("ERC1363: transfer to non ERC1363Receiver implementer");
} else {
/// @solidity memory-safe-assembly
assembly {
revert(add(32, reason), mload(reason))
}
}
}
}

/**
* @dev Internal function to invoke {IERC1363Spender-onApprovalReceived} on a target address.
* The call is not executed if the target address is not a contract.
*/
function _checkOnApprovalReceived(
address owner,
frangio marked this conversation as resolved.
Show resolved Hide resolved
address spender,
uint256 value,
bytes memory data
) private returns (bool) {
try IERC1363Spender(spender).onApprovalReceived(owner, value, data) returns (bytes4 retval) {
return retval == IERC1363Spender.onApprovalReceived.selector;
} catch (bytes memory reason) {
if (reason.length == 0) {
revert("ERC1363: transfer to non ERC1363Spender implementer");
} else {
/// @solidity memory-safe-assembly
assembly {
revert(add(32, reason), mload(reason))
}
}
}
}
}
2 changes: 1 addition & 1 deletion scripts/checks/inheritance-ordering.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ for (const artifact of artifacts) {
const linearized = [];

for (const source in solcOutput.contracts) {
if (source.includes('/mocks/')) {
if ([ '/mocks/', '/presets/' ].some(skip => source.includes(skip))) {
continue;
}

Expand Down
Loading