Compare commits
2 Commits
master
...
audit/2023
| Author | SHA1 | Date | |
|---|---|---|---|
| b027c3541c | |||
| ba8e296915 |
@ -2,4 +2,4 @@
|
|||||||
'openzeppelin-solidity': major
|
'openzeppelin-solidity': major
|
||||||
---
|
---
|
||||||
|
|
||||||
`ERC20`, `ERC1155`: Deleted `_beforeTokenTransfer` and `_afterTokenTransfer` hooks, added a new internal `_update` function for customizations, and refactored all extensions using those hooks to use `_update` instead. ([#3838](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3838), [#3876](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3876))
|
`ERC20`, `ERC721`, `ERC1155`: Deleted `_beforeTokenTransfer` and `_afterTokenTransfer` hooks, added a new internal `_update` function for customizations, and refactored all extensions using those hooks to use `_update` instead. ([#3838](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3838), [#3876](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3876), [#4377](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4377))
|
||||||
|
|||||||
5
.changeset/eighty-lemons-shake.md
Normal file
5
.changeset/eighty-lemons-shake.md
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
'openzeppelin-solidity': major
|
||||||
|
---
|
||||||
|
|
||||||
|
`ERC721`: `_approve` no longer allows approving the owner of the tokenId. `_setApprovalForAll` no longer allows setting address(0) as an operator.
|
||||||
5
.changeset/strong-poems-thank.md
Normal file
5
.changeset/strong-poems-thank.md
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
'openzeppelin-solidity': major
|
||||||
|
---
|
||||||
|
|
||||||
|
`DoubleEndedQueue`: refactor internal structure to use `uint128` instead of `int128`. This has no effect on the library interface.
|
||||||
14
CHANGELOG.md
14
CHANGELOG.md
@ -36,7 +36,7 @@ These removals were implemented in the following PRs: [#3637](https://github.com
|
|||||||
|
|
||||||
These breaking changes will require modifications to ERC20, ERC721, and ERC1155 contracts, since the `_afterTokenTransfer` and `_beforeTokenTransfer` functions were removed. Any customization made through those hooks should now be done overriding the new `_update` function instead.
|
These breaking changes will require modifications to ERC20, ERC721, and ERC1155 contracts, since the `_afterTokenTransfer` and `_beforeTokenTransfer` functions were removed. Any customization made through those hooks should now be done overriding the new `_update` function instead.
|
||||||
|
|
||||||
Minting and burning are implemented by `_update` and customizations should be done by overriding this function as well. `_mint` and `_burn` are no longer virtual (meaning they are not overridable) to guard against possible inconsistencies.
|
Minting and burning are implemented by `_update` and customizations should be done by overriding this function as well. `_transfer`, `_mint` and `_burn` are no longer virtual (meaning they are not overridable) to guard against possible inconsistencies.
|
||||||
|
|
||||||
For example, a contract using `ERC20`'s `_beforeTokenTransfer` hook would have to be changed in the following way.
|
For example, a contract using `ERC20`'s `_beforeTokenTransfer` hook would have to be changed in the following way.
|
||||||
|
|
||||||
@ -53,6 +53,18 @@ For example, a contract using `ERC20`'s `_beforeTokenTransfer` hook would have t
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### More about ERC721
|
||||||
|
|
||||||
|
In the case of `ERC721`, the `_update` function does not include a `from` parameter, as the sender is implicitly the previous owner of the `tokenId`. The address of
|
||||||
|
this previous owner is returned by the `_update` function, so it can be used for a posteriori checks. In addition to `to` and `tokenId`, a third parameter (`auth`) is
|
||||||
|
present in this function. This parameter enabled an optional check that the caller/spender is approved to do the transfer. This check cannot be performed after the transfer (because the transfer resets the approval), and doing it before `_update` would require a duplicate call to `_ownerOf`.
|
||||||
|
|
||||||
|
In this logic of removing hidden SLOADs, the `_isApprovedOrOwner` function was removed in favor of a new `_isAuthorized` function. Overrides that used to target the
|
||||||
|
`_isApprovedOrOwner` should now be performed on the `_isAuthorized` function. Calls to `_isApprovedOrOwner` that preceded a call to `_transfer`, `_burn` or `_approve`
|
||||||
|
should be removed in favor of using the `auth` argument in `_update` and `_approve`. This is showcased in `ERC721Burnable.burn` and in `ERC721Wrapper.withdrawTo`.
|
||||||
|
|
||||||
|
The `_exists` function was removed. Calls to this function can be replaced by `_ownerOf(tokenId) != address(0)`.
|
||||||
|
|
||||||
#### ERC165Storage
|
#### ERC165Storage
|
||||||
|
|
||||||
Users that were registering EIP-165 interfaces with `_registerInterface` from `ERC165Storage` should instead do so so by overriding the `supportsInterface` function as seen below:
|
Users that were registering EIP-165 interfaces with `_registerInterface` from `ERC165Storage` should instead do so so by overriding the `supportsInterface` function as seen below:
|
||||||
|
|||||||
@ -29,11 +29,11 @@ contract DoubleEndedQueueHarness {
|
|||||||
_deque.clear();
|
_deque.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
function begin() external view returns (int128) {
|
function begin() external view returns (uint128) {
|
||||||
return _deque._begin;
|
return _deque._begin;
|
||||||
}
|
}
|
||||||
|
|
||||||
function end() external view returns (int128) {
|
function end() external view returns (uint128) {
|
||||||
return _deque._end;
|
return _deque._end;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -28,6 +28,11 @@ const argv = require('yargs')
|
|||||||
type: 'number',
|
type: 'number',
|
||||||
default: 4,
|
default: 4,
|
||||||
},
|
},
|
||||||
|
verbose: {
|
||||||
|
alias: 'v',
|
||||||
|
type: 'count',
|
||||||
|
default: 0,
|
||||||
|
},
|
||||||
options: {
|
options: {
|
||||||
alias: 'o',
|
alias: 'o',
|
||||||
type: 'array',
|
type: 'array',
|
||||||
@ -65,6 +70,9 @@ for (const { spec, contract, files, options = [] } of specs) {
|
|||||||
// Run certora, aggregate the output and print it at the end
|
// Run certora, aggregate the output and print it at the end
|
||||||
async function runCertora(spec, contract, files, options = []) {
|
async function runCertora(spec, contract, files, options = []) {
|
||||||
const args = [...files, '--verify', `${contract}:certora/specs/${spec}.spec`, ...options];
|
const args = [...files, '--verify', `${contract}:certora/specs/${spec}.spec`, ...options];
|
||||||
|
if (argv.verbose) {
|
||||||
|
console.log('Running:', args.join(' '));
|
||||||
|
}
|
||||||
const child = proc.spawn('certoraRun', args);
|
const child = proc.spawn('certoraRun', args);
|
||||||
|
|
||||||
const stream = new PassThrough();
|
const stream = new PassThrough();
|
||||||
|
|||||||
@ -1,64 +1,25 @@
|
|||||||
import "helpers/helpers.spec"
|
import "helpers/helpers.spec";
|
||||||
|
|
||||||
methods {
|
methods {
|
||||||
pushFront(bytes32) envfree
|
function pushFront(bytes32) external envfree;
|
||||||
pushBack(bytes32) envfree
|
function pushBack(bytes32) external envfree;
|
||||||
popFront() returns (bytes32) envfree
|
function popFront() external returns (bytes32) envfree;
|
||||||
popBack() returns (bytes32) envfree
|
function popBack() external returns (bytes32) envfree;
|
||||||
clear() envfree
|
function clear() external envfree;
|
||||||
|
|
||||||
// exposed for FV
|
// exposed for FV
|
||||||
begin() returns (int128) envfree
|
function begin() external returns (uint128) envfree;
|
||||||
end() returns (int128) envfree
|
function end() external returns (uint128) envfree;
|
||||||
|
|
||||||
// view
|
// view
|
||||||
length() returns (uint256) envfree
|
function length() external returns (uint256) envfree;
|
||||||
empty() returns (bool) envfree
|
function empty() external returns (bool) envfree;
|
||||||
front() returns (bytes32) envfree
|
function front() external returns (bytes32) envfree;
|
||||||
back() returns (bytes32) envfree
|
function back() external returns (bytes32) envfree;
|
||||||
at_(uint256) returns (bytes32) envfree // at is a reserved word
|
function at_(uint256) external returns (bytes32) envfree; // at is a reserved word
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
definition full() returns bool = length() == max_uint128;
|
||||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
|
||||||
│ Helpers │
|
|
||||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
|
||||||
*/
|
|
||||||
|
|
||||||
function min_int128() returns mathint {
|
|
||||||
return -(1 << 127);
|
|
||||||
}
|
|
||||||
|
|
||||||
function max_int128() returns mathint {
|
|
||||||
return (1 << 127) - 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Could be broken in theory, but not in practice
|
|
||||||
function boundedQueue() returns bool {
|
|
||||||
return
|
|
||||||
max_int128() > to_mathint(end()) &&
|
|
||||||
min_int128() < to_mathint(begin());
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
|
||||||
│ Invariant: end is larger or equal than begin │
|
|
||||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
|
||||||
*/
|
|
||||||
invariant boundariesConsistency()
|
|
||||||
end() >= begin()
|
|
||||||
filtered { f -> !f.isView }
|
|
||||||
{ preserved { require boundedQueue(); } }
|
|
||||||
|
|
||||||
/*
|
|
||||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
|
||||||
│ Invariant: length is end minus begin │
|
|
||||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
|
||||||
*/
|
|
||||||
invariant lengthConsistency()
|
|
||||||
length() == to_mathint(end()) - to_mathint(begin())
|
|
||||||
filtered { f -> !f.isView }
|
|
||||||
{ preserved { require boundedQueue(); } }
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||||
@ -68,22 +29,19 @@ invariant lengthConsistency()
|
|||||||
invariant emptiness()
|
invariant emptiness()
|
||||||
empty() <=> length() == 0
|
empty() <=> length() == 0
|
||||||
filtered { f -> !f.isView }
|
filtered { f -> !f.isView }
|
||||||
{ preserved { require boundedQueue(); } }
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||||
│ Invariant: front points to the first index and back points to the last one │
|
│ Invariant: front points to the first index and back points to the last one │
|
||||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||||
*/
|
*/
|
||||||
invariant queueEndings()
|
invariant queueFront()
|
||||||
at_(length() - 1) == back() && at_(0) == front()
|
at_(0) == front()
|
||||||
|
filtered { f -> !f.isView }
|
||||||
|
|
||||||
|
invariant queueBack()
|
||||||
|
at_(require_uint256(length() - 1)) == back()
|
||||||
filtered { f -> !f.isView }
|
filtered { f -> !f.isView }
|
||||||
{
|
|
||||||
preserved {
|
|
||||||
requireInvariant boundariesConsistency();
|
|
||||||
require boundedQueue();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||||
@ -91,18 +49,18 @@ invariant queueEndings()
|
|||||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||||
*/
|
*/
|
||||||
rule pushFront(bytes32 value) {
|
rule pushFront(bytes32 value) {
|
||||||
require boundedQueue();
|
|
||||||
|
|
||||||
uint256 lengthBefore = length();
|
uint256 lengthBefore = length();
|
||||||
|
bool fullBefore = full();
|
||||||
|
|
||||||
pushFront@withrevert(value);
|
pushFront@withrevert(value);
|
||||||
|
bool success = !lastReverted;
|
||||||
|
|
||||||
// liveness
|
// liveness
|
||||||
assert !lastReverted, "never reverts";
|
assert success <=> !fullBefore, "never revert if not previously full";
|
||||||
|
|
||||||
// effect
|
// effect
|
||||||
assert front() == value, "front set to value";
|
assert success => front() == value, "front set to value";
|
||||||
assert length() == lengthBefore + 1, "queue extended";
|
assert success => to_mathint(length()) == lengthBefore + 1, "queue extended";
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -111,15 +69,13 @@ rule pushFront(bytes32 value) {
|
|||||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||||
*/
|
*/
|
||||||
rule pushFrontConsistency(uint256 index) {
|
rule pushFrontConsistency(uint256 index) {
|
||||||
require boundedQueue();
|
|
||||||
|
|
||||||
bytes32 beforeAt = at_(index);
|
bytes32 beforeAt = at_(index);
|
||||||
|
|
||||||
bytes32 value;
|
bytes32 value;
|
||||||
pushFront(value);
|
pushFront(value);
|
||||||
|
|
||||||
// try to read value
|
// try to read value
|
||||||
bytes32 afterAt = at_@withrevert(index + 1);
|
bytes32 afterAt = at_@withrevert(require_uint256(index + 1));
|
||||||
|
|
||||||
assert !lastReverted, "value still there";
|
assert !lastReverted, "value still there";
|
||||||
assert afterAt == beforeAt, "data is preserved";
|
assert afterAt == beforeAt, "data is preserved";
|
||||||
@ -131,18 +87,18 @@ rule pushFrontConsistency(uint256 index) {
|
|||||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||||
*/
|
*/
|
||||||
rule pushBack(bytes32 value) {
|
rule pushBack(bytes32 value) {
|
||||||
require boundedQueue();
|
|
||||||
|
|
||||||
uint256 lengthBefore = length();
|
uint256 lengthBefore = length();
|
||||||
|
bool fullBefore = full();
|
||||||
|
|
||||||
pushBack@withrevert(value);
|
pushBack@withrevert(value);
|
||||||
|
bool success = !lastReverted;
|
||||||
|
|
||||||
// liveness
|
// liveness
|
||||||
assert !lastReverted, "never reverts";
|
assert success <=> !fullBefore, "never revert if not previously full";
|
||||||
|
|
||||||
// effect
|
// effect
|
||||||
assert back() == value, "back set to value";
|
assert success => back() == value, "back set to value";
|
||||||
assert length() == lengthBefore + 1, "queue increased";
|
assert success => to_mathint(length()) == lengthBefore + 1, "queue increased";
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -151,8 +107,6 @@ rule pushBack(bytes32 value) {
|
|||||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||||
*/
|
*/
|
||||||
rule pushBackConsistency(uint256 index) {
|
rule pushBackConsistency(uint256 index) {
|
||||||
require boundedQueue();
|
|
||||||
|
|
||||||
bytes32 beforeAt = at_(index);
|
bytes32 beforeAt = at_(index);
|
||||||
|
|
||||||
bytes32 value;
|
bytes32 value;
|
||||||
@ -171,9 +125,6 @@ rule pushBackConsistency(uint256 index) {
|
|||||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||||
*/
|
*/
|
||||||
rule popFront {
|
rule popFront {
|
||||||
requireInvariant boundariesConsistency();
|
|
||||||
require boundedQueue();
|
|
||||||
|
|
||||||
uint256 lengthBefore = length();
|
uint256 lengthBefore = length();
|
||||||
bytes32 frontBefore = front@withrevert();
|
bytes32 frontBefore = front@withrevert();
|
||||||
|
|
||||||
@ -185,7 +136,7 @@ rule popFront {
|
|||||||
|
|
||||||
// effect
|
// effect
|
||||||
assert success => frontBefore == popped, "previous front is returned";
|
assert success => frontBefore == popped, "previous front is returned";
|
||||||
assert success => length() == lengthBefore - 1, "queue decreased";
|
assert success => to_mathint(length()) == lengthBefore - 1, "queue decreased";
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -194,9 +145,6 @@ rule popFront {
|
|||||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||||
*/
|
*/
|
||||||
rule popFrontConsistency(uint256 index) {
|
rule popFrontConsistency(uint256 index) {
|
||||||
requireInvariant boundariesConsistency();
|
|
||||||
require boundedQueue();
|
|
||||||
|
|
||||||
// Read (any) value that is not the front (this asserts the value exists / the queue is long enough)
|
// Read (any) value that is not the front (this asserts the value exists / the queue is long enough)
|
||||||
require index > 1;
|
require index > 1;
|
||||||
bytes32 before = at_(index);
|
bytes32 before = at_(index);
|
||||||
@ -204,7 +152,7 @@ rule popFrontConsistency(uint256 index) {
|
|||||||
popFront();
|
popFront();
|
||||||
|
|
||||||
// try to read value
|
// try to read value
|
||||||
bytes32 after = at_@withrevert(index - 1);
|
bytes32 after = at_@withrevert(require_uint256(index - 1));
|
||||||
|
|
||||||
assert !lastReverted, "value still exists in the queue";
|
assert !lastReverted, "value still exists in the queue";
|
||||||
assert before == after, "values are offset and not modified";
|
assert before == after, "values are offset and not modified";
|
||||||
@ -216,9 +164,6 @@ rule popFrontConsistency(uint256 index) {
|
|||||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||||
*/
|
*/
|
||||||
rule popBack {
|
rule popBack {
|
||||||
requireInvariant boundariesConsistency();
|
|
||||||
require boundedQueue();
|
|
||||||
|
|
||||||
uint256 lengthBefore = length();
|
uint256 lengthBefore = length();
|
||||||
bytes32 backBefore = back@withrevert();
|
bytes32 backBefore = back@withrevert();
|
||||||
|
|
||||||
@ -230,7 +175,7 @@ rule popBack {
|
|||||||
|
|
||||||
// effect
|
// effect
|
||||||
assert success => backBefore == popped, "previous back is returned";
|
assert success => backBefore == popped, "previous back is returned";
|
||||||
assert success => length() == lengthBefore - 1, "queue decreased";
|
assert success => to_mathint(length()) == lengthBefore - 1, "queue decreased";
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -239,11 +184,8 @@ rule popBack {
|
|||||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||||
*/
|
*/
|
||||||
rule popBackConsistency(uint256 index) {
|
rule popBackConsistency(uint256 index) {
|
||||||
requireInvariant boundariesConsistency();
|
|
||||||
require boundedQueue();
|
|
||||||
|
|
||||||
// Read (any) value that is not the back (this asserts the value exists / the queue is long enough)
|
// Read (any) value that is not the back (this asserts the value exists / the queue is long enough)
|
||||||
require index < length() - 1;
|
require to_mathint(index) < length() - 1;
|
||||||
bytes32 before = at_(index);
|
bytes32 before = at_(index);
|
||||||
|
|
||||||
popBack();
|
popBack();
|
||||||
@ -275,24 +217,25 @@ rule clear {
|
|||||||
│ Rule: front/back access reverts only if the queue is empty or querying out of bounds │
|
│ Rule: front/back access reverts only if the queue is empty or querying out of bounds │
|
||||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||||
*/
|
*/
|
||||||
rule onlyEmptyRevert(env e) {
|
rule onlyEmptyOrFullRevert(env e) {
|
||||||
require nonpayable(e);
|
require nonpayable(e);
|
||||||
requireInvariant boundariesConsistency();
|
|
||||||
require boundedQueue();
|
|
||||||
|
|
||||||
method f;
|
method f;
|
||||||
calldataarg args;
|
calldataarg args;
|
||||||
|
|
||||||
bool emptyBefore = empty();
|
bool emptyBefore = empty();
|
||||||
|
bool fullBefore = full();
|
||||||
|
|
||||||
f@withrevert(e, args);
|
f@withrevert(e, args);
|
||||||
|
|
||||||
assert lastReverted => (
|
assert lastReverted => (
|
||||||
(f.selector == front().selector && emptyBefore) ||
|
(f.selector == sig:front().selector && emptyBefore) ||
|
||||||
(f.selector == back().selector && emptyBefore) ||
|
(f.selector == sig:back().selector && emptyBefore) ||
|
||||||
(f.selector == popFront().selector && emptyBefore) ||
|
(f.selector == sig:popFront().selector && emptyBefore) ||
|
||||||
(f.selector == popBack().selector && emptyBefore) ||
|
(f.selector == sig:popBack().selector && emptyBefore) ||
|
||||||
f.selector == at_(uint256).selector // revert conditions are verified in onlyOutOfBoundsRevert
|
(f.selector == sig:pushFront(bytes32).selector && fullBefore ) ||
|
||||||
|
(f.selector == sig:pushBack(bytes32).selector && fullBefore ) ||
|
||||||
|
f.selector == sig:at_(uint256).selector // revert conditions are verified in onlyOutOfBoundsRevert
|
||||||
), "only revert if empty or out of bounds";
|
), "only revert if empty or out of bounds";
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -302,9 +245,6 @@ rule onlyEmptyRevert(env e) {
|
|||||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||||
*/
|
*/
|
||||||
rule onlyOutOfBoundsRevert(uint256 index) {
|
rule onlyOutOfBoundsRevert(uint256 index) {
|
||||||
requireInvariant boundariesConsistency();
|
|
||||||
require boundedQueue();
|
|
||||||
|
|
||||||
at_@withrevert(index);
|
at_@withrevert(index);
|
||||||
|
|
||||||
assert lastReverted <=> index >= length(), "only reverts if index is out of bounds";
|
assert lastReverted <=> index >= length(), "only reverts if index is out of bounds";
|
||||||
@ -316,9 +256,6 @@ rule onlyOutOfBoundsRevert(uint256 index) {
|
|||||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||||
*/
|
*/
|
||||||
rule noLengthChange(env e) {
|
rule noLengthChange(env e) {
|
||||||
requireInvariant boundariesConsistency();
|
|
||||||
require boundedQueue();
|
|
||||||
|
|
||||||
method f;
|
method f;
|
||||||
calldataarg args;
|
calldataarg args;
|
||||||
|
|
||||||
@ -327,11 +264,11 @@ rule noLengthChange(env e) {
|
|||||||
uint256 lengthAfter = length();
|
uint256 lengthAfter = length();
|
||||||
|
|
||||||
assert lengthAfter != lengthBefore => (
|
assert lengthAfter != lengthBefore => (
|
||||||
(f.selector == pushFront(bytes32).selector && lengthAfter == lengthBefore + 1) ||
|
(f.selector == sig:pushFront(bytes32).selector && to_mathint(lengthAfter) == lengthBefore + 1) ||
|
||||||
(f.selector == pushBack(bytes32).selector && lengthAfter == lengthBefore + 1) ||
|
(f.selector == sig:pushBack(bytes32).selector && to_mathint(lengthAfter) == lengthBefore + 1) ||
|
||||||
(f.selector == popBack().selector && lengthAfter == lengthBefore - 1) ||
|
(f.selector == sig:popBack().selector && to_mathint(lengthAfter) == lengthBefore - 1) ||
|
||||||
(f.selector == popFront().selector && lengthAfter == lengthBefore - 1) ||
|
(f.selector == sig:popFront().selector && to_mathint(lengthAfter) == lengthBefore - 1) ||
|
||||||
(f.selector == clear().selector && lengthAfter == 0)
|
(f.selector == sig:clear().selector && lengthAfter == 0)
|
||||||
), "length is only affected by clear/pop/push operations";
|
), "length is only affected by clear/pop/push operations";
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -341,9 +278,6 @@ rule noLengthChange(env e) {
|
|||||||
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||||
*/
|
*/
|
||||||
rule noDataChange(env e) {
|
rule noDataChange(env e) {
|
||||||
requireInvariant boundariesConsistency();
|
|
||||||
require boundedQueue();
|
|
||||||
|
|
||||||
method f;
|
method f;
|
||||||
calldataarg args;
|
calldataarg args;
|
||||||
|
|
||||||
@ -354,13 +288,13 @@ rule noDataChange(env e) {
|
|||||||
bool atAfterSuccess = !lastReverted;
|
bool atAfterSuccess = !lastReverted;
|
||||||
|
|
||||||
assert !atAfterSuccess <=> (
|
assert !atAfterSuccess <=> (
|
||||||
f.selector == clear().selector ||
|
(f.selector == sig:clear().selector ) ||
|
||||||
(f.selector == popBack().selector && index == length()) ||
|
(f.selector == sig:popBack().selector && index == length()) ||
|
||||||
(f.selector == popFront().selector && index == length())
|
(f.selector == sig:popFront().selector && index == length())
|
||||||
), "indexes of the queue are only removed by clear or pop";
|
), "indexes of the queue are only removed by clear or pop";
|
||||||
|
|
||||||
assert atAfterSuccess && atAfter != atBefore => (
|
assert atAfterSuccess && atAfter != atBefore => (
|
||||||
f.selector == popFront().selector ||
|
f.selector == sig:popFront().selector ||
|
||||||
f.selector == pushFront(bytes32).selector
|
f.selector == sig:pushFront(bytes32).selector
|
||||||
), "values of the queue are only changed by popFront or pushFront";
|
), "values of the queue are only changed by popFront or pushFront";
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,9 +2,6 @@
|
|||||||
definition nonpayable(env e) returns bool = e.msg.value == 0;
|
definition nonpayable(env e) returns bool = e.msg.value == 0;
|
||||||
definition nonzerosender(env e) returns bool = e.msg.sender != 0;
|
definition nonzerosender(env e) returns bool = e.msg.sender != 0;
|
||||||
|
|
||||||
// constants
|
|
||||||
definition max_uint48() returns mathint = (1 << 48) - 1;
|
|
||||||
|
|
||||||
// math
|
// math
|
||||||
definition min(mathint a, mathint b) returns mathint = a < b ? a : b;
|
definition min(mathint a, mathint b) returns mathint = a < b ? a : b;
|
||||||
definition max(mathint a, mathint b) returns mathint = a > b ? a : b;
|
definition max(mathint a, mathint b) returns mathint = a > b ? a : b;
|
||||||
|
|||||||
@ -28,25 +28,15 @@ contract ERC721ConsecutiveEnumerableMock is ERC721Consecutive, ERC721Enumerable
|
|||||||
return super._ownerOf(tokenId);
|
return super._ownerOf(tokenId);
|
||||||
}
|
}
|
||||||
|
|
||||||
function _mint(address to, uint256 tokenId) internal virtual override(ERC721, ERC721Consecutive) {
|
function _update(
|
||||||
super._mint(to, tokenId);
|
address to,
|
||||||
|
uint256 tokenId,
|
||||||
|
address auth
|
||||||
|
) internal virtual override(ERC721Consecutive, ERC721Enumerable) returns (address) {
|
||||||
|
return super._update(to, tokenId, auth);
|
||||||
}
|
}
|
||||||
|
|
||||||
function _beforeTokenTransfer(
|
function _increaseBalance(address account, uint128 amount) internal virtual override(ERC721, ERC721Enumerable) {
|
||||||
address from,
|
super._increaseBalance(account, amount);
|
||||||
address to,
|
|
||||||
uint256 firstTokenId,
|
|
||||||
uint256 batchSize
|
|
||||||
) internal virtual override(ERC721, ERC721Enumerable) {
|
|
||||||
super._beforeTokenTransfer(from, to, firstTokenId, batchSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
function _afterTokenTransfer(
|
|
||||||
address from,
|
|
||||||
address to,
|
|
||||||
uint256 firstTokenId,
|
|
||||||
uint256 batchSize
|
|
||||||
) internal virtual override(ERC721, ERC721Consecutive) {
|
|
||||||
super._afterTokenTransfer(from, to, firstTokenId, batchSize);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -41,26 +41,16 @@ contract ERC721ConsecutiveMock is ERC721Consecutive, ERC721Pausable, ERC721Votes
|
|||||||
return super._ownerOf(tokenId);
|
return super._ownerOf(tokenId);
|
||||||
}
|
}
|
||||||
|
|
||||||
function _mint(address to, uint256 tokenId) internal virtual override(ERC721, ERC721Consecutive) {
|
function _update(
|
||||||
super._mint(to, tokenId);
|
address to,
|
||||||
|
uint256 tokenId,
|
||||||
|
address auth
|
||||||
|
) internal virtual override(ERC721Consecutive, ERC721Pausable, ERC721Votes) returns (address) {
|
||||||
|
return super._update(to, tokenId, auth);
|
||||||
}
|
}
|
||||||
|
|
||||||
function _beforeTokenTransfer(
|
function _increaseBalance(address account, uint128 amount) internal virtual override(ERC721, ERC721Votes) {
|
||||||
address from,
|
super._increaseBalance(account, amount);
|
||||||
address to,
|
|
||||||
uint256 firstTokenId,
|
|
||||||
uint256 batchSize
|
|
||||||
) internal virtual override(ERC721, ERC721Pausable) {
|
|
||||||
super._beforeTokenTransfer(from, to, firstTokenId, batchSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
function _afterTokenTransfer(
|
|
||||||
address from,
|
|
||||||
address to,
|
|
||||||
uint256 firstTokenId,
|
|
||||||
uint256 batchSize
|
|
||||||
) internal virtual override(ERC721, ERC721Votes, ERC721Consecutive) {
|
|
||||||
super._afterTokenTransfer(from, to, firstTokenId, batchSize);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -113,16 +113,7 @@ abstract contract ERC721 is Context, ERC165, IERC721, IERC721Metadata, IERC721Er
|
|||||||
* @dev See {IERC721-approve}.
|
* @dev See {IERC721-approve}.
|
||||||
*/
|
*/
|
||||||
function approve(address to, uint256 tokenId) public virtual {
|
function approve(address to, uint256 tokenId) public virtual {
|
||||||
address owner = ownerOf(tokenId);
|
_approve(to, tokenId, _msgSender());
|
||||||
if (to == owner) {
|
|
||||||
revert ERC721InvalidOperator(owner);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_msgSender() != owner && !isApprovedForAll(owner, _msgSender())) {
|
|
||||||
revert ERC721InvalidApprover(_msgSender());
|
|
||||||
}
|
|
||||||
|
|
||||||
_approve(to, tokenId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -131,7 +122,7 @@ abstract contract ERC721 is Context, ERC165, IERC721, IERC721Metadata, IERC721Er
|
|||||||
function getApproved(uint256 tokenId) public view virtual returns (address) {
|
function getApproved(uint256 tokenId) public view virtual returns (address) {
|
||||||
_requireMinted(tokenId);
|
_requireMinted(tokenId);
|
||||||
|
|
||||||
return _tokenApprovals[tokenId];
|
return _getApproved(tokenId);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -152,17 +143,21 @@ abstract contract ERC721 is Context, ERC165, IERC721, IERC721Metadata, IERC721Er
|
|||||||
* @dev See {IERC721-transferFrom}.
|
* @dev See {IERC721-transferFrom}.
|
||||||
*/
|
*/
|
||||||
function transferFrom(address from, address to, uint256 tokenId) public virtual {
|
function transferFrom(address from, address to, uint256 tokenId) public virtual {
|
||||||
if (!_isApprovedOrOwner(_msgSender(), tokenId)) {
|
if (to == address(0)) {
|
||||||
revert ERC721InsufficientApproval(_msgSender(), tokenId);
|
revert ERC721InvalidReceiver(address(0));
|
||||||
|
}
|
||||||
|
// Setting an "auth" arguments enables the `_isApproved` check which verifies that the token exists
|
||||||
|
// (from != 0). Therefore, it is not needed to verify that the return value is not 0 here.
|
||||||
|
address previousOwner = _update(to, tokenId, _msgSender());
|
||||||
|
if (previousOwner != from) {
|
||||||
|
revert ERC721IncorrectOwner(from, tokenId, previousOwner);
|
||||||
}
|
}
|
||||||
|
|
||||||
_transfer(from, to, tokenId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @dev See {IERC721-safeTransferFrom}.
|
* @dev See {IERC721-safeTransferFrom}.
|
||||||
*/
|
*/
|
||||||
function safeTransferFrom(address from, address to, uint256 tokenId) public virtual {
|
function safeTransferFrom(address from, address to, uint256 tokenId) public {
|
||||||
safeTransferFrom(from, to, tokenId, "");
|
safeTransferFrom(from, to, tokenId, "");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -170,93 +165,114 @@ abstract contract ERC721 is Context, ERC165, IERC721, IERC721Metadata, IERC721Er
|
|||||||
* @dev See {IERC721-safeTransferFrom}.
|
* @dev See {IERC721-safeTransferFrom}.
|
||||||
*/
|
*/
|
||||||
function safeTransferFrom(address from, address to, uint256 tokenId, bytes memory data) public virtual {
|
function safeTransferFrom(address from, address to, uint256 tokenId, bytes memory data) public virtual {
|
||||||
if (!_isApprovedOrOwner(_msgSender(), tokenId)) {
|
transferFrom(from, to, tokenId);
|
||||||
revert ERC721InsufficientApproval(_msgSender(), tokenId);
|
_checkOnERC721Received(from, to, tokenId, data);
|
||||||
}
|
|
||||||
_safeTransfer(from, to, tokenId, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients
|
|
||||||
* are aware of the ERC721 protocol to prevent tokens from being forever locked.
|
|
||||||
*
|
|
||||||
* `data` is additional data, it has no specified format and it is sent in call to `to`.
|
|
||||||
*
|
|
||||||
* This internal function is equivalent to {safeTransferFrom}, and can be used to e.g.
|
|
||||||
* implement alternative mechanisms to perform token transfer, such as signature-based.
|
|
||||||
*
|
|
||||||
* Requirements:
|
|
||||||
*
|
|
||||||
* - `from` cannot be the zero address.
|
|
||||||
* - `to` cannot be the zero address.
|
|
||||||
* - `tokenId` token must exist and be owned by `from`.
|
|
||||||
* - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
|
|
||||||
*
|
|
||||||
* Emits a {Transfer} event.
|
|
||||||
*/
|
|
||||||
function _safeTransfer(address from, address to, uint256 tokenId, bytes memory data) internal virtual {
|
|
||||||
_transfer(from, to, tokenId);
|
|
||||||
if (!_checkOnERC721Received(from, to, tokenId, data)) {
|
|
||||||
revert ERC721InvalidReceiver(to);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @dev Returns the owner of the `tokenId`. Does NOT revert if token doesn't exist
|
* @dev Returns the owner of the `tokenId`. Does NOT revert if token doesn't exist
|
||||||
|
*
|
||||||
|
* IMPORTANT: Any overrides to this function that add ownership of tokens not tracked by the
|
||||||
|
* core ERC721 logic MUST be matched with the use of {_increaseBalance} to keep balances
|
||||||
|
* consistent with ownership. The invariant to preserve is that for any address `a` the value returned by
|
||||||
|
* `balanceOf(a)` must be equal to the number of tokens such that `_ownerOf(tokenId)` is `a`.
|
||||||
*/
|
*/
|
||||||
function _ownerOf(uint256 tokenId) internal view virtual returns (address) {
|
function _ownerOf(uint256 tokenId) internal view virtual returns (address) {
|
||||||
return _owners[tokenId];
|
return _owners[tokenId];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @dev Returns whether `tokenId` exists.
|
* @dev Returns the approved address for `tokenId`. Returns 0 if `tokenId` is not minted.
|
||||||
*
|
|
||||||
* Tokens can be managed by their owner or approved accounts via {approve} or {setApprovalForAll}.
|
|
||||||
*
|
|
||||||
* Tokens start existing when they are minted (`_mint`),
|
|
||||||
* and stop existing when they are burned (`_burn`).
|
|
||||||
*/
|
*/
|
||||||
function _exists(uint256 tokenId) internal view virtual returns (bool) {
|
function _getApproved(uint256 tokenId) internal view virtual returns (address) {
|
||||||
return _ownerOf(tokenId) != address(0);
|
return _tokenApprovals[tokenId];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @dev Returns whether `spender` is allowed to manage `tokenId`.
|
* @dev Returns whether `spender` is allowed to manage `owner`'s tokens, or `tokenId` in
|
||||||
|
* particular (ignoring whether it is owned by `owner`).
|
||||||
*
|
*
|
||||||
* Requirements:
|
* WARNING: This function doesn't check that `owner` is the actual owner of `tokenId`.
|
||||||
*
|
|
||||||
* - `tokenId` must exist.
|
|
||||||
*/
|
*/
|
||||||
function _isApprovedOrOwner(address spender, uint256 tokenId) internal view virtual returns (bool) {
|
function _isAuthorized(address owner, address spender, uint256 tokenId) internal view virtual returns (bool) {
|
||||||
address owner = ownerOf(tokenId);
|
return
|
||||||
return (spender == owner || isApprovedForAll(owner, spender) || getApproved(tokenId) == spender);
|
spender != address(0) &&
|
||||||
|
(owner == spender || isApprovedForAll(owner, spender) || _getApproved(tokenId) == spender);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @dev Safely mints `tokenId` and transfers it to `to`.
|
* @dev Checks if `spender` can operate on `tokenId`, assuming the provided `owner` is the actual owner.
|
||||||
|
* Reverts if `spender` has not approval for all assets of the provided `owner` nor the actual owner approved the `spender` for the specific `tokenId`.
|
||||||
*
|
*
|
||||||
* Requirements:
|
* WARNING: This function relies on {_isAuthorized}, so it doesn't check whether `owner` is the
|
||||||
|
* actual owner of `tokenId`.
|
||||||
|
*/
|
||||||
|
function _checkAuthorized(address owner, address spender, uint256 tokenId) internal view virtual {
|
||||||
|
if (!_isAuthorized(owner, spender, tokenId)) {
|
||||||
|
if (owner == address(0)) {
|
||||||
|
revert ERC721NonexistentToken(tokenId);
|
||||||
|
} else {
|
||||||
|
revert ERC721InsufficientApproval(spender, tokenId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev Unsafe write access to the balances, used by extensions that "mint" tokens using an {ownerOf} override.
|
||||||
*
|
*
|
||||||
* - `tokenId` must not exist.
|
* NOTE: the value is limited to type(uint128).max. This protect against _balance overflow. It is unrealistic that
|
||||||
* - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
|
* a uint256 would ever overflow from increments when these increments are bounded to uint128 values.
|
||||||
|
*
|
||||||
|
* WARNING: Increasing an account's balance using this function tends to be paired with an override of the
|
||||||
|
* {_ownerOf} function to resolve the ownership of the corresponding tokens so that balances and ownership
|
||||||
|
* remain consistent with one another.
|
||||||
|
*/
|
||||||
|
function _increaseBalance(address account, uint128 value) internal virtual {
|
||||||
|
unchecked {
|
||||||
|
_balances[account] += value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev Transfers `tokenId` from its current owner to `to`, or alternatively mints (or burns) if the current owner
|
||||||
|
* (or `to`) is the zero address. Returns the owner of the `tokenId` before the update.
|
||||||
|
*
|
||||||
|
* The `auth` argument is optional. If the value passed is non 0, then this function will check that
|
||||||
|
* `auth` is either the owner of the token, or approved to operate on the token (by the owner).
|
||||||
*
|
*
|
||||||
* Emits a {Transfer} event.
|
* Emits a {Transfer} event.
|
||||||
|
*
|
||||||
|
* NOTE: If overriding this function in a way that tracks balances, see also {_increaseBalance}.
|
||||||
*/
|
*/
|
||||||
function _safeMint(address to, uint256 tokenId) internal virtual {
|
function _update(address to, uint256 tokenId, address auth) internal virtual returns (address) {
|
||||||
_safeMint(to, tokenId, "");
|
address from = _ownerOf(tokenId);
|
||||||
|
|
||||||
|
// Perform (optional) operator check
|
||||||
|
if (auth != address(0)) {
|
||||||
|
_checkAuthorized(from, auth, tokenId);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// Execute the update
|
||||||
* @dev Same as {xref-ERC721-_safeMint-address-uint256-}[`_safeMint`], with an additional `data` parameter which is
|
if (from != address(0)) {
|
||||||
* forwarded in {IERC721Receiver-onERC721Received} to contract recipients.
|
delete _tokenApprovals[tokenId];
|
||||||
*/
|
unchecked {
|
||||||
function _safeMint(address to, uint256 tokenId, bytes memory data) internal virtual {
|
_balances[from] -= 1;
|
||||||
_mint(to, tokenId);
|
|
||||||
if (!_checkOnERC721Received(address(0), to, tokenId, data)) {
|
|
||||||
revert ERC721InvalidReceiver(to);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (to != address(0)) {
|
||||||
|
unchecked {
|
||||||
|
_balances[to] += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_owners[tokenId] = to;
|
||||||
|
|
||||||
|
emit Transfer(from, to, tokenId);
|
||||||
|
|
||||||
|
return from;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @dev Mints `tokenId` and transfers it to `to`.
|
* @dev Mints `tokenId` and transfers it to `to`.
|
||||||
*
|
*
|
||||||
@ -269,34 +285,37 @@ abstract contract ERC721 is Context, ERC165, IERC721, IERC721Metadata, IERC721Er
|
|||||||
*
|
*
|
||||||
* Emits a {Transfer} event.
|
* Emits a {Transfer} event.
|
||||||
*/
|
*/
|
||||||
function _mint(address to, uint256 tokenId) internal virtual {
|
function _mint(address to, uint256 tokenId) internal {
|
||||||
if (to == address(0)) {
|
if (to == address(0)) {
|
||||||
revert ERC721InvalidReceiver(address(0));
|
revert ERC721InvalidReceiver(address(0));
|
||||||
}
|
}
|
||||||
if (_exists(tokenId)) {
|
address previousOwner = _update(to, tokenId, address(0));
|
||||||
|
if (previousOwner != address(0)) {
|
||||||
revert ERC721InvalidSender(address(0));
|
revert ERC721InvalidSender(address(0));
|
||||||
}
|
}
|
||||||
|
|
||||||
_beforeTokenTransfer(address(0), to, tokenId, 1);
|
|
||||||
|
|
||||||
// Check that tokenId was not minted by `_beforeTokenTransfer` hook
|
|
||||||
if (_exists(tokenId)) {
|
|
||||||
revert ERC721InvalidSender(address(0));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
unchecked {
|
/**
|
||||||
// Will not overflow unless all 2**256 token ids are minted to the same owner.
|
* @dev Mints `tokenId`, transfers it to `to` and checks for `to` acceptance.
|
||||||
// Given that tokens are minted one by one, it is impossible in practice that
|
*
|
||||||
// this ever happens. Might change if we allow batch minting.
|
* Requirements:
|
||||||
// The ERC fails to describe this case.
|
*
|
||||||
_balances[to] += 1;
|
* - `tokenId` must not exist.
|
||||||
|
* - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
|
||||||
|
*
|
||||||
|
* Emits a {Transfer} event.
|
||||||
|
*/
|
||||||
|
function _safeMint(address to, uint256 tokenId) internal {
|
||||||
|
_safeMint(to, tokenId, "");
|
||||||
}
|
}
|
||||||
|
|
||||||
_owners[tokenId] = to;
|
/**
|
||||||
|
* @dev Same as {xref-ERC721-_safeMint-address-uint256-}[`_safeMint`], with an additional `data` parameter which is
|
||||||
emit Transfer(address(0), to, tokenId);
|
* forwarded in {IERC721Receiver-onERC721Received} to contract recipients.
|
||||||
|
*/
|
||||||
_afterTokenTransfer(address(0), to, tokenId, 1);
|
function _safeMint(address to, uint256 tokenId, bytes memory data) internal virtual {
|
||||||
|
_mint(to, tokenId);
|
||||||
|
_checkOnERC721Received(address(0), to, tokenId, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -310,26 +329,11 @@ abstract contract ERC721 is Context, ERC165, IERC721, IERC721Metadata, IERC721Er
|
|||||||
*
|
*
|
||||||
* Emits a {Transfer} event.
|
* Emits a {Transfer} event.
|
||||||
*/
|
*/
|
||||||
function _burn(uint256 tokenId) internal virtual {
|
function _burn(uint256 tokenId) internal {
|
||||||
address owner = ownerOf(tokenId);
|
address previousOwner = _update(address(0), tokenId, address(0));
|
||||||
|
if (previousOwner == address(0)) {
|
||||||
_beforeTokenTransfer(owner, address(0), tokenId, 1);
|
revert ERC721NonexistentToken(tokenId);
|
||||||
|
}
|
||||||
// Update ownership in case tokenId was transferred by `_beforeTokenTransfer` hook
|
|
||||||
owner = ownerOf(tokenId);
|
|
||||||
|
|
||||||
// Clear approvals
|
|
||||||
delete _tokenApprovals[tokenId];
|
|
||||||
|
|
||||||
// Decrease balance with checked arithmetic, because an `ownerOf` override may
|
|
||||||
// invalidate the assumption that `_balances[from] >= 1`.
|
|
||||||
_balances[owner] -= 1;
|
|
||||||
|
|
||||||
delete _owners[tokenId];
|
|
||||||
|
|
||||||
emit Transfer(owner, address(0), tokenId);
|
|
||||||
|
|
||||||
_afterTokenTransfer(owner, address(0), tokenId, 1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -339,65 +343,85 @@ abstract contract ERC721 is Context, ERC165, IERC721, IERC721Metadata, IERC721Er
|
|||||||
* Requirements:
|
* Requirements:
|
||||||
*
|
*
|
||||||
* - `to` cannot be the zero address.
|
* - `to` cannot be the zero address.
|
||||||
* - `tokenId` token must be owned by `from`.
|
* - `tokenId` token must exists and be owned by `from`.
|
||||||
*
|
*
|
||||||
* Emits a {Transfer} event.
|
* Emits a {Transfer} event.
|
||||||
*/
|
*/
|
||||||
function _transfer(address from, address to, uint256 tokenId) internal virtual {
|
function _transfer(address from, address to, uint256 tokenId) internal {
|
||||||
address owner = ownerOf(tokenId);
|
|
||||||
if (owner != from) {
|
|
||||||
revert ERC721IncorrectOwner(from, tokenId, owner);
|
|
||||||
}
|
|
||||||
if (to == address(0)) {
|
if (to == address(0)) {
|
||||||
revert ERC721InvalidReceiver(address(0));
|
revert ERC721InvalidReceiver(address(0));
|
||||||
}
|
}
|
||||||
|
address previousOwner = _update(to, tokenId, address(0));
|
||||||
_beforeTokenTransfer(from, to, tokenId, 1);
|
if (previousOwner == address(0)) {
|
||||||
|
revert ERC721NonexistentToken(tokenId);
|
||||||
// Check that tokenId was not transferred by `_beforeTokenTransfer` hook
|
} else if (previousOwner != from) {
|
||||||
owner = ownerOf(tokenId);
|
revert ERC721IncorrectOwner(from, tokenId, previousOwner);
|
||||||
if (owner != from) {
|
}
|
||||||
revert ERC721IncorrectOwner(from, tokenId, owner);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear approvals from the previous owner
|
/**
|
||||||
delete _tokenApprovals[tokenId];
|
* @dev Safely transfers `tokenId` token from `from` to `to`, checking that contract recipients
|
||||||
|
* are aware of the ERC721 standard to prevent tokens from being forever locked.
|
||||||
// Decrease balance with checked arithmetic, because an `ownerOf` override may
|
*
|
||||||
// invalidate the assumption that `_balances[from] >= 1`.
|
* `data` is additional data, it has no specified format and it is sent in call to `to`.
|
||||||
_balances[from] -= 1;
|
*
|
||||||
|
* This internal function is like {safeTransferFrom} in the sense that it invokes
|
||||||
unchecked {
|
* {IERC721Receiver-onERC721Received} on the receiver, and can be used to e.g.
|
||||||
// `_balances[to]` could overflow in the conditions described in `_mint`. That would require
|
* implement alternative mechanisms to perform token transfer, such as signature-based.
|
||||||
// all 2**256 token ids to be minted, which in practice is impossible.
|
*
|
||||||
_balances[to] += 1;
|
* Requirements:
|
||||||
|
*
|
||||||
|
* - `tokenId` token must exist and be owned by `from`.
|
||||||
|
* - `to` cannot be the zero address.
|
||||||
|
* - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
|
||||||
|
*
|
||||||
|
* Emits a {Transfer} event.
|
||||||
|
*/
|
||||||
|
function _safeTransfer(address from, address to, uint256 tokenId) internal {
|
||||||
|
_safeTransfer(from, to, tokenId, "");
|
||||||
}
|
}
|
||||||
|
|
||||||
_owners[tokenId] = to;
|
/**
|
||||||
|
* @dev Same as {xref-ERC721-_safeTransfer-address-address-uint256-}[`_safeTransfer`], with an additional `data` parameter which is
|
||||||
emit Transfer(from, to, tokenId);
|
* forwarded in {IERC721Receiver-onERC721Received} to contract recipients.
|
||||||
|
*/
|
||||||
_afterTokenTransfer(from, to, tokenId, 1);
|
function _safeTransfer(address from, address to, uint256 tokenId, bytes memory data) internal virtual {
|
||||||
|
_transfer(from, to, tokenId);
|
||||||
|
_checkOnERC721Received(from, to, tokenId, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @dev Approve `to` to operate on `tokenId`
|
* @dev Approve `to` to operate on `tokenId`
|
||||||
*
|
*
|
||||||
|
* The `auth` argument is optional. If the value passed is non 0, then this function will check that `auth` is
|
||||||
|
* either the owner of the token, or approved to operate on all tokens held by this owner.
|
||||||
|
*
|
||||||
* Emits an {Approval} event.
|
* Emits an {Approval} event.
|
||||||
*/
|
*/
|
||||||
function _approve(address to, uint256 tokenId) internal virtual {
|
function _approve(address to, uint256 tokenId, address auth) internal virtual returns (address) {
|
||||||
|
address owner = ownerOf(tokenId);
|
||||||
|
|
||||||
|
if (auth != address(0) && owner != auth && !isApprovedForAll(owner, auth)) {
|
||||||
|
revert ERC721InvalidApprover(auth);
|
||||||
|
}
|
||||||
|
|
||||||
_tokenApprovals[tokenId] = to;
|
_tokenApprovals[tokenId] = to;
|
||||||
emit Approval(ownerOf(tokenId), to, tokenId);
|
emit Approval(owner, to, tokenId);
|
||||||
|
|
||||||
|
return owner;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @dev Approve `operator` to operate on all of `owner` tokens
|
* @dev Approve `operator` to operate on all of `owner` tokens
|
||||||
*
|
*
|
||||||
|
* Requirements:
|
||||||
|
* - operator can't be the address zero.
|
||||||
|
*
|
||||||
* Emits an {ApprovalForAll} event.
|
* Emits an {ApprovalForAll} event.
|
||||||
*/
|
*/
|
||||||
function _setApprovalForAll(address owner, address operator, bool approved) internal virtual {
|
function _setApprovalForAll(address owner, address operator, bool approved) internal virtual {
|
||||||
if (owner == operator) {
|
if (operator == address(0)) {
|
||||||
revert ERC721InvalidOperator(owner);
|
revert ERC721InvalidOperator(operator);
|
||||||
}
|
}
|
||||||
_operatorApprovals[owner][operator] = approved;
|
_operatorApprovals[owner][operator] = approved;
|
||||||
emit ApprovalForAll(owner, operator, approved);
|
emit ApprovalForAll(owner, operator, approved);
|
||||||
@ -407,30 +431,26 @@ abstract contract ERC721 is Context, ERC165, IERC721, IERC721Metadata, IERC721Er
|
|||||||
* @dev Reverts if the `tokenId` has not been minted yet.
|
* @dev Reverts if the `tokenId` has not been minted yet.
|
||||||
*/
|
*/
|
||||||
function _requireMinted(uint256 tokenId) internal view virtual {
|
function _requireMinted(uint256 tokenId) internal view virtual {
|
||||||
if (!_exists(tokenId)) {
|
if (_ownerOf(tokenId) == address(0)) {
|
||||||
revert ERC721NonexistentToken(tokenId);
|
revert ERC721NonexistentToken(tokenId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @dev Private function to invoke {IERC721Receiver-onERC721Received} on a target address.
|
* @dev Private function to invoke {IERC721Receiver-onERC721Received} on a target address. This will revert if the
|
||||||
* The call is not executed if the target address is not a contract.
|
* recipient doesn't accept the token transfer. The call is not executed if the target address is not a contract.
|
||||||
*
|
*
|
||||||
* @param from address representing the previous owner of the given token ID
|
* @param from address representing the previous owner of the given token ID
|
||||||
* @param to target address that will receive the tokens
|
* @param to target address that will receive the tokens
|
||||||
* @param tokenId uint256 ID of the token to be transferred
|
* @param tokenId uint256 ID of the token to be transferred
|
||||||
* @param data bytes optional data to send along with the call
|
* @param data bytes optional data to send along with the call
|
||||||
* @return bool whether the call correctly returned the expected magic value
|
|
||||||
*/
|
*/
|
||||||
function _checkOnERC721Received(
|
function _checkOnERC721Received(address from, address to, uint256 tokenId, bytes memory data) private {
|
||||||
address from,
|
|
||||||
address to,
|
|
||||||
uint256 tokenId,
|
|
||||||
bytes memory data
|
|
||||||
) private returns (bool) {
|
|
||||||
if (to.code.length > 0) {
|
if (to.code.length > 0) {
|
||||||
try IERC721Receiver(to).onERC721Received(_msgSender(), from, tokenId, data) returns (bytes4 retval) {
|
try IERC721Receiver(to).onERC721Received(_msgSender(), from, tokenId, data) returns (bytes4 retval) {
|
||||||
return retval == IERC721Receiver.onERC721Received.selector;
|
if (retval != IERC721Receiver.onERC721Received.selector) {
|
||||||
|
revert ERC721InvalidReceiver(to);
|
||||||
|
}
|
||||||
} catch (bytes memory reason) {
|
} catch (bytes memory reason) {
|
||||||
if (reason.length == 0) {
|
if (reason.length == 0) {
|
||||||
revert ERC721InvalidReceiver(to);
|
revert ERC721InvalidReceiver(to);
|
||||||
@ -441,52 +461,6 @@ abstract contract ERC721 is Context, ERC165, IERC721, IERC721Metadata, IERC721Er
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @dev Hook that is called before any token transfer. This includes minting and burning. If {ERC721Consecutive} is
|
|
||||||
* used, the hook may be called as part of a consecutive (batch) mint, as indicated by `batchSize` greater than 1.
|
|
||||||
*
|
|
||||||
* Calling conditions:
|
|
||||||
*
|
|
||||||
* - When `from` and `to` are both non-zero, ``from``'s tokens will be transferred to `to`.
|
|
||||||
* - When `from` is zero, the tokens will be minted for `to`.
|
|
||||||
* - When `to` is zero, ``from``'s tokens will be burned.
|
|
||||||
* - `from` and `to` are never both zero.
|
|
||||||
* - `batchSize` is non-zero.
|
|
||||||
*
|
|
||||||
* To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
|
|
||||||
*/
|
|
||||||
function _beforeTokenTransfer(address from, address to, uint256 firstTokenId, uint256 batchSize) internal virtual {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @dev Hook that is called after any token transfer. This includes minting and burning. If {ERC721Consecutive} is
|
|
||||||
* used, the hook may be called as part of a consecutive (batch) mint, as indicated by `batchSize` greater than 1.
|
|
||||||
*
|
|
||||||
* Calling conditions:
|
|
||||||
*
|
|
||||||
* - When `from` and `to` are both non-zero, ``from``'s tokens were transferred to `to`.
|
|
||||||
* - When `from` is zero, the tokens were minted for `to`.
|
|
||||||
* - When `to` is zero, ``from``'s tokens were burned.
|
|
||||||
* - `from` and `to` are never both zero.
|
|
||||||
* - `batchSize` is non-zero.
|
|
||||||
*
|
|
||||||
* To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
|
|
||||||
*/
|
|
||||||
function _afterTokenTransfer(address from, address to, uint256 firstTokenId, uint256 batchSize) internal virtual {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @dev Unsafe write access to the balances, used by extensions that "mint" tokens using an {ownerOf} override.
|
|
||||||
*
|
|
||||||
* WARNING: Anyone calling this MUST ensure that the balances remain consistent with the ownership. The invariant
|
|
||||||
* being that for any address `a` the value returned by `balanceOf(a)` must be equal to the number of tokens such
|
|
||||||
* that `ownerOf(tokenId)` is `a`.
|
|
||||||
*/
|
|
||||||
// solhint-disable-next-line func-name-mixedcase
|
|
||||||
function __unsafe_increaseBalance(address account, uint256 value) internal {
|
|
||||||
_balances[account] += value;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -108,7 +108,7 @@ interface IERC721 is IERC165 {
|
|||||||
*
|
*
|
||||||
* Requirements:
|
* Requirements:
|
||||||
*
|
*
|
||||||
* - The `operator` cannot be the caller.
|
* - The `operator` cannot be the address zero.
|
||||||
*
|
*
|
||||||
* Emits an {ApprovalForAll} event.
|
* Emits an {ApprovalForAll} event.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -19,9 +19,8 @@ abstract contract ERC721Burnable is Context, ERC721 {
|
|||||||
* - The caller must own `tokenId` or be an approved operator.
|
* - The caller must own `tokenId` or be an approved operator.
|
||||||
*/
|
*/
|
||||||
function burn(uint256 tokenId) public virtual {
|
function burn(uint256 tokenId) public virtual {
|
||||||
if (!_isApprovedOrOwner(_msgSender(), tokenId)) {
|
// Setting an "auth" arguments enables the `_isApproved` check which verifies that the token exists
|
||||||
revert ERC721InsufficientApproval(_msgSender(), tokenId);
|
// (from != 0). Therefore, it is not needed to verify that the return value is not 0 here.
|
||||||
}
|
_update(address(0), tokenId, _msgSender());
|
||||||
_burn(tokenId);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -118,61 +118,44 @@ abstract contract ERC721Consecutive is IERC2309, ERC721 {
|
|||||||
revert ERC721ExceededMaxBatchMint(batchSize, maxBatchSize);
|
revert ERC721ExceededMaxBatchMint(batchSize, maxBatchSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
// hook before
|
|
||||||
_beforeTokenTransfer(address(0), to, next, batchSize);
|
|
||||||
|
|
||||||
// push an ownership checkpoint & emit event
|
// push an ownership checkpoint & emit event
|
||||||
uint96 last = next + batchSize - 1;
|
uint96 last = next + batchSize - 1;
|
||||||
_sequentialOwnership.push(last, uint160(to));
|
_sequentialOwnership.push(last, uint160(to));
|
||||||
|
|
||||||
// The invariant required by this function is preserved because the new sequentialOwnership checkpoint
|
// The invariant required by this function is preserved because the new sequentialOwnership checkpoint
|
||||||
// is attributing ownership of `batchSize` new tokens to account `to`.
|
// is attributing ownership of `batchSize` new tokens to account `to`.
|
||||||
__unsafe_increaseBalance(to, batchSize);
|
_increaseBalance(to, batchSize);
|
||||||
|
|
||||||
emit ConsecutiveTransfer(next, last, address(0), to);
|
emit ConsecutiveTransfer(next, last, address(0), to);
|
||||||
|
|
||||||
// hook after
|
|
||||||
_afterTokenTransfer(address(0), to, next, batchSize);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return next;
|
return next;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @dev See {ERC721-_mint}. Override version that restricts normal minting to after construction.
|
* @dev See {ERC721-_update}. Override version that restricts normal minting to after construction.
|
||||||
*
|
*
|
||||||
* WARNING: Using {ERC721Consecutive} prevents using {_mint} during construction in favor of {_mintConsecutive}.
|
* WARNING: Using {ERC721Consecutive} prevents minting during construction in favor of {_mintConsecutive}.
|
||||||
* After construction, {_mintConsecutive} is no longer available and {_mint} becomes available.
|
* After construction, {_mintConsecutive} is no longer available and minting through {_update} becomes available.
|
||||||
*/
|
*/
|
||||||
function _mint(address to, uint256 tokenId) internal virtual override {
|
function _update(address to, uint256 tokenId, address auth) internal virtual override returns (address) {
|
||||||
if (address(this).code.length == 0) {
|
address previousOwner = super._update(to, tokenId, auth);
|
||||||
|
|
||||||
|
// only mint after construction
|
||||||
|
if (previousOwner == address(0) && address(this).code.length == 0) {
|
||||||
revert ERC721ForbiddenMint();
|
revert ERC721ForbiddenMint();
|
||||||
}
|
}
|
||||||
super._mint(to, tokenId);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
// record burn
|
||||||
* @dev See {ERC721-_afterTokenTransfer}. Burning of tokens that have been sequentially minted must be explicit.
|
|
||||||
*/
|
|
||||||
function _afterTokenTransfer(
|
|
||||||
address from,
|
|
||||||
address to,
|
|
||||||
uint256 firstTokenId,
|
|
||||||
uint256 batchSize
|
|
||||||
) internal virtual override {
|
|
||||||
if (
|
if (
|
||||||
to == address(0) && // if we burn
|
to == address(0) && // if we burn
|
||||||
firstTokenId >= _firstConsecutiveId() &&
|
tokenId < _nextConsecutiveId() && // and the tokenId was minted in a batch
|
||||||
firstTokenId < _nextConsecutiveId() &&
|
!_sequentialBurn.get(tokenId) // and the token was never marked as burnt
|
||||||
!_sequentialBurn.get(firstTokenId)
|
) {
|
||||||
) // and the token was never marked as burnt
|
_sequentialBurn.set(tokenId);
|
||||||
{
|
|
||||||
if (batchSize != 1) {
|
|
||||||
revert ERC721ForbiddenBatchBurn();
|
|
||||||
}
|
}
|
||||||
_sequentialBurn.set(firstTokenId);
|
|
||||||
}
|
return previousOwner;
|
||||||
super._afterTokenTransfer(from, to, firstTokenId, batchSize);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -74,33 +74,23 @@ abstract contract ERC721Enumerable is ERC721, IERC721Enumerable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @dev See {ERC721-_beforeTokenTransfer}.
|
* @dev See {ERC721-_update}.
|
||||||
*/
|
*/
|
||||||
function _beforeTokenTransfer(
|
function _update(address to, uint256 tokenId, address auth) internal virtual override returns (address) {
|
||||||
address from,
|
address previousOwner = super._update(to, tokenId, auth);
|
||||||
address to,
|
|
||||||
uint256 firstTokenId,
|
|
||||||
uint256 batchSize
|
|
||||||
) internal virtual override {
|
|
||||||
super._beforeTokenTransfer(from, to, firstTokenId, batchSize);
|
|
||||||
|
|
||||||
if (batchSize > 1) {
|
if (previousOwner == address(0)) {
|
||||||
// Will only trigger during construction. Batch transferring (minting) is not available afterwards.
|
|
||||||
revert ERC721EnumerableForbiddenBatchMint();
|
|
||||||
}
|
|
||||||
|
|
||||||
uint256 tokenId = firstTokenId;
|
|
||||||
|
|
||||||
if (from == address(0)) {
|
|
||||||
_addTokenToAllTokensEnumeration(tokenId);
|
_addTokenToAllTokensEnumeration(tokenId);
|
||||||
} else if (from != to) {
|
} else if (previousOwner != to) {
|
||||||
_removeTokenFromOwnerEnumeration(from, tokenId);
|
_removeTokenFromOwnerEnumeration(previousOwner, tokenId);
|
||||||
}
|
}
|
||||||
if (to == address(0)) {
|
if (to == address(0)) {
|
||||||
_removeTokenFromAllTokensEnumeration(tokenId);
|
_removeTokenFromAllTokensEnumeration(tokenId);
|
||||||
} else if (to != from) {
|
} else if (previousOwner != to) {
|
||||||
_addTokenToOwnerEnumeration(to, tokenId);
|
_addTokenToOwnerEnumeration(to, tokenId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return previousOwner;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -109,7 +99,7 @@ abstract contract ERC721Enumerable is ERC721, IERC721Enumerable {
|
|||||||
* @param tokenId uint256 ID of the token to be added to the tokens list of the given address
|
* @param tokenId uint256 ID of the token to be added to the tokens list of the given address
|
||||||
*/
|
*/
|
||||||
function _addTokenToOwnerEnumeration(address to, uint256 tokenId) private {
|
function _addTokenToOwnerEnumeration(address to, uint256 tokenId) private {
|
||||||
uint256 length = balanceOf(to);
|
uint256 length = balanceOf(to) - 1;
|
||||||
_ownedTokens[to][length] = tokenId;
|
_ownedTokens[to][length] = tokenId;
|
||||||
_ownedTokensIndex[tokenId] = length;
|
_ownedTokensIndex[tokenId] = length;
|
||||||
}
|
}
|
||||||
@ -135,7 +125,7 @@ abstract contract ERC721Enumerable is ERC721, IERC721Enumerable {
|
|||||||
// To prevent a gap in from's tokens array, we store the last token in the index of the token to delete, and
|
// To prevent a gap in from's tokens array, we store the last token in the index of the token to delete, and
|
||||||
// then delete the last slot (swap and pop).
|
// then delete the last slot (swap and pop).
|
||||||
|
|
||||||
uint256 lastTokenIndex = balanceOf(from) - 1;
|
uint256 lastTokenIndex = balanceOf(from);
|
||||||
uint256 tokenIndex = _ownedTokensIndex[tokenId];
|
uint256 tokenIndex = _ownedTokensIndex[tokenId];
|
||||||
|
|
||||||
// When the token to delete is the last token, the swap operation is unnecessary
|
// When the token to delete is the last token, the swap operation is unnecessary
|
||||||
@ -175,4 +165,14 @@ abstract contract ERC721Enumerable is ERC721, IERC721Enumerable {
|
|||||||
delete _allTokensIndex[tokenId];
|
delete _allTokensIndex[tokenId];
|
||||||
_allTokens.pop();
|
_allTokens.pop();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See {ERC721-_increaseBalance}. We need that to account tokens that were minted in batch
|
||||||
|
*/
|
||||||
|
function _increaseBalance(address account, uint128 amount) internal virtual override {
|
||||||
|
if (amount > 0) {
|
||||||
|
revert ERC721EnumerableForbiddenBatchMint();
|
||||||
|
}
|
||||||
|
super._increaseBalance(account, amount);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -21,20 +21,17 @@ import {Pausable} from "../../../security/Pausable.sol";
|
|||||||
*/
|
*/
|
||||||
abstract contract ERC721Pausable is ERC721, Pausable {
|
abstract contract ERC721Pausable is ERC721, Pausable {
|
||||||
/**
|
/**
|
||||||
* @dev See {ERC721-_beforeTokenTransfer}.
|
* @dev See {ERC721-_update}.
|
||||||
*
|
*
|
||||||
* Requirements:
|
* Requirements:
|
||||||
*
|
*
|
||||||
* - the contract must not be paused.
|
* - the contract must not be paused.
|
||||||
*/
|
*/
|
||||||
function _beforeTokenTransfer(
|
function _update(
|
||||||
address from,
|
|
||||||
address to,
|
address to,
|
||||||
uint256 firstTokenId,
|
uint256 tokenId,
|
||||||
uint256 batchSize
|
address auth
|
||||||
) internal virtual override {
|
) internal virtual override whenNotPaused returns (address) {
|
||||||
super._beforeTokenTransfer(from, to, firstTokenId, batchSize);
|
return super._update(to, tokenId, auth);
|
||||||
|
|
||||||
_requireNotPaused();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -26,10 +26,15 @@ abstract contract ERC721Royalty is ERC2981, ERC721 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @dev See {ERC721-_burn}. This override additionally clears the royalty information for the token.
|
* @dev See {ERC721-_update}. When burning, this override will additionally clear the royalty information for the token.
|
||||||
*/
|
*/
|
||||||
function _burn(uint256 tokenId) internal virtual override {
|
function _update(address to, uint256 tokenId, address auth) internal virtual override returns (address) {
|
||||||
super._burn(tokenId);
|
address previousOwner = super._update(to, tokenId, auth);
|
||||||
|
|
||||||
|
if (to == address(0)) {
|
||||||
_resetTokenRoyalty(tokenId);
|
_resetTokenRoyalty(tokenId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return previousOwner;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -55,7 +55,7 @@ abstract contract ERC721URIStorage is IERC4906, ERC721 {
|
|||||||
* - `tokenId` must exist.
|
* - `tokenId` must exist.
|
||||||
*/
|
*/
|
||||||
function _setTokenURI(uint256 tokenId, string memory _tokenURI) internal virtual {
|
function _setTokenURI(uint256 tokenId, string memory _tokenURI) internal virtual {
|
||||||
if (!_exists(tokenId)) {
|
if (_ownerOf(tokenId) == address(0)) {
|
||||||
revert ERC721NonexistentToken(tokenId);
|
revert ERC721NonexistentToken(tokenId);
|
||||||
}
|
}
|
||||||
_tokenURIs[tokenId] = _tokenURI;
|
_tokenURIs[tokenId] = _tokenURI;
|
||||||
@ -64,15 +64,17 @@ abstract contract ERC721URIStorage is IERC4906, ERC721 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @dev See {ERC721-_burn}. This override additionally checks to see if a
|
* @dev See {ERC721-_update}. When burning, this override will additionally check if a
|
||||||
* token-specific URI was set for the token, and if so, it deletes the token URI from
|
* token-specific URI was set for the token, and if so, it deletes the token URI from
|
||||||
* the storage mapping.
|
* the storage mapping.
|
||||||
*/
|
*/
|
||||||
function _burn(uint256 tokenId) internal virtual override {
|
function _update(address to, uint256 tokenId, address auth) internal virtual override returns (address) {
|
||||||
super._burn(tokenId);
|
address previousOwner = super._update(to, tokenId, auth);
|
||||||
|
|
||||||
if (bytes(_tokenURIs[tokenId]).length != 0) {
|
if (to == address(0) && bytes(_tokenURIs[tokenId]).length != 0) {
|
||||||
delete _tokenURIs[tokenId];
|
delete _tokenURIs[tokenId];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return previousOwner;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -16,18 +16,16 @@ import {Votes} from "../../../governance/utils/Votes.sol";
|
|||||||
*/
|
*/
|
||||||
abstract contract ERC721Votes is ERC721, Votes {
|
abstract contract ERC721Votes is ERC721, Votes {
|
||||||
/**
|
/**
|
||||||
* @dev See {ERC721-_afterTokenTransfer}. Adjusts votes when tokens are transferred.
|
* @dev See {ERC721-_update}. Adjusts votes when tokens are transferred.
|
||||||
*
|
*
|
||||||
* Emits a {IVotes-DelegateVotesChanged} event.
|
* Emits a {IVotes-DelegateVotesChanged} event.
|
||||||
*/
|
*/
|
||||||
function _afterTokenTransfer(
|
function _update(address to, uint256 tokenId, address auth) internal virtual override returns (address) {
|
||||||
address from,
|
address previousOwner = super._update(to, tokenId, auth);
|
||||||
address to,
|
|
||||||
uint256 firstTokenId,
|
_transferVotingUnits(previousOwner, to, 1);
|
||||||
uint256 batchSize
|
|
||||||
) internal virtual override {
|
return previousOwner;
|
||||||
_transferVotingUnits(from, to, batchSize);
|
|
||||||
super._afterTokenTransfer(from, to, firstTokenId, batchSize);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -38,4 +36,12 @@ abstract contract ERC721Votes is ERC721, Votes {
|
|||||||
function _getVotingUnits(address account) internal view virtual override returns (uint256) {
|
function _getVotingUnits(address account) internal view virtual override returns (uint256) {
|
||||||
return balanceOf(account);
|
return balanceOf(account);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev See {ERC721-_increaseBalance}. We need that to account tokens that were minted in batch.
|
||||||
|
*/
|
||||||
|
function _increaseBalance(address account, uint128 amount) internal virtual override {
|
||||||
|
super._increaseBalance(account, amount);
|
||||||
|
_transferVotingUnits(address(0), account, amount);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -50,10 +50,9 @@ abstract contract ERC721Wrapper is ERC721, IERC721Receiver {
|
|||||||
uint256 length = tokenIds.length;
|
uint256 length = tokenIds.length;
|
||||||
for (uint256 i = 0; i < length; ++i) {
|
for (uint256 i = 0; i < length; ++i) {
|
||||||
uint256 tokenId = tokenIds[i];
|
uint256 tokenId = tokenIds[i];
|
||||||
if (!_isApprovedOrOwner(_msgSender(), tokenId)) {
|
// Setting an "auth" arguments enables the `_isApproved` check which verifies that the token exists
|
||||||
revert ERC721InsufficientApproval(_msgSender(), tokenId);
|
// (from != 0). Therefore, it is not needed to verify that the return value is not 0 here.
|
||||||
}
|
_update(address(0), tokenId, _msgSender());
|
||||||
_burn(tokenId);
|
|
||||||
// Checks were already performed at this point, and there's no way to retake ownership or approval from
|
// Checks were already performed at this point, and there's no way to retake ownership or approval from
|
||||||
// the wrapped tokenId after this point, so it's safe to remove the reentrancy check for the next line.
|
// the wrapped tokenId after this point, so it's safe to remove the reentrancy check for the next line.
|
||||||
// slither-disable-next-line reentrancy-no-eth
|
// slither-disable-next-line reentrancy-no-eth
|
||||||
|
|||||||
@ -22,6 +22,11 @@ library DoubleEndedQueue {
|
|||||||
*/
|
*/
|
||||||
error QueueEmpty();
|
error QueueEmpty();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev A push operation couldn't be completed due to the queue being full.
|
||||||
|
*/
|
||||||
|
error QueueFull();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @dev An operation (e.g. {at}) couldn't be completed due to an index being out of bounds.
|
* @dev An operation (e.g. {at}) couldn't be completed due to an index being out of bounds.
|
||||||
*/
|
*/
|
||||||
@ -40,18 +45,19 @@ library DoubleEndedQueue {
|
|||||||
* data[end - 1].
|
* data[end - 1].
|
||||||
*/
|
*/
|
||||||
struct Bytes32Deque {
|
struct Bytes32Deque {
|
||||||
int128 _begin;
|
uint128 _begin;
|
||||||
int128 _end;
|
uint128 _end;
|
||||||
mapping(int128 => bytes32) _data;
|
mapping(uint128 => bytes32) _data;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @dev Inserts an item at the end of the queue.
|
* @dev Inserts an item at the end of the queue.
|
||||||
*/
|
*/
|
||||||
function pushBack(Bytes32Deque storage deque, bytes32 value) internal {
|
function pushBack(Bytes32Deque storage deque, bytes32 value) internal {
|
||||||
int128 backIndex = deque._end;
|
|
||||||
deque._data[backIndex] = value;
|
|
||||||
unchecked {
|
unchecked {
|
||||||
|
uint128 backIndex = deque._end;
|
||||||
|
if (backIndex + 1 == deque._begin) revert QueueFull();
|
||||||
|
deque._data[backIndex] = value;
|
||||||
deque._end = backIndex + 1;
|
deque._end = backIndex + 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -62,27 +68,27 @@ library DoubleEndedQueue {
|
|||||||
* Reverts with `QueueEmpty` if the queue is empty.
|
* Reverts with `QueueEmpty` if the queue is empty.
|
||||||
*/
|
*/
|
||||||
function popBack(Bytes32Deque storage deque) internal returns (bytes32 value) {
|
function popBack(Bytes32Deque storage deque) internal returns (bytes32 value) {
|
||||||
if (empty(deque)) revert QueueEmpty();
|
|
||||||
int128 backIndex;
|
|
||||||
unchecked {
|
unchecked {
|
||||||
backIndex = deque._end - 1;
|
uint128 backIndex = deque._end;
|
||||||
}
|
if (backIndex == deque._begin) revert QueueEmpty();
|
||||||
|
--backIndex;
|
||||||
value = deque._data[backIndex];
|
value = deque._data[backIndex];
|
||||||
delete deque._data[backIndex];
|
delete deque._data[backIndex];
|
||||||
deque._end = backIndex;
|
deque._end = backIndex;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @dev Inserts an item at the beginning of the queue.
|
* @dev Inserts an item at the beginning of the queue.
|
||||||
*/
|
*/
|
||||||
function pushFront(Bytes32Deque storage deque, bytes32 value) internal {
|
function pushFront(Bytes32Deque storage deque, bytes32 value) internal {
|
||||||
int128 frontIndex;
|
|
||||||
unchecked {
|
unchecked {
|
||||||
frontIndex = deque._begin - 1;
|
uint128 frontIndex = deque._begin - 1;
|
||||||
}
|
if (frontIndex == deque._end) revert QueueFull();
|
||||||
deque._data[frontIndex] = value;
|
deque._data[frontIndex] = value;
|
||||||
deque._begin = frontIndex;
|
deque._begin = frontIndex;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @dev Removes the item at the beginning of the queue and returns it.
|
* @dev Removes the item at the beginning of the queue and returns it.
|
||||||
@ -90,11 +96,11 @@ library DoubleEndedQueue {
|
|||||||
* Reverts with `QueueEmpty` if the queue is empty.
|
* Reverts with `QueueEmpty` if the queue is empty.
|
||||||
*/
|
*/
|
||||||
function popFront(Bytes32Deque storage deque) internal returns (bytes32 value) {
|
function popFront(Bytes32Deque storage deque) internal returns (bytes32 value) {
|
||||||
if (empty(deque)) revert QueueEmpty();
|
unchecked {
|
||||||
int128 frontIndex = deque._begin;
|
uint128 frontIndex = deque._begin;
|
||||||
|
if (frontIndex == deque._end) revert QueueEmpty();
|
||||||
value = deque._data[frontIndex];
|
value = deque._data[frontIndex];
|
||||||
delete deque._data[frontIndex];
|
delete deque._data[frontIndex];
|
||||||
unchecked {
|
|
||||||
deque._begin = frontIndex + 1;
|
deque._begin = frontIndex + 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -106,8 +112,7 @@ library DoubleEndedQueue {
|
|||||||
*/
|
*/
|
||||||
function front(Bytes32Deque storage deque) internal view returns (bytes32 value) {
|
function front(Bytes32Deque storage deque) internal view returns (bytes32 value) {
|
||||||
if (empty(deque)) revert QueueEmpty();
|
if (empty(deque)) revert QueueEmpty();
|
||||||
int128 frontIndex = deque._begin;
|
return deque._data[deque._begin];
|
||||||
return deque._data[frontIndex];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -117,11 +122,9 @@ library DoubleEndedQueue {
|
|||||||
*/
|
*/
|
||||||
function back(Bytes32Deque storage deque) internal view returns (bytes32 value) {
|
function back(Bytes32Deque storage deque) internal view returns (bytes32 value) {
|
||||||
if (empty(deque)) revert QueueEmpty();
|
if (empty(deque)) revert QueueEmpty();
|
||||||
int128 backIndex;
|
|
||||||
unchecked {
|
unchecked {
|
||||||
backIndex = deque._end - 1;
|
return deque._data[deque._end - 1];
|
||||||
}
|
}
|
||||||
return deque._data[backIndex];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -131,10 +134,10 @@ library DoubleEndedQueue {
|
|||||||
* Reverts with `QueueOutOfBounds` if the index is out of bounds.
|
* Reverts with `QueueOutOfBounds` if the index is out of bounds.
|
||||||
*/
|
*/
|
||||||
function at(Bytes32Deque storage deque, uint256 index) internal view returns (bytes32 value) {
|
function at(Bytes32Deque storage deque, uint256 index) internal view returns (bytes32 value) {
|
||||||
// int256(deque._begin) is a safe upcast
|
if (index >= length(deque)) revert QueueOutOfBounds();
|
||||||
int128 idx = SafeCast.toInt128(int256(deque._begin) + SafeCast.toInt256(index));
|
unchecked {
|
||||||
if (idx >= deque._end) revert QueueOutOfBounds();
|
return deque._data[deque._begin + SafeCast.toUint128(index)];
|
||||||
return deque._data[idx];
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -152,10 +155,9 @@ library DoubleEndedQueue {
|
|||||||
* @dev Returns the number of items in the queue.
|
* @dev Returns the number of items in the queue.
|
||||||
*/
|
*/
|
||||||
function length(Bytes32Deque storage deque) internal view returns (uint256) {
|
function length(Bytes32Deque storage deque) internal view returns (uint256) {
|
||||||
// The interface preserves the invariant that begin <= end so we assume this will not overflow.
|
// We also assume there are at most uint128.max items in the queue.
|
||||||
// We also assume there are at most int256.max items in the queue.
|
|
||||||
unchecked {
|
unchecked {
|
||||||
return uint256(int256(deque._end) - int256(deque._begin));
|
return uint256(deque._end - deque._begin);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -163,6 +165,6 @@ library DoubleEndedQueue {
|
|||||||
* @dev Returns true if the queue is empty.
|
* @dev Returns true if the queue is empty.
|
||||||
*/
|
*/
|
||||||
function empty(Bytes32Deque storage deque) internal view returns (bool) {
|
function empty(Bytes32Deque storage deque) internal view returns (bool) {
|
||||||
return deque._end <= deque._begin;
|
return deque._end == deque._begin;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1 +1 @@
|
|||||||
certora-cli==3.6.4
|
certora-cli==4.3.1
|
||||||
|
|||||||
@ -524,14 +524,6 @@ function shouldBehaveLikeERC721(owner, newOwner, approved, anotherApproved, oper
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
context('when the address that receives the approval is the owner', function () {
|
|
||||||
it('reverts', async function () {
|
|
||||||
await expectRevertCustomError(this.token.approve(owner, tokenId, { from: owner }), 'ERC721InvalidOperator', [
|
|
||||||
owner,
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
context('when the sender does not own the given token ID', function () {
|
context('when the sender does not own the given token ID', function () {
|
||||||
it('reverts', async function () {
|
it('reverts', async function () {
|
||||||
await expectRevertCustomError(
|
await expectRevertCustomError(
|
||||||
@ -645,12 +637,12 @@ function shouldBehaveLikeERC721(owner, newOwner, approved, anotherApproved, oper
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
context('when the operator is the owner', function () {
|
context('when the operator is address zero', function () {
|
||||||
it('reverts', async function () {
|
it('reverts', async function () {
|
||||||
await expectRevertCustomError(
|
await expectRevertCustomError(
|
||||||
this.token.setApprovalForAll(owner, true, { from: owner }),
|
this.token.setApprovalForAll(constants.ZERO_ADDRESS, true, { from: owner }),
|
||||||
'ERC721InvalidOperator',
|
'ERC721InvalidOperator',
|
||||||
[owner],
|
[constants.ZERO_ADDRESS],
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -103,7 +103,7 @@ contract('ERC721Consecutive', function (accounts) {
|
|||||||
it('simple minting is possible after construction', async function () {
|
it('simple minting is possible after construction', async function () {
|
||||||
const tokenId = sum(...batches.map(b => b.amount)) + offset;
|
const tokenId = sum(...batches.map(b => b.amount)) + offset;
|
||||||
|
|
||||||
expect(await this.token.$_exists(tokenId)).to.be.equal(false);
|
await expectRevertCustomError(this.token.ownerOf(tokenId), 'ERC721NonexistentToken', [tokenId]);
|
||||||
|
|
||||||
expectEvent(await this.token.$_mint(user1, tokenId), 'Transfer', {
|
expectEvent(await this.token.$_mint(user1, tokenId), 'Transfer', {
|
||||||
from: constants.ZERO_ADDRESS,
|
from: constants.ZERO_ADDRESS,
|
||||||
@ -115,7 +115,7 @@ contract('ERC721Consecutive', function (accounts) {
|
|||||||
it('cannot mint a token that has been batched minted', async function () {
|
it('cannot mint a token that has been batched minted', async function () {
|
||||||
const tokenId = sum(...batches.map(b => b.amount)) + offset - 1;
|
const tokenId = sum(...batches.map(b => b.amount)) + offset - 1;
|
||||||
|
|
||||||
expect(await this.token.$_exists(tokenId)).to.be.equal(true);
|
expect(await this.token.ownerOf(tokenId)).to.be.not.equal(constants.ZERO_ADDRESS);
|
||||||
|
|
||||||
await expectRevertCustomError(this.token.$_mint(user1, tokenId), 'ERC721InvalidSender', [ZERO_ADDRESS]);
|
await expectRevertCustomError(this.token.$_mint(user1, tokenId), 'ERC721InvalidSender', [ZERO_ADDRESS]);
|
||||||
});
|
});
|
||||||
@ -151,13 +151,11 @@ contract('ERC721Consecutive', function (accounts) {
|
|||||||
it('tokens can be burned and re-minted #2', async function () {
|
it('tokens can be burned and re-minted #2', async function () {
|
||||||
const tokenId = web3.utils.toBN(sum(...batches.map(({ amount }) => amount)) + offset);
|
const tokenId = web3.utils.toBN(sum(...batches.map(({ amount }) => amount)) + offset);
|
||||||
|
|
||||||
expect(await this.token.$_exists(tokenId)).to.be.equal(false);
|
|
||||||
await expectRevertCustomError(this.token.ownerOf(tokenId), 'ERC721NonexistentToken', [tokenId]);
|
await expectRevertCustomError(this.token.ownerOf(tokenId), 'ERC721NonexistentToken', [tokenId]);
|
||||||
|
|
||||||
// mint
|
// mint
|
||||||
await this.token.$_mint(user1, tokenId);
|
await this.token.$_mint(user1, tokenId);
|
||||||
|
|
||||||
expect(await this.token.$_exists(tokenId)).to.be.equal(true);
|
|
||||||
expect(await this.token.ownerOf(tokenId), user1);
|
expect(await this.token.ownerOf(tokenId), user1);
|
||||||
|
|
||||||
// burn
|
// burn
|
||||||
@ -167,7 +165,6 @@ contract('ERC721Consecutive', function (accounts) {
|
|||||||
tokenId,
|
tokenId,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(await this.token.$_exists(tokenId)).to.be.equal(false);
|
|
||||||
await expectRevertCustomError(this.token.ownerOf(tokenId), 'ERC721NonexistentToken', [tokenId]);
|
await expectRevertCustomError(this.token.ownerOf(tokenId), 'ERC721NonexistentToken', [tokenId]);
|
||||||
|
|
||||||
// re-mint
|
// re-mint
|
||||||
@ -177,20 +174,8 @@ contract('ERC721Consecutive', function (accounts) {
|
|||||||
tokenId,
|
tokenId,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(await this.token.$_exists(tokenId)).to.be.equal(true);
|
|
||||||
expect(await this.token.ownerOf(tokenId), user2);
|
expect(await this.token.ownerOf(tokenId), user2);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('reverts burning batches of size != 1', async function () {
|
|
||||||
const tokenId = batches[0].amount + offset;
|
|
||||||
const receiver = batches[0].receiver;
|
|
||||||
|
|
||||||
await expectRevertCustomError(
|
|
||||||
this.token.$_afterTokenTransfer(receiver, ZERO_ADDRESS, tokenId, 2),
|
|
||||||
'ERC721ForbiddenBatchBurn',
|
|
||||||
[],
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@ -81,12 +81,6 @@ contract('ERC721Pausable', function (accounts) {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('exists', function () {
|
|
||||||
it('returns token existence', async function () {
|
|
||||||
expect(await this.token.$_exists(firstTokenId)).to.equal(true);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('isApprovedForAll', function () {
|
describe('isApprovedForAll', function () {
|
||||||
it('returns the approval of the operator', async function () {
|
it('returns the approval of the operator', async function () {
|
||||||
expect(await this.token.isApprovedForAll(owner, operator)).to.equal(false);
|
expect(await this.token.isApprovedForAll(owner, operator)).to.equal(false);
|
||||||
|
|||||||
@ -86,7 +86,6 @@ contract('ERC721URIStorage', function (accounts) {
|
|||||||
it('tokens without URI can be burnt ', async function () {
|
it('tokens without URI can be burnt ', async function () {
|
||||||
await this.token.$_burn(firstTokenId, { from: owner });
|
await this.token.$_burn(firstTokenId, { from: owner });
|
||||||
|
|
||||||
expect(await this.token.$_exists(firstTokenId)).to.equal(false);
|
|
||||||
await expectRevertCustomError(this.token.tokenURI(firstTokenId), 'ERC721NonexistentToken', [firstTokenId]);
|
await expectRevertCustomError(this.token.tokenURI(firstTokenId), 'ERC721NonexistentToken', [firstTokenId]);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -95,7 +94,6 @@ contract('ERC721URIStorage', function (accounts) {
|
|||||||
|
|
||||||
await this.token.$_burn(firstTokenId, { from: owner });
|
await this.token.$_burn(firstTokenId, { from: owner });
|
||||||
|
|
||||||
expect(await this.token.$_exists(firstTokenId)).to.equal(false);
|
|
||||||
await expectRevertCustomError(this.token.tokenURI(firstTokenId), 'ERC721NonexistentToken', [firstTokenId]);
|
await expectRevertCustomError(this.token.tokenURI(firstTokenId), 'ERC721NonexistentToken', [firstTokenId]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user