Omniscia Boson Protocol Audit

BundleBase Manual Review Findings

BundleBase Manual Review Findings

BBS-01M: Inexistent Validation of Valid Bundle Creation

TypeSeverityLocation
Logical FaultBundleBase.sol:L45, L48

Description:

The createBundleInternal function does not properly validate that either offerIds.length or twinIds.length is non-zero, permitting a bundle to be created with zero offer IDs and zero twin IDs.

Impact:

Incorrectly created bundles can lead to misbehaviours across the greater Boson Protocol ecosystem.

Example:

contracts/protocol/bases/BundleBase.sol
36function createBundleInternal(Bundle memory _bundle) internal {
37 // get message sender
38 address sender = msgSender();
39
40 // get seller id, make sure it exists and store it to incoming struct
41 (bool exists, uint256 sellerId) = getSellerIdByOperator(sender);
42 require(exists, NOT_OPERATOR);
43
44 // limit maximum number of offers to avoid running into block gas limit in a loop
45 require(_bundle.offerIds.length <= protocolLimits().maxOffersPerBundle, TOO_MANY_OFFERS);
46
47 // limit maximum number of twins to avoid running into block gas limit in a loop
48 require(_bundle.twinIds.length <= protocolLimits().maxTwinsPerBundle, TOO_MANY_TWINS);
49
50 // Get the next bundle and increment the counter
51 uint256 bundleId = protocolCounters().nextBundleId++;
52 // Sum of offers quantity available
53 uint256 offersTotalQuantityAvailable;
54
55 for (uint256 i = 0; i < _bundle.offerIds.length; i++) {
56 uint256 offerId = _bundle.offerIds[i];
57
58 // Calculate bundle offers total quantity available.
59 offersTotalQuantityAvailable = calculateOffersTotalQuantity(offersTotalQuantityAvailable, offerId);
60
61 (bool bundleByOfferExists, ) = fetchBundleIdByOffer(offerId);
62 require(!bundleByOfferExists, BUNDLE_OFFER_MUST_BE_UNIQUE);
63
64 (bool exchangeIdsForOfferExists, ) = getExchangeIdsByOffer(offerId);
65 // make sure exchange does not already exist for this offer id.
66 require(!exchangeIdsForOfferExists, EXCHANGE_FOR_OFFER_EXISTS);
67
68 // Add to bundleIdByOffer mapping
69 protocolLookups().bundleIdByOffer[offerId] = bundleId;
70 }
71
72 for (uint256 i = 0; i < _bundle.twinIds.length; i++) {
73 uint256 twinId = _bundle.twinIds[i];
74
75 // A twin can't belong to multiple bundles
76 (bool bundleForTwinExist, ) = fetchBundleIdByTwin(twinId);
77 require(!bundleForTwinExist, BUNDLE_TWIN_MUST_BE_UNIQUE);
78
79 if (_bundle.offerIds.length > 0) {
80 bundleSupplyChecks(offersTotalQuantityAvailable, twinId);
81 }
82
83 // Push to bundleIdsByTwin mapping
84 protocolLookups().bundleIdByTwin[_bundle.twinIds[i]] = bundleId;
85 }
86
87 // Get storage location for bundle
88 (, Bundle storage bundle) = fetchBundle(bundleId);
89
90 // Set bundle props individually since memory structs can't be copied to storage
91 bundle.id = _bundle.id = bundleId;
92 bundle.sellerId = _bundle.sellerId = sellerId;
93 bundle.offerIds = _bundle.offerIds;
94 bundle.twinIds = _bundle.twinIds;
95
96 // Notify watchers of state change
97 emit BundleCreated(bundleId, sellerId, _bundle, sender);
98}

Recommendation:

We advise this trait of the system to be re-evaluated as it currently permits incorrect bundle creation.

Alleviation (44009967e4f68092941d841e9e0f5dd2bb31bf0b):

The bundle creation process now mandates that at least one twin and one offer ID has been specified when a bundle is created thus alleviating this exhibit.