Skip to content

Commit

Permalink
Merge pull request #1117 from thesandboxgame/catalyst-audit/L-11-the-…
Browse files Browse the repository at this point in the history
…catalyst-contract-allows-the-burning-and-transfer-of-non-existent-tokens

Catalyst audit: L-11 The Catalyst Contract Allows the Burning and Transfer of Non-Existent Tokens
  • Loading branch information
rishabh0x00 authored Sep 11, 2023
2 parents 30ae019 + 65fa9d4 commit 63641ab
Show file tree
Hide file tree
Showing 20 changed files with 307 additions and 239 deletions.
2 changes: 1 addition & 1 deletion packages/asset/contracts/Asset.sol
Original file line number Diff line number Diff line change
Expand Up @@ -336,7 +336,7 @@ contract Asset is
return TokenIdUtils.isBridged(tokenId);
}

/// @notice This function is used to register Asset contract on the Operator Filterer Registry of Opensea.can only be called by admin.
/// @notice This function is used to register Asset contract on the Operator Filterer Registry of OpenSea.can only be called by admin.
/// @dev used to register contract and subscribe to the subscriptionOrRegistrantToCopy's black list.
/// @param subscriptionOrRegistrantToCopy registration address of the list to subscribe.
/// @param subscribe bool to signify subscription "true"" or to copy the list "false".
Expand Down
10 changes: 5 additions & 5 deletions packages/asset/contracts/Catalyst.sol
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ import {ICatalyst} from "./interfaces/ICatalyst.sol";

/// @title Catalyst
/// @author The Sandbox
/// @notice THis contract manages catalysts which are used to mint new assets.
/// @notice This contract manages catalysts which are used to mint new assets.
/// @dev An ERC1155 contract that manages catalysts, extends multiple OpenZeppelin contracts to
/// provide a variety of features including, AccessControl, URIStorage, Burnable and more.
/// The contract includes support for meta transactions.
Expand Down Expand Up @@ -77,7 +77,7 @@ contract Catalyst is
address _defaultMinter,
string[] memory _catalystIpfsCID,
address _royaltyManager
) public initializer {
) external initializer {
require(bytes(_baseUri).length != 0, "Catalyst: base uri can't be empty");
require(_trustedForwarder != address(0), "Catalyst: trusted forwarder can't be zero");
require(_subscription != address(0), "Catalyst: subscription can't be zero");
Expand Down Expand Up @@ -235,7 +235,7 @@ contract Catalyst is
/// @param to address to which the token will be transfered.
/// @param id the token type transfered.
/// @param value amount of token transfered.
/// @param data aditional data accompanying the transfer.
/// @param data additional data accompanying the transfer.
function safeTransferFrom(
address from,
address to,
Expand All @@ -252,7 +252,7 @@ contract Catalyst is
/// @param to address to which the token will be transfered.
/// @param ids ids of each token type transfered.
/// @param values amount of each token type transfered.
/// @param data aditional data accompanying the transfer.
/// @param data additional data accompanying the transfer.
function safeBatchTransferFrom(
address from,
address to,
Expand Down Expand Up @@ -293,7 +293,7 @@ contract Catalyst is
return super.supportsInterface(interfaceId);
}

/// @notice This function is used to register Catalyst contract on the Operator Filterer Registry of Opensea.can only be called by admin.
/// @notice This function is used to register Catalyst contract on the Operator Filterer Registry of OpenSea. Can only be called by admin.
/// @dev used to register contract and subscribe to the subscriptionOrRegistrantToCopy's black list.
/// @param subscriptionOrRegistrantToCopy registration address of the list to subscribe.
/// @param subscribe bool to signify subscription "true"" or to copy the list "false".
Expand Down
8 changes: 4 additions & 4 deletions packages/asset/docs/Asset.md
Original file line number Diff line number Diff line change
Expand Up @@ -176,14 +176,15 @@ Sets a new trusted forwarder for meta-transactions.
Parameters:

- `trustedForwarder` - The new trusted forwarder.

### setTokenRoyalties

```solidity
function setTokenRoyalties(
uint256 tokenId,
address payable recipient,
address creator
) external override onlyRole(DEFAULT_ADMIN_ROLE)
) external override onlyRole(DEFAULT_ADMIN_ROLE)
```

Sets token royalty i.e. the creator splitter address as EIP2981 royalty recipient. deploys a splitter if there is none deployed for a creator. Only admin can call it.
Expand Down Expand Up @@ -254,7 +255,6 @@ Parameters:

- `tokenId` - the id of the token.


### isBridged

```solidity
Expand All @@ -275,7 +275,7 @@ function registerAndSubscribe(address subscriptionOrRegistrantToCopy, bool subsc
onlyRole(DEFAULT_ADMIN_ROLE)
```

Used to register and subscribe on the operator filter registry of Opensea
Used to register and subscribe on the operator filter registry of OpenSea

Parameters:

Expand All @@ -286,7 +286,7 @@ Parameters:

```solidity
function setOperatorRegistry(address registry) external onlyRole(DEFAULT_ADMIN_ROLE)
```fu
```

Used to the address of the operator filter registry

Expand Down
24 changes: 12 additions & 12 deletions packages/asset/test/AssetRoyalty.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ describe('Asset Royalties', function () {
seller.address,
true
);
const splitter = await RoyaltyManagerContract._creatorRoyaltiesSplitter(
const splitter = await RoyaltyManagerContract.creatorRoyaltiesSplitter(
creator.address
);

Expand Down Expand Up @@ -103,7 +103,7 @@ describe('Asset Royalties', function () {
seller.address,
true
);
const splitter = await RoyaltyManagerContract._creatorRoyaltiesSplitter(
const splitter = await RoyaltyManagerContract.creatorRoyaltiesSplitter(
creator.address
);

Expand Down Expand Up @@ -170,7 +170,7 @@ describe('Asset Royalties', function () {
seller.address,
true
);
const splitter = await RoyaltyManagerContract._creatorRoyaltiesSplitter(
const splitter = await RoyaltyManagerContract.creatorRoyaltiesSplitter(
creator.address
);

Expand Down Expand Up @@ -416,7 +416,7 @@ describe('Asset Royalties', function () {
const id = generateAssetId(creator.address, 1);
await assetAsMinter.mint(seller.address, id, 1, '0x');

const splitter = await RoyaltyManagerContract._creatorRoyaltiesSplitter(
const splitter = await RoyaltyManagerContract.creatorRoyaltiesSplitter(
creator.address
);

Expand All @@ -425,15 +425,15 @@ describe('Asset Royalties', function () {
splitter
);

expect(await splitterContract._recipient()).to.be.equal(creator.address);
expect(await splitterContract.recipient()).to.be.equal(creator.address);

const tnx = await RoyaltyManagerContract.connect(
await ethers.getSigner(creator.address)
).setRoyaltyRecipient(royaltyReceiver.address);

await tnx.wait();

expect(await splitterContract._recipient()).to.be.equal(
expect(await splitterContract.recipient()).to.be.equal(
royaltyReceiver.address
);

Expand Down Expand Up @@ -666,22 +666,22 @@ describe('Asset Royalties', function () {

const id = generateAssetId(creator.address, 1);
await assetAsMinter.mint(seller.address, id, 1, '0x');
const splitter = await RoyaltyManagerContract._creatorRoyaltiesSplitter(
const splitter = await RoyaltyManagerContract.creatorRoyaltiesSplitter(
creator.address
);
const splitterContract = await ethers.getContractAt(
splitterAbi,
splitter
);

expect(await splitterContract._recipient()).to.be.equal(creator.address);
expect(await splitterContract.recipient()).to.be.equal(creator.address);
const tnx = await RoyaltyManagerContract.connect(
await ethers.getSigner(creator.address)
).setRoyaltyRecipient(royaltyReceiver.address);

await tnx.wait();

expect(await splitterContract._recipient()).to.be.equal(
expect(await splitterContract.recipient()).to.be.equal(
royaltyReceiver.address
);

Expand Down Expand Up @@ -835,20 +835,20 @@ describe('Asset Royalties', function () {

const id = generateAssetId(creator.address, 1);
await assetAsMinter.mint(seller.address, id, 1, '0x');
const splitter = await RoyaltyManagerContract._creatorRoyaltiesSplitter(
const splitter = await RoyaltyManagerContract.creatorRoyaltiesSplitter(
creator.address
);
const splitterContract = await ethers.getContractAt(
splitterAbi,
splitter
);

expect(await splitterContract._recipient()).to.be.equal(creator.address);
expect(await splitterContract.recipient()).to.be.equal(creator.address);
const tnx = await RoyaltyManagerContract.connect(
await ethers.getSigner(creator.address)
).setRoyaltyRecipient(seller.address);
await tnx.wait();
expect(await splitterContract._recipient()).to.be.equal(seller.address);
expect(await splitterContract.recipient()).to.be.equal(seller.address);
});

it('only creator could change the recipient for his splitter', async function () {
Expand Down
43 changes: 43 additions & 0 deletions packages/asset/test/Catalyst.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -613,6 +613,24 @@ describe('Catalyst (/packages/asset/contracts/Catalyst.sol)', function () {
expect(await catalyst.balanceOf(user1.address, 1)).to.be.equal(3);
expect(await catalyst.balanceOf(user1.address, 2)).to.be.equal(4);
});
it('should fail on burning non existing token', async function () {
const {catalystAsBurner, user1} = await runCatalystSetup();
await expect(
catalystAsBurner.burnFrom(user1.address, 1, 1)
).to.be.revertedWith('ERC1155: burn amount exceeds totalSupply');
});
it('should fail on batch burning non existing tokens', async function () {
const {catalystAsBurner, user1} = await runCatalystSetup();
const catalystId = [1, 2];
const catalystAmount = [2, 2];
await expect(
catalystAsBurner.burnBatchFrom(
user1.address,
catalystId,
catalystAmount
)
).to.be.revertedWith('ERC1155: burn amount exceeds totalSupply');
});
});
describe('Metadata', function () {
it("user can view token's metadata", async function () {
Expand Down Expand Up @@ -678,6 +696,31 @@ describe('Catalyst (/packages/asset/contracts/Catalyst.sol)', function () {
expect(await catalyst.balanceOf(user2.address, 1)).to.be.equal(10);
expect(await catalyst.balanceOf(user2.address, 2)).to.be.equal(10);
});
it('should fail on transfering non existing token', async function () {
const {catalyst, user1, user2} = await runCatalystSetup();

await expect(
catalyst
.connect(user1)
.safeTransferFrom(user1.address, user2.address, 1, 1, '0x')
).to.be.revertedWith('ERC1155: insufficient balance for transfer');
});
it('should fail on batch transfering non existing tokens', async function () {
const {catalyst, user1, user2} = await runCatalystSetup();
const catalystId = [1, 2];
const catalystAmount = [2, 2];
await expect(
catalyst
.connect(user1)
.safeBatchTransferFrom(
user1.address,
user2.address,
catalystId,
catalystAmount,
'0x'
)
).to.be.revertedWith('ERC1155: insufficient balance for transfer');
});
});
describe('OperatorFilterer', function () {
describe('common subscription setup', function () {
Expand Down
56 changes: 28 additions & 28 deletions packages/asset/test/Splitter.abi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,32 +88,6 @@ export const splitterAbi = [
name: 'RecipientSet',
type: 'event',
},
{
inputs: [],
name: '_recipient',
outputs: [
{
internalType: 'address payable',
name: '',
type: 'address',
},
],
stateMutability: 'view',
type: 'function',
},
{
inputs: [],
name: '_royaltyManager',
outputs: [
{
internalType: 'contract IRoyaltyManager',
name: '',
type: 'address',
},
],
stateMutability: 'view',
type: 'function',
},
{
inputs: [],
name: 'getRecipients',
Expand Down Expand Up @@ -143,12 +117,12 @@ export const splitterAbi = [
inputs: [
{
internalType: 'address payable',
name: 'recipient',
name: '_recipient',
type: 'address',
},
{
internalType: 'address',
name: 'royaltyManager',
name: '_royaltyManager',
type: 'address',
},
],
Expand Down Expand Up @@ -189,13 +163,39 @@ export const splitterAbi = [
stateMutability: 'view',
type: 'function',
},
{
inputs: [],
name: 'recipient',
outputs: [
{
internalType: 'address payable',
name: '',
type: 'address',
},
],
stateMutability: 'view',
type: 'function',
},
{
inputs: [],
name: 'renounceOwnership',
outputs: [],
stateMutability: 'nonpayable',
type: 'function',
},
{
inputs: [],
name: 'royaltyManager',
outputs: [
{
internalType: 'contract IRoyaltyManager',
name: '',
type: 'address',
},
],
stateMutability: 'view',
type: 'function',
},
{
inputs: [
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,27 +1,28 @@
# OperatorFilterRegistry
Opensea in an attempt to regularize market places and creator earnings deployed a registry(https://github.com/ProjectOpenSea/operator-filter-registry) and asked NFT token contract to add filter logic(https://github.com/ProjectOpenSea/operator-filter-registry/blob/main/src/OperatorFilterer.sol).
# OperatorFilterRegistry

These filter has two modifier
1st : onlyAllowedOperator
OpenSea in an attempt to regularize market places and creator earnings deployed a registry(https://github.com/ProjectOpenSea/operator-filter-registry) and asked NFT token contract to add filter logic(https://github.com/ProjectOpenSea/operator-filter-registry/blob/main/src/OperatorFilterer.sol).

These filter has two modifier
1st : onlyAllowedOperator
2nd : onlyAllowedOperatorApproval

These modifiers are added to to the transfer functions(onlyAllowedOperator) and approval function(onlyAllowedOperatorApproval) such that the when not an owner tried to transfer a Token(ex: Marketplace) or owner approves an operator(ex : Marketplace) they would be filtered on the OperatorFilterRegistry.
These modifiers are added to to the transfer functions(onlyAllowedOperator) and approval function(onlyAllowedOperatorApproval) such that the when not an owner tried to transfer a Token(ex: Marketplace) or owner approves an operator(ex : Marketplace) they would be filtered on the OperatorFilterRegistry.

If the operator or the token transfer is not approved by the registry the transaction would be reverted.

On OperatorFilterRegistry a contract owner or the contract can register and maintain there own blacklist or subscribe to other blacklists but that blacklist should contain the default marketplaces blacklisted by Opensea.
On OperatorFilterRegistry a contract owner or the contract can register and maintain there own blacklist or subscribe to other blacklists but that blacklist should contain the default marketplaces blacklisted by OpenSea.

# OperatorFiltererRegistrant
# OperatorFiltererRegistrant

The OperatorFiltererRegistrant contract is made to be registered on the OperatorFilterRegistry and copy the default blacklist of the openSea. This contract would then be subscribed by the contract such as AssetERC721 and AssetERC1155.
The OperatorFiltererRegistrant contract is made to be registered on the OperatorFilterRegistry and copy the default blacklist of the OpenSea. This contract would then be subscribed by the contract such as AssetERC721 and AssetERC1155.

The OperatorFiltererRegistrant would be the subscription for our token contracts on a layer(layer-1 : Ethereum , layer-2: Polygon), such that when a address is added or removed from OperatorFiltererRegistrant's blacklist it would be come in affect for each contact which subscribe to the OperatorFiltererRegistrant's blacklist.

# Intended usage

The OperatorFiltererRegistrant is so that sandbox will have a common blacklist that would be utilized by every Token contract on a layer. This would create a single list that would be subscribed by each contract to provide uniformity to which market places sandbox wants to blacklist. This would also provide a focal point to remove and add market places such that it would be applicable to every contract that subscribe to it.

# Implementation
# Implementation

We didn't use the npm package as its solidity pragma(solidity version) doesn't match the one we have for our Asset contracts and updating our solidity version for Assets would have been to time consuming.

Expand Down
Loading

1 comment on commit 63641ab

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Coverage for this commit

96.34%

Coverage Report
FileStmtsBranchesFuncsLinesUncovered Lines
packages/asset/contracts
   Asset.sol94.53%89.58%96.43%98.08%103, 188, 293, 293–294, 65
   AssetCreate.sol94.51%83.33%100%100%130, 132, 165, 281, 68
   AssetReveal.sol94.35%86.21%96.55%98.89%143, 147, 181, 376, 416, 424, 441, 75, 98
   AuthSuperValidator.sol100%100%100%100%
   Catalyst.sol95%91.94%95.45%98.21%125, 127, 140, 152, 224, 80
packages/asset/contracts/interfaces
   IAsset.sol100%100%100%100%
   IAssetCreate.sol100%100%100%100%
   IAssetReveal.sol100%100%100%100%
   ICatalyst.sol100%100%100%100%
   ITokenUtils.sol100%100%100%100%
packages/asset/contracts/libraries
   TokenIdUtils.sol100%100%100%100%
packages/dependency-metatx/contracts
   ERC2771Handler.sol100%100%100%100%
   ERC2771HandlerAbstract.sol100%100%100%100%
   ERC2771HandlerUpgradeable.sol95.45%83.33%100%100%43
packages/dependency-metatx/contracts/test
   ERC2771HandlerTest.sol100%100%100%100%
   ERC2771HandlerUpgradeableTest.sol100%100%100%100%
   MockTrustedForwarder.sol0%0%0%0%15, 18–19, 19, 19–20
packages/dependency-operator-filter/contracts
   OperatorFiltererUpgradeable.sol82.35%85%71.43%83.33%17, 51–52, 61–62, 71, 87
   OperatorFilterSubscription.sol60%50%100%50%19–20
packages/dependency-operator-filter/contracts/interfaces
   IOperatorFilterRegistry.sol100%100%100%100%
packages/dependency-royalty-management/contracts
   MultiRoyaltyDistributor.sol84.06%65%90%92.31%104, 144, 25, 41, 41, 41, 41, 60–61, 97
   RoyaltyDistributor.sol78.95%50%80%90%20, 50, 56
   RoyaltyManager.sol96.39%87.50%100%100%104, 47, 98
   RoyaltySplitter.sol92.08%75%92.31%97.06%117, 157, 185, 218, 65, 72, 83
packages/dependency-royalty-management/contracts/interfaces
   IERC20Approve.sol100%100%100%100%
   IMultiRoyaltyDistributor.sol100%100%100%100%
   IMultiRoyaltyRecipients.sol100%100%100%100%
   IRoyaltyManager.sol100%100%100%100%
   IRoyaltyUGC.sol100%100%100%100%

Please sign in to comment.