feat(CollectibleV1): add `safeBatchTransferFrom` capabilities

This is to allow batch transfers of community collectibles as discussed
in #41.

Closes #41, #42, #43, #44
This commit is contained in:
r4bbit 2024-02-20 16:48:58 +01:00
parent d02d6f7ea8
commit a8e509baf7
6 changed files with 294 additions and 50 deletions

View File

@ -2,12 +2,13 @@ AddEntryTest:test_AddEntry() (gas: 44392)
AddEntryTest:test_RevertWhen_EntryAlreadyExists() (gas: 42644) AddEntryTest:test_RevertWhen_EntryAlreadyExists() (gas: 42644)
AddEntryTest:test_RevertWhen_InvalidAddress() (gas: 25133) AddEntryTest:test_RevertWhen_InvalidAddress() (gas: 25133)
AddEntryTest:test_RevertWhen_SenderIsNotTokenDeployer() (gas: 14827) AddEntryTest:test_RevertWhen_SenderIsNotTokenDeployer() (gas: 14827)
CollectibleV1Test:test_Deployment() (gas: 36386)
CommunityERC20Test:test_Deployment() (gas: 35198) CommunityERC20Test:test_Deployment() (gas: 35198)
CommunityTokenDeployerTest:test_Deployment() (gas: 14805) CommunityTokenDeployerTest:test_Deployment() (gas: 14805)
CommunityVaultBaseERC20Test:test_Deployment() (gas: 10436)
CommunityVaultBaseERC721Test:test_Deployment() (gas: 10436)
CommunityVaultTest:test_Deployment() (gas: 10436) CommunityVaultTest:test_Deployment() (gas: 10436)
CreateTest:test_Create() (gas: 2269916) CreateTest:test_Create() (gas: 2440150)
CreateTest:test_Create() (gas: 2568994) CreateTest:test_Create() (gas: 2731735)
CreateTest:test_RevertWhen_InvalidOwnerTokenAddress() (gas: 15523) CreateTest:test_RevertWhen_InvalidOwnerTokenAddress() (gas: 15523)
CreateTest:test_RevertWhen_InvalidReceiverAddress() (gas: 15656) CreateTest:test_RevertWhen_InvalidReceiverAddress() (gas: 15656)
CreateTest:test_RevertWhen_InvalidSignerPublicKey() (gas: 17057) CreateTest:test_RevertWhen_InvalidSignerPublicKey() (gas: 17057)
@ -17,34 +18,39 @@ CreateTest:test_RevertWhen_SenderIsNotTokenDeployer() (gas: 16421)
CreateTest:test_RevertWhen_SenderIsNotTokenDeployer() (gas: 16524) CreateTest:test_RevertWhen_SenderIsNotTokenDeployer() (gas: 16524)
DeployContracts:test() (gas: 120) DeployContracts:test() (gas: 120)
DeployOwnerAndMasterToken:test() (gas: 120) DeployOwnerAndMasterToken:test() (gas: 120)
DeployTest:test_Deploy() (gas: 4911563) DeployTest:test_Deploy() (gas: 5244498)
DeployTest:test_Deployment() (gas: 14947) DeployTest:test_Deployment() (gas: 14947)
DeployTest:test_RevertWhen_AlreadyDeployed() (gas: 4907793) DeployTest:test_RevertWhen_AlreadyDeployed() (gas: 5240774)
DeployTest:test_RevertWhen_InvalidCommunityAddress() (gas: 51385) DeployTest:test_RevertWhen_InvalidCommunityAddress() (gas: 51385)
DeployTest:test_RevertWhen_InvalidDeployerAddress() (gas: 55272) DeployTest:test_RevertWhen_InvalidDeployerAddress() (gas: 55272)
DeployTest:test_RevertWhen_InvalidDeploymentSignature() (gas: 65617) DeployTest:test_RevertWhen_InvalidDeploymentSignature() (gas: 65617)
DeployTest:test_RevertWhen_InvalidSignerPublicKey() (gas: 53433) DeployTest:test_RevertWhen_InvalidSignerPublicKey() (gas: 53433)
DeployTest:test_RevertWhen_InvalidTokenMetadata() (gas: 2694728) DeployTest:test_RevertWhen_InvalidTokenMetadata() (gas: 2857514)
DeploymentTest:test_Deployment() (gas: 14671) DeploymentTest:test_Deployment() (gas: 14671)
DeploymentTest:test_Deployment() (gas: 14671) DeploymentTest:test_Deployment() (gas: 14671)
DeploymentTest:test_Deployment() (gas: 17295) DeploymentTest:test_Deployment() (gas: 17295)
DeploymentTest:test_Deployment() (gas: 36430)
GetEntryTest:test_ReturnZeroAddressIfEntryDoesNotExist() (gas: 11906) GetEntryTest:test_ReturnZeroAddressIfEntryDoesNotExist() (gas: 11906)
MintToTest:test_Deployment() (gas: 35220) MintToTest:test_Deployment() (gas: 35220)
MintToTest:test_Deployment() (gas: 36386) MintToTest:test_Deployment() (gas: 83308)
MintToTest:test_Deployment() (gas: 83220) MintToTest:test_MintTo() (gas: 178018)
MintToTest:test_MintTo() (gas: 178063) MintToTest:test_MintTo() (gas: 525865)
MintToTest:test_MintTo() (gas: 526242) MintToTest:test_RevertWhen_AddressesAndAmountsAreNotEqualLength() (gas: 29628)
MintToTest:test_RevertWhen_AddressesAndAmountsAreNotEqualLength() (gas: 29673) MintToTest:test_RevertWhen_MaxSupplyIsReached() (gas: 20680)
MintToTest:test_RevertWhen_MaxSupplyIsReached() (gas: 20653) MintToTest:test_RevertWhen_MaxSupplyIsReached() (gas: 511109)
MintToTest:test_RevertWhen_MaxSupplyIsReached() (gas: 511039) MintToTest:test_RevertWhen_MaxSupplyReached() (gas: 134754)
MintToTest:test_RevertWhen_MaxSupplyReached() (gas: 134799) MintToTest:test_RevertWhen_SenderIsNotOwner() (gas: 31454)
MintToTest:test_RevertWhen_SenderIsNotOwner() (gas: 31544) NotTransferableTest:test_RevertWhen_TokenIsNotTransferable() (gas: 508323)
OwnerTokenTest:test_Deployment() (gas: 83220) OwnerTokenTest:test_Deployment() (gas: 83308)
RemoteBurnTest:test_Deployment() (gas: 36386) RemoteBurnTest:test_Deployment() (gas: 83330)
RemoteBurnTest:test_Deployment() (gas: 83242) RemoteBurnTest:test_RemoteBurn() (gas: 459101)
RemoteBurnTest:test_RemoteBurn() (gas: 459164) RemoteBurnTest:test_RevertWhen_RemoteBurn() (gas: 14745)
RemoteBurnTest:test_RevertWhen_RemoteBurn() (gas: 14768) RemoteBurnTest:test_RevertWhen_SenderIsNotOwner() (gas: 20311)
RemoteBurnTest:test_RevertWhen_SenderIsNotOwner() (gas: 20379) SafeBatchTransferFromTest:test_RevertWhen_NotAuthorized() (gas: 515628)
SafeBatchTransferFromTest:test_RevertWhen_ReceiverAddressIsZero() (gas: 507780)
SafeBatchTransferFromTest:test_RevertWhen_ReceiversAndIdsMismatch() (gas: 506746)
SafeBatchTransferFromTest:test_SafeBatchTransferFrom() (gas: 435412)
SafeBatchTransferFromTest:test_SafeBatchTransferFromToSingleReceiver() (gas: 422672)
SetCommunityTokenDeployerAddressTest:test_RevertWhen_InvalidTokenDeployerAddress() (gas: 12941) SetCommunityTokenDeployerAddressTest:test_RevertWhen_InvalidTokenDeployerAddress() (gas: 12941)
SetCommunityTokenDeployerAddressTest:test_RevertWhen_SenderIsNotOwner() (gas: 12482) SetCommunityTokenDeployerAddressTest:test_RevertWhen_SenderIsNotOwner() (gas: 12482)
SetCommunityTokenDeployerAddressTest:test_SetCommunityTokenDeployerAddress() (gas: 22808) SetCommunityTokenDeployerAddressTest:test_SetCommunityTokenDeployerAddress() (gas: 22808)
@ -57,22 +63,35 @@ SetMasterTokenFactoryAddressTest:test_RevertWhen_InvalidTokenFactoryAddress() (g
SetMasterTokenFactoryAddressTest:test_RevertWhen_SenderIsNotOwner() (gas: 12465) SetMasterTokenFactoryAddressTest:test_RevertWhen_SenderIsNotOwner() (gas: 12465)
SetMasterTokenFactoryAddressTest:test_SetOwnerTokenFactoryAddress() (gas: 22861) SetMasterTokenFactoryAddressTest:test_SetOwnerTokenFactoryAddress() (gas: 22861)
SetMaxSupplyTest:test_Deployment() (gas: 35198) SetMaxSupplyTest:test_Deployment() (gas: 35198)
SetMaxSupplyTest:test_Deployment() (gas: 83242) SetMaxSupplyTest:test_Deployment() (gas: 83330)
SetMaxSupplyTest:test_RevertWhen_CalledBecauseMaxSupplyIsLocked() (gas: 14327) SetMaxSupplyTest:test_RevertWhen_CalledBecauseMaxSupplyIsLocked() (gas: 14304)
SetMaxSupplyTest:test_RevertWhen_MaxSupplyLowerThanTotalSupply() (gas: 163641) SetMaxSupplyTest:test_RevertWhen_MaxSupplyLowerThanTotalSupply() (gas: 163551)
SetMaxSupplyTest:test_RevertWhen_SenderIsNotOwner() (gas: 12527) SetMaxSupplyTest:test_RevertWhen_SenderIsNotOwner() (gas: 12459)
SetMaxSupplyTest:test_RevertWhen_SenderIsNotOwner() (gas: 21445) SetMaxSupplyTest:test_RevertWhen_SenderIsNotOwner() (gas: 21355)
SetMaxSupplyTest:test_SetMaxSupply() (gas: 23998) SetMaxSupplyTest:test_SetMaxSupply() (gas: 23953)
SetOwnerTokenFactoryAddressTest:test_Deployment() (gas: 14805) SetOwnerTokenFactoryAddressTest:test_Deployment() (gas: 14805)
SetOwnerTokenFactoryAddressTest:test_RevertWhen_InvalidTokenFactoryAddress() (gas: 12970) SetOwnerTokenFactoryAddressTest:test_RevertWhen_InvalidTokenFactoryAddress() (gas: 12970)
SetOwnerTokenFactoryAddressTest:test_RevertWhen_SenderIsNotOwner() (gas: 12443) SetOwnerTokenFactoryAddressTest:test_RevertWhen_SenderIsNotOwner() (gas: 12443)
SetOwnerTokenFactoryAddressTest:test_SetOwnerTokenFactoryAddress() (gas: 22840) SetOwnerTokenFactoryAddressTest:test_SetOwnerTokenFactoryAddress() (gas: 22840)
SetSignerPublicKeyTest:test_Deployment() (gas: 83220) SetSignerPublicKeyTest:test_Deployment() (gas: 83308)
SetSignerPublicKeyTest:test_RevertWhen_SenderIsNotOwner() (gas: 13222) SetSignerPublicKeyTest:test_RevertWhen_SenderIsNotOwner() (gas: 13154)
SetSignerPublicKeyTest:test_SetSignerPublicKey() (gas: 24163) SetSignerPublicKeyTest:test_SetSignerPublicKey() (gas: 24162)
SetTokenDeployerAddressTest:test_RevertWhen_InvalidTokenDeployerAddress() (gas: 12964) SetTokenDeployerAddressTest:test_RevertWhen_InvalidTokenDeployerAddress() (gas: 12964)
SetTokenDeployerAddressTest:test_RevertWhen_InvalidTokenDeployerAddress() (gas: 12964) SetTokenDeployerAddressTest:test_RevertWhen_InvalidTokenDeployerAddress() (gas: 12964)
SetTokenDeployerAddressTest:test_RevertWhen_SenderIsNotOwner() (gas: 12438) SetTokenDeployerAddressTest:test_RevertWhen_SenderIsNotOwner() (gas: 12438)
SetTokenDeployerAddressTest:test_RevertWhen_SenderIsNotOwner() (gas: 12438) SetTokenDeployerAddressTest:test_RevertWhen_SenderIsNotOwner() (gas: 12438)
SetTokenDeployerAddressTest:test_SetTokenDeployerAddress() (gas: 22768) SetTokenDeployerAddressTest:test_SetTokenDeployerAddress() (gas: 22768)
SetTokenDeployerAddressTest:test_SetTokenDeployerAddress() (gas: 22768) SetTokenDeployerAddressTest:test_SetTokenDeployerAddress() (gas: 22768)
TransferERC20ByAdminTest:test_AdminCanTransferERC20() (gas: 84406)
TransferERC20ByAdminTest:test_Deployment() (gas: 10556)
TransferERC20ByAdminTest:test_LengthMismatch() (gas: 31872)
TransferERC20ByAdminTest:test_NoRecipients() (gas: 25198)
TransferERC20ByAdminTest:test_TransferAmountZero() (gas: 61686)
TransferERC20ByNonAdminTest:test_Deployment() (gas: 10458)
TransferERC20ByNonAdminTest:test_revertIfCalledByNonAdmin() (gas: 35570)
TransferERC721ByAdminTest:test_AdminCanTransferERC721() (gas: 107114)
TransferERC721ByAdminTest:test_Deployment() (gas: 10556)
TransferERC721ByAdminTest:test_LengthMismatch() (gas: 31875)
TransferERC721ByAdminTest:test_NoRecipients() (gas: 25213)
TransferERC721ByNonAdminTest:test_Deployment() (gas: 10458)
TransferERC721ByNonAdminTest:test_RevertIfCalledByNonAdmin() (gas: 35563)

View File

@ -8,13 +8,14 @@ Below is a list of all documented properties and invariants of this project that
- **Risk** - One of **High**, **Medium** and **Low**, depending on the property's risk factor - **Risk** - One of **High**, **Medium** and **Low**, depending on the property's risk factor
- **Tested** - Whether this property has been (fuzz) tested - **Tested** - Whether this property has been (fuzz) tested
| **Property** | **Type** | **Risk** | **Tested** | | **Property** | **Type** | **Risk** | **Tested** |
| ------------------------------------------------------------------------- | ------------------- | -------- | ---------- | | --------------------------------------------------------------------------------------------------------- | ------------------- | -------- | ---------- |
| Only allows deployment with valid signature | Unit test | High | Yes | | Only allows deployment with valid signature | Unit test | High | Yes |
| Adds Owner token entry to registry upon deployment | Unit test | Low | Yes | | Adds Owner token entry to registry upon deployment | Unit test | Low | Yes |
| Only one deployment per account allowed | Unit test | Medium | Yes | | Only one deployment per account allowed | Unit test | Medium | Yes |
| One and only one owner token address exists in the registry per community | Valid state | High | Yes | | One and only one owner token address exists in the registry per community | Valid state | High | Yes |
| If deployment registry address changes, sender must be owner | Variable transition | High | Yes | | If deployment registry address changes, sender must be owner | Variable transition | High | Yes |
| If owner token factory address changes, sender must be owner | Variable transition | High | Yes | | If owner token factory address changes, sender must be owner | Variable transition | High | Yes |
| If master token factory address changes, sender must be owner | Variable transition | High | Yes | | If master token factory address changes, sender must be owner | Variable transition | High | Yes |
| Registry grows as the more accounts perform a deployment | High-Level Property | Low | No | | Registry grows as the more accounts perform a deployment | High-Level Property | Low | No |
| No addresses other than the receiver addresses change an addresse's balance when batch transfering tokens | High-Level Property | High | Yes |

View File

@ -162,6 +162,7 @@ Below is a description of all community tokens that can be deployed and minted t
- The ability to configure a maximum supply. This is used for both `MasterToken` and `CollectibleV1` tokens. - The ability to configure a maximum supply. This is used for both `MasterToken` and `CollectibleV1` tokens.
- A `mintTo` function that allows for minting tokens to multiple addresses at once. - A `mintTo` function that allows for minting tokens to multiple addresses at once.
- A mechanism to burn tokens "remotely". The use case here is to remove token masters or admins privileges. - A mechanism to burn tokens "remotely". The use case here is to remove token masters or admins privileges.
- The ability to batch transfer tokens to multiple receivers.
Not all inheriting contracts make use of all of the custom functionality. Not all inheriting contracts make use of all of the custom functionality.

View File

@ -7,6 +7,7 @@ methods {
function setMaxSupply(uint256 newMaxSupply) external returns (uint); function setMaxSupply(uint256 newMaxSupply) external returns (uint);
function mintTo(address[]) external; function mintTo(address[]) external;
function mintedCount() external returns (uint) envfree; function mintedCount() external returns (uint) envfree;
function safeBatchTransferFrom(address from, address[] to, uint256[] tokenIds, bytes data) external;
function countAddressOccurrences(address[], address) external returns (uint) envfree; function countAddressOccurrences(address[], address) external returns (uint) envfree;
function _.onERC721Received(address, address, uint256, bytes) external => NONDET; function _.onERC721Received(address, address, uint256, bytes) external => NONDET;
} }
@ -128,12 +129,52 @@ rule mintToRelations() {
assert balance_s1 == balance_s2; assert balance_s1 == balance_s2;
} }
rule shouldPass { rule safeBatchTransferFromReverts() {
assert true;
address from;
address[] to;
uint256[] tokenIds;
bytes data;
env e;
mathint supply_before = totalSupply();
mathint max_supply = maxSupply();
mathint minted_count = mintedCount();
require supply_before <= max_supply;
require minted_count >= supply_before;
safeBatchTransferFrom@withrevert(e, from, to, tokenIds, data);
bool reverted = lastReverted;
assert (tokenIds.length != to.length) => reverted;
} }
/* rule sanity(env e, method f) { */ rule safeBatchTransferFromRelations() {
/* calldataarg args; */ address from;
/* f(e, args); */ address[] to;
/* assert false; */ uint256[] tokenIds;
/* } */ bytes data;
env e;
require to.length == tokenIds.length;
mathint supply_before = totalSupply();
mathint max_supply = maxSupply();
mathint minted_count = mintedCount();
require supply_before <= max_supply;
require minted_count >= supply_before;
address a;
storage s1 = lastStorage;
safeBatchTransferFrom(e, from, to, tokenIds, data);
mathint balance_s1 = balanceOf(a);
safeBatchTransferFrom(e, from, to, tokenIds, data) at s1;
mathint balance_s2 = balanceOf(a);
assert balance_s1 == balance_s2;
}

View File

@ -15,6 +15,8 @@ abstract contract BaseToken is Context, ERC721Enumerable, CommunityOwnable {
error BaseToken_MaxSupplyReached(); error BaseToken_MaxSupplyReached();
error BaseToken_NotRemoteBurnable(); error BaseToken_NotRemoteBurnable();
error BaseToken_NotTransferable(); error BaseToken_NotTransferable();
error BaseToken_NotAuthorized();
error BaseToken_ReceiversAndIdsMismatch();
/// @notice Emits a custom mint event for Status applications to listen to /// @notice Emits a custom mint event for Status applications to listen to
/// @dev This is doubling the {Transfer} event from ERC721 but we need to emit this /// @dev This is doubling the {Transfer} event from ERC721 but we need to emit this
@ -134,8 +136,8 @@ abstract contract BaseToken is Context, ERC721Enumerable, CommunityOwnable {
} }
/** /**
* @notice * @notice Overrides `ERC721Enumerable` to add a check for transferability
* @dev * @dev See {ERC721-_beforeTokenTransfer}.
*/ */
function _beforeTokenTransfer( function _beforeTokenTransfer(
address from, address from,
@ -153,5 +155,37 @@ abstract contract BaseToken is Context, ERC721Enumerable, CommunityOwnable {
super._beforeTokenTransfer(from, to, firstTokenId, batchSize); super._beforeTokenTransfer(from, to, firstTokenId, batchSize);
} }
/**
* @notice Batch transfers tokens by passing an array of token IDs.
* @dev This is simply delegates to `safeTranferFrom` for each token ID in the array.
*/
function safeBatchTransferFrom(
address from,
address[] calldata receivers,
uint256[] calldata ids,
bytes memory data
)
public
virtual
{
_safeBatchTransferFrom(from, receivers, ids, data);
}
function _safeBatchTransferFrom(
address from,
address[] memory receivers,
uint256[] calldata ids,
bytes memory data
)
internal
{
if (receivers.length != ids.length) {
revert BaseToken_ReceiversAndIdsMismatch();
}
for (uint256 i = 0; i < ids.length; ++i) {
safeTransferFrom(from, receivers[i], ids[i], data);
}
}
// Private functions // Private functions
} }

View File

@ -37,7 +37,9 @@ contract CollectibleV1Test is Test {
accounts[2] = makeAddr("three"); accounts[2] = makeAddr("three");
accounts[3] = makeAddr("four"); accounts[3] = makeAddr("four");
} }
}
contract DeploymentTest is CollectibleV1Test {
function test_Deployment() public { function test_Deployment() public {
assertEq(collectibleV1.name(), name); assertEq(collectibleV1.name(), name);
assertEq(collectibleV1.symbol(), symbol); assertEq(collectibleV1.symbol(), symbol);
@ -116,3 +118,149 @@ contract RemoteBurnTest is CollectibleV1Test {
assertEq(collectibleV1.totalSupply(), maxSupply - 1); assertEq(collectibleV1.totalSupply(), maxSupply - 1);
} }
} }
contract SafeBatchTransferFromTest is CollectibleV1Test {
function setUp() public virtual override {
CollectibleV1Test.setUp();
}
function test_RevertWhen_ReceiversAndIdsMismatch() public {
// ensure sender owns a token
vm.prank(deployer);
collectibleV1.mintTo(accounts);
address[] memory receivers = new address[](1);
receivers[0] = accounts[1];
// ids must be of same length as `receivers`
uint256[] memory ids = new uint256[](2);
ids[0] = 0;
ids[1] = 1;
vm.prank(accounts[0]);
vm.expectRevert(BaseToken.BaseToken_ReceiversAndIdsMismatch.selector);
collectibleV1.safeBatchTransferFrom(accounts[0], receivers, ids, "");
}
function test_RevertWhen_NotAuthorized() public {
vm.prank(deployer);
collectibleV1.mintTo(accounts);
address[] memory receivers = new address[](1);
receivers[0] = accounts[3];
// ids must be of same length as `accounts`
uint256[] memory ids = new uint256[](1);
ids[0] = 0;
// ensure accounts[3] has no ownership or approval of token with id `0`
assertEq(collectibleV1.ownerOf(ids[0]), accounts[0]);
assertEq(collectibleV1.isApprovedForAll(accounts[0], receivers[0]), false);
vm.prank(receivers[0]);
vm.expectRevert(bytes("ERC721: caller is not token owner or approved"));
collectibleV1.safeBatchTransferFrom(accounts[0], receivers, ids, "");
}
function test_RevertWhen_ReceiverAddressIsZero() public {
// ensure sender owns a token
vm.prank(deployer);
collectibleV1.mintTo(accounts);
address[] memory receivers = new address[](1);
receivers[0] = address(0);
uint256[] memory ids = new uint256[](1);
ids[0] = 0;
vm.prank(accounts[0]);
vm.expectRevert(bytes("ERC721: transfer to the zero address"));
collectibleV1.safeBatchTransferFrom(accounts[0], receivers, ids, "");
}
function test_SafeBatchTransferFrom() public {
// ensure sender owns a token
vm.prank(deployer);
address[] memory sameAddresses = new address[](3);
sameAddresses[0] = accounts[0];
sameAddresses[1] = accounts[0];
sameAddresses[2] = accounts[0];
collectibleV1.mintTo(sameAddresses);
address[] memory receivers = new address[](3);
receivers[0] = accounts[1];
receivers[1] = accounts[2];
receivers[2] = accounts[3];
uint256[] memory ids = new uint256[](3);
ids[0] = 0;
ids[1] = 1;
ids[2] = 2;
vm.prank(accounts[0]);
collectibleV1.safeBatchTransferFrom(accounts[0], receivers, ids, "");
assertEq(collectibleV1.balanceOf(accounts[0]), 0);
assertEq(collectibleV1.balanceOf(accounts[1]), 1);
assertEq(collectibleV1.balanceOf(accounts[2]), 1);
assertEq(collectibleV1.balanceOf(accounts[3]), 1);
}
function test_SafeBatchTransferFromToSingleReceiver() public {
// ensure sender owns a token
vm.prank(deployer);
address[] memory sameAddresses = new address[](3);
sameAddresses[0] = accounts[0];
sameAddresses[1] = accounts[0];
sameAddresses[2] = accounts[0];
collectibleV1.mintTo(sameAddresses);
address[] memory receivers = new address[](3);
receivers[0] = accounts[1];
receivers[1] = accounts[1];
receivers[2] = accounts[1];
uint256[] memory ids = new uint256[](3);
ids[0] = 0;
ids[1] = 1;
ids[2] = 2;
vm.prank(accounts[0]);
collectibleV1.safeBatchTransferFrom(accounts[0], receivers, ids, "");
assertEq(collectibleV1.balanceOf(accounts[0]), 0);
assertEq(collectibleV1.balanceOf(accounts[1]), 3);
}
}
contract NotTransferableTest is CollectibleV1Test {
function setUp() public virtual override {
DeployOwnerAndMasterToken deployment = new DeployOwnerAndMasterToken();
(OwnerToken ownerToken, MasterToken masterToken, DeploymentConfig deploymentConfig) = deployment.run();
deployer = deploymentConfig.deployer();
collectibleV1 = new CollectibleV1(
name, symbol, maxSupply, remoteBurnable, false, baseURI, address(ownerToken), address(masterToken)
);
accounts[0] = makeAddr("one");
accounts[1] = makeAddr("two");
accounts[2] = makeAddr("three");
accounts[3] = makeAddr("four");
}
function test_RevertWhen_TokenIsNotTransferable() public {
// ensure accounts own tokens
vm.prank(deployer);
collectibleV1.mintTo(accounts);
uint256[] memory ids = new uint256[](4);
ids[0] = 0;
ids[1] = 1;
ids[2] = 2;
ids[3] = 3;
vm.prank(accounts[0]);
vm.expectRevert(BaseToken.BaseToken_NotTransferable.selector);
collectibleV1.safeBatchTransferFrom(accounts[0], accounts, ids, "");
}
}