Skip to content

Commit

Permalink
feat(CollectibleV1): add safeBatchTransferFrom capabilities
Browse files Browse the repository at this point in the history
This is to allow batch transfers of community collectibles as discussed
in #41.

Closes #41, #42, #43, #44
  • Loading branch information
0x-r4bbit committed Feb 26, 2024
1 parent d02d6f7 commit bc7db03
Show file tree
Hide file tree
Showing 6 changed files with 294 additions and 50 deletions.
81 changes: 50 additions & 31 deletions .gas-snapshot
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@ AddEntryTest:test_AddEntry() (gas: 44392)
AddEntryTest:test_RevertWhen_EntryAlreadyExists() (gas: 42644)
AddEntryTest:test_RevertWhen_InvalidAddress() (gas: 25133)
AddEntryTest:test_RevertWhen_SenderIsNotTokenDeployer() (gas: 14827)
CollectibleV1Test:test_Deployment() (gas: 36386)
CommunityERC20Test:test_Deployment() (gas: 35198)
CommunityTokenDeployerTest:test_Deployment() (gas: 14805)
CommunityVaultBaseERC20Test:test_Deployment() (gas: 10436)
CommunityVaultBaseERC721Test:test_Deployment() (gas: 10436)
CommunityVaultTest:test_Deployment() (gas: 10436)
CreateTest:test_Create() (gas: 2269916)
CreateTest:test_Create() (gas: 2568994)
CreateTest:test_Create() (gas: 2440150)
CreateTest:test_Create() (gas: 2731735)
CreateTest:test_RevertWhen_InvalidOwnerTokenAddress() (gas: 15523)
CreateTest:test_RevertWhen_InvalidReceiverAddress() (gas: 15656)
CreateTest:test_RevertWhen_InvalidSignerPublicKey() (gas: 17057)
Expand All @@ -17,34 +18,39 @@ CreateTest:test_RevertWhen_SenderIsNotTokenDeployer() (gas: 16421)
CreateTest:test_RevertWhen_SenderIsNotTokenDeployer() (gas: 16524)
DeployContracts:test() (gas: 120)
DeployOwnerAndMasterToken:test() (gas: 120)
DeployTest:test_Deploy() (gas: 4911563)
DeployTest:test_Deploy() (gas: 5244498)
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_InvalidDeployerAddress() (gas: 55272)
DeployTest:test_RevertWhen_InvalidDeploymentSignature() (gas: 65617)
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: 17295)
DeploymentTest:test_Deployment() (gas: 36430)
GetEntryTest:test_ReturnZeroAddressIfEntryDoesNotExist() (gas: 11906)
MintToTest:test_Deployment() (gas: 35220)
MintToTest:test_Deployment() (gas: 36386)
MintToTest:test_Deployment() (gas: 83220)
MintToTest:test_MintTo() (gas: 178063)
MintToTest:test_MintTo() (gas: 526242)
MintToTest:test_RevertWhen_AddressesAndAmountsAreNotEqualLength() (gas: 29673)
MintToTest:test_RevertWhen_MaxSupplyIsReached() (gas: 20653)
MintToTest:test_RevertWhen_MaxSupplyIsReached() (gas: 511039)
MintToTest:test_RevertWhen_MaxSupplyReached() (gas: 134799)
MintToTest:test_RevertWhen_SenderIsNotOwner() (gas: 31544)
OwnerTokenTest:test_Deployment() (gas: 83220)
RemoteBurnTest:test_Deployment() (gas: 36386)
RemoteBurnTest:test_Deployment() (gas: 83242)
RemoteBurnTest:test_RemoteBurn() (gas: 459164)
RemoteBurnTest:test_RevertWhen_RemoteBurn() (gas: 14768)
RemoteBurnTest:test_RevertWhen_SenderIsNotOwner() (gas: 20379)
MintToTest:test_Deployment() (gas: 83308)
MintToTest:test_MintTo() (gas: 178018)
MintToTest:test_MintTo() (gas: 525865)
MintToTest:test_RevertWhen_AddressesAndAmountsAreNotEqualLength() (gas: 29628)
MintToTest:test_RevertWhen_MaxSupplyIsReached() (gas: 20680)
MintToTest:test_RevertWhen_MaxSupplyIsReached() (gas: 511109)
MintToTest:test_RevertWhen_MaxSupplyReached() (gas: 134754)
MintToTest:test_RevertWhen_SenderIsNotOwner() (gas: 31454)
NotTransferableTest:test_RevertWhen_TokenIsNotTransferable() (gas: 508323)
OwnerTokenTest:test_Deployment() (gas: 83308)
RemoteBurnTest:test_Deployment() (gas: 83330)
RemoteBurnTest:test_RemoteBurn() (gas: 459101)
RemoteBurnTest:test_RevertWhen_RemoteBurn() (gas: 14745)
RemoteBurnTest:test_RevertWhen_SenderIsNotOwner() (gas: 20311)
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_SenderIsNotOwner() (gas: 12482)
SetCommunityTokenDeployerAddressTest:test_SetCommunityTokenDeployerAddress() (gas: 22808)
Expand All @@ -57,22 +63,35 @@ SetMasterTokenFactoryAddressTest:test_RevertWhen_InvalidTokenFactoryAddress() (g
SetMasterTokenFactoryAddressTest:test_RevertWhen_SenderIsNotOwner() (gas: 12465)
SetMasterTokenFactoryAddressTest:test_SetOwnerTokenFactoryAddress() (gas: 22861)
SetMaxSupplyTest:test_Deployment() (gas: 35198)
SetMaxSupplyTest:test_Deployment() (gas: 83242)
SetMaxSupplyTest:test_RevertWhen_CalledBecauseMaxSupplyIsLocked() (gas: 14327)
SetMaxSupplyTest:test_RevertWhen_MaxSupplyLowerThanTotalSupply() (gas: 163641)
SetMaxSupplyTest:test_RevertWhen_SenderIsNotOwner() (gas: 12527)
SetMaxSupplyTest:test_RevertWhen_SenderIsNotOwner() (gas: 21445)
SetMaxSupplyTest:test_SetMaxSupply() (gas: 23998)
SetMaxSupplyTest:test_Deployment() (gas: 83330)
SetMaxSupplyTest:test_RevertWhen_CalledBecauseMaxSupplyIsLocked() (gas: 14304)
SetMaxSupplyTest:test_RevertWhen_MaxSupplyLowerThanTotalSupply() (gas: 163551)
SetMaxSupplyTest:test_RevertWhen_SenderIsNotOwner() (gas: 12459)
SetMaxSupplyTest:test_RevertWhen_SenderIsNotOwner() (gas: 21355)
SetMaxSupplyTest:test_SetMaxSupply() (gas: 23953)
SetOwnerTokenFactoryAddressTest:test_Deployment() (gas: 14805)
SetOwnerTokenFactoryAddressTest:test_RevertWhen_InvalidTokenFactoryAddress() (gas: 12970)
SetOwnerTokenFactoryAddressTest:test_RevertWhen_SenderIsNotOwner() (gas: 12443)
SetOwnerTokenFactoryAddressTest:test_SetOwnerTokenFactoryAddress() (gas: 22840)
SetSignerPublicKeyTest:test_Deployment() (gas: 83220)
SetSignerPublicKeyTest:test_RevertWhen_SenderIsNotOwner() (gas: 13222)
SetSignerPublicKeyTest:test_SetSignerPublicKey() (gas: 24163)
SetSignerPublicKeyTest:test_Deployment() (gas: 83308)
SetSignerPublicKeyTest:test_RevertWhen_SenderIsNotOwner() (gas: 13154)
SetSignerPublicKeyTest:test_SetSignerPublicKey() (gas: 24162)
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_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)
21 changes: 11 additions & 10 deletions PROPERTIES.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
- **Tested** - Whether this property has been (fuzz) tested

| **Property** | **Type** | **Risk** | **Tested** |
| ------------------------------------------------------------------------- | ------------------- | -------- | ---------- |
| Only allows deployment with valid signature | Unit test | High | Yes |
| Adds Owner token entry to registry upon deployment | Unit test | Low | 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 |
| 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 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 |
| **Property** | **Type** | **Risk** | **Tested** |
| --------------------------------------------------------------------------------------------------------- | ------------------- | -------- | ---------- |
| Only allows deployment with valid signature | Unit test | High | Yes |
| Adds Owner token entry to registry upon deployment | Unit test | Low | 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 |
| 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 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 |
| No addresses other than the receiver addresses change an addresse's balance when batch transfering tokens | High-Level Property | High | Yes |
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
- 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.
- The ability to batch transfer tokens to multiple receivers.

Not all inheriting contracts make use of all of the custom functionality.

Expand Down
55 changes: 48 additions & 7 deletions certora/specs/CollectibleV1.spec
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ methods {
function setMaxSupply(uint256 newMaxSupply) external returns (uint);
function mintTo(address[]) external;
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 _.onERC721Received(address, address, uint256, bytes) external => NONDET;
}
Expand Down Expand Up @@ -128,12 +129,52 @@ rule mintToRelations() {
assert balance_s1 == balance_s2;
}

rule shouldPass {
assert true;
rule safeBatchTransferFromReverts() {

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) { */
/* calldataarg args; */
/* f(e, args); */
/* assert false; */
/* } */
rule safeBatchTransferFromRelations() {
address from;
address[] to;
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;
}
38 changes: 36 additions & 2 deletions contracts/tokens/BaseToken.sol
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ abstract contract BaseToken is Context, ERC721Enumerable, CommunityOwnable {
error BaseToken_MaxSupplyReached();
error BaseToken_NotRemoteBurnable();
error BaseToken_NotTransferable();
error BaseToken_NotAuthorized();
error BaseToken_ReceiversAndIdsMismatch();

/// @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
Expand Down Expand Up @@ -134,8 +136,8 @@ abstract contract BaseToken is Context, ERC721Enumerable, CommunityOwnable {
}

/**
* @notice
* @dev
* @notice Overrides `ERC721Enumerable` to add a check for transferability
* @dev See {ERC721-_beforeTokenTransfer}.
*/
function _beforeTokenTransfer(
address from,
Expand All @@ -153,5 +155,37 @@ abstract contract BaseToken is Context, ERC721Enumerable, CommunityOwnable {
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
}
Loading

0 comments on commit bc7db03

Please sign in to comment.