Skip to content

Commit

Permalink
Add batch burn function
Browse files Browse the repository at this point in the history
  • Loading branch information
ScreamingHawk committed Nov 22, 2023
1 parent 6c04848 commit 20642b9
Show file tree
Hide file tree
Showing 9 changed files with 240 additions and 7 deletions.
13 changes: 11 additions & 2 deletions src/tokens/ERC1155/ERC1155BaseToken.sol
Original file line number Diff line number Diff line change
Expand Up @@ -81,13 +81,22 @@ abstract contract ERC1155BaseToken is ERC1155MintBurn, ERC1155Meta, ERC1155Metad

/**
* Allows the owner of the token to burn their tokens.
* @param tokenId Id of token to burn.
* @param amount Amount of tokens to burn.
* @param tokenId Id of token to burn
* @param amount Amount of tokens to burn
*/
function burn(uint256 tokenId, uint256 amount) public virtual {
_burn(msg.sender, tokenId, amount);
}

/**
* Burn tokens of given token id for each (tokenIds[i], amounts[i]) pair.
* @param tokenIds Array of token ids to burn
* @param amounts Array of the amount to be burned
*/
function batchBurn(uint256[] memory tokenIds, uint256[] memory amounts) public virtual {
_batchBurn(msg.sender, tokenIds, amounts);
}

//
// Views
//
Expand Down
2 changes: 0 additions & 2 deletions src/tokens/ERC1155/extensions/supply/ERC1155Supply.sol
Original file line number Diff line number Diff line change
Expand Up @@ -98,13 +98,11 @@ abstract contract ERC1155Supply is ERC1155BaseToken, IERC1155Supply {
* @param _amounts Array of the amount to be burned
*/
function _batchBurn(address _from, uint256[] memory _ids, uint256[] memory _amounts) internal virtual override {
// Number of mints to execute
uint256 nBurn = _ids.length;
if (nBurn != _amounts.length) {
revert InvalidArrayLength();
}

// Executing all minting
for (uint256 i = 0; i < nBurn; i++) {
// Update balances
balances[_from][_ids[i]] -= _amounts[i];
Expand Down
2 changes: 1 addition & 1 deletion src/tokens/ERC20/ERC20BaseToken.sol
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ abstract contract ERC20BaseToken is ERC20, AccessControl {

/**
* Allows the owner of the token to burn their tokens.
* @param amount Amount of tokens to burn.
* @param amount Amount of tokens to burn
*/
function burn(uint256 amount) public virtual {
_burn(msg.sender, amount);
Expand Down
15 changes: 13 additions & 2 deletions src/tokens/ERC721/ERC721BaseToken.sol
Original file line number Diff line number Diff line change
Expand Up @@ -91,13 +91,24 @@ abstract contract ERC721BaseToken is ERC721AQueryable, ERC2981Controlled {
//

/**
* Allows the owner of the token to burn their tokens.
* @param tokenId Id of token to burn.
* Allows the owner of the token to burn their token.
* @param tokenId Id of token to burn
*/
function burn(uint256 tokenId) public virtual {
_burn(tokenId, true);
}

/**
* Allows the owner of the tokens to burn their tokens.
* @param tokenIds Array of token ids to burn
*/
function batchBurn(uint256[] memory tokenIds) public virtual {
uint256 nBurn = tokenIds.length;
for (uint256 i = 0; i < nBurn; i++) {
_burn(tokenIds[i], true);
}
}

//
// Views
//
Expand Down
7 changes: 7 additions & 0 deletions test/TestHelper.sol
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,11 @@ abstract contract TestHelper is Test {
vm.assume(addr.code.length == 0); // Non contract
}

function assumeNoDuplicates(uint256[] memory values) internal pure {
for (uint256 i = 0; i < values.length; i++) {
for (uint256 j = i + 1; j < values.length; j++) {
vm.assume(values[i] != values[j]);
}
}
}
}
72 changes: 72 additions & 0 deletions test/tokens/ERC1155/presets/ERC1155Items.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,78 @@ contract ERC1155ItemsTest is TestHelper, IERC1155ItemsSignals {
token.burn(tokenId, burnAmount);
}

function testBurnBatchSuccess(address caller, uint256[] memory tokenIds, uint256[] memory amounts, uint256[] memory burnAmounts) public {
assumeSafeAddress(caller);
vm.assume(caller != owner);
vm.assume(caller != proxyOwner);

uint256 nTokenIds = tokenIds.length > 3 ? 3 : tokenIds.length;
vm.assume(nTokenIds > 0);
vm.assume(nTokenIds <= amounts.length);
vm.assume(nTokenIds <= burnAmounts.length);
// Bind array sizes
assembly {
mstore(tokenIds, nTokenIds)
mstore(amounts, nTokenIds)
mstore(burnAmounts, nTokenIds)
}

for (uint256 i; i < nTokenIds; i++) {
// Lower the values
tokenIds[i] = tokenIds[i] % 256;
amounts[i] = amounts[i] % 256;

// Ensure we don't burn too many
if (burnAmounts[i] > amounts[i]) {
burnAmounts[i] = amounts[i];
}
}
assumeNoDuplicates(tokenIds);

vm.prank(owner);
token.batchMint(caller, tokenIds, amounts, "");

vm.expectEmit(true, true, true, false, address(token));
emit TransferBatch(caller, caller, address(0), tokenIds, burnAmounts);

vm.prank(caller);
token.batchBurn(tokenIds, burnAmounts);

for (uint256 i; i < nTokenIds; i++) {
assertEq(token.balanceOf(caller, tokenIds[i]), amounts[i] - burnAmounts[i]);
}
}

function testBurnBatchInvalidOwnership(address caller, uint256[] memory tokenIds, uint256[] memory amounts) public {
assumeSafeAddress(caller);
vm.assume(caller != owner);
vm.assume(caller != proxyOwner);

uint256 nTokenIds = tokenIds.length > 3 ? 3 : tokenIds.length;
vm.assume(nTokenIds > 0);
vm.assume(nTokenIds <= amounts.length);
// Bind array sizes
assembly {
mstore(tokenIds, nTokenIds)
mstore(amounts, nTokenIds)
}

for (uint256 i; i < nTokenIds; i++) {
// Lower the values
tokenIds[i] = tokenIds[i] % 256;
amounts[i] = amounts[i] % 256;
}
assumeNoDuplicates(tokenIds);

vm.prank(owner);
token.batchMint(caller, tokenIds, amounts, "");

amounts[0]++; // Now we burn too many

vm.expectRevert(stdError.arithmeticError);
token.batchBurn(tokenIds, amounts);
}

//
// Metadata
//
Expand Down
66 changes: 66 additions & 0 deletions test/tokens/ERC1155/presets/ERC1155Sale.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -605,6 +605,72 @@ contract ERC1155SaleTest is TestHelper, Merkle, IERC1155SaleSignals, IERC1155Sup
token.burn(tokenId, burnAmount);
}

function testBurnBatchSuccess(address caller, uint256[] memory tokenIds, uint256[] memory amounts, uint256[] memory burnAmounts) public {
assumeSafeAddress(caller);

uint256 nTokenIds = tokenIds.length > 3 ? 3 : tokenIds.length;
vm.assume(nTokenIds > 0);
vm.assume(nTokenIds <= amounts.length);
vm.assume(nTokenIds <= burnAmounts.length);
// Bind array sizes
assembly {
mstore(tokenIds, nTokenIds)
mstore(amounts, nTokenIds)
mstore(burnAmounts, nTokenIds)
}

for (uint256 i; i < nTokenIds; i++) {
// Lower the values
tokenIds[i] = tokenIds[i] % 256;
amounts[i] = amounts[i] % 256;

// Ensure we don't burn too many
if (burnAmounts[i] > amounts[i]) {
burnAmounts[i] = amounts[i];
}
}
assumeNoDuplicates(tokenIds);

token.mintAdmin(caller, tokenIds, amounts, "");

vm.expectEmit(true, true, true, false, address(token));
emit TransferBatch(caller, caller, address(0), tokenIds, burnAmounts);

vm.prank(caller);
token.batchBurn(tokenIds, burnAmounts);

for (uint256 i; i < nTokenIds; i++) {
assertEq(token.balanceOf(caller, tokenIds[i]), amounts[i] - burnAmounts[i]);
}
}

function testBurnBatchInvalidOwnership(address caller, uint256[] memory tokenIds, uint256[] memory amounts) public {
assumeSafeAddress(caller);

uint256 nTokenIds = tokenIds.length > 3 ? 3 : tokenIds.length;
vm.assume(nTokenIds > 0);
vm.assume(nTokenIds <= amounts.length);
// Bind array sizes
assembly {
mstore(tokenIds, nTokenIds)
mstore(amounts, nTokenIds)
}

for (uint256 i; i < nTokenIds; i++) {
// Lower the values
tokenIds[i] = tokenIds[i] % 256;
amounts[i] = amounts[i] % 256;
}
assumeNoDuplicates(tokenIds);

token.mintAdmin(caller, tokenIds, amounts, "");

amounts[0]++; // Now we burn too many

vm.expectRevert(stdError.arithmeticError);
token.batchBurn(tokenIds, amounts);
}

//
// Royalty
//
Expand Down
36 changes: 36 additions & 0 deletions test/tokens/ERC721/presets/ERC721Items.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,42 @@ contract ERC721ItemsTest is TestHelper, IERC721ItemsSignals {
token.burn(0);
}

function testBurnBatchSuccess(address caller) public {
assumeSafeAddress(caller);

vm.prank(owner);
token.mint(caller, 2);

uint256[] memory ids = new uint256[](2);
ids[0] = 0;
ids[1] = 1;

vm.expectEmit(true, true, true, false, address(token));
emit Transfer(caller, address(0), 0);
vm.expectEmit(true, true, true, false, address(token));
emit Transfer(caller, address(0), 1);

vm.prank(caller);
token.batchBurn(ids);

vm.expectRevert(IERC721A.OwnerQueryForNonexistentToken.selector);
token.ownerOf(0);
}

function testBurnBatchInvalidOwnership(address caller) public {
assumeSafeAddress(caller);

vm.prank(owner);
token.mint(caller, 2);

uint256[] memory ids = new uint256[](2);
ids[0] = 0;
ids[1] = 1;

vm.expectRevert(IERC721A.TransferCallerNotOwnerNorApproved.selector);
token.batchBurn(ids);
}

//
// Metadata
//
Expand Down
34 changes: 34 additions & 0 deletions test/tokens/ERC721/presets/ERC721Sale.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -396,6 +396,40 @@ contract ERC721SaleTest is TestHelper, Merkle, IERC721SaleSignals, IMerkleProofS
token.burn(0);
}

function testBurnBatchSuccess(address caller) public {
assumeSafeAddress(caller);

token.mintAdmin(caller, 2);

uint256[] memory ids = new uint256[](2);
ids[0] = 0;
ids[1] = 1;

vm.expectEmit(true, true, true, false, address(token));
emit Transfer(caller, address(0), 0);
vm.expectEmit(true, true, true, false, address(token));
emit Transfer(caller, address(0), 1);

vm.prank(caller);
token.batchBurn(ids);

vm.expectRevert(IERC721A.OwnerQueryForNonexistentToken.selector);
token.ownerOf(0);
}

function testBurnBatchInvalidOwnership(address caller) public {
assumeSafeAddress(caller);

token.mintAdmin(caller, 2);

uint256[] memory ids = new uint256[](2);
ids[0] = 0;
ids[1] = 1;

vm.expectRevert(IERC721A.TransferCallerNotOwnerNorApproved.selector);
token.batchBurn(ids);
}

//
// Royalty
//
Expand Down

0 comments on commit 20642b9

Please sign in to comment.