Skip to content

Commit cd48b3e

Browse files
clauBv23Amxxfrangioernestognw
authored
Add validation in Governor on ERC-721 or ERC-1155 received (#4314)
Co-authored-by: Hadrien Croubois <[email protected]> Co-authored-by: Francisco <[email protected]> Co-authored-by: Ernesto García <[email protected]>
1 parent 6724873 commit cd48b3e

File tree

8 files changed

+306
-37
lines changed

8 files changed

+306
-37
lines changed

.changeset/mighty-donuts-smile.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
'openzeppelin-solidity': patch
3+
---
4+
5+
`Governor`: Add validation in ERC1155 and ERC721 receiver hooks to ensure Governor is the executor.
6+

.changeset/thin-camels-matter.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+
`ERC1155`: Bubble errors triggered in the `onERC1155Received` and `onERC1155BatchReceived` hooks.

contracts/governance/Governor.sol

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ abstract contract Governor is Context, ERC165, EIP712, IGovernor, IERC721Receive
7070
* governance protocol (since v4.6).
7171
*/
7272
modifier onlyGovernance() {
73-
if (_msgSender() != _executor()) {
73+
if (_executor() != _msgSender()) {
7474
revert GovernorOnlyExecutor(_msgSender());
7575
}
7676
if (_executor() != address(this)) {
@@ -631,20 +631,29 @@ abstract contract Governor is Context, ERC165, EIP712, IGovernor, IERC721Receive
631631

632632
/**
633633
* @dev See {IERC721Receiver-onERC721Received}.
634+
* Receiving tokens is disabled if the governance executor is other than the governor itself (eg. when using with a timelock).
634635
*/
635636
function onERC721Received(address, address, uint256, bytes memory) public virtual returns (bytes4) {
637+
if (_executor() != address(this)) {
638+
revert GovernorDisabledDeposit();
639+
}
636640
return this.onERC721Received.selector;
637641
}
638642

639643
/**
640644
* @dev See {IERC1155Receiver-onERC1155Received}.
645+
* Receiving tokens is disabled if the governance executor is other than the governor itself (eg. when using with a timelock).
641646
*/
642647
function onERC1155Received(address, address, uint256, uint256, bytes memory) public virtual returns (bytes4) {
648+
if (_executor() != address(this)) {
649+
revert GovernorDisabledDeposit();
650+
}
643651
return this.onERC1155Received.selector;
644652
}
645653

646654
/**
647655
* @dev See {IERC1155Receiver-onERC1155BatchReceived}.
656+
* Receiving tokens is disabled if the governance executor is other than the governor itself (eg. when using with a timelock).
648657
*/
649658
function onERC1155BatchReceived(
650659
address,
@@ -653,6 +662,9 @@ abstract contract Governor is Context, ERC165, EIP712, IGovernor, IERC721Receive
653662
uint256[] memory,
654663
bytes memory
655664
) public virtual returns (bytes4) {
665+
if (_executor() != address(this)) {
666+
revert GovernorDisabledDeposit();
667+
}
656668
return this.onERC1155BatchReceived.selector;
657669
}
658670

contracts/mocks/token/ERC1155ReceiverMock.sol

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,24 @@ import "../../token/ERC1155/IERC1155Receiver.sol";
66
import "../../utils/introspection/ERC165.sol";
77

88
contract ERC1155ReceiverMock is ERC165, IERC1155Receiver {
9+
enum RevertType {
10+
None,
11+
Empty,
12+
String,
13+
Custom
14+
}
15+
916
bytes4 private _recRetval;
10-
bool private _recReverts;
17+
RevertType private _recReverts;
1118
bytes4 private _batRetval;
12-
bool private _batReverts;
19+
RevertType private _batReverts;
1320

1421
event Received(address operator, address from, uint256 id, uint256 value, bytes data, uint256 gas);
1522
event BatchReceived(address operator, address from, uint256[] ids, uint256[] values, bytes data, uint256 gas);
1623

17-
constructor(bytes4 recRetval, bool recReverts, bytes4 batRetval, bool batReverts) {
24+
error ERC1155ReceiverMockError();
25+
26+
constructor(bytes4 recRetval, RevertType recReverts, bytes4 batRetval, RevertType batReverts) {
1827
_recRetval = recRetval;
1928
_recReverts = recReverts;
2029
_batRetval = batRetval;
@@ -28,7 +37,14 @@ contract ERC1155ReceiverMock is ERC165, IERC1155Receiver {
2837
uint256 value,
2938
bytes calldata data
3039
) external returns (bytes4) {
31-
require(!_recReverts, "ERC1155ReceiverMock: reverting on receive");
40+
if (_recReverts == RevertType.Empty) {
41+
revert();
42+
} else if (_recReverts == RevertType.String) {
43+
revert("ERC1155ReceiverMock: reverting on receive");
44+
} else if (_recReverts == RevertType.Custom) {
45+
revert ERC1155ReceiverMockError();
46+
}
47+
3248
emit Received(operator, from, id, value, data, gasleft());
3349
return _recRetval;
3450
}
@@ -40,7 +56,14 @@ contract ERC1155ReceiverMock is ERC165, IERC1155Receiver {
4056
uint256[] calldata values,
4157
bytes calldata data
4258
) external returns (bytes4) {
43-
require(!_batReverts, "ERC1155ReceiverMock: reverting on batch receive");
59+
if (_batReverts == RevertType.Empty) {
60+
revert();
61+
} else if (_batReverts == RevertType.String) {
62+
revert("ERC1155ReceiverMock: reverting on batch receive");
63+
} else if (_batReverts == RevertType.Custom) {
64+
revert ERC1155ReceiverMockError();
65+
}
66+
4467
emit BatchReceived(operator, from, ids, values, data, gasleft());
4568
return _batRetval;
4669
}

contracts/token/ERC1155/ERC1155.sol

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -364,11 +364,16 @@ abstract contract ERC1155 is Context, ERC165, IERC1155, IERC1155MetadataURI, IER
364364
// Tokens rejected
365365
revert ERC1155InvalidReceiver(to);
366366
}
367-
} catch Error(string memory reason) {
368-
revert(reason);
369-
} catch {
370-
// non-ERC1155Receiver implementer
371-
revert ERC1155InvalidReceiver(to);
367+
} catch (bytes memory reason) {
368+
if (reason.length == 0) {
369+
// non-ERC1155Receiver implementer
370+
revert ERC1155InvalidReceiver(to);
371+
} else {
372+
/// @solidity memory-safe-assembly
373+
assembly {
374+
revert(add(32, reason), mload(reason))
375+
}
376+
}
372377
}
373378
}
374379
}
@@ -389,11 +394,16 @@ abstract contract ERC1155 is Context, ERC165, IERC1155, IERC1155MetadataURI, IER
389394
// Tokens rejected
390395
revert ERC1155InvalidReceiver(to);
391396
}
392-
} catch Error(string memory reason) {
393-
revert(reason);
394-
} catch {
395-
// non-ERC1155Receiver implementer
396-
revert ERC1155InvalidReceiver(to);
397+
} catch (bytes memory reason) {
398+
if (reason.length == 0) {
399+
// non-ERC1155Receiver implementer
400+
revert ERC1155InvalidReceiver(to);
401+
} else {
402+
/// @solidity memory-safe-assembly
403+
assembly {
404+
revert(add(32, reason), mload(reason))
405+
}
406+
}
397407
}
398408
}
399409
}

test/governance/extensions/GovernorTimelockCompound.test.js

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ const { shouldSupportInterfaces } = require('../../utils/introspection/SupportsI
1111
const Timelock = artifacts.require('CompTimelock');
1212
const Governor = artifacts.require('$GovernorTimelockCompoundMock');
1313
const CallReceiver = artifacts.require('CallReceiverMock');
14+
const ERC721 = artifacts.require('$ERC721');
15+
const ERC1155 = artifacts.require('$ERC1155');
1416

1517
function makeContractAddress(creator, nonce) {
1618
return web3.utils.toChecksumAddress(
@@ -212,6 +214,70 @@ contract('GovernorTimelockCompound', function (accounts) {
212214
]);
213215
});
214216
});
217+
218+
describe('on safe receive', function () {
219+
describe('ERC721', function () {
220+
const name = 'Non Fungible Token';
221+
const symbol = 'NFT';
222+
const tokenId = web3.utils.toBN(1);
223+
224+
beforeEach(async function () {
225+
this.token = await ERC721.new(name, symbol);
226+
await this.token.$_mint(owner, tokenId);
227+
});
228+
229+
it("can't receive an ERC721 safeTransfer", async function () {
230+
await expectRevertCustomError(
231+
this.token.safeTransferFrom(owner, this.mock.address, tokenId, { from: owner }),
232+
'GovernorDisabledDeposit',
233+
[],
234+
);
235+
});
236+
});
237+
238+
describe('ERC1155', function () {
239+
const uri = 'https://token-cdn-domain/{id}.json';
240+
const tokenIds = {
241+
1: web3.utils.toBN(1000),
242+
2: web3.utils.toBN(2000),
243+
3: web3.utils.toBN(3000),
244+
};
245+
246+
beforeEach(async function () {
247+
this.token = await ERC1155.new(uri);
248+
await this.token.$_mintBatch(owner, Object.keys(tokenIds), Object.values(tokenIds), '0x');
249+
});
250+
251+
it("can't receive ERC1155 safeTransfer", async function () {
252+
await expectRevertCustomError(
253+
this.token.safeTransferFrom(
254+
owner,
255+
this.mock.address,
256+
...Object.entries(tokenIds)[0], // id + amount
257+
'0x',
258+
{ from: owner },
259+
),
260+
'GovernorDisabledDeposit',
261+
[],
262+
);
263+
});
264+
265+
it("can't receive ERC1155 safeBatchTransfer", async function () {
266+
await expectRevertCustomError(
267+
this.token.safeBatchTransferFrom(
268+
owner,
269+
this.mock.address,
270+
Object.keys(tokenIds),
271+
Object.values(tokenIds),
272+
'0x',
273+
{ from: owner },
274+
),
275+
'GovernorDisabledDeposit',
276+
[],
277+
);
278+
});
279+
});
280+
});
215281
});
216282

217283
describe('cancel', function () {

test/governance/extensions/GovernorTimelockControl.test.js

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ const { shouldSupportInterfaces } = require('../../utils/introspection/SupportsI
1010
const Timelock = artifacts.require('TimelockController');
1111
const Governor = artifacts.require('$GovernorTimelockControlMock');
1212
const CallReceiver = artifacts.require('CallReceiverMock');
13+
const ERC721 = artifacts.require('$ERC721');
14+
const ERC1155 = artifacts.require('$ERC1155');
1315

1416
const TOKENS = [
1517
{ Token: artifacts.require('$ERC20Votes'), mode: 'blocknumber' },
@@ -412,6 +414,70 @@ contract('GovernorTimelockControl', function (accounts) {
412414
expect(await this.mock.timelock()).to.be.bignumber.equal(this.newTimelock.address);
413415
});
414416
});
417+
418+
describe('on safe receive', function () {
419+
describe('ERC721', function () {
420+
const name = 'Non Fungible Token';
421+
const symbol = 'NFT';
422+
const tokenId = web3.utils.toBN(1);
423+
424+
beforeEach(async function () {
425+
this.token = await ERC721.new(name, symbol);
426+
await this.token.$_mint(owner, tokenId);
427+
});
428+
429+
it("can't receive an ERC721 safeTransfer", async function () {
430+
await expectRevertCustomError(
431+
this.token.safeTransferFrom(owner, this.mock.address, tokenId, { from: owner }),
432+
'GovernorDisabledDeposit',
433+
[],
434+
);
435+
});
436+
});
437+
438+
describe('ERC1155', function () {
439+
const uri = 'https://token-cdn-domain/{id}.json';
440+
const tokenIds = {
441+
1: web3.utils.toBN(1000),
442+
2: web3.utils.toBN(2000),
443+
3: web3.utils.toBN(3000),
444+
};
445+
446+
beforeEach(async function () {
447+
this.token = await ERC1155.new(uri);
448+
await this.token.$_mintBatch(owner, Object.keys(tokenIds), Object.values(tokenIds), '0x');
449+
});
450+
451+
it("can't receive ERC1155 safeTransfer", async function () {
452+
await expectRevertCustomError(
453+
this.token.safeTransferFrom(
454+
owner,
455+
this.mock.address,
456+
...Object.entries(tokenIds)[0], // id + amount
457+
'0x',
458+
{ from: owner },
459+
),
460+
'GovernorDisabledDeposit',
461+
[],
462+
);
463+
});
464+
465+
it("can't receive ERC1155 safeBatchTransfer", async function () {
466+
await expectRevertCustomError(
467+
this.token.safeBatchTransferFrom(
468+
owner,
469+
this.mock.address,
470+
Object.keys(tokenIds),
471+
Object.values(tokenIds),
472+
'0x',
473+
{ from: owner },
474+
),
475+
'GovernorDisabledDeposit',
476+
[],
477+
);
478+
});
479+
});
480+
});
415481
});
416482

417483
it('clear queue of pending governor calls', async function () {

0 commit comments

Comments
 (0)