Skip to content

Commit 5ec3210

Browse files
authored
Merge pull request #62 from nisedo/fix/issue-56-remove-usedid
Fix #56: Remove redundant usedId from ERC721 implementation and update README
2 parents 1d14bfe + 03b7134 commit 5ec3210

File tree

5 files changed

+242
-128
lines changed

5 files changed

+242
-128
lines changed

README.md

Lines changed: 60 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -73,10 +73,9 @@ To test an ERC20 token, follow these steps:
7373

7474
You can see the output for a [compliant token](#example-output-for-a-compliant-token), and [non compliant token](#example-output-for-a-non-compliant-token).
7575

76-
7776
#### Integration
7877

79-
Decide if you want to do internal or external testing. Both approaches have advantages and disadvantages, you can check more information about them [here](https://secure-contracts.com/program-analysis/echidna/basic/common-testing-approaches.html).
78+
Decide if you want to do internal or external testing. Both approaches have advantages and disadvantages, you can check more information about them [here](https://secure-contracts.com/program-analysis/echidna/basic/common-testing-approaches.html).
8079

8180
For internal testing, create a new Solidity file containing the `CryticERC20InternalHarness` contract. `USER1`, `USER2` and `USER3` constants are initialized by default in `PropertiesConstants` contract to be the addresses from where echidna sends transactions, and `INITIAL_BALANCE` is by default `1000e18`:
8281

@@ -147,44 +146,40 @@ sender: ["0x10000", "0x20000", "0x30000"]
147146
#allContracts: true
148147
```
149148

150-
**Medusa**
149+
**Medusa**
151150

152151
Create the following Medusa config file:
153152

154153
```json
155154
{
156-
"fuzzing": {
155+
"fuzzing": {
157156
"testLimit": 100000,
158-
"corpusDirectory": "tests/medusa-corpus",
159-
"deployerAddress": "0x10000",
160-
"senderAddresses": [
161-
"0x10000",
162-
"0x20000",
163-
"0x30000"
164-
],
165-
"assertionTesting": {
166-
"enabled": true
167-
},
168-
"propertyTesting": {
169-
"enabled": false
170-
},
157+
"corpusDirectory": "tests/medusa-corpus",
158+
"deployerAddress": "0x10000",
159+
"senderAddresses": ["0x10000", "0x20000", "0x30000"],
160+
"assertionTesting": {
161+
"enabled": true
162+
},
163+
"propertyTesting": {
164+
"enabled": false
165+
},
171166
"optimizationTesting": {
172-
"enabled": false,
173-
},
174-
},
175-
// Uncomment the following lines for external testing
176-
// "testing": {
177-
// "testAllContracts": true
178-
// },
179-
"compilation": {
180-
"platform": "crytic-compile",
181-
"platformConfig": {
182-
"target": ".",
183-
"solcVersion": "",
184-
"exportDirectory": "",
185-
"args": ["--foundry-compile-all"]
186-
}
187-
}
167+
"enabled": false
168+
}
169+
},
170+
// Uncomment the following lines for external testing
171+
// "testing": {
172+
// "testAllContracts": true
173+
// },
174+
"compilation": {
175+
"platform": "crytic-compile",
176+
"platformConfig": {
177+
"target": ".",
178+
"solcVersion": "",
179+
"exportDirectory": "",
180+
"args": ["--foundry-compile-all"]
181+
}
182+
}
188183
}
189184
```
190185

@@ -260,11 +255,12 @@ To test an ERC721 token, follow these steps:
260255

261256
You can see the output for a [compliant token](#example-output-for-a-compliant-token-1), and [non compliant token](#example-output-for-a-non-compliant-token-1).
262257

263-
264258
#### Integration
265-
Decide if you want to do internal or external testing. Both approaches have advantages and disadvantages, you can check more information about them [here](https://secure-contracts.com/program-analysis/echidna/basic/common-testing-approaches.html).
259+
260+
Decide if you want to do internal or external testing. Both approaches have advantages and disadvantages, you can check more information about them [here](https://secure-contracts.com/program-analysis/echidna/basic/common-testing-approaches.html).
266261

267262
For internal testing, create a new Solidity file containing the `CryticERC721InternalHarness` contract. `USER1`, `USER2` and `USER3` constants are initialized by default in `PropertiesConstants` contract to be the addresses from where echidna sends transactions.
263+
268264
```Solidity
269265
pragma solidity ^0.8.0;
270266
import "@crytic/properties/contracts/ERC721/internal/properties/ERC721BasicProperties.sol";
@@ -282,29 +278,40 @@ In the `CryticERC721ExternalHarness` contract you can specify which properties t
282278
```Solidity
283279
pragma solidity ^0.8.0;
284280
import "./MyToken.sol";
285-
import {ITokenMock} from "@crytic/properties/contracts/ERC721/external/util/ITokenMock.sol";
281+
import {IERC721Internal} from "@crytic/properties/contracts/ERC721/util/IERC721Internal.sol";
286282
import {CryticERC721ExternalBasicProperties} from "@crytic/properties/contracts/ERC721/external/properties/ERC721ExternalBasicProperties.sol";
287283
import {PropertiesConstants} from "@crytic/properties/contracts/util/PropertiesConstants.sol";
288284
289285
290-
contract CryticERC721ExternalHarness is CryticERC721ExternalBasicProperties {
286+
contract CryticERC721ExternalHarness is CryticERC721ExternalBasicProperties {
291287
constructor() {
292288
// Deploy ERC721
293-
token = ITokenMock(address(new CryticTokenMock()));
289+
token = IERC721Internal(address(new CryticTokenMock()));
294290
}
295291
}
296292
297293
contract CryticTokenMock is MyToken, PropertiesConstants {
298-
294+
uint256 public counter;
299295
bool public isMintableOrBurnable;
300296
301-
constructor () {
297+
constructor() {
302298
isMintableOrBurnable = true;
303299
}
300+
301+
function burn(uint256 tokenId) public {
302+
_burn(tokenId);
303+
}
304+
305+
function _customMint(address to, uint256 amount) public {
306+
for (uint256 i; i < amount; i++) {
307+
_mint(to, counter++);
308+
}
309+
}
304310
}
305311
```
306312

307313
#### Configuration
314+
308315
Create the following Echidna config file
309316

310317
```yaml
@@ -323,20 +330,23 @@ allContracts: true
323330
324331
To perform more than one test, save the files with a descriptive path, to identify what test each file or corpus belongs to. For these examples, we use `tests/crytic/erc721/echidna-internal.yaml` and `tests/crytic/erc721/echidna-external.yaml` for the Echidna tests for ERC721. We recommended to modify the `corpusDir` for external tests accordingly.
325332

326-
The above configuration will start Echidna in assertion mode. Contract will be deployed from address `0x10000`, and transactions will be sent from the owner and two different users (`0x20000` and `0x30000`). There is an initial limit of `100000` tests, but depending on the token code complexity, this can be increased. Finally, once Echidna finishes the fuzzing campaign, corpus and coverage results will be available in the `tests/crytic/erc721/echidna-corpus-internal` directory.
333+
The above configuration will start Echidna in assertion mode. Contract will be deployed from address `0x10000`, and transactions will be sent from the owner and two different users (`0x20000` and `0x30000`). There is an initial limit of `100000` tests, but depending on the token code complexity, this can be increased. Finally, once Echidna finishes the fuzzing campaign, corpus and coverage results will be available in the `tests/crytic/erc721/echidna-corpus-internal` directory.
327334

328335
#### Run
329-
Run Echidna:
330-
- For internal testing: `echidna . --contract CryticERC721InternalHarness --config tests/crytic/erc721/echidna-internal.yaml`
331-
- For external testing: `echidna . --contract CryticERC721ExternalHarness --config tests/crytic/erc721/echidna-external.yaml`
332-
336+
337+
Run Echidna:
338+
339+
- For internal testing: `echidna . --contract CryticERC721InternalHarness --config tests/crytic/erc721/echidna-internal.yaml`
340+
- For external testing: `echidna . --contract CryticERC721ExternalHarness --config tests/crytic/erc721/echidna-external.yaml`
341+
333342
Finally, inspect the coverage report in `tests/crytic/erc721/echidna-corpus-internal` or `tests/crytic/erc721/echidna-corpus-external` when it finishes.
334343

335344
#### Example: Output for a compliant token
336345

337346
If the token under test is compliant and no properties will fail during fuzzing, the Echidna output should be similar to the screen below:
347+
338348
```
339-
$ echidna . --contract CryticERC721InternalHarness --config tests/echidna.config.yaml
349+
$ echidna . --contract CryticERC721InternalHarness --config tests/echidna.config.yaml
340350
Loaded total of 23 transactions from corpus/coverage
341351
Analyzing contract: contracts/ERC721/CryticERC721InternalHarness.sol:CryticERC721InternalHarness
342352
name(): passed! 🎉
@@ -350,13 +360,15 @@ totalSupply(): passed! 🎉
350360
#### Example: Output for a non-compliant token
351361

352362
For this example, the ExampleToken's balanceOf function was modified so it does not check that `owner` is the zero address:
363+
353364
```
354365
function balanceOf(address owner) public view virtual override returns (uint256) {
355366
return _balances[owner];
356367
}
357368
```
358369

359370
In this case, the Echidna output should be similar to the screen below, notice that all functions that rely on `balanceOf()` to work correctly will have their assertions broken, and will report the situation.
371+
360372
```
361373
$ echidna . --contract CryticERC721ExternalHarness --config tests/echidna.config.yaml
362374
Loaded total of 25 transactions from corpus/coverage
@@ -365,7 +377,7 @@ name(): passed! 🎉
365377
test_ERC721_external_transferFromUpdatesOwner(): passed! 🎉
366378
approve(address,uint256): passed! 🎉
367379
...
368-
test_ERC721_external_balanceOfZeroAddressMustRevert(): failed!💥
380+
test_ERC721_external_balanceOfZeroAddressMustRevert(): failed!💥
369381
Call sequence:
370382
test_ERC721_external_balanceOfZeroAddressMustRevert()
371383

@@ -376,11 +388,11 @@ Event sequence: Panic(1), AssertFail("address(0) balance query should have rever
376388
### ERC4626 Tests
377389
378390
To test an ERC4626 token, follow these steps:
391+
379392
1. [Integration](#integration-2)
380393
2. [Configuration](#configuration-2)
381394
3. [Run](#run-2)
382395
383-
384396
#### Integration
385397
386398
Create a new Solidity file containing the `CryticERC4626Harness` contract. Make sure it properly initializes your ERC4626 vault with a test token (`TestERC20Token`):
@@ -454,7 +466,6 @@ We provide a number of tests related with fundamental mathematical properties of
454466
1. [Integration](#integration-3)
455467
2. [Run](#run-3)
456468

457-
458469
#### Integration
459470

460471
Create a new Solidity file containing the `ABDKMath64x64Harness` contract:

contracts/ERC721/external/properties/ERC721ExternalMintableProperties.sol

Lines changed: 33 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,36 +3,59 @@ pragma solidity ^0.8.13;
33

44
import "../util/ERC721ExternalTestBase.sol";
55

6-
abstract contract CryticERC721ExternalMintableProperties is CryticERC721ExternalTestBase {
6+
abstract contract CryticERC721ExternalMintableProperties is
7+
CryticERC721ExternalTestBase
8+
{
79
using Address for address;
810

911
////////////////////////////////////////
1012
// Properties
1113
// mint increases the total supply.
12-
function test_ERC721_external_mintIncreasesSupply(uint256 amount) public virtual {
14+
function test_ERC721_external_mintIncreasesSupply(
15+
uint256 amount
16+
) public virtual {
1317
require(token.isMintableOrBurnable());
14-
18+
1519
uint256 selfBalance = token.balanceOf(address(this));
1620
uint256 oldTotalSupply = token.totalSupply();
1721

1822
try token._customMint(address(this), amount) {
19-
assertEq(oldTotalSupply + amount, token.totalSupply(), "Total supply was not correctly increased");
20-
assertEq(selfBalance + amount, token.balanceOf(address(this)), "Receiver supply was not correctly increased");
23+
assertEq(
24+
oldTotalSupply + amount,
25+
token.totalSupply(),
26+
"Total supply was not correctly increased"
27+
);
28+
assertEq(
29+
selfBalance + amount,
30+
token.balanceOf(address(this)),
31+
"Receiver supply was not correctly increased"
32+
);
2133
} catch {
2234
assertWithMsg(false, "Minting unexpectedly reverted");
2335
}
2436
}
2537

2638
// mint creates a fresh token.
27-
function test_ERC721_external_mintCreatesFreshToken(uint256 amount) public virtual {
39+
function test_ERC721_external_mintCreatesFreshToken(
40+
uint256 amount
41+
) public virtual {
2842
require(token.isMintableOrBurnable());
2943

3044
uint256 selfBalance = token.balanceOf(address(this));
3145
try token._customMint(address(this), amount) {
32-
uint256 tokenId = token.tokenOfOwnerByIndex(address(this), selfBalance);
33-
assertWithMsg(token.ownerOf(tokenId) == address(this), "Token ID was not minted to receiver");
34-
assertWithMsg(!token.usedId(tokenId), "Token ID minted is not new");
35-
assertEq(selfBalance + amount, token.balanceOf(address(this)), "Receiver supply was not correctly increased");
46+
uint256 tokenId = token.tokenOfOwnerByIndex(
47+
address(this),
48+
selfBalance
49+
);
50+
assertWithMsg(
51+
token.ownerOf(tokenId) == address(this),
52+
"Token ID was not minted to receiver"
53+
);
54+
assertEq(
55+
selfBalance + amount,
56+
token.balanceOf(address(this)),
57+
"Receiver supply was not correctly increased"
58+
);
3659
} catch {
3760
assertWithMsg(false, "Minting unexpectedly reverted");
3861
}

contracts/ERC721/external/test/ERC721Compliant.sol

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,10 @@ import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol";
66
import {IERC721Internal} from "../../util/IERC721Internal.sol";
77

88
contract ERC721Compliant is ERC721, ERC721Enumerable, IERC721Internal {
9-
109
uint256 public counter;
1110
bool public isMintableOrBurnable;
12-
mapping(uint256 => bool) public usedId;
1311

14-
constructor() ERC721("OZERC721","OZ") {
12+
constructor() ERC721("OZERC721", "OZ") {
1513
isMintableOrBurnable = true;
1614
}
1715

@@ -20,21 +18,24 @@ contract ERC721Compliant is ERC721, ERC721Enumerable, IERC721Internal {
2018
}
2119

2220
function _customMint(address to, uint256 amount) public virtual {
23-
for(uint256 i; i < amount; i++) {
21+
for (uint256 i; i < amount; i++) {
2422
_mint(to, counter++);
2523
}
2624
}
27-
25+
2826
// The following functions are overrides required by Solidity.
29-
function _beforeTokenTransfer(address from, address to, uint256 tokenId, uint256 batchSize)
30-
internal
31-
virtual
32-
override(ERC721, ERC721Enumerable)
33-
{
27+
function _beforeTokenTransfer(
28+
address from,
29+
address to,
30+
uint256 tokenId,
31+
uint256 batchSize
32+
) internal virtual override(ERC721, ERC721Enumerable) {
3433
super._beforeTokenTransfer(from, to, tokenId, batchSize);
3534
}
3635

37-
function supportsInterface(bytes4 interfaceId)
36+
function supportsInterface(
37+
bytes4 interfaceId
38+
)
3839
public
3940
view
4041
virtual
@@ -43,4 +44,4 @@ contract ERC721Compliant is ERC721, ERC721Enumerable, IERC721Internal {
4344
{
4445
return super.supportsInterface(interfaceId);
4546
}
46-
}
47+
}

0 commit comments

Comments
 (0)