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

Add ERC721 Wrapper #3863

Merged
merged 38 commits into from
Feb 9, 2023
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
36f15d1
Feature: ERC721 Wrapper
ernestognw Dec 7, 2022
50cceb1
Add PR link
ernestognw Dec 7, 2022
e0c02e6
Added tests
ernestognw Dec 10, 2022
42a681a
Update version
ernestognw Dec 12, 2022
73e498b
Add batching
ernestognw Dec 16, 2022
83f3a0a
Update wording
ernestognw Dec 19, 2022
b76c741
Check ownership or approval in `withdrawTo`
ernestognw Jan 4, 2023
8d8788d
Support minting on `onERC721Received` check
ernestognw Jan 4, 2023
4fdb735
Merge branch 'master' into feature/erc721-wrapper
ernestognw Jan 4, 2023
89d13d8
Fix `onERC721Received` param location and ran prettier
ernestognw Jan 4, 2023
bf1dbb7
Applied prettier to tests
ernestognw Jan 4, 2023
4348d23
Switched to hardhat-exposed features
ernestognw Jan 4, 2023
59a7491
Remove ERC721WrapperMock
ernestognw Jan 4, 2023
cd5c4b9
Cache sloads
Amxx Jan 4, 2023
265d596
Fix lint
ernestognw Jan 9, 2023
aa7f791
Update ERC721.behavior.js
Amxx Jan 10, 2023
7c35759
add test: onERC721Received to another account
Amxx Jan 10, 2023
75074df
Add extra comment
ernestognw Jan 24, 2023
3d0438c
Merge branch 'master' into feature/erc721-wrapper
ernestognw Jan 24, 2023
77fcda8
Merge branch 'master' into feature/erc721-wrapper
ernestognw Jan 24, 2023
3cc752c
Add changeset
ernestognw Jan 24, 2023
24ac3dc
Accept suggestion
ernestognw Jan 24, 2023
935e84b
Fix: Add reentrancy note
ernestognw Jan 24, 2023
f9ee65e
Remove warning
ernestognw Jan 24, 2023
0adf63e
Update docs/modules/ROOT/pages/governance.adoc
ernestognw Jan 24, 2023
16daba8
Remove unnecesary file
ernestognw Feb 3, 2023
1695aaa
Move `underlying` to private and complete branch coverage tests
ernestognw Feb 3, 2023
fb22e8d
Add magic value
ernestognw Feb 3, 2023
3f5b0f3
Switch to bytes12 for magic value
ernestognw Feb 3, 2023
17a5475
Lint
ernestognw Feb 3, 2023
24b1033
Change magic value check
ernestognw Feb 3, 2023
1fd1094
Merge branch 'master' into feature/erc721-wrapper
ernestognw Feb 4, 2023
ca26854
Make magic value public
ernestognw Feb 7, 2023
29f31f7
Missing change
ernestognw Feb 7, 2023
d51374b
Merge branch 'master' into feature/erc721-wrapper
ernestognw Feb 7, 2023
92d650b
Merge branch 'master' into feature/erc721-wrapper
Amxx Feb 8, 2023
b49c187
Make `onERC721Received` more strict with the magic value
ernestognw Feb 8, 2023
e6eb16d
Merge branch 'master' into feature/erc721-wrapper
ernestognw Feb 8, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
17 changes: 14 additions & 3 deletions contracts/token/ERC721/extensions/ERC721Wrapper.sol
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ import "../utils/ERC721Holder.sol";
abstract contract ERC721Wrapper is ERC721, ERC721Holder {
IERC721 private immutable _underlying;

// Kept as bytes12 so it can be packed with an address
// Equal to 0xb125e89df18e2ceac5fd2fa8
bytes12 public constant WRAPPER_ACCEPT_MAGIC = bytes12(keccak256("WRAPPER_ACCEPT_MAGIC"));
Amxx marked this conversation as resolved.
Show resolved Hide resolved

constructor(IERC721 underlyingToken) {
_underlying = underlyingToken;
}
Expand All @@ -25,7 +29,7 @@ abstract contract ERC721Wrapper is ERC721, ERC721Holder {
* @dev Allow a user to deposit underlying tokens and mint the corresponding tokenIds.
*/
function depositFor(address account, uint256[] memory tokenIds) public virtual returns (bool) {
bytes memory data = abi.encode(account);
bytes memory data = abi.encodePacked(WRAPPER_ACCEPT_MAGIC, account);

uint256 length = tokenIds.length;
for (uint256 i = 0; i < length; ++i) {
Expand Down Expand Up @@ -57,6 +61,10 @@ abstract contract ERC721Wrapper is ERC721, ERC721Holder {
* @dev Overrides {IERC721Receiver-onERC721Received} to allow minting on direct ERC721 transfers to
* this contract.
*
* In case there's data attached, it validates that the sender is aware of this contract's existence and behavior
* by checking a magic value (`WRAPPER_ACCEPT_MAGIC`) in the first 12 bytes. If it also matches, the rest 20
* bytes are used as an address to send the tokens to.
*
* WARNING: Doesn't work with unsafe transfers (eg. {IERC721-transferFrom}). Use {ERC721Wrapper-_recover}
* for recovering in that scenario.
*/
Expand All @@ -66,8 +74,11 @@ abstract contract ERC721Wrapper is ERC721, ERC721Holder {
uint256 tokenId,
bytes memory data
) public override returns (bytes4) {
require(msg.sender == address(underlying()), "ERC721Wrapper: caller is not underlying");
_safeMint(data.length == 0 ? from : abi.decode(data, (address)), tokenId);
require(address(underlying()) == _msgSender(), "ERC721Wrapper: caller is not underlying");
if (data.length == 32 && WRAPPER_ACCEPT_MAGIC == bytes12(data)) {
from = address(bytes20(bytes32(data) << 96));
}
frangio marked this conversation as resolved.
Show resolved Hide resolved
_safeMint(from, tokenId);
return IERC721Receiver.onERC721Received.selector;
}

Expand Down
35 changes: 28 additions & 7 deletions test/token/ERC721/extensions/ERC721Wrapper.test.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
const { BN, expectEvent, constants, expectRevert } = require('@openzeppelin/test-helpers');
const { expect } = require('chai');
const { keccakFromString, bufferToHex } = require('ethereumjs-util');

const { shouldBehaveLikeERC721 } = require('../ERC721.behavior');

Expand Down Expand Up @@ -219,11 +220,31 @@ contract('ERC721Wrapper', function (accounts) {
});

describe('onERC721Received', function () {
it('mints a token upon receival', async function () {
const WRAPPER_ACCEPT_MAGIC = bufferToHex(keccakFromString('WRAPPER_ACCEPT_MAGIC')).slice(0, 26); // Include 0x

const magicWithAddresss = address =>
web3.utils.encodePacked(
{
value: WRAPPER_ACCEPT_MAGIC,
type: 'bytes12',
},
{
value: address,
type: 'address',
},
);

it('mints a token to sender with arbitrary data', async function () {
// `safeTransferFrom` guarantees `onERC721Received` check
const { tx } = await this.underlying.safeTransferFrom(initialHolder, this.token.address, firstTokenId, {
from: initialHolder,
});
const { tx } = await this.underlying.methods['safeTransferFrom(address,address,uint256,bytes)'](
initialHolder,
this.token.address,
firstTokenId,
'0x0123',
{
from: initialHolder,
},
);

await expectEvent.inTransaction(tx, this.token, 'Transfer', {
from: constants.ZERO_ADDRESS,
Expand All @@ -232,12 +253,12 @@ contract('ERC721Wrapper', function (accounts) {
});
});

it('mints token to specific holder', async function () {
it('mints token to specific holder with address after magic value', async function () {
const { tx } = await this.underlying.methods['safeTransferFrom(address,address,uint256,bytes)'](
initialHolder,
this.token.address,
firstTokenId,
web3.eth.abi.encodeParameters(['address'], [anotherAccount]),
magicWithAddresss(anotherAccount),
{
from: initialHolder,
},
Expand All @@ -256,7 +277,7 @@ contract('ERC721Wrapper', function (accounts) {
initialHolder,
this.token.address,
firstTokenId,
web3.eth.abi.encodeParameters(['address'], [anotherAccount]),
magicWithAddresss(anotherAccount), // Correct data
{ from: anotherAccount },
),
'ERC721Wrapper: caller is not underlying',
Expand Down