GithubHelp home page GithubHelp logo

nuklai-contracts's People

Contributors

apostolosmavro avatar galimba avatar najlachamseddine avatar pash7ka avatar rafael-ab avatar

Stargazers

 avatar  avatar

Watchers

 avatar  avatar  avatar

nuklai-contracts's Issues

[GSD-03M] Incorrect Dependency Set

GSD-03M: Incorrect Dependency Set

Type Severity Location
Logical Fault GenericSingleDatasetSubscriptionManager.sol:L6, L18

Description:

The GenericSingleDatasetSubscriptionManager contract is meant to be an upgradeable contract, however, it utilizes non-upgradeable variants of its OpenZeppelin dependency tree.

As a result, derivative contracts such as ERC20SubscriptionManager are unable to initialize the ERC721Enumerable implementation properly.

Impact:

The ERC20SubscriptionManager deployment will contain an empty EIP-721 name and symbol as its dependency is initialized solely in the logic implementation and not in the upgradeable implementation.

Example:

import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
import {ERC721Enumerable} from "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol";
import {ISubscriptionManager} from "../interfaces/ISubscriptionManager.sol";
import {IDatasetNFT} from "../interfaces/IDatasetNFT.sol";

/**
 * @title GenericSingleDatasetSubscriptionManager contract
 * @author Data Tunnel
 * @notice Abstract contract serving as the foundation for managing single Dataset subscriptions and related operations.
 * Derived contracts mint ERC721 tokens that represent subscriptions to the managed Dataset, thus, subscriptions
 * have unique IDs which are the respective minted ERC721 tokens' IDs.
 * @dev Extends ISubscriptionManager, Initializable, ERC721Enumerable
 */
abstract contract GenericSingleDatasetSubscriptionManager is ISubscriptionManager, Initializable, ERC721Enumerable {

Recommendation:

We advise the code as a whole to make user of upgradeable OpenZeppelin contracts, ensuring that the ERC20SubscriptionManager can properly initialize the ERC721Enumerable contract in its ERC20SubscriptionManager::initialize function.

[GSD-01M] Incorrect Restriction of Subscription Extension

GSD-01M: Incorrect Restriction of Subscription Extension

Type Severity Location
Logical Fault GenericSingleDatasetSubscriptionManager.sol:L300, L317

Description:

A GenericSingleDatasetSubscriptionManager::_extendSubscription operation permits a subscription to be extended beyond the 365 day limit a subscription duration should abide by.

In detail, it can exceed the 365 day limit by a maximum of 30 days, leading to the actual maximum duration achievable as being 395 days.

Impact:

A subscription's duration can presently exceed the 365 limit enforced by its creation via the extension mechanism the contract exposes.

Example:

function _extendSubscription(uint256 subscription, uint256 extraDurationInDays, uint256 extraConsumers) internal {
  _requireMinted(subscription);

  if (extraDurationInDays > 365) revert SUBSCRIPTION_DURATION_INVALID(1, 365, extraDurationInDays);

  SubscriptionDetails storage sd = _subscriptions[subscription];
  uint256 newDurationInDays;
  uint256 newValidSince;
  uint256 currentFee;

  if (sd.validTill > block.timestamp) {
    // Subscription is still valid but remaining duration must be <= 30 days to extend it
    if (extraDurationInDays > 0)
      if ((sd.validTill - block.timestamp) > 30 * 1 days)
        revert SUBSCRIPTION_REMAINING_DURATION(30 * 1 days, (sd.validTill - block.timestamp));

    // (sd.validTill - sd.validSince) was enforced during subscription to be an integral multiple of a day in seconds
    uint256 currentDurationInDays = (sd.validTill - sd.validSince) / 1 days;
    (, currentFee) = _calculateFee(currentDurationInDays, sd.paidConsumers);
    newValidSince = sd.validSince;
    newDurationInDays = currentDurationInDays + extraDurationInDays;
  } else {
    // Subscription is already invalid
    // currentFee = 0;
    newValidSince = block.timestamp;
    newDurationInDays = extraDurationInDays;
  }
  uint256 newConsumers = sd.paidConsumers + extraConsumers;
  (, uint256 newFee) = _calculateFee(newDurationInDays, newConsumers);
  if (newFee <= currentFee) revert NOTHING_TO_PAY();

  _charge(_msgSender(), newFee - currentFee);

  sd.validSince = newValidSince;
  sd.validTill = newValidSince + (newDurationInDays * 1 days);
  sd.paidConsumers = newConsumers;
  emit SubscriptionPaid(subscription, sd.validSince, sd.validTill, sd.paidConsumers);
}

Recommendation:

We advise the extension code of GenericSingleDatasetSubscriptionManager::_extendSubscription to validate the newDurationInDays value rather than the extraDurationInDays value when enforcing the subscription duration limit, ensuring that a subscription can at most have a duration of 365 days.

[DMR-03M] Potential Loss of Funds

DMR-03M: Potential Loss of Funds

Type Severity Location
Language Specific DistributionManager.sol:L196

Description:

The DistributionManager::receivePayment function does not prevent native funds from being sent alongside EIP-20 assets, leading to a potential loss of funds that are transmitted in such a call.

Impact:

It is presently possible to lock native funds in the DistributionManager contract by specifying a non-zero amount of native funds alongside a DistributionManager::receivePayment call with an EIP-20 asset.

Example:

if (address(token) == address(0)) {
  if (amount != msg.value) revert MSG_VALUE_MISMATCH(msg.value, amount);
} else {
  IERC20(token).safeTransferFrom(_msgSender(), address(this), amount);
}

Recommendation:

We advise the code to ensure that the msg.value is 0 in case the token specified is non-zero, ensuring that native funds cannot be accidentally locked in the contract when supplying native funds alongside an EIP-20 DistributionManager::receivePayment function execution.

[DNF-03C] Loop Iterator Optimization

DNF-03C: Loop Iterator Optimization

Type Severity Location
Gas Optimization DatasetNFT.sol:L192

Description:

The linked for loop increments / decrements the iterator "safely" due to Solidity's built-in safe arithmetics (post-0.8.X).

Example:

for (uint256 i; i < models.length; i++) {

Recommendation:

We advise the increment / decrement operation to be performed in an unchecked code block as the last statement within the for loop to optimize its execution cost.

[DNF-01S] Data Location Optimizations

DNF-01S: Data Location Optimizations

Type Severity Location
Gas Optimization DatasetNFT.sol:L283, L284

Description:

The linked input arguments are set as memory in external function(s).

Example:

function proposeManyFragments(
  uint256 datasetId,
  address[] memory owners,
  bytes32[] memory tags,
  bytes calldata signature
) external {

Recommendation:

We advise them to be set as calldata optimizing their read-access gas cost.

[FNF-01S] Data Location Optimizations

FNF-01S: Data Location Optimizations

Type Severity Location
Gas Optimization FragmentNFT.sol:L277, L278

Description:

The linked input arguments are set as memory in external function(s).

Example:

function proposeMany(
  address[] memory owners,
  bytes32[] memory tags_,
  bytes calldata signature
) external onlyDatasetNFT {

Recommendation:

We advise them to be set as calldata optimizing their read-access gas cost.

[VMR-01C] Inexistent Event Indexing

VMR-01C: Inexistent Event Indexing

Type Severity Location
Language Specific VerifierManager.sol:L26, L27

Description:

The FragmentPending, and FragmentResolved events of the VerifierManager contract do not contain any indexed arguments.

Example:

event FragmentPending(uint256 id);
event FragmentResolved(uint256 id, bool accept);

Recommendation:

We advise them to be introduced, particularly in relation to the id to ensure off-chain processes can optimally filter these events.

[GSD-01C] Ineffectual Usage of Safe Arithmetics

GSD-01C: Ineffectual Usage of Safe Arithmetics

Type Severity Location
Language Specific GenericSingleDatasetSubscriptionManager.sol:L133, L310, L311, L328

Description:

The linked mathematical operations are guaranteed to be performed safely by surrounding conditionals evaluated in either require checks or if-else constructs.

Example:

return (newFee > currentFee) ? (newFee - currentFee) : 0;

Recommendation:

Given that safe arithmetics are toggled on by default in pragma versions of 0.8.X, we advise the linked statements to be wrapped in unchecked code blocks thereby optimizing their execution cost.

[DMR-04M] Inexistent Sanitization of Sensitive Module Implementations

DMR-04M: Inexistent Sanitization of Sensitive Module Implementations

Type Severity Location
Logical Fault DistributionManager.sol:L160-L178

Description:

The DatasetNFT::setManagers function permits the subscriptionManager, distributionManager, and verifierManager implementations that the system will utilize to be arbitrarily specified which is a significant security flaw in the system given that these modules are expected to utilize user funds, get approved by users to spend funds, and generally act as integral modules to the DatasetNFT contract's overall operation.

Impact:

Arbitrarily specified subscription and distribution implementations can lead to direct fund loss (by exploiting approvals) or to "free" data-set contributions (as the distributor could f.e. distribute all funds to a single party).

Example:

function _setDatasetOwnerPercentage(uint256 percentage) internal {
  if (percentage > 5e17) revert PERCENTAGE_VALUE_INVALID(5e17, percentage);
  datasetOwnerPercentage = percentage;
}

/**
 * @notice Internal function that sets the weights of the respective provided tags.
 * @dev Called by `setTagWeights()` and `setDSOwnerPercentageAndTagWeights`
 * @param tags The tags participating in the payment distributions
 * @param weights The weights of the respective tags to set
 */
function _setTagWeights(bytes32[] calldata tags, uint256[] calldata weights) internal {
  EnumerableMap.Bytes32ToUintMap storage tagWeights = _versionedTagWeights.push();
  uint256 weightSum;
  for (uint256 i; i < weights.length; i++) {
    weightSum += weights[i];
    tagWeights.set(tags[i], weights[i]);
  }
  if (weightSum != 1e18) revert TAG_WEIGHTS_SUM_INVALID(1e18, weightSum);

Recommendation:

We advise the code to maintain an administrator-controlled implementation whitelist per manager type that the DatasetNFT::setManagers function sanitizes its inputs against, ensuring that the implementations attached to a particular data-set ID are secure and valid.

[FNF-03C] Inefficient Loop Limit Evaluation

FNF-03C: Inefficient Loop Limit Evaluation

Type Severity Location
Gas Optimization FragmentNFT.sol:L208

Description:

The linked for loop evaluates its limit inefficiently on each iteration.

Example:

for (uint256 i; i < tagCount.length(); i++) {

Recommendation:

We advise the statements within the for loop limit to be relocated outside to a local variable declaration that is consequently utilized for the evaluation to significantly reduce the codebase's gas cost. We should note the same optimization is applicable for storage reads present in such limits as they are newly read on each iteration (i.e. length members of arrays in storage).

[AMV-01M] Insecure Utilization of Fragment NFT

AMV-01M: Insecure Utilization of Fragment NFT

Type Severity Location
Logical Fault AcceptManuallyVerifier.sol:L28-L30, L36-L38, L73-L75, L77, L91-L93, L95, L108-L110, L114

Description:

All referenced statements utilize the fragmentNFT the caller specifies and utilize its getter functions to assess the address at which an IVerifierManager::resolve call should be performed at.

This evaluation path is insecure as it is possible for a user to submit a malicious fragmentNFT that passes the relevant access-control checks for the AcceptManuallyVerifier::onlyVerifierManager / AcceptManuallyVerifier::onlyDatasetOwner and ultimately yields a valid IVerifierManager where the VerifierManager::resolve call is made to.

As the VerifierManager::resolve function will evaluate the IFragmentNFT to perform a call to via the dataset and datasetId entries it contains, a malicious contract can be utilized to trick a "manual" acceptance of a fragment ID.

Impact:

THe AcceptManuallyVerifier can be completely bypassed by a carefully crafted IFragmentNFT and IDatasetNFT compliant malicious contract.

Example:

/**
 * @notice Resolves a single contribution proposal
 * @dev Only callable by the Dataset owner.
 * Emits a {FragmentResolved} event.
 * @param fragmentNFT The address of the FragmentNFT contract instance
 * @param id The ID of the pending Fragment associated with the contribution proposal
 * @param accept Flag to indicate acceptance (`true`) or rejection (`true`)
 */
function resolve(address fragmentNFT, uint256 id, bool accept) external onlyDatasetOwner(fragmentNFT) {
  VerifierManager vm = VerifierManager(
    IDatasetNFT(IFragmentNFT(fragmentNFT).dataset()).verifierManager(IFragmentNFT(fragmentNFT).datasetId())
  );
  _pendingFragments[fragmentNFT].remove(id);
  vm.resolve(id, accept);
  emit FragmentResolved(fragmentNFT, id, accept);
}

Recommendation:

We advise the code to re-evaluate its approach, configuring the relevant addresses it is meant to invoke in an AcceptManuallyVerifier::constructor, ensuring that the input fragmentNFT is validated as a correct implementation by querying the IDatasetNFT::fragmentNFT getter function with the relevant datasetId.

[VMR-01S] Inexistent Event Emissions

VMR-01S: Inexistent Event Emissions

Type Severity Location
Language Specific VerifierManager.sol:L66-L68, L77-L79, L90

Description:

The linked functions adjust sensitive contract variables yet do not emit an event for it.

Example:

function setDefaultVerifier(address defaultVerifier_) external onlyDatasetOwner {
  defaultVerifier = defaultVerifier_;
}

Recommendation:

We advise an event to be declared and correspondingly emitted for each function to ensure off-chain processes can properly react to this system adjustment.

[ERC-01M] Inexistent Prevention of Native Funds

ERC-01M: Inexistent Prevention of Native Funds

Type Severity Location
Language Specific ERC20SubscriptionManager.sol:L91-L96

Description:

The ERC20SubscriptionManager::_charge function will not prevent native funds from being sent alongside the call the function is executed in, resulting in the GenericSingleDatasetSubscriptionManager::subscribe and GenericSingleDatasetSubscriptionManager::extendSubscription functions locking all native funds that may be accidentally sent when they are invoked.

Impact:

It is presently possible to lock funds in the ERC20SubscriptionManager by supplying native funds in either the GenericSingleDatasetSubscriptionManager::subscribe or GenericSingleDatasetSubscriptionManager::extendSubscription functions.

Example:

function _charge(address subscriber, uint256 amount) internal override {
  token.safeTransferFrom(subscriber, address(this), amount);
  address distributionManager = dataset.distributionManager(datasetId);
  token.approve(distributionManager, amount);
  IDistributionManager(distributionManager).receivePayment(address(token), amount);
}

Recommendation:

We advise the code to introduce a require check, ensuring that the msg.value of the call is 0 to prevent native funds from being accidentally locked in the ERC20SubscriptionManager.

[FNF-01M] Proposal Tag ID Corruption Re-Entrancy

FNF-01M: Proposal Tag ID Corruption Re-Entrancy

Type Severity Location
Language Specific FragmentNFT.sol:L293-L296

Description:

The FragmentNFT::proposeMany function contains a re-entrancy attack vector that manifests when the VerifierManager calls the FragmentNFT::accept function in the same call.

Specifically, the FragmentNFT::_proposeManyMessageHash function will validate a signed payload with a specific _mintCounter range, however, this range can be invalidated by re-entering the contract during an interim IVerifierManager::propose call and invoking f.e. DatasetNFT::proposeFragment, causing the _mintCounter to be incremented while the loop within FragmentNFT::proposeMany has not concluded and thus leading to out-of-order tag and pending fragment owner IDs.

Impact:

The current FragmentNFT::proposeMany mechanism is insecure as the signed message hash validated in FragmentNFT::_proposeManyMessageHash can be "invalidated" via a re-entrancy when the IVerifierManager instantly "accepts" proposals and thus leads to the ERC721::_safeMint operation being executed within FragmentNFT::accept which is re-entrant.

Example:

function proposeMany(
  address[] memory owners,
  bytes32[] memory tags_,
  bytes calldata signature
) external onlyDatasetNFT {
  if (tags_.length != owners.length) revert ARRAY_LENGTH_MISMATCH();
  bytes32 msgHash = _proposeManyMessageHash(_mintCounter + 1, _mintCounter + tags_.length, owners, tags_);
  address signer = ECDSA.recover(msgHash, signature);
  if (!dataset.isSigner(signer)) revert BAD_SIGNATURE(msgHash, signer);

  for (uint256 i; i < owners.length; i++) {
    uint256 id = ++_mintCounter;
    bytes32 tag = tags_[i];
    pendingFragmentOwners[id] = owners[i];
    tags[id] = tag;
    emit FragmentPending(id, tag);

    // Here we call VeriferManager and EXPECT it to call `accept()`
    // during this call OR at any following transaction.
    // DO NOT implement any state changes after this point!
    IVerifierManager(dataset.verifierManager(datasetId)).propose(id, tag);
  }
}

Recommendation:

We advise the code to cache the _mintCounter to a local cachedMintCounter variable before the for loop's execution and to validate it afterwards as being strictly equal to the _mintCounter (i.e. cachedMintCounter + tags_.length == _mintCounter).

This mechanism will prevent re-entrancy attacks from affecting the sole storage related entry utilized within the vulnerable for loop within FragmentNFT::proposeMany.

[GSD-02C] Inefficient Loop Limit Evaluation

GSD-02C: Inefficient Loop Limit Evaluation

Type Severity Location
Gas Optimization GenericSingleDatasetSubscriptionManager.sol:L93

Description:

The linked for loop evaluates its limit inefficiently on each iteration.

Example:

for (uint256 i; i < subscrs.length(); i++) {

Recommendation:

We advise the statements within the for loop limit to be relocated outside to a local variable declaration that is consequently utilized for the evaluation to significantly reduce the codebase's gas cost. We should note the same optimization is applicable for storage reads present in such limits as they are newly read on each iteration (i.e. length members of arrays in storage).

[DMR-01C] Inefficient Loop Limit Evaluations

DMR-01C: Inefficient Loop Limit Evaluations

Type Severity Location
Gas Optimization DistributionManager.sol:L305, L331, L352, L377

Description:

The linked for loops evaluate their limit inefficiently on each iteration.

Example:

for (uint256 i = firstUnclaimedPayout; i < payments.length; i++) {

Recommendation:

We advise the statements within the for loop limits to be relocated outside to a local variable declaration that is consequently utilized for the evaluations to significantly reduce the codebase's gas cost. We should note the same optimization is applicable for storage reads present in those limits as they are newly read on each iteration (i.e. length members of arrays in storage).

[GSD-04C] Loop Iterator Optimizations

GSD-04C: Loop Iterator Optimizations

Type Severity Location
Gas Optimization GenericSingleDatasetSubscriptionManager.sol:L93, L348, L368, L394

Description:

The linked for loops increment / decrement their iterator "safely" due to Solidity's built - in safe arithmetics (post-0.8.X).

Example:

for (uint256 i; i < subscrs.length(); i++) {

Recommendation:

We advise the increment / decrement operations to be performed in an unchecked code block as the last statement within each for loop to optimize their execution cost.

[DNF-02M] Insecure Signature Validation

DNF-02M: Insecure Signature Validation

Type Severity Location
Logical Fault DatasetNFT.sol:L141

Description:

The DatasetNFT::_mintMessageHash function will yield a message digest that does not contain the to member the mint operation will create an NFT to.

As such, all mint operations are susceptible to on-chain race conditions whereby a DatasetNFT::mint operation is detected and a malicious actor submits a transaction with a higher priority that changes the to argument to their choosing.

Impact:

The signature validation mechanism in DatasetNFT::mint is ineffectual as it does not validate the to member the NFT is being minted for, permitting anyone to claim any signature.

Example:

function mint(bytes32 uuidHashed, address to, bytes calldata signature) external returns (uint256) {
  bytes32 msgHash = _mintMessageHash(uuidHashed);
  address signer = ECDSA.recover(msgHash, signature);
  if (!hasRole(SIGNER_ROLE, signer)) revert BAD_SIGNATURE(msgHash, signer);

  uint256 id = uint256(uuidHashed);

  _mint(to, id);

  return id;
}

Recommendation:

We advise the code to properly attach the to member with the uuidHashed, preventing on-chain race conditions from being able to "steal" the mint operation of a particular data-set ID.

[FNF-02C] Ineffectual Usage of Safe Arithmetics

FNF-02C: Ineffectual Usage of Safe Arithmetics

Type Severity Location
Language Specific FragmentNFT.sol:L480, L495, L563

Description:

The linked mathematical operations are guaranteed to be performed safely by surrounding conditionals evaluated in either require checks or if-else constructs.

Example:

if (arr.length == 0) arr.push();
return arr[arr.length - 1];

Recommendation:

Given that safe arithmetics are toggled on by default in pragma versions of 0.8.X, we advise the linked statements to be wrapped in unchecked code blocks thereby optimizing their execution cost.

[DMR-01M] Incompatibility of Fee-Based Tokens

DMR-01M: Incompatibility of Fee-Based Tokens

Type Severity Location
Standard Conformity DistributionManager.sol:L196

Description:

The DistributionManager::receivePayment function will greatly misbehave when dealing with tokens that contain a fee-on-transfer mechanism as it will assume to have received the full amount transferred.

Impact:

The severity of this exhibit will be adjusted depending on whether the Nexera Protocol team intended to support fee-on-transfer tokens in their DistributionManager system.

Example:

function receivePayment(address token, uint256 amount) external payable onlySubscriptionManager nonReentrant {
  if (_versionedTagWeights.length == 0) revert TAG_WEIGHTS_NOT_INITIALIZED();
  if (address(token) == address(0)) {
    if (amount != msg.value) revert MSG_VALUE_MISMATCH(msg.value, amount);
  } else {
    IERC20(token).safeTransferFrom(_msgSender(), address(this), amount);
  }
  uint256 snapshotId = fragmentNFT.snapshot();

  // Deployer fee
  uint256 deployerFee = (amount * dataset.deployerFeePercentage(datasetId)) / 1e18;

Recommendation:

We advise the code to either explicitly specify that fee-on-transfer tokens are not supported or to properly support them by tracking the before-and-after balance measurements of the contract, either of which we consider an adequate resolution to this exhibit.

[GSD-05C] Repetitive Value Literals

GSD-05C: Repetitive Value Literals

Type Severity Location
Code Style GenericSingleDatasetSubscriptionManager.sol:L114, L272, L300, L310, L311

Description:

The linked value literals are repeated across the codebase multiple times.

Example:

if (durationInDays == 0 || durationInDays > 365) revert SUBSCRIPTION_DURATION_INVALID(1, 365, durationInDays);

Recommendation:

We advise each to be set to its dedicated constant variable instead optimizing the legibility of the codebase.

[FNF-05C] Inefficient `mapping` Lookups

FNF-05C: Inefficient mapping Lookups

Type Severity Location
Gas Optimization FragmentNFT.sol:L427, L430, L452, L455

Description:

The linked statements perform key-based lookup operations on mapping declarations from storage multiple times for the same key redundantly.

Example:

function _updateAccountSnapshot(address account, uint256 firstTokenId, uint256 batchSize, bool add) private {
  uint256 currentSnapshot = _currentSnapshotId();
  EnumerableMap.Bytes32ToUintMap storage currentAccountTagCount = _snapshots[currentSnapshot].accountTagCount[
    account
  ];
  uint256 lastAccountSnapshot = _lastUint256ArrayElement(_accountSnapshotIds[account]);
  if (lastAccountSnapshot < currentSnapshot) {
    _copy(_snapshots[lastAccountSnapshot].accountTagCount[account], currentAccountTagCount);
    _accountSnapshotIds[account].push(currentSnapshot);
  }
  for (uint256 i; i < batchSize; i++) {
    uint256 id = firstTokenId + i;
    bytes32 tag = tags[id];
    (, uint256 currentCount) = currentAccountTagCount.tryGet(tag);
    currentAccountTagCount.set(tag, add ? (currentCount + 1) : (currentCount - 1));
  }
}

Recommendation:

As the lookups internally perform an expensive keccak256 operation, we advise the lookups to be cached wherever possible to a single local declaration that either holds the value of the mapping in case of primitive types or holds a storage pointer to the struct contained.

[DNF-02C] Inefficient `mapping` Lookups

DNF-02C: Inefficient mapping Lookups

Type Severity Location
Gas Optimization DatasetNFT.sol:L162, L163, L166, L167, L170, L171

Description:

The linked statements perform key-based lookup operations on mapping declarations from storage multiple times for the same key redundantly.

Example:

function setManagers(uint256 id, ManagersConfig calldata config) external onlyTokenOwner(id) {
  bool changed;
  if (configurations[id].subscriptionManager != config.subscriptionManager) {
    proxies[id].subscriptionManager = _cloneAndInitialize(config.subscriptionManager, id);
    changed = true;
  }
  if (configurations[id].distributionManager != config.distributionManager) {
    proxies[id].distributionManager = _cloneAndInitialize(config.distributionManager, id);
    changed = true;
  }
  if (configurations[id].verifierManager != config.verifierManager) {
    proxies[id].verifierManager = _cloneAndInitialize(config.verifierManager, id);
    changed = true;
  }
  if (changed) {
    configurations[id] = config;
    emit ManagersConfigChange(id);
  }
}

Recommendation:

As the lookups internally perform an expensive keccak256 operation, we advise the lookups to be cached wherever possible to a single local declaration that either holds the value of the mapping in case of primitive types or holds a storage pointer to the struct contained.

[FNF-06C] Loop Iterator Optimizations

FNF-06C: Loop Iterator Optimizations

Type Severity Location
Gas Optimization FragmentNFT.sol:L184, L208, L234, L286, L365, L457, L549

Description:

The linked for loops increment / decrement their iterator "safely" due to Solidity's built - in safe arithmetics (post-0.8.X).

Example:

for (uint256 i; i < length; i++) {

Recommendation:

We advise the increment / decrement operations to be performed in an unchecked code block as the last statement within each for loop to optimize their execution cost.

[DMR-02C] Loop Iterator Optimizations

DMR-02C: Loop Iterator Optimizations

Type Severity Location
Gas Optimization DistributionManager.sol:L120, L174, L305, L331, L352, L377, L410

Description:

The linked for loops increment / decrement their iterator "safely" due to Solidity's built - in safe arithmetics (post-0.8.X).

Example:

for (uint256 i; i < tagsLength; i++) {

Recommendation:

We advise the increment / decrement operations to be performed in an unchecked code block as the last statement within each for loop to optimize their execution cost.

[DFY-01C] Inefficient Contract Imports

DFY-01C: Inefficient Contract Imports

Type Severity Location
Gas Optimization DatasetFactory.sol:L6-L8

Description:

The DatasetFactory contract will contain an inflated bytecode size as it imports the full VerifierManager, DistributionManager, and ERC20SubscriptionManager implementations even though they are solely utilized as interface declarations.

Example:

import {VerifierManager} from "./verifier/VerifierManager.sol";
import {DistributionManager} from "./distribution/DistributionManager.sol";
import {ERC20SubscriptionManager} from "./subscription/ERC20SubscriptionManager.sol";

Recommendation:

We advise a proper interface to be coded for each contract (i.e. IVerifierManager) and to be imported to the DatasetFactory, minimizing its deployed bytecode size.

[DMR-03C] Redundant Function Argument

DMR-03C: Redundant Function Argument

Type Severity Location
Gas Optimization DistributionManager.sol:L369

Description:

The DistributionManager::_claimPayouts function is solely invoked by the DistributionManager::claimDatasetOwnerAndFragmentPayouts function which will in turn supply the ContextUpgradeable::_msgSender as the function's argument at all times.

Example:

function _claimPayouts(address beneficiary) internal {
  // Claim payouts
  uint256 firstUnclaimedPayout = _firstUnclaimedContribution[beneficiary];
  if (firstUnclaimedPayout >= payments.length) return; // Nothing to claim
  _firstUnclaimedContribution[beneficiary] = payments.length; // CEI pattern to prevent reentrancy

  address collectToken = payments[firstUnclaimedPayout].token;
  uint256 collectAmount;
  for (uint256 i = firstUnclaimedPayout; i < payments.length; i++) {
    Payment storage p = payments[i];
    if (collectToken != p.token) {
      // Payment token changed, send what we've already collected
      _sendPayout(collectToken, collectAmount, beneficiary);
      collectToken = p.token;
      collectAmount = 0;
    }
    collectAmount += _calculatePayout(p, beneficiary);
  }
  // send collected and not sent yet
  _sendPayout(collectToken, collectAmount, beneficiary);
}

Recommendation:

We advise the code to omit its input argument and to utilize the ContextUpgradeable::_msgSender value directly, optimizing its gas cost.

This will also permit the function to be invoked by the DistributionManager::claimPayouts function, reducing the overall bytecode size of the contract.

[AMV-01C] Inefficient Contract Import

AMV-01C: Inefficient Contract Import

Type Severity Location
Gas Optimization AcceptManuallyVerifier.sol:L8

Description:

The AcceptManuallyVerifier contract will contain an inflated bytecode size as it imports the full VerifierManager implementation even though it is solely utilized as an interface.

Example:

import {VerifierManager} from "./VerifierManager.sol";

Recommendation:

We advise a proper interface to be coded for the relevant contract (i.e. IVerifierManager) and to be imported to the AcceptManuallyVerifier, minimizing its deployed bytecode size.

[VMR-02S] Inexistent Sanitization of Input Address

VMR-02S: Inexistent Sanitization of Input Address

Type Severity Location
Input Sanitization VerifierManager.sol:L66-L68

Description:

The linked function accepts an address argument yet does not properly sanitize it.

Impact:

The presence of zero-value addresses, especially in constructor implementations, can cause the contract to be permanently inoperable. These checks are advised as zero-value inputs are a common side-effect of off-chain software related bugs.

Example:

function setDefaultVerifier(address defaultVerifier_) external onlyDatasetOwner {
  defaultVerifier = defaultVerifier_;
}

Recommendation:

We advise some basic sanitization to be put in place by ensuring that the address specified is non-zero.

[DNF-01C] Duplicate Invocation of Getter Function

DNF-01C: Duplicate Invocation of Getter Function

Type Severity Location
Gas Optimization DatasetNFT.sol:L107, L108

Description:

The DatasetNFT::_contractURI functions are invoked twice redundantly in the DatasetNFT::tokenURI function.

Example:

function tokenURI(uint256 tokenId) public view override(ERC721Upgradeable, IDatasetNFT) returns (string memory) {
  if (!_exists(tokenId)) revert TOKEN_ID_NOT_EXISTS(tokenId);
  string memory contractURI_ = string.concat(_contractURI(), "/");
  return bytes(_contractURI()).length > 0 ? string.concat(contractURI_, tokenId.toString()) : "";
}

Recommendation:

We advise the function to be invoked once and its result to be stored to a string memory variable that is consequently re-used in place of its second invocation, optimizing the function's gas cost.

[DMR-04C] Repetitive Value Literals

DMR-04C: Repetitive Value Literals

Type Severity Location
Code Style DistributionManager.sol:L161, L201, L211

Description:

The linked value literals are repeated across the codebase multiple times.

Example:

if (percentage > 5e17) revert PERCENTAGE_VALUE_INVALID(5e17, percentage);

Recommendation:

We advise each to be set to its dedicated constant variable instead optimizing the legibility of the codebase.

[DMR-01S] Illegible Numeric Value Representation

DMR-01S: Illegible Numeric Value Representation

Type Severity Location
Code Style DistributionManager.sol:L161

Description:

The linked representation of a numeric literal is sub-optimally represented decreasing the legibility of the codebase.

Example:

if (percentage > 5e17) revert PERCENTAGE_VALUE_INVALID(5e17, percentage);

Recommendation:

To properly illustrate the value's purpose, we advise the following guidelines to be followed.
For values meant to depict fractions with a base of 1e18, we advise fractions to be utilized directly (i.e. 1e17 becomes 0.1e18) as they are supported.
For values meant to represent a percentage base, we advise each value to utilize the underscore (_) separator to discern the percentage decimal (i.e. 10000 becomes 100_00, 300 becomes 3_00 and so on).
Finally, for large numeric values we simply advise the underscore character to be utilized again to represent them (i.e. 1000000 becomes 1_000_000).

[GSD-02M] Inexistent Surcharge Protection of Subscriber

GSD-02M: Inexistent Surcharge Protection of Subscriber

Type Severity Location
Logical Fault GenericSingleDatasetSubscriptionManager.sol:L277, L328

Description:

The GenericSingleDatasetSubscriptionManager::_subscribe and GenericSingleDatasetSubscriptionManager::_extendSubscription functions do not permit the caller to control how much funds they are charged for a particular subscription, permitting a malicious subscription manager to arbitrarily charge them.

In detail, the ERC20SubscriptionManager::setFee function permits the fee to be arbitrarily set by the data-set owner leading to an on-chain race condition whereby a data-set owner detects a subscription and alters the fee of the subscription with a higher-priority transaction.

Impact:

The vulnerability is trivial to exploit by creating a seemingly lucrative data-set whose price is consequentially altered maliciously the moment a subscription is detected, leading to "loss of funds" for the subscriber.

Example:

function _subscribe(uint256 ds, uint256 durationInDays, uint256 consumers) internal returns (uint256 sid) {
  _requireCorrectDataset(ds);
  if (balanceOf(_msgSender()) != 0) revert CONSUMER_ALREADY_SUBSCRIBED(_msgSender());
  if (durationInDays == 0 || durationInDays > 365) revert SUBSCRIPTION_DURATION_INVALID(1, 365, durationInDays);

  if (consumers == 0) revert CONSUMER_ZERO();

  (, uint256 fee) = _calculateFee(durationInDays, consumers);
  _charge(_msgSender(), fee);

  sid = ++_mintCounter;
  SubscriptionDetails storage sd = _subscriptions[sid];
  sd.validSince = block.timestamp;
  sd.validTill = block.timestamp + (durationInDays * 1 days);
  sd.paidConsumers = consumers;
  _safeMint(_msgSender(), sid);
  emit SubscriptionPaid(sid, sd.validSince, sd.validTill, sd.paidConsumers);
}

Recommendation:

We advise the higher-level GenericSingleDatasetSubscriptionManager::subscribe and GenericSingleDatasetSubscriptionManager::extendSubscription functions to accept an additional argument that permits the subscriber to enforce an upper-limit on the amount they are willing to be charged for the particular operation, preventing the aforementioned race condition from manifesting.

[GSD-03C] Inexistent Event Indexing

GSD-03C: Inexistent Event Indexing

Type Severity Location
Language Specific GenericSingleDatasetSubscriptionManager.sol:L22-L24

Description:

The SubscriptionPaid, ConsumerAdded, and ConsumerRemoved events of the GenericSingleDatasetSubscriptionManager contract do not contain any indexed arguments.

Example:

event SubscriptionPaid(uint256 id, uint256 validSince, uint256 validTill, uint256 paidConsumers);
event ConsumerAdded(uint256 id, address consumer);
event ConsumerRemoved(uint256 id, address consumer);

Recommendation:

We advise them to be introduced, particularly in relation to the id as well as consumer to ensure off-chain processes can optimally filter these events.

[FNF-04C] Inefficient Maintenance of Account Snapshots

FNF-04C: Inefficient Maintenance of Account Snapshots

Type Severity Location
Gas Optimization FragmentNFT.sol:L427, L452, L561-L564

Description:

In the current structure of the FragmentNFT, the _accountSnapshotIds will always possess an empty 0 entry as its first element which would indicate that the first snapshot ID is the "last" snapshot of all accounts incorrectly.

Example:

function _updateAccountSnapshot(address account, uint256 firstTokenId, uint256 batchSize, bool add) private {
  uint256 currentSnapshot = _currentSnapshotId();
  EnumerableMap.Bytes32ToUintMap storage currentAccountTagCount = _snapshots[currentSnapshot].accountTagCount[
    account
  ];
  uint256 lastAccountSnapshot = _lastUint256ArrayElement(_accountSnapshotIds[account]);
  if (lastAccountSnapshot < currentSnapshot) {
    _copy(_snapshots[lastAccountSnapshot].accountTagCount[account], currentAccountTagCount);
    _accountSnapshotIds[account].push(currentSnapshot);
  }
  for (uint256 i; i < batchSize; i++) {
    uint256 id = firstTokenId + i;
    bytes32 tag = tags[id];
    (, uint256 currentCount) = currentAccountTagCount.tryGet(tag);
    currentAccountTagCount.set(tag, add ? (currentCount + 1) : (currentCount - 1));
  }
}

Recommendation:

We advise the code to simply initialize the _snapshots entry with two empty snapshots rather than one in FragmentNFT::initialize.

This will ensure that the valid FragmentNFT::_currentSnapshotId values start from 1 rather than 0, allowing the FragmentNFT::_lastUint256ArrayElement function to immediately yield 0 instead of pushing an empty element to its arr, significantly optimizing the account snapshot system.

[DMR-02M] Incorrect Initialization Methodology

DMR-02M: Incorrect Initialization Methodology

Type Severity Location
Logical Fault DistributionManager.sol:L91

Description:

The DistributionManager::initialize function will incorrectly initialize the distribution manager when no fragment NFT has been deployed yet for a particular datasetId as the DatasetNFT::setManagers function permits the configuration of a distributionManager even if no fragment NFT has yet been deployed.

Impact:

A misconfiguration of the DistributionManager would be undetected by front-end processes as the DatasetNFT::setManagers function can be invoked at any time, leading to the vulnerability manifesting when the DistributionManager attempts to receive funds via the DistributionManager::receivePayment function.

Example:

function initialize(address dataset_, uint256 datasetId_) external initializer {
  __ReentrancyGuard_init();
  dataset = IDatasetNFT(dataset_);
  datasetId = datasetId_;
  fragmentNFT = IFragmentNFT(dataset.fragmentNFT(datasetId));
}

Recommendation:

We advise the code to properly ensure that the fragmentNFT entry extracted from the dataset is non-zero, reverting in any other case to prevent deploying a DistributionManager with a misconfigured fragmentNFT.

[FNF-01C] Duplicate Invocation of Getter Functions

FNF-01C: Duplicate Invocation of Getter Functions

Type Severity Location
Gas Optimization FragmentNFT.sol:L122, L123, L142, L144

Description:

The FragmentNFT::_contractURI and FragmentNFT::_baseURI functions are invoked twice redundantly in the FragmentNFT::tokenURI and FragmentNFT::_contractURI functions respectively.

Example:

function tokenURI(uint256 tokenId) public view override returns (string memory) {
  if (!_exists(tokenId)) revert TOKEN_ID_NOT_EXISTS(tokenId);
  string memory contractURI_ = string.concat(_contractURI(), "/");
  return bytes(_contractURI()).length > 0 ? string.concat(contractURI_, tokenId.toString()) : "";
}

Recommendation:

We advise each function to be invoked once and its result to be stored to a string memory variable that is consequently re-used in place of its second invocation, optimizing each function's gas cost.

[VMR-02C] Loop Iterator Optimization

VMR-02C: Loop Iterator Optimization

Type Severity Location
Gas Optimization VerifierManager.sol:L89

Description:

The linked for loop increments / decrements the iterator "safely" due to Solidity's built-in safe arithmetics (post-0.8.X).

Example:

for (uint256 i; i < tags.length; i++) {

Recommendation:

We advise the increment / decrement operation to be performed in an unchecked code block as the last statement within the for loop to optimize its execution cost.

[FNF-02S] Inexistent Validation of Proposal Recipients

FNF-02S: Inexistent Validation of Proposal Recipients

Type Severity Location
Input Sanitization FragmentNFT.sol:L253, L277

Description:

The FragmentNFT::propose and FragmentNFT::proposeMany functions do not validate their perspective address arguments (to / owners) as being non-zero, potentially leading to proposals that can neither be accepted or rejected.

Impact:

The current FragmentNFT system permits unfulfillable proposals to be created and the relevant DatasetNFT functions (DatasetNFT::proposeFragment / DatasetNFT::proposeManyFragments) do not perform any validation to prevent this.

Example:

/**
 * @notice Accepts a specific proposed contribution by minting the respective pending Fragment NFT to contributor
 * @dev Only callable by VerifierManager contract.
 * Emits a {FragmentAccepted} event.
 * @param id The ID of the pending Fragment NFT associated with the proposed contribution to be accepted
 */
function accept(uint256 id) external onlyVerifierManager {
  address to = pendingFragmentOwners[id];
  if (_exists(id) || to == address(0)) revert NOT_PENDING_FRAGMENT(id);
  delete pendingFragmentOwners[id];
  _safeMint(to, id);
  emit FragmentAccepted(id);
}

/**
 * @notice Rejects a specific proposed contribution by removing the associated pending Fragment NFT
 * @dev Only callable by VerifierManager contract.
 * Emits a {FragmentRejected} event.
 * @param id The ID of the pending Fragment NFT associated with the proposed contribution to be rejected
 */
function reject(uint256 id) external onlyVerifierManager {
  address to = pendingFragmentOwners[id];
  if (_exists(id) || to == address(0)) revert NOT_PENDING_FRAGMENT(id);
  delete pendingFragmentOwners[id];
  delete tags[id];
  emit FragmentRejected(id);
}

Recommendation:

We advise the code to properly validate the address arguments as being non-zero, preventing unfulfillable proposals from being created that would need to be manually removed via the FragmentNFT::remove function available to the data-set owner.

[DNF-01M] Improper Enforcement Flow of Deployer Fees

DNF-01M: Improper Enforcement Flow of Deployer Fees

Type Severity Location
Logical Fault DatasetNFT.sol:L187-L199, L216-L219

Description:

The DatasetNFT::setDeployerFeeModelPercentages function can be invoked even when the contract has no deployerFeeBeneficiary specified. This can cause significant misbehaviours in the DistributionManager::receivePayment function as it will fatally fail if the deployerFeeBeneficiary has not been specified when a non-zero fee is expected to be applied.

Impact:

While the misconfiguration is trivial to resolve, it should be prohibited at the code level as otherwise valid subscriptions may fail to execute due to the DistributionManager::receivePayment function's failure, in turn leading to the subscription being lost as the subscriber may not wish to retry their operation.

Example:

/**
 * @notice Sets the fee percentages for the provided Deployer Fee Models
 * @dev Only callable by DatasetNFT ADMIN.
 * Percentages are encoded such that 100% is represented as 1e18.
 * @param models An array of Deployer Fee Models to set percentages for (see `IDatasetNFT.sol`)
 * @param percentages An array of corresponding fee percentages
 */
function setDeployerFeeModelPercentages(
  DeployerFeeModel[] calldata models,
  uint256[] calldata percentages
) external onlyRole(DEFAULT_ADMIN_ROLE) {
  if (models.length != percentages.length) revert ARRAY_LENGTH_MISMATCH();
  for (uint256 i; i < models.length; i++) {
    DeployerFeeModel m = models[i];
    if (uint8(m) == 0) revert INVALID_ZERO_MODEL_FEE();
    uint256 p = percentages[i];
    if (p > 1e18) revert PERCENTAGE_VALUE_INVALID(1e18, p);
    deployerFeeModelPercentage[m] = p;
  }
}

/**
 * @notice Sets the deployer fee model for a specific Dataset
 * @dev Only callable by DatasetNFT ADMIN
 * @param datasetId The ID of the target Dataset NFT token
 * @param model The Deployer Fee Model to set
 */
function setDeployerFeeModel(uint256 datasetId, DeployerFeeModel model) external onlyRole(DEFAULT_ADMIN_ROLE) {
  deployerFeeModels[datasetId] = model;
}

/**
 * @notice Sets the address of the deployer fee beneficiary
 * @dev Only callable by DatasetNFT ADMIN
 * @param deployerFeeBeneficiary_  The address to set as the beneficiary
 */
function setDeployerFeeBeneficiary(address deployerFeeBeneficiary_) external onlyRole(DEFAULT_ADMIN_ROLE) {
  if (deployerFeeBeneficiary_ == address(0)) revert ZERO_ADDRESS();
  deployerFeeBeneficiary = deployerFeeBeneficiary_;
}

Recommendation:

We advise the DatasetNFT::setDeployerFeeModelPercentages function to ensure that the deployerFeeBeneficiary has been specified to a non-zero value when it is invoked, preventing a non-zero fee from being applied to a particular model and data-set ID when no beneficiary for it has been specified.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.