From 6bee0c3cea3ddabc9575187462d2443ee9ba7b21 Mon Sep 17 00:00:00 2001 From: Caliber Date: Thu, 15 Jun 2023 01:11:25 +0530 Subject: [PATCH] Added ERC1155 properties --- contracts/ERC1155/README.md | 36 +++ .../external/ERC1155ExternalPropertyTests.sol | 12 + .../ERC1155ExternalBasicProperties.sol | 212 ++++++++++++++++++ .../ERC1155ExternalBurnableProperties.sol | 156 +++++++++++++ .../ERC1155ExternalMintableProperties.sol | 50 +++++ .../external/test/ERC1155Compliant.sol | 37 +++ .../ERC1155/external/test/echidna.config.yaml | 10 + .../test/standard/ERC1155BasicTests.sol | 22 ++ .../test/standard/ERC1155BurnableTests.sol | 20 ++ .../test/standard/ERC1155CompliantTests.sol | 22 ++ .../test/standard/ERC1155MintableTests.sol | 22 ++ .../external/util/ERC1155ExternalTestBase.sol | 19 ++ .../external/util/ERC1155IncorrectBasic.sol | 54 +++++ .../util/ERC1155IncorrectBurnable.sol | 56 +++++ .../util/ERC1155IncorrectMintable.sol | 54 +++++ .../ERC1155/external/util/MockReceiver.sol | 27 +++ .../internal/ERC1155InternalPropertyTests.sol | 33 +++ .../properties/ERC1155BasicProperties.sol | 203 +++++++++++++++++ .../properties/ERC1155BurnableProperties.sol | 172 ++++++++++++++ .../properties/ERC1155MintableProperties.sol | 48 ++++ .../ERC1155/internal/test/echidna.config.yaml | 10 + .../test/standard/ERC1155BasicTests.sol | 74 ++++++ .../test/standard/ERC1155BurnableTests.sol | 94 ++++++++ .../test/standard/ERC1155Compliant.sol | 55 +++++ .../test/standard/ERC1155MintableTests.sol | 76 +++++++ .../ERC1155/internal/util/ERC1155TestBase.sol | 34 +++ .../ERC1155/internal/util/MockReceiver.sol | 27 +++ contracts/ERC1155/util/IERC1155Internal.sol | 12 + 28 files changed, 1647 insertions(+) create mode 100644 contracts/ERC1155/README.md create mode 100644 contracts/ERC1155/external/ERC1155ExternalPropertyTests.sol create mode 100644 contracts/ERC1155/external/properties/ERC1155ExternalBasicProperties.sol create mode 100644 contracts/ERC1155/external/properties/ERC1155ExternalBurnableProperties.sol create mode 100644 contracts/ERC1155/external/properties/ERC1155ExternalMintableProperties.sol create mode 100644 contracts/ERC1155/external/test/ERC1155Compliant.sol create mode 100644 contracts/ERC1155/external/test/echidna.config.yaml create mode 100644 contracts/ERC1155/external/test/standard/ERC1155BasicTests.sol create mode 100644 contracts/ERC1155/external/test/standard/ERC1155BurnableTests.sol create mode 100644 contracts/ERC1155/external/test/standard/ERC1155CompliantTests.sol create mode 100644 contracts/ERC1155/external/test/standard/ERC1155MintableTests.sol create mode 100644 contracts/ERC1155/external/util/ERC1155ExternalTestBase.sol create mode 100644 contracts/ERC1155/external/util/ERC1155IncorrectBasic.sol create mode 100644 contracts/ERC1155/external/util/ERC1155IncorrectBurnable.sol create mode 100644 contracts/ERC1155/external/util/ERC1155IncorrectMintable.sol create mode 100644 contracts/ERC1155/external/util/MockReceiver.sol create mode 100644 contracts/ERC1155/internal/ERC1155InternalPropertyTests.sol create mode 100644 contracts/ERC1155/internal/properties/ERC1155BasicProperties.sol create mode 100644 contracts/ERC1155/internal/properties/ERC1155BurnableProperties.sol create mode 100644 contracts/ERC1155/internal/properties/ERC1155MintableProperties.sol create mode 100644 contracts/ERC1155/internal/test/echidna.config.yaml create mode 100644 contracts/ERC1155/internal/test/standard/ERC1155BasicTests.sol create mode 100644 contracts/ERC1155/internal/test/standard/ERC1155BurnableTests.sol create mode 100644 contracts/ERC1155/internal/test/standard/ERC1155Compliant.sol create mode 100644 contracts/ERC1155/internal/test/standard/ERC1155MintableTests.sol create mode 100644 contracts/ERC1155/internal/util/ERC1155TestBase.sol create mode 100644 contracts/ERC1155/internal/util/MockReceiver.sol create mode 100644 contracts/ERC1155/util/IERC1155Internal.sol diff --git a/contracts/ERC1155/README.md b/contracts/ERC1155/README.md new file mode 100644 index 0000000..e0597f1 --- /dev/null +++ b/contracts/ERC1155/README.md @@ -0,0 +1,36 @@ +### Vanilla OpenZeppelin ERC1155 + +- External: +`echidna . --contract ERC1155CompliantExternalHarness --config contracts/ERC1155/external/test/echidna.config.yaml` + +- Internal: +`echidna . --contract ERC1155InternalCompliant --config ./contracts/ERC1155/internal/test/echidna.config.yaml` + +[EIP-1155 Spec](https://eips.ethereum.org/EIPS/eip-1155) + +# Properties Tested + +### Basic properties +- `balanceOf()` should revert on address zero +- `balanceOfBatch` Works as expected +- `safeTransferFrom()` should revert while transferring unapproved token +- `safeTransferFrom()` correctly update balances +- `safeTransferFrom()` should revert if `from` is the zero address +- `safeTransferFrom()` should revert if `to` is the zero address +- `safeTransferFrom()` to self should not break accounting +- `safeBatchTransferFrom()` to self should not break accounting +- `safeBatchTransferFrom()` correctly update balances +- `safeTransferFrom()` should revert if receiver is a contract that does not implement onERC1155Received() +- `safeBatchTransferFrom()` should revert if receiver is a contract that does not implement onERC1155Received() + +### Burnable properties +- `burn()` destroys token(s) +- `burn()` destroys token(s) from approved address +- `burnBatch()` destroys token(s) +- `burnBatch()` destroys token(s) from approved address +- cannot transfer a burned token +- burned token(s) should not be transferrable when burned with burnBatch + +### Mintable properties +- Should mint tokens and should increase balance +- Should mint tokens in batch and should increase balance \ No newline at end of file diff --git a/contracts/ERC1155/external/ERC1155ExternalPropertyTests.sol b/contracts/ERC1155/external/ERC1155ExternalPropertyTests.sol new file mode 100644 index 0000000..188ae36 --- /dev/null +++ b/contracts/ERC1155/external/ERC1155ExternalPropertyTests.sol @@ -0,0 +1,12 @@ +pragma solidity ^0.8.13; + +import {CryticERC1155ExternalTestBase} from "./util/ERC1155ExternalTestBase.sol"; +import {CryticERC1155ExternalBasicProperties} from "./properties/ERC1155ExternalBasicProperties.sol"; +import {CryticERC1155ExternalBurnableProperties} from "./properties/ERC1155ExternalBurnableProperties.sol"; +import {CryticERC1155ExternalMintableProperties} from "./properties/ERC1155ExternalMintableProperties.sol"; + +/// @notice Aggregator contract for various ERC1155 property tests. Inherit from this & echidna will test all properties at the same time. +abstract contract CryticERC1155ExternalPropertyTests is CryticERC1155ExternalBasicProperties, CryticERC1155ExternalMintableProperties, CryticERC1155ExternalBurnableProperties { + function _customMint(address to,uint id,uint amount) internal virtual override(CryticERC1155ExternalBasicProperties,CryticERC1155ExternalMintableProperties); + function _customMintBatch(address target, uint256[] memory ids, uint256[] memory amounts) internal virtual override(CryticERC1155ExternalBasicProperties,CryticERC1155ExternalBurnableProperties,CryticERC1155ExternalMintableProperties); +} diff --git a/contracts/ERC1155/external/properties/ERC1155ExternalBasicProperties.sol b/contracts/ERC1155/external/properties/ERC1155ExternalBasicProperties.sol new file mode 100644 index 0000000..17ce1a1 --- /dev/null +++ b/contracts/ERC1155/external/properties/ERC1155ExternalBasicProperties.sol @@ -0,0 +1,212 @@ +pragma solidity ^0.8.13; + +import "../util/ERC1155ExternalTestBase.sol"; +import "../../../util/Hevm.sol"; +abstract contract CryticERC1155ExternalBasicProperties is CryticERC1155ExternalTestBase { + using Address for address; + mapping(uint256 => uint256) private idToAmount; + + //////////////////////////////////////// + // Properties + + // Querying the balance of address(0) should throw + function test_ERC1155_external_balanceOfZeroAddressMustRevert() public virtual { + token.balanceOf(address(0),1); + assertWithMsg(false, "address(0) balance query should have reverted"); + } + + // balanceOfBatch works as expected + function test_ERC1155_external_balanceOfBatchWorksAsExpected(address[] memory targets,uint256[] memory ids,uint256[] memory amounts) public virtual{ + // target,ids and amounts should have same length. + require(targets.length==ids.length); + + for (uint256 i = 0; i < ids.length; i++) { + require(token.balanceOf(targets[i],ids[i])==0); + } + + // mint tokens to target + for (uint256 i = 0; i < ids.length; i++) { + _customMint(targets[i],ids[i],amounts[i]); + } + + // check balanceOfBatch gives same balances + uint256[] memory balances; + balances = token.balanceOfBatch(targets,ids); + for (uint256 i = 0; i < ids.length; i++) { + assertWithMsg(balances[i]==token.balanceOf(targets[i],ids[i]), "Failed to return expected balance amount to for id"); + } + } + + // safeTransferFrom a token that the caller is not approved for should revert + function test_ERC1155_external_transferFromNotApproved(address target, uint256 id,uint256 amount) public virtual { + uint256 selfBalance = token.balanceOf(msg.sender,id); + require(selfBalance >= amount); + require(target != address(this)); + require(target != msg.sender); + + bool isApproved = token.isApprovedForAll(msg.sender,address(this)); + require(!isApproved); + + token.safeTransferFrom( msg.sender, target, id, amount,""); + assertWithMsg(false, "using safeTransferFrom without being approved should have reverted"); + } + + // safeTransferFrom correctly updates balance + function test_ERC1155_external_transferFromUpdatesBalance(address target, uint256 id,uint256 amount) public virtual { + uint256 selfBalanceBefore = token.balanceOf(msg.sender,id); + uint256 targetBalanceBefore = token.balanceOf(target,id); + require(selfBalanceBefore >= amount); + require(target != address(this)); + require(target != msg.sender); + require(!Address.isContract(target)); + hevm.prank(msg.sender); + try token.safeTransferFrom(msg.sender,target, id,amount,"") { + uint256 targetBalanceAfter = token.balanceOf(target,id); + uint256 selfBalanceAfter = token.balanceOf(msg.sender,id); + assertWithMsg(targetBalanceBefore+amount ==targetBalanceAfter, "Token balance of receiver not updated"); + assertWithMsg(selfBalanceBefore-amount ==selfBalanceAfter, "Token balance of target not updated"); + } catch { + assertWithMsg(false, "safeTransferFrom unexpectedly reverted"); + } + } + + // transfer from zero address should revert/throw + function test_ERC1155_transferFromZeroAddress(uint256 id,uint256 amount) public virtual { + token.safeTransferFrom(address(0), msg.sender, id,amount,""); + + assertWithMsg(false, "Transfer from zero address did not revert"); + } + + // Transfer to the zero address should revert + function test_ERC1155_external_transferFromToZeroAddress(uint256 id,uint256 amount) public virtual { + uint256 selfBalance = token.balanceOf(msg.sender,id); + require(selfBalance >= amount); + + hevm.prank(msg.sender); + token.safeTransferFrom(msg.sender, address(0), id,amount,""); + + assertWithMsg(false, "Transfer to zero address did not revert"); + } + + // Transfer to self should not break accounting + function test_ERC1155_external_transferFromSelf(uint256 id,uint256 amount) public virtual { + uint256 selfBalance = token.balanceOf(msg.sender,id); + require(selfBalance >= amount); + + hevm.prank(msg.sender); + + // transfer amount of id to self address from self address. + try token.safeTransferFrom(msg.sender, msg.sender, id,amount,"") { + assertWithMsg(selfBalance==token.balanceOf(msg.sender,id), "Self transfer breaks accounting"); + } catch { + assertWithMsg(false, "safeTransferFrom unexpectedly reverted"); + } + } + + // Batch transfer to self should not break accounting + function test_ERC1155_external_safeBatchTransferFromSelf(uint256[] memory ids,uint256[] memory amounts) public virtual { + // ids and amounts should have same length. + require(ids.length==amounts.length); + + for (uint256 i = 0; i < ids.length; i++) { + require(amounts[i]>0); + require(token.balanceOf(msg.sender,ids[i])==0); + idToAmount[ids[i]]+=amounts[i]; + } + + // batch mint tokens + _customMintBatch(msg.sender,ids,amounts); + + // balance for ids should be similar to amounts + for (uint256 i = 0; i < ids.length; i++) { + require(token.balanceOf(msg.sender,ids[i])==idToAmount[ids[i]]); + } + + hevm.prank(msg.sender); + + // batch transfer tokens + token.safeBatchTransferFrom(msg.sender,msg.sender,ids,amounts,""); + + // Balance should be same. + for (uint256 i = 0; i < ids.length; i++) { + assertWithMsg(token.balanceOf(msg.sender,ids[i])==idToAmount[ids[i]], "Failed to mint expected balance amount to ids"); + } + + for (uint256 i = 0; i < ids.length; i++) { + delete idToAmount[ids[i]]; + } + } + + // safeBatchTransferFrom correctly updates balance + function test_ERC1155_external_safeBatchTransferFrom(address target,uint256[] memory ids,uint256[] memory amounts) public virtual { + // ids and amounts should have same length. + require(ids.length==amounts.length); + // sender's balance for ids should be equal to 0. + for (uint256 i = 0; i < ids.length; i++) { + require(token.balanceOf(msg.sender,ids[i])==0); + idToAmount[ids[i]]+=amounts[i]; + } + + // batch mint tokens + _customMintBatch(msg.sender,ids,amounts); + + for (uint256 i = 0; i < ids.length; i++) { + require(token.balanceOf(msg.sender,ids[i])==idToAmount[ids[i]]); + } + + // receiver's balance for ids should be 0 + for (uint256 i = 0; i < ids.length; i++) { + require(token.balanceOf(target,ids[i])==0); + } + + hevm.prank(msg.sender); + + // batch transfer tokens + token.safeBatchTransferFrom(msg.sender,target,ids,amounts,""); + + for (uint256 i = 0; i < ids.length; i++) { + assertWithMsg(token.balanceOf(msg.sender,ids[i])==0, "Failed to update sender's balance"); + } + + // Balance should have increased + for (uint256 i = 0; i < ids.length; i++) { + assertWithMsg(token.balanceOf(target,ids[i])==idToAmount[ids[i]], "Failed to mint expected balance amount to target"); + } + + for (uint256 i = 0; i < ids.length; i++) { + delete idToAmount[ids[i]]; + } + } + + // safeTransferFrom reverts if receiver does not implement the callback + function test_ERC1155_external_safeTransferFromRevertsOnNoncontractReceiver(uint256 id,uint256 amount) public virtual { + uint256 selfBalance = token.balanceOf(msg.sender,id); + require(selfBalance >= amount); + + hevm.prank(msg.sender); + + token.safeTransferFrom(msg.sender, address(unsafeReceiver), id,amount,""); + assertWithMsg(false, "safeTransferFrom does not revert if receiver does not implement ERC1155.onERC1155Received"); + } + + // safeBatchTransferFrom reverts if receiver does not implement the callback + function test_ERC1155_exernal_safeBatchTransferFromRevertsOnNoncontractReceiver(uint256[] memory ids,uint256[] memory amounts) public virtual { + // ids and amounts should have same length. + require(ids.length==amounts.length); + + // batch mint tokens + _customMintBatch(msg.sender,ids,amounts); + + for (uint256 i = 0; i < ids.length; i++) { + require(token.balanceOf(msg.sender,ids[i])>=amounts[i]); + } + + hevm.prank(msg.sender); + token.safeBatchTransferFrom(msg.sender, address(unsafeReceiver), ids,amounts,""); + assertWithMsg(false, "safeBatchTransferFrom does not revert if receiver does not implement ERC1155.onERC1155Received"); + } + + function _customMint(address to,uint id,uint amount) internal virtual; + + function _customMintBatch(address target, uint256[] memory ids, uint256[] memory amounts) internal virtual; +} diff --git a/contracts/ERC1155/external/properties/ERC1155ExternalBurnableProperties.sol b/contracts/ERC1155/external/properties/ERC1155ExternalBurnableProperties.sol new file mode 100644 index 0000000..b50d7c4 --- /dev/null +++ b/contracts/ERC1155/external/properties/ERC1155ExternalBurnableProperties.sol @@ -0,0 +1,156 @@ +pragma solidity ^0.8.13; + +import "../util/ERC1155ExternalTestBase.sol"; +import "../../../util/Hevm.sol"; + +abstract contract CryticERC1155ExternalBurnableProperties is CryticERC1155ExternalTestBase{ + using Address for address; + mapping(uint256 => uint256) private idToAmount; + //////////////////////////////////////// + // Properties + + // The burn function should destroy tokens and balance + function test_ERC1155_external_burnDestroysTokens(uint256 id,uint256 amount) public virtual { + require(token.isMintableOrBurnable()); + uint256 selfBalance = token.balanceOf(msg.sender,id); + require(selfBalance >= amount); + + hevm.prank(msg.sender); + token.burn(msg.sender,id,selfBalance); + + assertWithMsg(token.balanceOf(msg.sender,id) == 0, "failed to update balance after burning"); + } + + function test_ERC1155_external_burnDestroysTokensFromApprovedAddress(address target,uint256 id,uint256 amount) public virtual { + require(token.isMintableOrBurnable()); + uint256 selfBalance = token.balanceOf(msg.sender,id); + require(selfBalance >= amount); + require(target != msg.sender); + + // set approval + hevm.prank(msg.sender); + token.setApprovalForAll(target,true); + + hevm.prank(target); + token.burn(msg.sender,id,selfBalance); + + assertWithMsg(token.balanceOf(msg.sender,id) == 0, "failed to update balance after burning"); + } + + // A burned token should not be transferrable + function test_ERC1155_external_burnRevertOnTransferFromPreviousOwner(address target,uint256 id,uint256 amount) public virtual{ + require(token.isMintableOrBurnable()); + uint256 selfBalance = token.balanceOf(msg.sender,id); + require(selfBalance >= amount); + + hevm.prank(msg.sender); + token.burn(msg.sender,id,selfBalance); + + token.safeTransferFrom(msg.sender,target, id,1,""); + assertWithMsg(false, "Transferring a burned token didn't revert"); + } + + // burned token(s) should not be transferrable when burned with burnBatch + function test_ERC1155_external_burnBatchRevertOnTransferFromPreviousOwner(address target,uint256[] memory ids, uint256[] memory amounts) public virtual{ + require(token.isMintableOrBurnable()); + // ids and amounts should have same length. + require(ids.length==amounts.length); + for (uint256 i = 0; i < ids.length; i++) { + require(amounts[i]>0); + idToAmount[ids[i]]+=amounts[i]; + require(token.balanceOf(msg.sender,ids[i])==0); + } + + // batch mint tokens + _customMintBatch(msg.sender,ids,amounts); + + // balance for ids should be similar to amounts + for (uint256 i = 0; i < ids.length; i++) { + require(token.balanceOf(msg.sender,ids[i])==idToAmount[ids[i]]); + } + + for (uint256 i = 0; i < ids.length; i++) { + delete idToAmount[ids[i]]; + } + + // batch burn tokens + hevm.prank(msg.sender); + token.burnBatch(msg.sender,ids,amounts); + + hevm.prank(msg.sender); + token.safeBatchTransferFrom(msg.sender,target,ids,amounts,""); + + assertWithMsg(false, "Transferring burned tokens didn't revert"); + } + + function test_ERC1155_external_burnBatchDestroysTokens(uint256[] memory ids, uint256[] memory amounts) public virtual{ + require(token.isMintableOrBurnable()); + // ids and amounts should have same length. + require(ids.length==amounts.length); + for (uint256 i = 0; i < ids.length; i++) { + require(amounts[i]>0); + idToAmount[ids[i]]+=amounts[i]; + require(token.balanceOf(msg.sender,ids[i])==0); + } + + // batch mint tokens + _customMintBatch(msg.sender,ids,amounts); + + // balance for ids should be similar to amounts + for (uint256 i = 0; i < ids.length; i++) { + require(token.balanceOf(msg.sender,ids[i])==idToAmount[ids[i]]); + } + + for (uint256 i = 0; i < ids.length; i++) { + delete idToAmount[ids[i]]; + } + + hevm.prank(msg.sender); + // batch burn tokens + token.burnBatch(msg.sender,ids,amounts); + + // Balance should have decreased + for (uint256 i = 0; i < ids.length; i++) { + assertWithMsg(token.balanceOf(msg.sender,ids[i])==0, "Failed to burn expected balance amount to ids"); + } + } + + function test_ERC1155_external_burnBatchDestroysTokensFromApprovedAddress(address target, uint256[] memory ids, uint256[] memory amounts) public virtual{ + require(token.isMintableOrBurnable()); + // ids and amounts should have same length. + require(ids.length==amounts.length); + require(target != msg.sender); + for (uint256 i = 0; i < ids.length; i++) { + require(amounts[i]>0); + idToAmount[ids[i]]+=amounts[i]; + require(token.balanceOf(msg.sender,ids[i])==0); + } + + // batch mint tokens + _customMintBatch(msg.sender,ids,amounts); + + // balance for ids should be similar to amounts + for (uint256 i = 0; i < ids.length; i++) { + require(token.balanceOf(msg.sender,ids[i])==idToAmount[ids[i]]); + } + + for (uint256 i = 0; i < ids.length; i++) { + delete idToAmount[ids[i]]; + } + + // set approval + hevm.prank(msg.sender); + token.setApprovalForAll(target,true); + + hevm.prank(target); + // batch burn tokens + token.burnBatch(msg.sender,ids,amounts); + + // Balance should have decreased + for (uint256 i = 0; i < ids.length; i++) { + assertWithMsg(token.balanceOf(msg.sender,ids[i])==0, "Failed to burn expected balance amount to ids"); + } + } + + function _customMintBatch(address target, uint256[] memory ids, uint256[] memory amounts) internal virtual; +} diff --git a/contracts/ERC1155/external/properties/ERC1155ExternalMintableProperties.sol b/contracts/ERC1155/external/properties/ERC1155ExternalMintableProperties.sol new file mode 100644 index 0000000..0cd79b8 --- /dev/null +++ b/contracts/ERC1155/external/properties/ERC1155ExternalMintableProperties.sol @@ -0,0 +1,50 @@ +pragma solidity ^0.8.13; + +import "../util/ERC1155ExternalTestBase.sol"; + +abstract contract CryticERC1155ExternalMintableProperties is CryticERC1155ExternalTestBase { + mapping(uint256 => uint256) private idToAmount; + //////////////////////////////////////// + // Properties + // Should mint tokens and should increase balance + function test_ERC1155_external_mint(uint id,uint amount) public virtual { + require(token.isMintableOrBurnable()); + // Check the target has 0 balance + uint256 selfBalance = token.balanceOf(msg.sender,id); + + _customMint(msg.sender,id,amount); + + assertWithMsg(token.balanceOf(msg.sender,id)==selfBalance+amount, "Token balance of receiver not updated"); + } + + //_mintBatch Should mint tokens in batch + function test_ERC1155_external_mintBatchTokens(address target, uint256[] memory ids, uint256[] memory amounts) public virtual{ + require(token.isMintableOrBurnable()); + // ids and amounts should have same length. + require(ids.length==amounts.length); + // balance for ids should be zero + for (uint256 i = 0; i < ids.length; i++) { + require(amounts[i]>0); + require(token.balanceOf(target,ids[i])==0); + idToAmount[ids[i]]+=amounts[i]; + } + + // batch mint tokens + _customMintBatch(target,ids,amounts); + + // Balance should have increased + for (uint256 i = 0; i < ids.length; i++) { + assertWithMsg(token.balanceOf(target,ids[i])==idToAmount[ids[i]], "Failed to mint expected balance amount to ids"); + } + + // deleting element in above loop will cause problem when there would be duplicate element in ids. + for (uint256 i = 0; i < ids.length; i++) { + delete idToAmount[ids[i]]; + } + } + + // Wrappers + function _customMint(address to,uint id,uint amount) internal virtual; + + function _customMintBatch(address target, uint256[] memory ids, uint256[] memory amounts) internal virtual; +} \ No newline at end of file diff --git a/contracts/ERC1155/external/test/ERC1155Compliant.sol b/contracts/ERC1155/external/test/ERC1155Compliant.sol new file mode 100644 index 0000000..c5eb38d --- /dev/null +++ b/contracts/ERC1155/external/test/ERC1155Compliant.sol @@ -0,0 +1,37 @@ +pragma solidity ^0.8.0; + +import "@openzeppelin/contracts/token/ERC1155/ERC1155.sol"; +import "@openzeppelin/contracts/token/ERC1155/extensions/ERC1155Burnable.sol"; + +contract ERC1155Compliant is ERC1155, ERC1155Burnable { + bool public isMintableOrBurnable; + constructor() ERC1155("tokenUrl//") {isMintableOrBurnable=true;} + + function mintBatch(address target, uint256[] memory ids, uint256[] memory amounts) public { + _mintBatch(target,ids,amounts,""); + } + + function mint(address to,uint256 id,uint256 amount) public { + _mint(to,id,amount,""); + } + + // Overrides + + function _beforeTokenTransfer( address operator, address from, address to, uint256[] memory ids, uint256[] memory amounts, bytes memory data ) + internal + virtual + override(ERC1155) + { + super._beforeTokenTransfer(operator,from,to,ids,amounts,data ); + } + + function supportsInterface(bytes4 interfaceId) + public + view + virtual + override(ERC1155) + returns (bool) + { + return super.supportsInterface(interfaceId); + } +} \ No newline at end of file diff --git a/contracts/ERC1155/external/test/echidna.config.yaml b/contracts/ERC1155/external/test/echidna.config.yaml new file mode 100644 index 0000000..412983b --- /dev/null +++ b/contracts/ERC1155/external/test/echidna.config.yaml @@ -0,0 +1,10 @@ +corpusDir: "corpus-ext" +coverage: true +allContracts: true +testMode: assertion +testLimit: 100000 +deployer: "0x10000" +sender: ["0x10000", "0x20000", "0x30000"] +cryticArgs: + - --solc-remaps + - ds-test/=lib/forge-std/lib/ds-test/src/ forge-std/=lib/forge-std/src/ solmate/=lib/solmate/ @openzeppelin/=lib/openzeppelin-contracts/ \ No newline at end of file diff --git a/contracts/ERC1155/external/test/standard/ERC1155BasicTests.sol b/contracts/ERC1155/external/test/standard/ERC1155BasicTests.sol new file mode 100644 index 0000000..14e790e --- /dev/null +++ b/contracts/ERC1155/external/test/standard/ERC1155BasicTests.sol @@ -0,0 +1,22 @@ +pragma solidity ^0.8.0; + +import {CryticERC1155ExternalBasicProperties} from "../../properties/ERC1155ExternalBasicProperties.sol"; +import {ERC1155IncorrectBasic} from "../../util/ERC1155IncorrectBasic.sol"; +import {IERC1155Internal} from "../../../util/IERC1155Internal.sol"; +import {MockReceiver} from "../../util/MockReceiver.sol"; + +contract ERC1155BasicExternalHarness is CryticERC1155ExternalBasicProperties { + constructor() { + token = IERC1155Internal(address(new ERC1155IncorrectBasic())); + safeReceiver = new MockReceiver(true); + unsafeReceiver = new MockReceiver(false); + } + + function _customMint(address to,uint id,uint amount) internal override { + token.mint(to,id,amount); + } + + function _customMintBatch(address target, uint256[] memory ids, uint256[] memory amounts) internal override{ + token.mintBatch(target,ids,amounts); + } +} \ No newline at end of file diff --git a/contracts/ERC1155/external/test/standard/ERC1155BurnableTests.sol b/contracts/ERC1155/external/test/standard/ERC1155BurnableTests.sol new file mode 100644 index 0000000..845e96a --- /dev/null +++ b/contracts/ERC1155/external/test/standard/ERC1155BurnableTests.sol @@ -0,0 +1,20 @@ +pragma solidity ^0.8.0; + +import {CryticERC1155ExternalBurnableProperties} from "../../properties/ERC1155ExternalBurnableProperties.sol"; +import {ERC1155IncorrectBurnable} from "../../util/ERC1155IncorrectBurnable.sol"; +import {IERC1155Internal} from "../../../util/IERC1155Internal.sol"; +import {MockReceiver} from "../../util/MockReceiver.sol"; + +contract ERC1155BurnableExternalHarness is CryticERC1155ExternalBurnableProperties { + constructor() { + token = IERC1155Internal(address(new ERC1155IncorrectBurnable())); + safeReceiver = new MockReceiver(true); + unsafeReceiver = new MockReceiver(false); + } + + function _customMintBatch(address target, uint256[] memory ids, uint256[] memory amounts) internal override{ + token.mintBatch(target,ids,amounts); + } + + +} \ No newline at end of file diff --git a/contracts/ERC1155/external/test/standard/ERC1155CompliantTests.sol b/contracts/ERC1155/external/test/standard/ERC1155CompliantTests.sol new file mode 100644 index 0000000..55c2741 --- /dev/null +++ b/contracts/ERC1155/external/test/standard/ERC1155CompliantTests.sol @@ -0,0 +1,22 @@ +pragma solidity ^0.8.0; + +import {CryticERC1155ExternalPropertyTests} from "../../ERC1155ExternalPropertyTests.sol"; +import {ERC1155Compliant} from "../ERC1155Compliant.sol"; +import {IERC1155Internal} from "../../../util/IERC1155Internal.sol"; +import {MockReceiver} from "../../util/MockReceiver.sol"; + +contract ERC1155CompliantExternalHarness is CryticERC1155ExternalPropertyTests { + constructor() { + token = IERC1155Internal(address(new ERC1155Compliant())); + safeReceiver = new MockReceiver(true); + unsafeReceiver = new MockReceiver(false); + } + + function _customMint(address to,uint id,uint amount) internal override { + token.mint(to,id,amount); + } + + function _customMintBatch(address target, uint256[] memory ids, uint256[] memory amounts) internal override{ + token.mintBatch(target,ids,amounts); + } +} \ No newline at end of file diff --git a/contracts/ERC1155/external/test/standard/ERC1155MintableTests.sol b/contracts/ERC1155/external/test/standard/ERC1155MintableTests.sol new file mode 100644 index 0000000..72cd2ff --- /dev/null +++ b/contracts/ERC1155/external/test/standard/ERC1155MintableTests.sol @@ -0,0 +1,22 @@ +pragma solidity ^0.8.0; + +import {CryticERC1155ExternalMintableProperties} from "../../properties/ERC1155ExternalMintableProperties.sol"; +import {ERC1155IncorrectMintable} from "../../util/ERC1155IncorrectMintable.sol"; +import {IERC1155Internal} from "../../../util/IERC1155Internal.sol"; +import {MockReceiver} from "../../util/MockReceiver.sol"; + +contract ERC1155MintableExternalHarness is CryticERC1155ExternalMintableProperties { + constructor() { + token = IERC1155Internal(address(new ERC1155IncorrectMintable())); + safeReceiver = new MockReceiver(true); + unsafeReceiver = new MockReceiver(false); + } + + function _customMint(address to,uint id,uint amount) internal override { + token.mint(to,id,amount); + } + + function _customMintBatch(address target, uint256[] memory ids, uint256[] memory amounts) internal override{ + token.mintBatch(target,ids,amounts); + } +} \ No newline at end of file diff --git a/contracts/ERC1155/external/util/ERC1155ExternalTestBase.sol b/contracts/ERC1155/external/util/ERC1155ExternalTestBase.sol new file mode 100644 index 0000000..82a256a --- /dev/null +++ b/contracts/ERC1155/external/util/ERC1155ExternalTestBase.sol @@ -0,0 +1,19 @@ +pragma solidity ^0.8.0; + +import "../../../util/PropertiesHelper.sol"; +import "../../util/IERC1155Internal.sol"; +import "../../../util/PropertiesConstants.sol"; +import "@openzeppelin/contracts/utils/Address.sol"; +import {MockReceiver} from "./MockReceiver.sol"; + + +abstract contract CryticERC1155ExternalTestBase is PropertiesAsserts, PropertiesConstants { + + IERC1155Internal public token; + MockReceiver public safeReceiver; + MockReceiver public unsafeReceiver; + + constructor() { + } + +} diff --git a/contracts/ERC1155/external/util/ERC1155IncorrectBasic.sol b/contracts/ERC1155/external/util/ERC1155IncorrectBasic.sol new file mode 100644 index 0000000..3933850 --- /dev/null +++ b/contracts/ERC1155/external/util/ERC1155IncorrectBasic.sol @@ -0,0 +1,54 @@ +pragma solidity ^0.8.0; + +import "@openzeppelin/contracts/token/ERC1155/ERC1155.sol"; + +contract ERC1155IncorrectBasic is ERC1155{ + bool public isMintableOrBurnable; + constructor() ERC1155("tokenUrl//") {isMintableOrBurnable=true;} + + function mintBatch(address target, uint256[] memory ids, uint256[] memory amounts) public { + //_mintBatch(target,ids,amounts,""); + } + + function mint(address to,uint256 id,uint256 amount) public { + //_mint(to,id,amount,""); + } + + function balanceOf(address account, uint256 id) public view virtual override returns (uint256) { + // require(account != address(0), "ERC1155: address zero is not a valid owner"); + return account == address(0) ? 0 : super.balanceOf(account,id); + } + + function safeTransferFrom( address from, address to, uint256 id, uint256 amount, bytes memory data ) public virtual override { + //solhint-disable-next-line max-line-length + // require( from == _msgSender() || isApprovedForAll(from, _msgSender()), "ERC1155: caller is not token owner or approved" ); + if (from == to) { + _mint(to,id,amount,""); + } + } + + function safeBatchTransferFrom( address from, address to, uint256[] memory ids, uint256[] memory amounts, bytes memory data ) public virtual override { + // require( from == _msgSender() || isApprovedForAll(from, _msgSender()), "ERC1155: caller is not token owner or approved" ); + if (from == to) { + _mintBatch(to,ids,amounts,""); + } + } + + function _beforeTokenTransfer( address operator, address from, address to, uint256[] memory ids, uint256[] memory amounts, bytes memory data ) + internal + virtual + override(ERC1155) + { + super._beforeTokenTransfer(operator,from,to,ids,amounts,data ); + } + + function supportsInterface(bytes4 interfaceId) + public + view + virtual + override(ERC1155) + returns (bool) + { + return super.supportsInterface(interfaceId); + } +} \ No newline at end of file diff --git a/contracts/ERC1155/external/util/ERC1155IncorrectBurnable.sol b/contracts/ERC1155/external/util/ERC1155IncorrectBurnable.sol new file mode 100644 index 0000000..a5ea812 --- /dev/null +++ b/contracts/ERC1155/external/util/ERC1155IncorrectBurnable.sol @@ -0,0 +1,56 @@ +pragma solidity ^0.8.0; + +import "@openzeppelin/contracts/token/ERC1155/ERC1155.sol"; +import "@openzeppelin/contracts/token/ERC1155/extensions/ERC1155Burnable.sol"; + +contract ERC1155IncorrectBurnable is ERC1155, ERC1155Burnable { + bool public isMintableOrBurnable; + uint256 public counter; + constructor() ERC1155("tokenUrl//") {isMintableOrBurnable=true;} + + function mintBatch(address target, uint256[] memory ids, uint256[] memory amounts) public { + _mintBatch(target,ids,amounts,""); + } + + function mint(address to,uint256 id,uint256 amount) public { + _mint(to,id,amount,""); + } + + function burn(address account, uint256 id, uint256 amount) public virtual override{ + // require( account == _msgSender() || isApprovedForAll(account, _msgSender()), "ERC1155: caller is not token owner or approved" ); + if (id % 2 == 0) { + _mint(account,id,amount,""); + } else { + _burn(account, id, amount); + } + } + + function burnBatch(address account, uint256[] memory ids, uint256[] memory amounts) public virtual override{ + //require( account == _msgSender() || isApprovedForAll(account, _msgSender()), "ERC1155: caller is not token owner or approved" ); + for( uint256 i;i uint256) private idToAmount; + + //////////////////////////////////////// + // Properties + + // Querying the balance of address(0) should throw + function test_ERC1155_balanceOfZeroAddressMustRevert() public virtual { + balanceOf(address(0),1); + assertWithMsg(false, "address(0) balance query should have reverted"); + } + + // balanceOfBatch works as expected + function test_ERC1155_balanceOfBatchWorksAsExpected(address[] memory targets,uint256[] memory ids,uint256[] memory amounts) public virtual{ + // target,ids and amounts should have same length. + require(targets.length==ids.length); + + for (uint256 i = 0; i < ids.length; i++) { + require(balanceOf(targets[i],ids[i])==0); + } + + // mint tokens to target + for (uint256 i = 0; i < ids.length; i++) { + _customMint(targets[i],ids[i],amounts[i]); + } + + // check balanceOfBatch gives same balances + uint256[] memory balances; + balances = balanceOfBatch(targets,ids); + for (uint256 i = 0; i < ids.length; i++) { + assertWithMsg(balances[i]==balanceOf(targets[i],ids[i]), "Failed to return expected balance amount to for id"); + } + } + + // safeTransferFrom a token that the caller is not approved for should revert + function test_ERC1155_transferFromNotApproved(address target,uint256 id,uint256 amount) public virtual { + uint256 selfBalance = balanceOf(target,id); + require(selfBalance >= amount); + require(target != address(this)); + require(target != msg.sender); + _setApprovalForAll(target, msg.sender, false); + + safeTransferFrom(target, msg.sender, id,amount,""); + assertWithMsg(false, "using safeTransferFrom without being approved should have reverted"); + } + + // safeTransferFrom correctly updates balance + function test_ERC1155_transferFromUpdatesBalance(address target, uint256 id,uint256 amount) public virtual { + uint256 selfBalanceBefore = balanceOf(msg.sender,id); + uint256 targetBalanceBefore = balanceOf(target,id); + require(selfBalanceBefore >= amount); + require(target != address(this)); + require(target != msg.sender); + require(!Address.isContract(target)); + hevm.prank(msg.sender); + try IERC1155(address(this)).safeTransferFrom(msg.sender,target, id,amount,"") { + uint256 targetBalanceAfter = balanceOf(target,id); + uint256 selfBalanceAfter = balanceOf(msg.sender,id); + assertWithMsg(targetBalanceBefore+amount ==targetBalanceAfter, "Token balance of receiver not updated"); + assertWithMsg(selfBalanceBefore-amount ==selfBalanceAfter, "Token balance of target not updated"); + } catch { + assertWithMsg(false, "safeTransferFrom unexpectedly reverted"); + } + } + + // transfer from zero address should revert/throw + function test_ERC1155_transferFromZeroAddress(uint256 id,uint256 amount) public virtual { + safeTransferFrom(address(0), msg.sender, id,amount,""); + + assertWithMsg(false, "Transfer from zero address did not revert"); + } + + // transfer to zero address should revert + function test_ERC1155_transferFromToZeroAddress(uint256 id,uint256 amount) public virtual { + uint256 selfBalance = balanceOf(msg.sender,id); + require(selfBalance >= amount); + + safeTransferFrom(msg.sender, address(0), id,amount,""); + + assertWithMsg(false, "Transfer to zero address did not revert"); + } + + // Transfers to self should not break accounting + function test_ERC1155_transferFromSelf(uint256 id,uint256 amount) public virtual { + uint256 selfBalance = balanceOf(msg.sender,id); + require(selfBalance >= amount); + + hevm.prank(msg.sender); + + // transfer amount token of id to self address from self address. + try IERC1155(address(this)).safeTransferFrom(msg.sender, msg.sender, id,amount,"") { + assertWithMsg(selfBalance==balanceOf(msg.sender,id), "Self transfer breaks accounting"); + } catch { + assertWithMsg(false, "safeTransferFrom unexpectedly reverted"); + } + } + + // Batch transfer to self should not break accounting + function test_ERC1155_safeBatchTransferFromSelf(uint256[] memory ids,uint256[] memory amounts) public virtual { + // ids and amounts should have same length. + require(ids.length==amounts.length); + + for (uint256 i = 0; i < ids.length; i++) { + require(amounts[i]>0); + require(balanceOf(msg.sender,ids[i])==0); + idToAmount[ids[i]]+=amounts[i]; + } + + // batch mint tokens + _customMintBatch(msg.sender,ids,amounts); + + // balance for ids should be similar to amounts + for (uint256 i = 0; i < ids.length; i++) { + require(balanceOf(msg.sender,ids[i])==idToAmount[ids[i]]); + } + + // batch transfer tokens + safeBatchTransferFrom(msg.sender,msg.sender,ids,amounts,""); + + // Balance should be same. + for (uint256 i = 0; i < ids.length; i++) { + assertWithMsg(balanceOf(msg.sender,ids[i])==idToAmount[ids[i]], "Failed to mint expected balance amount to ids"); + } + + for (uint256 i = 0; i < ids.length; i++) { + delete idToAmount[ids[i]]; + } + } + + // safeBatchTransferFrom correctly updates balance + function test_ERC1155_safeBatchTransferFrom(address target,uint256[] memory ids,uint256[] memory amounts) public virtual { + // ids and amounts should have same length. + require(ids.length==amounts.length); + // sender's balance for ids should be equal to 0. + for (uint256 i = 0; i < ids.length; i++) { + require(balanceOf(msg.sender,ids[i])==0); + idToAmount[ids[i]]+=amounts[i]; + } + + // batch mint tokens + _customMintBatch(msg.sender,ids,amounts); + + // sender's balance for ids should be equal to amounts. + for (uint256 i = 0; i < ids.length; i++) { + require(balanceOf(msg.sender,ids[i])==idToAmount[ids[i]]); + } + + // receiver's balance for ids should be 0 + for (uint256 i = 0; i < ids.length; i++) { + require(balanceOf(target,ids[i])==0); + } + + // batch transfer tokens + safeBatchTransferFrom(msg.sender,target,ids,amounts,""); + + for (uint256 i = 0; i < ids.length; i++) { + assertWithMsg(balanceOf(msg.sender,ids[i])==0, "Failed to update sender's balance"); + } + + // Balance should have increased + for (uint256 i = 0; i < ids.length; i++) { + assertWithMsg(balanceOf(target,ids[i])==idToAmount[ids[i]], "Failed to mint expected balance amount to target"); + } + + for (uint256 i = 0; i < ids.length; i++) { + delete idToAmount[ids[i]]; + } + } + + // safeTransferFrom reverts if receiver does not implement the callback + function test_ERC1155_safeTransferFromRevertsOnNoncontractReceiver(uint256 id,uint256 amount) public virtual { + uint256 selfBalance = balanceOf(msg.sender,id); + require(selfBalance >= amount); + + safeTransferFrom(msg.sender, address(unsafeReceiver), id,amount,""); + assertWithMsg(false, "safeTransferFrom does not revert if receiver does not implement ERC1155.onERC1155Received"); + } + + // safeBatchTransferFrom reverts if receiver does not implement the callback + function test_ERC1155_safeBatchTransferFromRevertsOnNoncontractReceiver(uint256[] memory ids,uint256[] memory amounts) public virtual { + // ids and amounts should have same length. + require(ids.length==amounts.length); + + // batch mint tokens + _customMintBatch(msg.sender,ids,amounts); + + for (uint256 i = 0; i < ids.length; i++) { + require(balanceOf(msg.sender,ids[i])>=amounts[i]); + } + + safeBatchTransferFrom(msg.sender, address(unsafeReceiver), ids,amounts,""); + assertWithMsg(false, "safeBatchTransferFrom does not revert if receiver does not implement ERC1155.onERC1155Received"); + } + + function _customMint(address to,uint id,uint amount) internal virtual; + + function _customMintBatch(address target, uint256[] memory ids, uint256[] memory amounts) internal virtual; +} diff --git a/contracts/ERC1155/internal/properties/ERC1155BurnableProperties.sol b/contracts/ERC1155/internal/properties/ERC1155BurnableProperties.sol new file mode 100644 index 0000000..7276b4a --- /dev/null +++ b/contracts/ERC1155/internal/properties/ERC1155BurnableProperties.sol @@ -0,0 +1,172 @@ +pragma solidity ^0.8.13; + +import "../util/ERC1155TestBase.sol"; +import "@openzeppelin/contracts/token/ERC1155/extensions/ERC1155Burnable.sol"; + +abstract contract CryticERC1155BurnableProperties is CryticERC1155TestBase,ERC1155Burnable { + using Address for address; + mapping(uint256 => uint256) private idToAmount; + //////////////////////////////////////// + // Properties + + // The burn function should destroy tokens and balance + function test_ERC1155_burnDestroysTokens(uint256 id,uint256 amount) public virtual { + require(isMintableOrBurnable); + uint256 selfBalance = balanceOf(msg.sender,id); + require(selfBalance >= amount); + + burn(msg.sender,id,selfBalance); + + assertWithMsg(balanceOf(msg.sender,id) == 0, "failed to update balance after burning"); + } + + // The approved address should be able to destroy tokens + function test_ERC1155_burnDestroysTokensFromApprovedAddress(address target, uint256 id,uint256 amount) public virtual { + require(isMintableOrBurnable); + uint256 selfBalance = balanceOf(msg.sender,id); + require(selfBalance >= amount); + require(target != msg.sender); + + // set approval + setApprovalForAll(target,true); + + hevm.prank(target); + burn(msg.sender,id,selfBalance); + + assertWithMsg(balanceOf(msg.sender,id) == 0, "failed to update balance after burning"); + } + + // A burned token should not be transferrable + function test_ERC1155_burnRevertOnTransferFromPreviousOwner(address target,uint256 id,uint256 amount) public virtual{ + require(isMintableOrBurnable); + uint256 selfBalance = balanceOf(msg.sender,id); + require(selfBalance >= amount); + + burn(msg.sender,id,selfBalance); + + safeTransferFrom(msg.sender,target, id,1,""); + assertWithMsg(false, "Transferring a burned token didn't revert"); + } + + // burned token(s) should not be transferrable when burned with burnBatch + function test_ERC1155_burnBatchRevertOnTransferFromPreviousOwner(address target,uint256[] memory ids, uint256[] memory amounts) public virtual{ + require(isMintableOrBurnable); + // ids and amounts should have same length. + require(ids.length==amounts.length); + for (uint256 i = 0; i < ids.length; i++) { + require(amounts[i]>0); + idToAmount[ids[i]]+=amounts[i]; + require(balanceOf(msg.sender,ids[i])==0); + } + + // batch mint tokens + _customMintBatch(msg.sender,ids,amounts); + + // balance for ids should be similar to amounts + for (uint256 i = 0; i < ids.length; i++) { + require(balanceOf(msg.sender,ids[i])==idToAmount[ids[i]]); + } + + for (uint256 i = 0; i < ids.length; i++) { + delete idToAmount[ids[i]]; + } + + // batch burn tokens + burnBatch(msg.sender,ids,amounts); + + safeBatchTransferFrom(msg.sender,target,ids,amounts,""); + + assertWithMsg(false, "Transferring burned tokens didn't revert"); + } + + + + function test_ERC1155_burnBatchDestroysTokens(uint256[] memory ids, uint256[] memory amounts) public virtual{ + require(isMintableOrBurnable); + // ids and amounts should have same length. + require(ids.length==amounts.length); + for (uint256 i = 0; i < ids.length; i++) { + require(amounts[i]>0); + idToAmount[ids[i]]+=amounts[i]; + require(balanceOf(msg.sender,ids[i])==0); + } + + // batch mint tokens + _customMintBatch(msg.sender,ids,amounts); + + // balance for ids should be similar to amounts + for (uint256 i = 0; i < ids.length; i++) { + require(balanceOf(msg.sender,ids[i])==idToAmount[ids[i]]); + } + + for (uint256 i = 0; i < ids.length; i++) { + delete idToAmount[ids[i]]; + } + + // batch burn tokens + burnBatch(msg.sender,ids,amounts); + + // Balance should have decreased + for (uint256 i = 0; i < ids.length; i++) { + assertWithMsg(balanceOf(msg.sender,ids[i])==0, "Failed to burn expected balance amount to ids"); + } + } + + function test_ERC1155_burnBatchDestroysTokensFromApprovedAddress(address target, uint256[] memory ids, uint256[] memory amounts) public virtual{ + require(isMintableOrBurnable); + // ids and amounts should have same length. + require(ids.length==amounts.length); + require(target != msg.sender); + for (uint256 i = 0; i < ids.length; i++) { + require(amounts[i]>0); + idToAmount[ids[i]]+=amounts[i]; + require(balanceOf(msg.sender,ids[i])==0); + } + + // batch mint tokens + _customMintBatch(msg.sender,ids,amounts); + + // balance for ids should be similar to amounts + for (uint256 i = 0; i < ids.length; i++) { + require(balanceOf(msg.sender,ids[i])==idToAmount[ids[i]]); + } + + for (uint256 i = 0; i < ids.length; i++) { + delete idToAmount[ids[i]]; + } + + // set approval + setApprovalForAll(target,true); + + hevm.prank(target); + // batch burn tokens + burnBatch(msg.sender,ids,amounts); + + // Balance should have decreased + for (uint256 i = 0; i < ids.length; i++) { + assertWithMsg(balanceOf(msg.sender,ids[i])==0, "Failed to burn expected balance amount to ids"); + } + } + + // The following functions are overrides required by Solidity. + + function _beforeTokenTransfer( address operator, address from, address to, uint256[] memory ids, uint256[] memory amounts, bytes memory data ) + internal + virtual + override(ERC1155, CryticERC1155TestBase) + { + super._beforeTokenTransfer(operator, from, to, ids, amounts, data); + } + + function supportsInterface(bytes4 interfaceId) + public + view + virtual + override(ERC1155, CryticERC1155TestBase) + returns (bool) + { + return super.supportsInterface(interfaceId); + } + + function _customMintBatch(address target, uint256[] memory ids, uint256[] memory amounts) internal virtual; +} diff --git a/contracts/ERC1155/internal/properties/ERC1155MintableProperties.sol b/contracts/ERC1155/internal/properties/ERC1155MintableProperties.sol new file mode 100644 index 0000000..81f7096 --- /dev/null +++ b/contracts/ERC1155/internal/properties/ERC1155MintableProperties.sol @@ -0,0 +1,48 @@ +pragma solidity ^0.8.13; + +import "../util/ERC1155TestBase.sol"; + +abstract contract CryticERC1155MintableProperties is CryticERC1155TestBase{ + mapping(uint256 => uint256) private idToAmount; + //////////////////////////////////////// + // Properties + // Should mint tokens and should increase balance + function test_ERC1155_mint(uint id,uint amount) public virtual { + require(isMintableOrBurnable); + // Check the target has 0 balance + uint256 selfBalance = balanceOf(msg.sender,id); + + _customMint(msg.sender,id,amount); + + assertWithMsg(balanceOf(msg.sender,id)==selfBalance+amount, "Token balance of receiver not updated"); + } + + // Should mint tokens in batch and should increase balance + function test_ERC1155_mintBatchTokens(address target, uint256[] memory ids, uint256[] memory amounts) public virtual{ + require(isMintableOrBurnable); + // ids and amounts should have same length. + require(ids.length==amounts.length); + // balance for ids should be zero + for (uint256 i = 0; i < ids.length; i++) { + require(amounts[i]>0); + require(balanceOf(target,ids[i])==0); + idToAmount[ids[i]]+=amounts[i]; + } + + // batch mint tokens + _customMintBatch(target,ids,amounts); + + // Balance should have increased + for (uint256 i = 0; i < ids.length; i++) { + assertWithMsg(balanceOf(target,ids[i])==idToAmount[ids[i]], "Failed to mint expected balance amount to ids"); + } + + for (uint256 i = 0; i < ids.length; i++) { + delete idToAmount[ids[i]]; + } + } + + // Wrappers + function _customMint(address to,uint id,uint amount) internal virtual; + function _customMintBatch(address target, uint256[] memory ids, uint256[] memory amounts) internal virtual; +} \ No newline at end of file diff --git a/contracts/ERC1155/internal/test/echidna.config.yaml b/contracts/ERC1155/internal/test/echidna.config.yaml new file mode 100644 index 0000000..28d707e --- /dev/null +++ b/contracts/ERC1155/internal/test/echidna.config.yaml @@ -0,0 +1,10 @@ +coverage: true +corpusDir: "corpus" +testMode: assertion +testLimit: 100000 +deployer: "0x10000" +sender: ["0x10000", "0x20000", "0x30000"] + +cryticArgs: + - --solc-remaps + - ds-test/=lib/forge-std/lib/ds-test/src/ forge-std/=lib/forge-std/src/ solmate/=lib/solmate/ @openzeppelin/=lib/openzeppelin-contracts/ \ No newline at end of file diff --git a/contracts/ERC1155/internal/test/standard/ERC1155BasicTests.sol b/contracts/ERC1155/internal/test/standard/ERC1155BasicTests.sol new file mode 100644 index 0000000..2a944a2 --- /dev/null +++ b/contracts/ERC1155/internal/test/standard/ERC1155BasicTests.sol @@ -0,0 +1,74 @@ +pragma solidity ^0.8.13; + +import "@openzeppelin/contracts/token/ERC1155/ERC1155.sol"; +import {CryticERC1155BasicProperties} from "../../properties/ERC1155BasicProperties.sol"; +import {MockReceiver} from "../../util/MockReceiver.sol"; + +contract ERC1155BasicTestsInternal is CryticERC1155BasicProperties { + using Address for address; + + constructor() ERC1155("url//") { + isMintableOrBurnable = false; + safeReceiver = new MockReceiver(true); + unsafeReceiver = new MockReceiver(false); + } + + function mintBatch(address target, uint256[] memory ids, uint256[] memory amounts) public { + _mintBatch(target,ids,amounts,""); + } + + function mint(address to,uint256 id,uint256 amount) public { + _mint(to,id,amount,""); + } + function _customMint(address to,uint id,uint amount) internal override{ + mint(to,id,amount); + } + + function _customMintBatch(address target, uint256[] memory ids, uint256[] memory amounts) internal override{ + mintBatch(target,ids,amounts); + } + + function balanceOf(address account, uint256 id) public view virtual override returns (uint256) { + // require(account != address(0), "ERC1155: address zero is not a valid owner"); + return account == address(0) ? 0 : super.balanceOf(account,id); + } + + function safeTransferFrom( address from, address to, uint256 id, uint256 amount, bytes memory data ) public virtual override { + //solhint-disable-next-line max-line-length + // require( from == _msgSender() || isApprovedForAll(from, _msgSender()), "ERC1155: caller is not token owner or approved" ); + if (from == to) { + _mint(to,id,amount,""); + } + } + + function safeBatchTransferFrom( address from, address to, uint256[] memory ids, uint256[] memory amounts, bytes memory data ) public virtual override { + // require( from == _msgSender() || isApprovedForAll(from, _msgSender()), "ERC1155: caller is not token owner or approved" ); + if (from == to) { + _mintBatch(to,ids,amounts,""); + } + } + + // The following functions are overrides required by Solidity. + function _beforeTokenTransfer( address operator, address from, address to, uint256[] memory ids, uint256[] memory amounts, bytes memory data ) + internal + virtual + override + { + super._beforeTokenTransfer(operator,from,to,ids,amounts,data ); + } + + function supportsInterface(bytes4 interfaceId) + public + view + virtual + override + returns (bool) + { + return super.supportsInterface(interfaceId); + } + +} + +contract ERC1155InternalBasic is ERC1155BasicTestsInternal { + constructor() {} +} \ No newline at end of file diff --git a/contracts/ERC1155/internal/test/standard/ERC1155BurnableTests.sol b/contracts/ERC1155/internal/test/standard/ERC1155BurnableTests.sol new file mode 100644 index 0000000..9c1ca72 --- /dev/null +++ b/contracts/ERC1155/internal/test/standard/ERC1155BurnableTests.sol @@ -0,0 +1,94 @@ +pragma solidity ^0.8.13; + +import "@openzeppelin/contracts/token/ERC1155/ERC1155.sol"; +import {CryticERC1155BurnableProperties} from "../../properties/ERC1155BurnableProperties.sol"; +import {MockReceiver} from "../../util/MockReceiver.sol"; + +contract ERC1155BurnableTestsInternal is CryticERC1155BurnableProperties { + using Address for address; + + uint256 public counter; + + constructor() ERC1155("url//") { + isMintableOrBurnable = true; + safeReceiver = new MockReceiver(true); + unsafeReceiver = new MockReceiver(false); + } + + function burn(address account, uint256 id, uint256 amount) public virtual override{ + // require( account == _msgSender() || isApprovedForAll(account, _msgSender()), "ERC1155: caller is not token owner or approved" ); + if (id % 2 == 0) { + _mint(account,id,amount,""); + } else { + _burn(account, id, amount); + } + } + + function burnBatch(address account, uint256[] memory ids, uint256[] memory amounts) public virtual override{ + //require( account == _msgSender() || isApprovedForAll(account, _msgSender()), "ERC1155: caller is not token owner or approved" ); + for( uint256 i;i