Add ERC1363 implementation (#4631)
Co-authored-by: Hadrien Croubois <hadrien.croubois@gmail.com> Co-authored-by: ernestognw <ernestognw@gmail.com>pull/4854/head
parent
a51f1e1354
commit
e5f02bc608
@ -0,0 +1,5 @@ |
||||
--- |
||||
'openzeppelin-solidity': minor |
||||
--- |
||||
|
||||
`ERC1363`: Add implementation of the token payable standard allowing execution of contract code after transfers and approvals. |
@ -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. |
@ -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); |
||||
} |
||||
} |
@ -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) |
||||
} |
||||
} |
||||
} |
@ -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; |
||||
} |
||||
} |
@ -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; |
||||
} |
||||
} |
@ -0,0 +1,47 @@ |
||||
// SPDX-License-Identifier: MIT |
||||
|
||||
pragma solidity ^0.8.20; |
||||
|
||||
import {IERC1363Spender} from "../../interfaces/IERC1363Spender.sol"; |
||||
|
||||
contract ERC1363SpenderMock is IERC1363Spender { |
||||
enum RevertType { |
||||
None, |
||||
RevertWithoutMessage, |
||||
RevertWithMessage, |
||||
RevertWithCustomError, |
||||
Panic |
||||
} |
||||
|
||||
bytes4 private _retval; |
||||
RevertType private _error; |
||||
|
||||
event Approved(address owner, uint256 value, bytes data); |
||||
error CustomError(bytes4); |
||||
|
||||
constructor() { |
||||
_retval = IERC1363Spender.onApprovalReceived.selector; |
||||
_error = RevertType.None; |
||||
} |
||||
|
||||
function setUp(bytes4 retval, RevertType error) public { |
||||
_retval = retval; |
||||
_error = error; |
||||
} |
||||
|
||||
function onApprovalReceived(address owner, uint256 value, bytes calldata data) external override returns (bytes4) { |
||||
if (_error == RevertType.RevertWithoutMessage) { |
||||
revert(); |
||||
} else if (_error == RevertType.RevertWithMessage) { |
||||
revert("ERC1363SpenderMock: reverting"); |
||||
} else if (_error == RevertType.RevertWithCustomError) { |
||||
revert CustomError(_retval); |
||||
} else if (_error == RevertType.Panic) { |
||||
uint256 a = uint256(0) / uint256(0); |
||||
a; |
||||
} |
||||
|
||||
emit Approved(owner, value, data); |
||||
return _retval; |
||||
} |
||||
} |
@ -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)) |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,370 @@ |
||||
const { ethers } = require('hardhat'); |
||||
const { expect } = require('chai'); |
||||
const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); |
||||
|
||||
const { |
||||
shouldBehaveLikeERC20, |
||||
shouldBehaveLikeERC20Transfer, |
||||
shouldBehaveLikeERC20Approve, |
||||
} = require('../ERC20.behavior.js'); |
||||
const { shouldSupportInterfaces } = require('../../../utils/introspection/SupportsInterface.behavior'); |
||||
const { RevertType } = require('../../../helpers/enums.js'); |
||||
|
||||
const name = 'My Token'; |
||||
const symbol = 'MTKN'; |
||||
const value = 1000n; |
||||
const data = '0x123456'; |
||||
|
||||
async function fixture() { |
||||
// this.accounts is used by shouldBehaveLikeERC20
|
||||
const accounts = await ethers.getSigners(); |
||||
const [holder, other] = accounts; |
||||
|
||||
const receiver = await ethers.deployContract('ERC1363ReceiverMock'); |
||||
const spender = await ethers.deployContract('ERC1363SpenderMock'); |
||||
const token = await ethers.deployContract('$ERC1363', [name, symbol]); |
||||
|
||||
await token.$_mint(holder, value); |
||||
|
||||
return { |
||||
accounts, |
||||
holder, |
||||
other, |
||||
token, |
||||
receiver, |
||||
spender, |
||||
selectors: { |
||||
onTransferReceived: receiver.interface.getFunction('onTransferReceived(address,address,uint256,bytes)').selector, |
||||
onApprovalReceived: spender.interface.getFunction('onApprovalReceived(address,uint256,bytes)').selector, |
||||
}, |
||||
}; |
||||
} |
||||
|
||||
describe('ERC1363', function () { |
||||
beforeEach(async function () { |
||||
Object.assign(this, await loadFixture(fixture)); |
||||
}); |
||||
|
||||
shouldSupportInterfaces(['ERC165', 'ERC1363']); |
||||
shouldBehaveLikeERC20(value); |
||||
|
||||
describe('transferAndCall', function () { |
||||
describe('as a transfer', function () { |
||||
beforeEach(async function () { |
||||
this.recipient = this.receiver; |
||||
this.transfer = (holder, ...rest) => |
||||
this.token.connect(holder).getFunction('transferAndCall(address,uint256)')(...rest); |
||||
}); |
||||
|
||||
shouldBehaveLikeERC20Transfer(value); |
||||
}); |
||||
|
||||
it('reverts transferring to an EOA', async function () { |
||||
await expect(this.token.connect(this.holder).getFunction('transferAndCall(address,uint256)')(this.other, value)) |
||||
.to.be.revertedWithCustomError(this.token, 'ERC1363InvalidReceiver') |
||||
.withArgs(this.other.address); |
||||
}); |
||||
|
||||
it('succeeds without data', async function () { |
||||
await expect( |
||||
this.token.connect(this.holder).getFunction('transferAndCall(address,uint256)')(this.receiver, value), |
||||
) |
||||
.to.emit(this.token, 'Transfer') |
||||
.withArgs(this.holder.address, this.receiver.target, value) |
||||
.to.emit(this.receiver, 'Received') |
||||
.withArgs(this.holder.address, this.holder.address, value, '0x'); |
||||
}); |
||||
|
||||
it('succeeds with data', async function () { |
||||
await expect( |
||||
this.token.connect(this.holder).getFunction('transferAndCall(address,uint256,bytes)')( |
||||
this.receiver, |
||||
value, |
||||
data, |
||||
), |
||||
) |
||||
.to.emit(this.token, 'Transfer') |
||||
.withArgs(this.holder.address, this.receiver.target, value) |
||||
.to.emit(this.receiver, 'Received') |
||||
.withArgs(this.holder.address, this.holder.address, value, data); |
||||
}); |
||||
|
||||
it('reverts with reverting hook (without reason)', async function () { |
||||
await this.receiver.setUp(this.selectors.onTransferReceived, RevertType.RevertWithoutMessage); |
||||
|
||||
await expect( |
||||
this.token.connect(this.holder).getFunction('transferAndCall(address,uint256,bytes)')( |
||||
this.receiver, |
||||
value, |
||||
data, |
||||
), |
||||
) |
||||
.to.be.revertedWithCustomError(this.token, 'ERC1363InvalidReceiver') |
||||
.withArgs(this.receiver.target); |
||||
}); |
||||
|
||||
it('reverts with reverting hook (with reason)', async function () { |
||||
await this.receiver.setUp(this.selectors.onTransferReceived, RevertType.RevertWithMessage); |
||||
|
||||
await expect( |
||||
this.token.connect(this.holder).getFunction('transferAndCall(address,uint256,bytes)')( |
||||
this.receiver, |
||||
value, |
||||
data, |
||||
), |
||||
).to.be.revertedWith('ERC1363ReceiverMock: reverting'); |
||||
}); |
||||
|
||||
it('reverts with reverting hook (with custom error)', async function () { |
||||
const reason = '0x12345678'; |
||||
await this.receiver.setUp(reason, RevertType.RevertWithCustomError); |
||||
|
||||
await expect( |
||||
this.token.connect(this.holder).getFunction('transferAndCall(address,uint256,bytes)')( |
||||
this.receiver, |
||||
value, |
||||
data, |
||||
), |
||||
) |
||||
.to.be.revertedWithCustomError(this.receiver, 'CustomError') |
||||
.withArgs(reason); |
||||
}); |
||||
|
||||
it('panics with reverting hook (with panic)', async function () { |
||||
await this.receiver.setUp(this.selectors.onTransferReceived, RevertType.Panic); |
||||
|
||||
await expect( |
||||
this.token.connect(this.holder).getFunction('transferAndCall(address,uint256,bytes)')( |
||||
this.receiver, |
||||
value, |
||||
data, |
||||
), |
||||
).to.be.revertedWithPanic(); |
||||
}); |
||||
|
||||
it('reverts with bad return value', async function () { |
||||
await this.receiver.setUp('0x12345678', RevertType.None); |
||||
|
||||
await expect( |
||||
this.token.connect(this.holder).getFunction('transferAndCall(address,uint256,bytes)')( |
||||
this.receiver, |
||||
value, |
||||
data, |
||||
), |
||||
) |
||||
.to.be.revertedWithCustomError(this.token, 'ERC1363InvalidReceiver') |
||||
.withArgs(this.receiver.target); |
||||
}); |
||||
}); |
||||
|
||||
describe('transferFromAndCall', function () { |
||||
beforeEach(async function () { |
||||
await this.token.connect(this.holder).approve(this.other, ethers.MaxUint256); |
||||
}); |
||||
|
||||
describe('as a transfer', function () { |
||||
beforeEach(async function () { |
||||
this.recipient = this.receiver; |
||||
this.transfer = this.token.connect(this.other).getFunction('transferFromAndCall(address,address,uint256)'); |
||||
}); |
||||
|
||||
shouldBehaveLikeERC20Transfer(value); |
||||
}); |
||||
|
||||
it('reverts transferring to an EOA', async function () { |
||||
await expect( |
||||
this.token.connect(this.other).getFunction('transferFromAndCall(address,address,uint256)')( |
||||
this.holder, |
||||
this.other, |
||||
value, |
||||
), |
||||
) |
||||
.to.be.revertedWithCustomError(this.token, 'ERC1363InvalidReceiver') |
||||
.withArgs(this.other.address); |
||||
}); |
||||
|
||||
it('succeeds without data', async function () { |
||||
await expect( |
||||
this.token.connect(this.other).getFunction('transferFromAndCall(address,address,uint256)')( |
||||
this.holder, |
||||
this.receiver, |
||||
value, |
||||
), |
||||
) |
||||
.to.emit(this.token, 'Transfer') |
||||
.withArgs(this.holder.address, this.receiver.target, value) |
||||
.to.emit(this.receiver, 'Received') |
||||
.withArgs(this.other.address, this.holder.address, value, '0x'); |
||||
}); |
||||
|
||||
it('succeeds with data', async function () { |
||||
await expect( |
||||
this.token.connect(this.other).getFunction('transferFromAndCall(address,address,uint256,bytes)')( |
||||
this.holder, |
||||
this.receiver, |
||||
value, |
||||
data, |
||||
), |
||||
) |
||||
.to.emit(this.token, 'Transfer') |
||||
.withArgs(this.holder.address, this.receiver.target, value) |
||||
.to.emit(this.receiver, 'Received') |
||||
.withArgs(this.other.address, this.holder.address, value, data); |
||||
}); |
||||
|
||||
it('reverts with reverting hook (without reason)', async function () { |
||||
await this.receiver.setUp(this.selectors.onTransferReceived, RevertType.RevertWithoutMessage); |
||||
|
||||
await expect( |
||||
this.token.connect(this.other).getFunction('transferFromAndCall(address,address,uint256,bytes)')( |
||||
this.holder, |
||||
this.receiver, |
||||
value, |
||||
data, |
||||
), |
||||
) |
||||
.to.be.revertedWithCustomError(this.token, 'ERC1363InvalidReceiver') |
||||
.withArgs(this.receiver.target); |
||||
}); |
||||
|
||||
it('reverts with reverting hook (with reason)', async function () { |
||||
await this.receiver.setUp(this.selectors.onTransferReceived, RevertType.RevertWithMessage); |
||||
|
||||
await expect( |
||||
this.token.connect(this.other).getFunction('transferFromAndCall(address,address,uint256,bytes)')( |
||||
this.holder, |
||||
this.receiver, |
||||
value, |
||||
data, |
||||
), |
||||
).to.be.revertedWith('ERC1363ReceiverMock: reverting'); |
||||
}); |
||||
|
||||
it('reverts with reverting hook (with custom error)', async function () { |
||||
const reason = '0x12345678'; |
||||
await this.receiver.setUp(reason, RevertType.RevertWithCustomError); |
||||
|
||||
await expect( |
||||
this.token.connect(this.other).getFunction('transferFromAndCall(address,address,uint256,bytes)')( |
||||
this.holder, |
||||
this.receiver, |
||||
value, |
||||
data, |
||||
), |
||||
) |
||||
.to.be.revertedWithCustomError(this.receiver, 'CustomError') |
||||
.withArgs(reason); |
||||
}); |
||||
|
||||
it('panics with reverting hook (with panic)', async function () { |
||||
await this.receiver.setUp(this.selectors.onTransferReceived, RevertType.Panic); |
||||
|
||||
await expect( |
||||
this.token.connect(this.other).getFunction('transferFromAndCall(address,address,uint256,bytes)')( |
||||
this.holder, |
||||
this.receiver, |
||||
value, |
||||
data, |
||||
), |
||||
).to.be.revertedWithPanic(); |
||||
}); |
||||
|
||||
it('reverts with bad return value', async function () { |
||||
await this.receiver.setUp('0x12345678', RevertType.None); |
||||
|
||||
await expect( |
||||
this.token.connect(this.other).getFunction('transferFromAndCall(address,address,uint256,bytes)')( |
||||
this.holder, |
||||
this.receiver, |
||||
value, |
||||
data, |
||||
), |
||||
) |
||||
.to.be.revertedWithCustomError(this.token, 'ERC1363InvalidReceiver') |
||||
.withArgs(this.receiver.target); |
||||
}); |
||||
}); |
||||
|
||||
describe('approveAndCall', function () { |
||||
describe('as an approval', function () { |
||||
beforeEach(async function () { |
||||
this.recipient = this.spender; |
||||
this.approve = (holder, ...rest) => |
||||
this.token.connect(holder).getFunction('approveAndCall(address,uint256)')(...rest); |
||||
}); |
||||
|
||||
shouldBehaveLikeERC20Approve(value); |
||||
}); |
||||
|
||||
it('reverts approving an EOA', async function () { |
||||
await expect(this.token.connect(this.holder).getFunction('approveAndCall(address,uint256)')(this.other, value)) |
||||
.to.be.revertedWithCustomError(this.token, 'ERC1363InvalidSpender') |
||||
.withArgs(this.other.address); |
||||
}); |
||||
|
||||
it('succeeds without data', async function () { |
||||
await expect(this.token.connect(this.holder).getFunction('approveAndCall(address,uint256)')(this.spender, value)) |
||||
.to.emit(this.token, 'Approval') |
||||
.withArgs(this.holder.address, this.spender.target, value) |
||||
.to.emit(this.spender, 'Approved') |
||||
.withArgs(this.holder.address, value, '0x'); |
||||
}); |
||||
|
||||
it('succeeds with data', async function () { |
||||
await expect( |
||||
this.token.connect(this.holder).getFunction('approveAndCall(address,uint256,bytes)')(this.spender, value, data), |
||||
) |
||||
.to.emit(this.token, 'Approval') |
||||
.withArgs(this.holder.address, this.spender.target, value) |
||||
.to.emit(this.spender, 'Approved') |
||||
.withArgs(this.holder.address, value, data); |
||||
}); |
||||
|
||||
it('with reverting hook (without reason)', async function () { |
||||
await this.spender.setUp(this.selectors.onApprovalReceived, RevertType.RevertWithoutMessage); |
||||
|
||||
await expect( |
||||
this.token.connect(this.holder).getFunction('approveAndCall(address,uint256,bytes)')(this.spender, value, data), |
||||
) |
||||
.to.be.revertedWithCustomError(this.token, 'ERC1363InvalidSpender') |
||||
.withArgs(this.spender.target); |
||||
}); |
||||
|
||||
it('reverts with reverting hook (with reason)', async function () { |
||||
await this.spender.setUp(this.selectors.onApprovalReceived, RevertType.RevertWithMessage); |
||||
|
||||
await expect( |
||||
this.token.connect(this.holder).getFunction('approveAndCall(address,uint256,bytes)')(this.spender, value, data), |
||||
).to.be.revertedWith('ERC1363SpenderMock: reverting'); |
||||
}); |
||||
|
||||
it('reverts with reverting hook (with custom error)', async function () { |
||||
const reason = '0x12345678'; |
||||
await this.spender.setUp(reason, RevertType.RevertWithCustomError); |
||||
|
||||
await expect( |
||||
this.token.connect(this.holder).getFunction('approveAndCall(address,uint256,bytes)')(this.spender, value, data), |
||||
) |
||||
.to.be.revertedWithCustomError(this.spender, 'CustomError') |
||||
.withArgs(reason); |
||||
}); |
||||
|
||||
it('panics with reverting hook (with panic)', async function () { |
||||
await this.spender.setUp(this.selectors.onApprovalReceived, RevertType.Panic); |
||||
|
||||
await expect( |
||||
this.token.connect(this.holder).getFunction('approveAndCall(address,uint256,bytes)')(this.spender, value, data), |
||||
).to.be.revertedWithPanic(); |
||||
}); |
||||
|
||||
it('reverts with bad return value', async function () { |
||||
await this.spender.setUp('0x12345678', RevertType.None); |
||||
|
||||
await expect( |
||||
this.token.connect(this.holder).getFunction('approveAndCall(address,uint256,bytes)')(this.spender, value, data), |
||||
) |
||||
.to.be.revertedWithCustomError(this.token, 'ERC1363InvalidSpender') |
||||
.withArgs(this.spender.target); |
||||
}); |
||||
}); |
||||
}); |
Loading…
Reference in new issue