-
Notifications
You must be signed in to change notification settings - Fork 12.3k
ERC1363 #3017
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
Closed
Closed
ERC1363 #3017
Changes from 6 commits
Commits
Show all changes
18 commits
Select commit
Hold shift + click to select a range
0616203
erc1363
Amxx 786fa7c
fix lint
Amxx 553002b
improve coverage
Amxx 65ac716
skip mocks and preset
Amxx b88a69b
refactor test for readability
Amxx 0208e27
fix lint
Amxx 914a6d2
Merge branch 'master' into feature/ERC1363
Amxx 1ec46cf
refactor preset skip
Amxx c24145f
allow transfer and call to EOA
Amxx a3ef6b4
revert allow transferAndCall to EOA
Amxx ee2b256
Merge branch 'master' into feature/ERC1363
Amxx eadcad5
fix test for compilers >= 0.8.10
Amxx 08dd234
code consistency
Amxx abccc64
Merge branch 'master' into feature/ERC1363
Amxx 7e2bbfd
add changeset and update to hardat-expose
Amxx b482786
fix lint
Amxx 3c0df20
cleanup diff
Amxx 561310c
we actaully need this until 4.9 :/
Amxx File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,58 @@ | ||
| // 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) 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) assert(false); | ||
| } | ||
| emit ApprovalReceived(owner, value, data); | ||
| return this.onApprovalReceived.selector; | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,88 @@ | ||
| // 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"; | ||
|
|
||
| abstract contract ERC1363 is IERC1363, ERC20, ERC165 { | ||
| function supportsInterface(bytes4 interfaceId) public view virtual override(IERC165, ERC165) returns (bool) { | ||
| return interfaceId == type(IERC1363).interfaceId || super.supportsInterface(interfaceId); | ||
| } | ||
|
|
||
| function transferAndCall(address to, uint256 value) public override returns (bool) { | ||
| return transferAndCall(to, value, bytes("")); | ||
| } | ||
|
|
||
| function transferAndCall( | ||
| address to, | ||
| uint256 value, | ||
| bytes memory data | ||
| ) public override returns (bool) { | ||
| require(transfer(to, value)); | ||
| try IERC1363Receiver(to).onTransferReceived(_msgSender(), _msgSender(), value, data) returns (bytes4 selector) { | ||
| require( | ||
| selector == IERC1363Receiver(to).onTransferReceived.selector, | ||
| "ERC1363: onTransferReceived invalid result" | ||
| ); | ||
| } catch Error(string memory reason) { | ||
| revert(reason); | ||
| } catch { | ||
| revert("ERC1363: onTransferReceived reverted without reason"); | ||
| } | ||
| return true; | ||
| } | ||
|
|
||
| function transferFromAndCall( | ||
| address from, | ||
| address to, | ||
| uint256 value | ||
| ) public override returns (bool) { | ||
| return transferFromAndCall(from, to, value, bytes("")); | ||
| } | ||
|
|
||
| function transferFromAndCall( | ||
| address from, | ||
| address to, | ||
| uint256 value, | ||
| bytes memory data | ||
| ) public override returns (bool) { | ||
| require(transferFrom(from, to, value)); | ||
| try IERC1363Receiver(to).onTransferReceived(_msgSender(), from, value, data) returns (bytes4 selector) { | ||
| require( | ||
| selector == IERC1363Receiver(to).onTransferReceived.selector, | ||
| "ERC1363: onTransferReceived invalid result" | ||
| ); | ||
| } catch Error(string memory reason) { | ||
| revert(reason); | ||
| } catch { | ||
| revert("ERC1363: onTransferReceived reverted without reason"); | ||
| } | ||
| return true; | ||
| } | ||
|
|
||
| function approveAndCall(address spender, uint256 value) public override returns (bool) { | ||
| return approveAndCall(spender, value, bytes("")); | ||
| } | ||
|
|
||
| function approveAndCall( | ||
| address spender, | ||
| uint256 value, | ||
| bytes memory data | ||
| ) public override returns (bool) { | ||
| require(approve(spender, value)); | ||
| try IERC1363Spender(spender).onApprovalReceived(_msgSender(), value, data) returns (bytes4 selector) { | ||
| require( | ||
| selector == IERC1363Spender(spender).onApprovalReceived.selector, | ||
| "ERC1363: onApprovalReceived invalid result" | ||
| ); | ||
| } catch Error(string memory reason) { | ||
| revert(reason); | ||
| } catch { | ||
| revert("ERC1363: onApprovalReceived reverted without reason"); | ||
| } | ||
| return true; | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,215 @@ | ||
| const { BN, expectEvent, expectRevert } = require('@openzeppelin/test-helpers'); | ||
| const { shouldSupportInterfaces } = require('../../../utils/introspection/SupportsInterface.behavior'); | ||
|
|
||
| const ERC1363Mock = artifacts.require('ERC1363Mock'); | ||
| const ERC1363ReceiverMock = artifacts.require('ERC1363ReceiverMock'); | ||
|
|
||
| contract('ERC1363', function (accounts) { | ||
| const [ holder, operator ] = accounts; | ||
|
|
||
| const initialSupply = new BN(100); | ||
| const value = new BN(10); | ||
|
|
||
| const name = 'My Token'; | ||
| const symbol = 'MTKN'; | ||
|
|
||
| beforeEach(async function () { | ||
| this.token = await ERC1363Mock.new(name, symbol, holder, initialSupply); | ||
| this.receiver = await ERC1363ReceiverMock.new(); | ||
| }); | ||
|
|
||
| shouldSupportInterfaces([ | ||
| 'ERC165', | ||
| 'ERC1363', | ||
| ]); | ||
|
|
||
| describe('transferAndCall', function () { | ||
| it('without data', async function () { | ||
| this.function = 'transferAndCall(address,uint256)'; | ||
| this.operator = holder; | ||
| }); | ||
|
|
||
| it('with data', async function () { | ||
| this.function = 'transferAndCall(address,uint256,bytes)'; | ||
| this.data = '0x123456'; | ||
| this.operator = holder; | ||
| }); | ||
|
|
||
| it('invalid return value', async function () { | ||
| this.function = 'transferAndCall(address,uint256,bytes)'; | ||
| this.data = '0x00'; | ||
| this.operator = holder; | ||
| this.revert = 'ERC1363: onTransferReceived invalid result'; | ||
| }); | ||
|
|
||
| it('hook reverts with message', async function () { | ||
| this.function = 'transferAndCall(address,uint256,bytes)'; | ||
| this.data = '0x01'; | ||
| this.operator = holder; | ||
| this.revert = 'onTransferReceived revert'; | ||
| }); | ||
|
|
||
| it('hook reverts with error', async function () { | ||
| this.function = 'transferAndCall(address,uint256,bytes)'; | ||
| this.data = '0x02'; | ||
| this.operator = holder; | ||
| this.revert = 'ERC1363: onTransferReceived reverted without reason'; | ||
| }); | ||
|
|
||
| afterEach(async function () { | ||
| const txPromise = this.token.methods[this.function](...[ | ||
| this.receiver.address, | ||
| value, | ||
| this.data, | ||
| { from: this.operator }, | ||
| ].filter(Boolean)); | ||
|
|
||
| if (this.revert === undefined) { | ||
| const { tx } = await txPromise; | ||
| await expectEvent.inTransaction(tx, this.token, 'Transfer', { | ||
| from: this.from || this.operator, | ||
| to: this.receiver.address, | ||
| value, | ||
| }); | ||
| await expectEvent.inTransaction(tx, this.receiver, 'TransferReceived', { | ||
| operator: this.operator, | ||
| from: this.from || this.operator, | ||
| value, | ||
| data: this.data || null, | ||
| }); | ||
| } else { | ||
| await expectRevert(txPromise, this.revert); | ||
| } | ||
| }); | ||
| }); | ||
|
|
||
| describe('transferFromAndCall', function () { | ||
| beforeEach(async function () { | ||
| await this.token.approve(operator, initialSupply, { from: holder }); | ||
| }); | ||
|
|
||
| it('without data', async function () { | ||
| this.function = 'transferFromAndCall(address,address,uint256)'; | ||
| this.from = holder; | ||
| this.operator = operator; | ||
| }); | ||
|
|
||
| it('with data', async function () { | ||
| this.function = 'transferFromAndCall(address,address,uint256,bytes)'; | ||
| this.data = '0x123456'; | ||
| this.from = holder; | ||
| this.operator = operator; | ||
| }); | ||
|
|
||
| it('invalid return value', async function () { | ||
| this.function = 'transferFromAndCall(address,address,uint256,bytes)'; | ||
| this.data = '0x00'; | ||
| this.from = holder; | ||
| this.operator = operator; | ||
| this.revert = 'ERC1363: onTransferReceived invalid result'; | ||
| }); | ||
|
|
||
| it('hook reverts with message', async function () { | ||
| this.function = 'transferFromAndCall(address,address,uint256,bytes)'; | ||
| this.data = '0x01'; | ||
| this.from = holder; | ||
| this.operator = operator; | ||
| this.revert = 'onTransferReceived revert'; | ||
| }); | ||
|
|
||
| it('hook reverts with error', async function () { | ||
| this.function = 'transferFromAndCall(address,address,uint256,bytes)'; | ||
| this.data = '0x02'; | ||
| this.from = holder; | ||
| this.operator = operator; | ||
| this.revert = 'ERC1363: onTransferReceived reverted without reason'; | ||
| }); | ||
|
|
||
| afterEach(async function () { | ||
| const txPromise = this.token.methods[this.function](...[ | ||
| this.from, | ||
| this.receiver.address, | ||
| value, | ||
| this.data, | ||
| { from: this.operator }, | ||
| ].filter(Boolean)); | ||
|
|
||
| if (this.revert === undefined) { | ||
| const { tx } = await txPromise; | ||
| await expectEvent.inTransaction(tx, this.token, 'Transfer', { | ||
| from: this.from || this.operator, | ||
| to: this.receiver.address, | ||
| value, | ||
| }); | ||
| await expectEvent.inTransaction(tx, this.receiver, 'TransferReceived', { | ||
| operator: this.operator, | ||
| from: this.from || this.operator, | ||
| value, | ||
| data: this.data || null, | ||
| }); | ||
| } else { | ||
| await expectRevert(txPromise, this.revert); | ||
| } | ||
| }); | ||
| }); | ||
|
|
||
| describe('approveAndCall', function () { | ||
| it('without data', async function () { | ||
| this.function = 'approveAndCall(address,uint256)'; | ||
| this.owner = holder; | ||
| }); | ||
|
|
||
| it('with data', async function () { | ||
| this.function = 'approveAndCall(address,uint256,bytes)'; | ||
| this.data = '0x123456'; | ||
| this.owner = holder; | ||
| }); | ||
|
|
||
| it('invalid return value', async function () { | ||
| this.function = 'approveAndCall(address,uint256,bytes)'; | ||
| this.data = '0x00'; | ||
| this.owner = holder; | ||
| this.revert = 'ERC1363: onApprovalReceived invalid result'; | ||
| }); | ||
|
|
||
| it('hook reverts with message', async function () { | ||
| this.function = 'approveAndCall(address,uint256,bytes)'; | ||
| this.data = '0x01'; | ||
| this.owner = holder; | ||
| this.revert = 'onApprovalReceived revert'; | ||
| }); | ||
|
|
||
| it('hook reverts with error', async function () { | ||
| this.function = 'approveAndCall(address,uint256,bytes)'; | ||
| this.data = '0x02'; | ||
| this.owner = holder; | ||
| this.revert = 'ERC1363: onApprovalReceived reverted without reason'; | ||
| }); | ||
|
|
||
| afterEach(async function () { | ||
| const txPromise = this.token.methods[this.function](...[ | ||
| this.receiver.address, | ||
| value, | ||
| this.data, | ||
| { from: this.owner }, | ||
| ].filter(Boolean)); | ||
|
|
||
| if (this.revert === undefined) { | ||
| const { tx } = await txPromise; | ||
|
|
||
| await expectEvent.inTransaction(tx, this.token, 'Approval', { | ||
| owner: this.owner, | ||
| spender: this.receiver.address, | ||
| value, | ||
| }); | ||
| await expectEvent.inTransaction(tx, this.receiver, 'ApprovalReceived', { | ||
| owner: this.owner, | ||
| value, | ||
| data: this.data || null, | ||
| }); | ||
| } else { | ||
| await expectRevert(txPromise, this.revert); | ||
| } | ||
| }); | ||
| }); | ||
| }); |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.