Skip to content

Commit

Permalink
add burn functions
Browse files Browse the repository at this point in the history
  • Loading branch information
nxt3d committed Feb 21, 2024
1 parent 245d731 commit eaa6a02
Show file tree
Hide file tree
Showing 6 changed files with 161 additions and 23 deletions.
52 changes: 43 additions & 9 deletions contracts/l2/FuseController.sol
Original file line number Diff line number Diff line change
Expand Up @@ -107,15 +107,6 @@ contract FuseController is Ownable, IFuseController {
// Make sure the CANNOT_TRANSFER fuse is not burned.
require((td.fuses & CANNOT_TRANSFER) == 0, "Cannot transfer");

// if the 'to' address is the zero address, then the token is being burned, and
// set all the values to the default values.
if (to == address(0)) {
// Make sure the CANNOT_BURN_NAME fuse is not burned.
require((td.fuses & CANNOT_BURN_NAME) == 0, "Cannot burn name");
return
_pack(address(this), address(0), address(0), 0, 0, address(0));
}

return
_pack(
address(this),
Expand All @@ -127,6 +118,49 @@ contract FuseController is Ownable, IFuseController {
);
}

function burn(
bytes calldata tokenData,
address operator,
address from,
uint256 /*id*/,
uint256 value,
bytes calldata /*data*/,
bool operatorApproved
) external view returns (bytes memory) {
TokenData memory td;

// Make sure the tokenData is of the correct length.
if (tokenData.length < 96) {
revert("Invalid tokenData length");
}

(
td.owner,
td.resolver,
td.expiry,
td.fuses,
td.renewalController
) = _unpack(tokenData);

require(msg.sender == address(registry), "Caller is not the registry");
require(value == 1);
require(from == td.owner, "From is not the owner");
require(
operator == td.owner || operatorApproved,
"Operator not approved"
);
(bool isExpired, , , , , ) = _isExpired(tokenData);
require(!isExpired, "Token is expired");

// Make sure the CANNOT_BURN_NAME and CANNOT_TRANSFER fuse is not burned.
require(
(td.fuses & (CANNOT_BURN_NAME | CANNOT_TRANSFER)) == 0,
"Cannot burn or transfer"
);

return _pack(address(this), address(0), address(0), 0, 0, address(0));
}

function balanceOf(
bytes calldata tokenData,
address _owner,
Expand Down
10 changes: 10 additions & 0 deletions contracts/l2/IController.sol
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,16 @@ interface IController {
bool isApproved
) external returns (bytes memory);

function burn(
bytes calldata tokenData,
address operator,
address from,
uint256 id,
uint256 value,
bytes calldata data,
bool operatorApproved
) external view returns (bytes memory);

function balanceOf(
bytes calldata tokenData,
address owner,
Expand Down
49 changes: 49 additions & 0 deletions contracts/l2/L2Registry.sol
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,26 @@ contract L2Registry is Ownable, IERC1155, IERC1155MetadataURI {
);
}

function burn(address from, uint256 id, uint256 /*value*/) external {
_burn(from, id);
emit TransferSingle(msg.sender, from, address(0), id, 1);
}

function burnBatch(
address from,
uint256[] memory ids,
uint256[] calldata /*values*/
) external {
// make an empty uint256 array for the value of 1 for each id
uint256[] memory onesArray = new uint256[](ids.length);
for (uint256 i = 0; i < ids.length; i++) {
_burn(from, ids[i]);
// fill the ones array with 1s
onesArray[i] = 1;
}
emit TransferBatch(msg.sender, from, address(0), ids, onesArray);
}

function setApprovalForAll(address operator, bool approved) external {
approvals[msg.sender][operator] = approved;
emit ApprovalForAll(msg.sender, operator, approved);
Expand Down Expand Up @@ -309,6 +329,13 @@ contract L2Registry is Ownable, IERC1155, IERC1155MetadataURI {
uint256 value,
bytes calldata data
) internal {
if (to == address(0)) {
revert("Cannot transfer to the zero address");
}
if (from == address(0)) {
revert("Cannot transfer from the zero address");
}

bytes memory tokenData = tokens[id].data;
IController oldController = _getController(tokenData);
if (address(oldController) == address(0)) {
Expand All @@ -331,6 +358,28 @@ contract L2Registry is Ownable, IERC1155, IERC1155MetadataURI {
tokens[id].data = newTokenData;
}

function _burn(address from, uint256 id) internal {
bytes memory tokenData = tokens[id].data;
IController oldController = _getController(tokenData);
if (address(oldController) == address(0)) {
revert TokenDoesNotExist(id);
}
bool isApproved = approvals[from][msg.sender] ||
tokenApprovals[from][tokenApprovalsNonce[from]][id][msg.sender];

bytes memory newTokenData = oldController.burn(
tokenData,
msg.sender,
from,
id,
1,
bytes(""),
isApproved
);

tokens[id].data = newTokenData;
}

function _doSafeTransferAcceptanceCheck(
address operator,
address from,
Expand Down
13 changes: 13 additions & 0 deletions contracts/l2/RootController.sol
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ contract RootController is Ownable, IController {
}

error CannotTransfer();
error CannotBurn();

event NewResolver(uint256 id, address resolver);

Expand Down Expand Up @@ -48,6 +49,18 @@ contract RootController is Ownable, IController {
revert CannotTransfer();
}

function burn(
bytes calldata /*tokenData*/,
address /*operator*/,
address /*from*/,
uint256 /*id*/,
uint256 /*value*/,
bytes calldata /*data*/,
bool /*operatorApproved*/
) external view returns (bytes memory) {
revert CannotBurn();
}

function balanceOf(
bytes calldata /*tokenData*/,
address _owner,
Expand Down
10 changes: 10 additions & 0 deletions contracts/l2/mocks/FuseControllerUpgraded.sol
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,16 @@ contract FuseControllerUpgraded is
_pack(to, td.resolver, td.expiry, td.fuses, td.renewalController);
}

function burn(
bytes calldata /*tokenData*/,
address /*operator*/,
address /*from*/,
uint256 /*id*/,
uint256 /*value*/,
bytes calldata /*data*/,
bool /*operatorApproved*/
) external view returns (bytes memory) {}

function balanceOf(
bytes calldata tokenData,
address _owner,
Expand Down
50 changes: 36 additions & 14 deletions test/l2/TestL2Registry.js
Original file line number Diff line number Diff line change
Expand Up @@ -361,24 +361,46 @@ describe.only('L2Registry', () => {
).to.be.revertedWith('')
})

it('should transfer the name to the zero address', async () => {
await registry.safeTransferFrom(
ownerAddress,
ZERO_ADDRESS,
// Make sure that a subname called 'sub' can be created and then burned by the owner
it('should create and burn a subnode', async () => {
await controller.setSubnode(
TEST_NODE,
labelhash('sub'),
subnodeOwnerAddress,
resolver.address,
MAX_EXPIRY,
0, // no fuse
EMPTY_ADDRESS, // no controller
{ from: ownerAddress },
)
assert.equal(await registry.controller(TEST_SUBNODE), controller.address)
assert.equal(
await registry.balanceOf(subnodeOwnerAddress, TEST_SUBNODE),
1,
'0x',
{
from: ownerAddress,
},
)
assert.equal(await registry.balanceOf(ownerAddress, TEST_NODE), 0)
assert.equal(await registry.resolver(TEST_NODE), EMPTY_ADDRESS)
assert.equal(await controller.ownerOf(TEST_NODE), EMPTY_ADDRESS)
assert.equal(await controller.expiryOf(TEST_NODE), 0)
assert.equal(await controller.fusesOf(TEST_NODE), 0)
assert.equal(await registry.resolver(TEST_SUBNODE), resolver.address)
assert.equal(await controller.ownerOf(TEST_SUBNODE), subnodeOwnerAddress)
assert.equal(await controller.expiryOf(TEST_SUBNODE), MAX_EXPIRY)
assert.equal(await controller.fusesOf(TEST_SUBNODE), 0)
assert.equal(
await controller.renewalControllerOf(TEST_SUBNODE),
EMPTY_ADDRESS,
)

await registry.burn(subnodeOwnerAddress, TEST_SUBNODE, 1, {
from: subnodeOwnerAddress,
})

assert.equal(
await controller.renewalControllerOf(TEST_NODE),
await registry.balanceOf(subnodeOwnerAddress, TEST_SUBNODE),
0,
)
assert.equal(await registry.resolver(TEST_SUBNODE), EMPTY_ADDRESS)
assert.equal(await controller.ownerOf(TEST_SUBNODE), EMPTY_ADDRESS)
assert.equal(await controller.expiryOf(TEST_SUBNODE), 0)
assert.equal(await controller.fusesOf(TEST_SUBNODE), 0)
assert.equal(
await controller.renewalControllerOf(TEST_SUBNODE),
EMPTY_ADDRESS,
)
})
Expand Down

0 comments on commit eaa6a02

Please sign in to comment.