Spirals Audit Report
Copyright © 2022 by Verilog Solutions. All rights reserved.September 12, 2022
by Verilog Solutions
This report presents our engineering engagement with the Spirals dev team on their vault contracts on the Celo network. In this audit, we focus on the impact vault contracts and the migration functions as Spirals team plans to migrate from the old staking contracts to the new impact vault contract.
Project Name | Spirals Protocol |
---|---|
Repository Link | https://github.com/spiralsprotocol/protocol |
Commit Hash | First: ede0470; Final: ee1c18c; |
Language | Solidity |
Chain | Celo |
About Verilog Solutions
About Verilog Solutions
Founded by a group of cryptography researchers and smart contract engineers in North America, Verilog Solutions elevates the security standards for Web3 ecosystems by being a full-stack Web3 security firm covering smart contract security, consensus security, and operational security for Web3 projects.
Verilog Solutions team works closely with major ecosystems and Web3 projects and applies a quality above quantity approach with a continuous security model. Verilog Solutions onboards the best and most innovative projects and provides the best-in-class advisory services on security needs, including on-chain and off-chain components.
Table of Contents
Table of Contents
Service Scope
Service Scope
Service Stages
Service Stages
- Architecture Consultancy Service
- Protocol Security & Design Discussion Meeting
- As a part of the audit service, the Verilog Solutions team worked closely with the Spirals development team to discuss potential vulnerability and smart contract development best practices in a timely fashion. Verilog Solutions team is very appreciative of establishing an efficient and effective communication channel with the Spirals team, as new findings were exchanged promptly and fixes were deployed quickly, during the preliminary report stage.
- Protocol Security & Design Discussion Meeting
- Smart Contract Auditing Service
- The Verilog Solutions team analyzed the entire project using a detailed-oriented approach to capture the fundamental logic and suggested improvements to the existing code. Details can be found under Findings & Improvement Suggestions.
Methodology
Methodology
- Code Assessment
- We evaluate the overall quality of the code and comments as well as the architecture of the repository.
- We help the project dev team improve the overall quality of the repository by providing suggestions on refactorization to follow the best practice of Web3 software engineering.
- Code Logic Analysis
- We dive into the data structures and algorithms in the repository and provide suggestions to improve the data structures and algorithms for the lower time and space complexities.
- We analyze the hierarchy among multiple modules and the relations among the source code files in the repository and provide suggestions to improve the code architecture with better readability, reusability, and extensibility.
- Access Control Analysis
- We perform a comprehensive assessment of the special roles of the project, including their authorities and privileges.
- We provide suggestions regarding the best practice of privilege role management according to the standard operating procedures (SOP).
- Off-Chain Components Analysis
- We analyze the off-chain modules that are interacting with the on-chain functionalities and provide suggestions according to the SOP.
- We conduct a comprehensive investigation for potential risks and hacks that may happen on the off-chain components and provide suggestions for patches.
Audit Scope
Audit Scope
File |
src/vaults/ImpactVault.sol |
src/vaults/ImpactVaultManager.sol |
src/vaults/SpiralsCeloImpactVault.sol |
src/vaults/SpiralsCUSDImpactVault.sol |
src/vaults/SPRL.sol |
src/vaults/wmcUSD.sol |
src/staking/SpiralsStaking.sol |
src/staking/SpiralsStakingV2.sol |
src/staking/migration/IMigrationToImpactVault.sol |
src/staking/migration/SpiralsCUSDVaultMigration.sol |
src/staking/migration/SpiralsStakingMigration.sol |
Project Summary
Project Summary
Spirals Protocol is a lightweight protocol that redirects block rewards to climate impact. We work directly with validators to support their climate impact journey.
With Spirals, block rewards aren't used as profit. Instead, they are directed into a smart contract which is governed by all stakers and validators that contribute to the accumulation of block rewards. All stakeholders can collectively vote on which projects receive funding.
The Spirals ecosystem has 5 key stakeholders:
- Celo holders can make a climate impact while earning yield
- Validators can effortlessly make a climate impact and attract more delegators
- Climate Experts are elected to a committee and have the authority to add and remove climate projects eligible for support by the Spirals Community
- Natural Asset Tokens (which are approved by experts) are purchased proportional to member preferences
- Members hold $SPRL and can vote on funding allocation & climate experts. You can become a member by earning $SPRL governance through staking, validating, or reviewing projects as a climate expert.
Spirals V1 System Architecture
There are two main components that shape the protocol, namely the ImpactVault
contracts which define the assets and the functionality of the staking process of that asset, and the ImpactVaultManager.sol
which manages the contracts that will be considered for calculating the yield that will be directed towards the climate impact. Spirals V1 consists mainly of three contracts:
ImpactVaultManager.sol
contains the logic for managing the ImpactVault
contracts.
This contract mainly has the following functionality:
- Register a new
ImpactVault
.
- Remove a registered
ImpactVault
.
- Harvest users’ yields and mint same amount of the SPRL governance tokens to users.
SpiralsCeloImpactVault.sol
contains the logic for staking CELO
.
This contract mainly has the following functionality:
- Set dependencies on the contract (registry and manager address).
- Deposit
CELO
into thestCELO
manager contract.
- Withdraw from the
stCELO
manager contract.
SpiralsCUSDImpactVault.sol
contains the logic for staking cUSD
.
This contract mainly has the following functionality:
- Set dependencies on the contract (Moola contract address).
- Deposit
cUSD
into the Moola lending pool and wrapmcUSD
intowmcUSD
.
- Unwrap
wmcUSD
tomcUSC
. Then withdrawcUSD
from moola lending pool withmcUSD
.
Use Case Scenario
Use Case Scenario
Spirals is a staking platform that allows redirecting rewards towards positive climate impact. When you stake in this platform, Spirals stakes on your behalf on Celo’s core smart contracts. Instead of staking yield, stakers receive proof of climate impact as well as SPRL governance tokens to vote on how funding should be allocated.
Findings & Improvement Suggestions
Findings & Improvement Suggestions
Severity | Total | Acknowledged | Resolved |
High | 0 | 0 | 0 |
Medium | 2 | 2 | 2 |
Low | 2 | 2 | 2 |
Informational | 7 | 7 | 6 |
High
High
None ; )
Medium
Medium
activeVotes ≥ value
Severity Medium File src/staking/SpiralsStakingV2.sol#L93; Commit dc3d7e3; Status Resolved in commit ee1c18c; Description
The only requirements are
activeVotes + pendingVotes >= _value
, so there are following combinations:activeVotes
pendingVotes
≥ _value
≥ _value
≥ _value
≤ _value
≤ _value
≥ _value
≤ _value
≤ _value
In the
if-else
condition, whenactiveVotes ≤ _value
,pendingVotes
can be bigger or smaller or equal to_value
.if (activeVotes >= _value) { revokeActive(_value); } else { revokePending(_value); } unlock(_value);
So in the
else
condition, the revoke pending will fail whenpendingVotes < value
.
Exploit Scenario
- Alice has less
pendingVotes
andactiveVotes
than the value that she wants to unstake, but the conditionactiveVotes + pendingVotes >= _value
is still fulfilled.
- Alice calls the
unstake()
function since the property of the sum is fulfilled.
- The
revokePending()
function will fail.
- Alice has less
Recommendations
Add check to guarantee that
pendingVotes >= value
whenactiveVotes ≤ _value
, i.e., contemplate this case in the else condition of the if statement.
Results
Resolved in commit ee1c18c;
- Yield cannot be locked in the SpiralsCUSDVaultMigration contract
Severity Medium File src/staking/migration/SpiralsCUSDVaultMigration.sol; Commit d7debb4; Status Resolved in commit 6dcce64; Description
The yields cannot be withdrawn from the current CUSD vault and the migration only migrates the principals for users to the new impact vault. A function should be added to the contract to withdraw the yields from the current contract.
The
SpiralsCUSDVaultMigration.sol
contract is a vault that supports yield-bearing tokens likemcUSD
. User can only withdraw their principals back because of the fixed 1:1 asset and staked asset ratio. Their balance of vault token (SpiralsCUSDVaultMigration.balanceOf()
) will always be the principal amount.When doing the migration for the CUSD vault, the
SpiralsCUSDVaultMigration.transferAssetsToImpactVaultForAddress()
will withdraw CUSD from the Moola lending pool and deposit the CUSD into the new impact vault for users. However, only principals are deposited into the new impact vault for users because the migration amount for the user is read from the functionSpiralsCUSDVaultMigration.balanceOf()
directly. So after migration, the yields will still stay in the current CUSD vault and no one can touch it. A function should be added to deal with those yields left in the contract after migration.
Exploit Scenario
- Owner executes migration.
- All users principles are withdrawn and deposited for them to the new impact vault.
- There rest of the staked asset in vault (yield) will still be locked in the vault.
Recommendations
Calculate the respective yield for each of the users that have staked and send that yield to the
ImpactVault
contract.
Results
Resolved in commit 6dcce64.
Now the yield can be manual passed into the migration functions in both the
SpiralsCUSDVaultMigration.sol
andSpiralsStakingMigration.sol
contracts to transfer it to theImpactVault
. As it is not easy to track the precise amount of yields each user earned, Spirals team decided to do an approximate calculation. The generated yields will be assigned to users proportionally to their share.
Low
Low
- Unused events
Severity Low File src/vaults/ImpactVaultManager.sol#L33, L34, L35; src/vaults/SpiralsCUSDImpactVault.sol#L24; Commit dc3d7e3; Status Resolved in commit ee1c18c; Description
Those specified events are defined but not used.
Exploit Scenario
N/A.
Recommendations
Use events when applicable. Operations that transfer value or perform critical operations should trigger events so that users and off-chain monitoring tools can account for important state changes.
Results
Resolved in commit ee1c18c;
- Incorrect event emission for critical operations
Severity Low File src/vaults/ImpactVault.sol#L172; Commit dc3d7e3; Status Resolved in commit 90291c1; Description
Function
withdrawYieldAsset()
emits the same event as thewithdraw()
function. However, they perform different operations, namelywithdraw()
transfers the underlying staked asset to the receiver, andwithdrawYieldAsset()
transfers the yield asset. TheWithdrawYield
event is defined and should be used in the line specified.
Exploit Scenario
N/A.
Recommendations
Emit
WithdrawYield
event instead ofWithdraw
when a user withdraws yield.
Informational
Informational
- Floating solidity pragma version
Severity Informational File Global; Commit dc3d7e3; Status Resolved in commit 90291c1; Description
Current smart contracts use
^0.8.10
. And compilers within versions≥ 0.8.10
and<0.9.0
can be used to compile those contracts. Therefore, the contract may be deployed with a newer or latest compiler version which generally has higher risks of undiscovered bugs.It is a good practice to fix the solidity pragma version if the contract is not designed as a package or library that will be used by other projects or developers.
Exploit Scenario
N/A.
Recommendations
Use the fixed solidity pragma version.
- Unused Custom error
Severity Informational File src/vaults/ImpactVault.sol#L35; Commit dc3d7e3; Status Resolved in commit 90291c1; Description
Custom error in line specified is defined but not used.
error WithdrawExceedsYield(uint256 requested, uint256 total);
Exploit Scenario
N/A.
Recommendations
Remove the unused custom error or use it when the withdraw exceeds the yield.
- Typos
Severity Informational File src/vaults/ImpactVault.sol#L136; Commit dc3d7e3; Status Resolved in commit 90291c1; Description
There’s a typo in line 136.
* @notice Withdraws underlying asset by converting equivlaent value in
Exploit Scenario
N/A.
Recommendations
Correct the line specified to the following:
* @notice Withdraws underlying asset by converting equivalent value in
- Missing fields in event
Severity Informational File src/vaults/SpiralsCeloImpactVault.sol#L40; Commit dc3d7e3; Status Resolved in commit 90291c1; Description
Some important fields are missing in event
DependenciesUpdated
.Event
DependenciesUpdated
contains the information of two variables:event DependenciesUpdated( address indexed manager, address indexed registry );
But function
setDependencies()
updates with the following three parameters:function setDependencies( address _stCeloTokenAddress, address _stCeloManagerAddress, address _celoRegistryAddress )
The Celo registry address should also be included to the event field
Exploit Scenario
N/A.
Recommendations
Add missing field to the
DependenciesUpdated
event.
hasOutstandingWithdrawal()
should also check if the value is non-zeroSeverity Informational File src/vaults/SpiralsCeloImpactVault.sol#L209; Commit dc3d7e3; Status Resolved in commit 7d48c3c; Description
Function
_withdraw()
callshasOutstandingWithdrawal()
to check if the current user has an outstanding withdrawal. Besides from timestamp, the value should also be checked otherwise user who input zero value to withdraw will trigger withdraw lock automatically.function hasOutstandingWithdrawal(address _address) public view returns (bool) { return withdrawals[_address].timestamp != 0; }
When users try to withdraw funds from Celo impact vault,
_withdraw()
will called. It updates users withdraw timestamp with an unlocking period when user has outstanding withdraw and the withdraw value withe the value passed in by user.Whether the user qualifies the withdraw only depends on the timestamp in function
hasOutstandingWithdrawal()
. However, user might accidentally pass in a zero value and it will get user trapped in an unlocking waiting period and user can only withdraw zero value after that.
Exploit Scenario
N/A.
Recommendations
Also check if
withdrawInfo.value
is non-zero inhasOutstandingWithdrawal()
function to prevent users accidentally lock themselves for an unlocking period of time.
- Redundant indexed in events
Severity Informational File ImpactVaultManager.sol#L33, L34; SpiralsCeloImpactVault.sol#L38, L39; SpiralsCUSDImpactVault#L24 Commit dc3d7e3; Status Acknowledged; Description
The parameter
amount
of the events isindexed
. A numerical value usually should not be indexed as it won’t accelerate searching.event Receive(address indexed sender, uint256 indexed amount); event Claim(address indexed receiver, uint256 indexed amount);
Exploit Scenario
N/A.
Recommendations
Remove the
indexed
keyword from the numerical values in the events:event Receive(address indexed sender, uint256 amount); event Claim(address indexed receiver, uint256 amount);
Results
Acknowledged.
- Deposit only when the amount is not zero
Severity Informational File src/staking/migration/SpiralsStakingMigration.sol#L42; src/staking/migration/SpiralsCUSDVaultMigration.sol#L39; Commit d7debb4; Status Resolved in commit 6dcce64; Description
For function
transferAssetsToImpactVaultForAddress()
, a condition check can be added to make sure it only deposits into the new impact vault when the amount is not zero.
Exploit Scenario
N/A.
Recommendations
Add the following checks:
function transferAssetsToImpactVaultForAddress(address _address, ImpactVault _vault) public onlyOwner whenPaused { /// ... if(amount != 0) { celoVault.depositCelo{value: amount}(_address); } /// ... }
function transferAssetsToImpactVaultForAddress(address _address, ImpactVault _vault) public onlyOwner whenPaused { /// ... if(cUSDAmount != 0) { cUSDVault.deposit(cUSDAmount, _address); } /// ... }
Results
Resolved in commit 6dcce64.
Require statement was added to check if the deposit amount is zero.
Access Control Analysis
Access Control Analysis
The workflow of the protocol consists of creating ImpactVault
contracts that define the assets that can be staked and then registering them to the ImpactVaultManager.sol
contract. This allows the ImpactVaultManager.sol
contract to keep track of all the funds staked. The privileged roles and access control for this two-step process are the following:
ImpactVault contracts
ImpactVault contracts
- The deployer of these contracts should define the principal asset, yield asset, and
ImpactVaultManager
address.
- The
ImpactVaultManager.sol
contract can withdraw the yield accumulated by the asset.
ImpactVaultManager.sol
ImpactVaultManager.sol
The deployer of the contract has control over the following functionality:
- Set the governance token that will be associated with the yield generated by the
ImpactValut
contracts.
- Register new
ImpactVault
contracts.
- Remove a registered
ImpactVault
contract.
Appendix I: Severity Categories
Appendix I: Severity Categories
Severity | Description |
---|---|
High | Issues that are highly exploitable security vulnerabilities. It may cause direct loss of funds / permanent freezing of funds. All high severity issues should be resolved. |
Medium | Issues that are only exploitable under some conditions or with some privileged access to the system. Users’ yields/rewards/information is at risk. All medium severity issues should be resolved unless there is a clear reason not to. |
Low | Issues that are low risk. Not fixing those issues will not result in the failure of the system. A fix on low severity issues is recommended but subject to the clients’ decisions. |
Informational | Issues that pose no risk to the system and are related to the security best practices. Not fixing those issues will not result in the failure of the system. A fix on informational issues or adoption of those security best practices-related suggestions is recommended but subject to clients’ decision. |
Appendix II: Status Categories
Appendix II: Status Categories
Status | Description |
---|---|
Unresolved | The issue is not acknowledged and not resolved. |
Partially Resolved | The issue has been partially resolved. |
Acknowledged | The Finding / Suggestion is acknowledged but not fixed / not implemented. |
Resolved | The issue has been sufficiently resolved. |
Disclaimer
Disclaimer
Verilog Solutions receives compensation from one or more clients for performing the smart contract and auditing analysis contained in these reports. The report created is solely for Clients and published with their consent. As such, the scope of our audit is limited to a review of code, and only the code we note as being within the scope of our audit detailed in this report. It is important to note that the Solidity code itself presents unique and unquantifiable risks since the Solidity language itself remains under current development and is subject to unknown risks and flaws. Our sole goal is to help reduce the attack vectors and the high level of variance associated with utilizing new and consistently changing technologies. Thus, Verilog Solutions in no way claims any guarantee of security or functionality of the technology we agree to analyze.
InIn addition, Verilog Solutions reports do not provide any indication of the technologies proprietors, business, business model, or legal compliance. As such, reports do not provide investment advice and should not be used to make decisions about investment or involvement with any particular project. Verilog Solutions has the right to distribute the Report through other means, including via Verilog Solutions publications and other distributions. Verilog Solutions makes the reports available to parties other than the Clients (i.e., “third parties”) – on its website in hopes that it can help the blockchain ecosystem develop technical best practices in this rapidly evolving area of innovation.