Royco
Incentive Locker

Covered the Incentive Locker, the core contract connecting Royco Boosts' reward campaign system.
report id
0485
category
Token & RWA
language
Solidity
final report
April 17, 2025
lines of code
919
Content

Download audit report

1

Executive Summary

This document presents the security review performed by Nethermind Security for Royco contracts. The audit review focused on Royco Boosts protocol which has one major component that contains the core logic and connects the system entirely namely; Incentive locker.

The audit comprises 919 lines of solidity code. The audit was performed using (a) manual analysis of the codebase, (b) automated analysis tools, and (c) creation of test cases.

Audit assessment
Audit TypeSecurity Review
Final ReportApril 17, 2025
Repositoriesroyco-boosts
Initial Commit1716b71
Final Commit4d89987
DocumentationProvided in the code's natspec
Documentation AssessmentMedium
Test Suite AssessmentHigh
Total findings
3
Fixed
2
Acknowledged
1
Mitigated
0
Unresolved
0

Findings by severity

  • Critical0
  • High0
  • Medium0
  • Low2
  • Informational1
  • Best Practices0

4

System Overview

Royco Boosts introduces Incentivized Action Markets (IAMs), enabling anyone to set up a market that rewards specific actions, whether on-chain or off-chain. Within these markets, Incentive Providers (IPs) propose rewards—such as tokens or points—for Action Providers (APs) willing to complete certain tasks. IPs and APs negotiate terms by exchanging offers until they settle on a mutually acceptable reward. Once an agreement is reached, the AP is expected to carry out the agreed-upon actions as outlined in the on-chain contract to receive the promised incentive.

The protocol is composed of three main contracts with the IncentiveLocker being the main contract that contains the core logic.

Diagram from the audit report

The IncentiveLocker is the central contract to manage incentive campaigns. Incentive Providers (IPs) use it to deposit tokens or points into campaigns that incentivize specific actions. The main flows include the following:

  • Campaign Creation: IPs create campaigns by specifying an action verifier and incentive details.
  • Incentive Management: IPs and co-IPs can add incentive tokens during the campaign, while only IPs can remove them.
  • Incentives Claims: Action Providers (APs) claim rewards by proving action completion (verified by ActionVerifier hooks).

UmaMerkleChefAV is an action verifier used by the IncentiveLocker to validate and process incentive claims based on Merkle proofs. It enables streamed rewards using UMA’s optimistic oracle. The main flows include:

  • Merkle Root Publishing: An off-chain oracle periodically posts Merkle roots containing AP reward data.
  • Stream Adjustment: Incentive emission rates can be increased or decreased over time upon adding or removing incentives, respectively.
  • Merkle Proofs Attestations: Whitelisted attesters or IPs submit Merkle proofs of owed amounts, which are verified and confirmed or disputed by the off-chain oracle.

The MultiplierMarketHub enables negotiation and opt-in between IPs and APs for multiplier-based campaigns, and the contract is used off-chain for rewards calculations.

  • APs express interest in a campaign by opting in.
  • APs submit counter-offers that include a reward multiplier and optional parameters (e.g., task size).
  • IPs can accept AP offers to finalize agreements, which signals readiness for the AP to perform the incentivized action.

5

Risk Rating Methodology

The risk rating methodology used by Nethermind Security follows the principles established by the OWASP Foundation. The severity of each finding is determined by two factors: Likelihood and Impact.

Likelihood measures how likely the finding is to be uncovered and exploited by an attacker. This factor will be one of the following values:

  1. High: the issue is trivial to exploit and has no specific conditions that need to be met;
  2. Medium: the issue is moderately complex and may have some conditions that need to be met;
  3. Low: the issue is very complex and requires very specific conditions to be met.

When defining the likelihood of a finding, other factors are also considered. These can include but are not limited to motive, opportunity, exploit accessibility, ease of discovery, and ease of exploit.

Impact is a measure of the damage that may be caused if an attacker exploits the finding. This factor will be one of the following values:

  1. High: the issue can cause significant damage, such as loss of funds or the protocol entering an unrecoverable state;
  2. Medium: the issue can cause moderate damage, such as impacts that only affect a small group of users or only a particular part of the protocol;
  3. Low: the issue can cause little to no damage, such as bugs that are easily recoverable or cause unexpected interactions that cause minor inconveniences.

When defining the impact of a finding, other factors are also considered. These can include but are not limited to data and state integrity, loss of availability, financial loss, and reputation damage. After defining the likelihood and impact of an issue, the severity is determined according to the table below.

Impact / LikelihoodLowMediumHigh
HighMediumHighCritical
MediumLowMediumHigh
LowInfo / Best PracticesLowMedium
UndeterminedUndeterminedUndeterminedUndetermined

To address issues that do not fit a High, Medium or Low severity, Nethermind Security also uses three more finding severities: Informational, Best Practices, and Undetermined.

  1. Informational findings do not pose any risk to the application, but they carry some information that the audit team intends to pass to the client formally;
  2. Best Practice findings are used when some piece of code does not conform with smart contract development best practices;
  3. Undetermined findings are used when we cannot predict the impact or likelihood of the issue.

6

Issues

6.1 Duplicate entries in _modifyIncentiveStreams(...) result in loss of incentives

FileFix commitTypeStatus
UmaMerkleChefAV.solPR #6LowFixed

The IncentiveLocker::createIncentiveCampaign(...) function is used to create a campaign which invokes UmaMerkleChefAV::processIncentiveCampaignCreation(...). The processIncentiveCampaignCreation(...) invokes _modifyIncentiveStreams(...) to update the current rate for each incentive in incentiveCampaignIdToIncentiveToCurrentRate mapping.

function processIncentiveCampaignCreation(
bytes32 _incentiveCampaignId,
address[] memory _incentivesOffered,
uint256[] memory _incentiveAmountsOffered,
bytes memory _actionParams,
address /*_ip*/
)
external
override
onlyIncentiveLocker
{
ActionParams memory params = abi.decode(_actionParams, (ActionParams));
uint32 startTimestamp = params.startTimestamp;
uint32 endTimestamp = params.endTimestamp;

// ...

// Apply the modification to streams
_modifyIncentiveStreams(Modifications.INIT_STREAMS, _incentiveCampaignId, startTimestamp, endTimestamp, _incentivesOffered, _incentiveAmountsOffered);
}

function _modifyIncentiveStreams(
Modifications _modification,
bytes32 _incentiveCampaignId,
uint32 _startTimestamp,
uint32 _endTimestamp,
address[] memory _incentives,
uint256[] memory _incentiveAmounts
)
internal
{
// ...

// Make modifications to the appropriate incentive streams
uint256 numIncentives = _incentives.length;
uint256[] memory updatedRates = new uint256[](numIncentives);
for (uint256 i = 0; i < numIncentives; ++i) {
// If creating a new campaign
if (_modification == Modifications.INIT_STREAMS) {
// Initialize the rate on creation
updatedRates[i] = (_incentiveAmounts[i]).divWadDown(_endTimestamp - _startTimestamp);
// If adding or removing incentives from an already created campaign
} else {
// ...
}

// Update the rate for this incentive to the current rate
// @audit If _incentives includes duplicate entries, it will override the rate corresponding to previous entry.
incentiveCampaignIdToIncentiveToCurrentRate[_incentiveCampaignId][_incentives[i]] = updatedRates[i];
}

// ...
}

However, if _incentives in _modifyIncentiveStreams(...) includes duplicate entries, it will override the rate corresponding to the previous entry. This would result in incorrect accounting.

Although, the merkle root submitted by IP can still correspond to correct accounting.

Recommendation

Consider handling the case where incentives array while creating a campaign includes duplicate entries.

6.2 Malicious incentive token can restrict claiming of other incentives

FileFix commitTypeStatus
IncentiveLocker.sol—LowAcknowledged

The claimIncentives(...) function is responsible for claiming incentives from a specific incentive campaign. It uses _remitIncentivesAndFees(...) to process individual incentive claims by iterating over each offered incentive. To transfer incentive amount and account for protocol fee, it calls _pushIncentivesAndAccountFees(...).

function _remitIncentivesAndFees(
ICS storage _ics,
address _ap,
address _protocolFeeClaimant,
address[] memory _incentives,
uint256[] memory _incentiveAmountsOwed
)
internal
returns (uint256[] memory incentiveAmountsPaid, uint256[] memory protocolFeesPaid)
{
// Cache for gas op
uint256 numIncentives = _incentives.length;
// ...
// Initialize array for event emission
incentiveAmountsPaid = new uint256[](numIncentives);
protocolFeesPaid = new uint256[](numIncentives);

for (uint256 i = 0; i < numIncentives; ++i) {
// Cache for gas op
address incentive = _incentives[i];
uint256 incentiveAmountOwed = _incentiveAmountsOwed[i];

// Account for spent incentives to prevent co-mingling of incentives
_ics.incentiveToAmountRemaining[incentive] -= incentiveAmountOwed;

// Calculate fee amounts based on the claim ratio.
protocolFeesPaid[i] = incentiveAmountOwed.mulWadDown(_ics.protocolFee);

// Calculate the net incentive amount to be paid after applying fees.
incentiveAmountsPaid[i] = incentiveAmountOwed - protocolFeesPaid[i];

// Push incentives to the action provider and account for fees.
// @audit If this reverts for one incentive, then the transfer for all other incentives will also revert.
_pushIncentivesAndAccountFees(incentive, _ap, _protocolFeeClaimant, incentiveAmountsPaid[i], protocolFeesPaid[i]);
}
}

function _pushIncentivesAndAccountFees(
address incentive,
address ap,
address protocolFeeClaimant,
uint256 incentiveAmount,
uint256 protocolFeeAmount
)
internal
{
// Take fees and push incentives to action provider
if (isPointsProgram(incentive)) {
// ...
} else {
// Make fees claimable by fee claimant
feeClaimantToTokenToAmount[protocolFeeClaimant][incentive] += protocolFeeAmount;
// Transfer incentives to the action provider
ERC20(incentive).safeTransfer(ap, incentiveAmount);
}
}

However, If _pushIncentivesAndAccountFees(...) reverts for one incentive, then all other incentives can't be transferred too. This exploit allows IP or Co-IP to add a malicious token as incentive which behaves maliciously after the merkle root is verified, restricting claiming of incentives.

Although, a whitelisted asserter can then add a merkle root such that it excludes the malicious token.

Recommendation

Consider updating the implementation for transferring the incentives to handle the revert case.

Resolution

The asserters would just post another merkle root with the malicious tokens excluded from the leaves.

6.3 UmaMerkleOracleBase::blacklistAsserters(...) does not revoke assertion privileges

FileFix commitTypeStatus
UmaMerkleOracleBase.sol3f16f86InfoFixed

The blacklistAsserters(...) function in UmaMerkleOracleBase is intended to revoke assertion privileges by blacklisting asserters. However, the current implementation mistakenly sets the asserterToIsWhitelisted mapping to true for the provided addresses, which has the opposite effect—whitelisting the asserters instead of blacklisting them:

function blacklistAsserters(address[] memory _blacklistedAsserters) public virtual onlyOwner {
uint256 numAsserters = _blacklistedAsserters.length;
for (uint256 i = 0; i < numAsserters; ++i) {
==> asserterToIsWhitelisted[_blacklistedAsserters[i]] = true;
}
emit AssertersBlacklisted(_blacklistedAsserters);
}

Though malicious asserters attempting to assert an incorrect Merkle root will lose the bond amount since the oracle will dispute their assertions.

Recommendation

Consider setting the asserterToIsWhitelisted to false instead of true.

7

Documentation Evaluation

Software documentation refers to the written or visual information that describes the functionality, architecture, design, and implementation of software. It provides a comprehensive overview of the software system and helps users, developers, and stakeholders understand how the software works, how to use it, and how to maintain it. Software documentation can take different forms, such as user manuals, system manuals, technical specifications, requirements documents, design documents, and code comments. Software documentation is critical in software development, enabling effective communication between developers, testers, users, and other stakeholders. It helps to ensure that everyone involved in the development process has a shared understanding of the software system and its functionality. Moreover, software documentation can improve software maintenance by providing a clear and complete understanding of the software system, making it easier for developers to maintain, modify, and update the software over time. Smart contracts can use various types of software documentation. Some of the most common types include:

  • Technical whitepaper: A technical whitepaper is a comprehensive document describing the smart contract’s design and technical details. It includes information about the purpose of the contract, its architecture, its components, and how they interact with each other;
  • User manual: A user manual is a document that provides information about how to use the smart contract. It includes step-by-step instructions on how to perform various tasks and explains the different features and functionalities of the contract;
  • Code documentation: Code documentation is a document that provides details about the code of the smart contract. It includes information about the functions, variables, and classes used in the code, as well as explanations of how they work;
  • API documentation: API documentation is a document that provides information about the API (Application Programming Interface) of the smart contract. It includes details about the methods, parameters, and responses that can be used to interact with the contract;
  • Testing documentation: Testing documentation is a document that provides information about how the smart contract was tested. It includes details about the test cases that were used, the results of the tests, and any issues that were identified during testing;
  • Audit documentation: Audit documentation includes reports, notes, and other materials related to the security audit of the smart contract. This type of documentation is critical in ensuring that the smart contract is secure and free from vulnerabilities.

These types of documentation are essential for smart contract development and maintenance. They help ensure that the contract is properly designed, implemented, and tested, and they provide a reference for developers who need to modify or maintain the contract in the future.

8

Remarks & Test Suite

The Royco team has provided a comprehensive walkthrough of the project in the kick-off call, and the code natspec includes a detailed explanation of the intended functionalities. Moreover, the team addressed all questions and concerns raised by the Nethermind Security team, providing valuable insights and a comprehensive understanding of the project's technical aspects.

All the relevant issues raised by the AuditAgent have been incorporated into this report. The AuditAgent is an AI-powered smart contract auditing tool that analyses code, detects vulnerabilities, and provides actionable fixes. It accelerates the security analysis process, complementing human expertise with advanced AI models to deliver efficient and comprehensive smart contract audits. Available at https://app.auditagent.nethermind.io.

Test suite
30Tests passed2Failed

The complete test output is in the full report.

As auditors, we recommend that any changes or updates made to the audited codebase undergo a re-audit or security review to address potential vulnerabilities or risks introduced by the modifications. By conducting a re-audit or security review of the modified codebase, you can significantly enhance the overall security of your system and reduce the likelihood of exploitation. However, we do not possess the authority or right to impose obligations or restrictions on our clients regarding codebase updates, modifications, or subsequent audits. Accordingly, the decision to seek a re-audit or security review lies solely with you.

This report is based on the scope of materials and documentation provided by you to Nethermind in order that Nethermind could conduct the security review outlined in 1. Executive Summary and 2. Audited Files. The results set out in this report may not be complete nor inclusive of all vulnerabilities. Nethermind has provided the review and this report on an as-is, where-is, and as-available basis. You agree that your access and/or use, including but not limited to any associated services, products, protocols, platforms, content, and materials, will be at your sole risk. Blockchain technology remains under development and is subject to unknown risks and flaws. The review does not extend to the compiler layer, or any other areas beyond the programming language, or other programming aspects that could present security risks. This report does not indicate the endorsement of any particular project or team, nor guarantee its security. No third party should rely on this report in any way, including for the purpose of making any decisions to buy or sell a product, service or any other asset. To the fullest extent permitted by law, Nethermind disclaims any liability in connection with this report, its content, and any related services and products and your use thereof, including, without limitation, the implied warranties of merchantability, fitness for a particular purpose, and non-infringement. Nethermind does not warrant, endorse, guarantee, or assume responsibility for any product or service advertised or offered by a third party through the product, any open source or third-party software, code, libraries, materials, or information linked to, called by, referenced by or accessible through the report, its content, and the related services and products, any hyperlinked websites, any websites or mobile applications appearing on any advertising, and Nethermind will not be a party to or in any way be responsible for monitoring any transaction between you and any third-party providers of products or services. As with the purchase or use of a product or service through any medium or in any environment, you should use your best judgment and exercise caution where appropriate. FOR AVOIDANCE OF DOUBT, THE REPORT, ITS CONTENT, ACCESS, AND/OR USAGE THEREOF, INCLUDING ANY ASSOCIATED SERVICES OR MATERIALS, SHALL NOT BE CONSIDERED OR RELIED UPON AS ANY FORM OF FINANCIAL, INVESTMENT, TAX, LEGAL, REGULATORY, OR OTHER ADVICE.