Platform: Code4rena
Start Date: 21/12/2023
Pot Size: $90,500 USDC
Total HM: 10
Participants: 39
Period: 18 days
Judge: LSDan
Total Solo HM: 5
Id: 315
League: ETH
Rank: 13/39
Findings: 2
Award: $385.84
馃専 Selected for report: 0
馃殌 Solo Findings: 0
馃専 Selected for report: IllIllI
Also found by: 0x11singh99, 0xA5DF, 0xMilenov, 0xTheC0der, 7ashraf, Bauchibred, EV_om, Kaysoft, Sathish9098, SpicyMeatball, cheatc0d3, erebus, hash, imare, immeas, joaovwfreire, lil_eth, lsaudit, oakcobalt, para8956, peanuts, rvierdiiev, slvDev, trachev
21.8971 USDC - $21.90
https://github.com/code-423n4/2023-12-autonolas/blob/main/tokenomics/contracts/Depository.sol#L331-L334 https://github.com/code-423n4/2023-12-autonolas/blob/main/tokenomics/contracts/Depository.sol#L445-L463
The getBonds view function does a loop which amount of iterations is equal to bondCounter -1. The issue is whenever a user makes a deposit, bondCounter increases.
As more deposits happen within the contract, it will be impossible to call getBonds from other contracts as the loop will consume all gas available.
uint256 numBonds = bondCounter; bool[] memory positions = new bool[](numBonds); // Record the bond number if it belongs to the account address and was not yet redeemed for (uint256 i = 0; i < numBonds; ++i) { // Check if the bond belongs to the account // If not and the address is zero, the bond was redeemed or never existed if (mapUserBonds[i].account == account) { // Check if requested bond is not matured but owned by the account address if (!matured || // Or if the requested bond is matured, i.e., the bond maturity timestamp passed block.timestamp >= mapUserBonds[i].maturity) { positions[i] = true; ++numAccountBonds; // The payout is always bigger than zero if the bond exists payout += mapUserBonds[i].payout; } } }
bondCounter being increased at the deposit external permissionless function :
// Create and add a new bond, update the bond counter bondId = bondCounter; mapUserBonds[bondId] = Bond(msg.sender, uint96(payout), uint32(maturity), uint32(productId)); bondCounter = uint32(bondId + 1);
Manual review
Attempt to index such data off-chain. If the feature is extremely needed, index it at a mapping that maps an array composed of bond ids to the bond owner address.
Loop
#0 - c4-pre-sort
2024-01-10T15:25:25Z
alex-ppg marked the issue as duplicate of #270
#1 - c4-pre-sort
2024-01-10T15:25:30Z
alex-ppg marked the issue as sufficient quality report
#2 - c4-judge
2024-01-18T20:33:13Z
dmvt changed the severity to QA (Quality Assurance)
#3 - c4-judge
2024-01-18T20:49:46Z
dmvt marked the issue as grade-b
馃専 Selected for report: hunter_w3b
Also found by: Sathish9098, joaovwfreire, pavankv, peanuts
363.9423 USDC - $363.94
Head | Details |
---|---|
Protocol Overview | Sources utilized to get context. |
Methods | Codebase evaluation methodology. |
Codebase | Codebase quality and recommendations. |
Risks | Concerns associated with Autonolas' architecture, business-logic and centralization. |
Time spent | The amount of time spent on the research. |
Acknowledgments |
Autonolas Protocol facilitates autonomous on-chain services through the use of smart contracts to orchestrating providers and receivers. These contracts are central to coordinating software contributions from developers within the blockchain ecosystem.
Foundational Concepts: Autonolas is developed with a focus on autonomy. It aims to provide solutions for blockchain-based autonomous services, addressing the current needs and functionalities required in this domain.
Technical Architecture: The protocol employs a Multi-Agent Systems Architecture to ensure secure and efficient consensus mechanisms. This architecture supports crypto-native agent services and is integral to the on-chain operations of the protocol. Key components include Agent Components, Canonical Agents, Agent Instances, and Services.
Tokenomics: The OLAS token plays a crucial role in the Autonolas ecosystem. It is used to balance economic factors and encourage participation. The protocol's economic model includes bonding and discount factors, vital for its long-term economic health.
Use Cases: Autonolas has applications in several areas, particularly in DAO operations and protocol infrastructure. It supports functions such as treasury management, yield optimization, and trading strategy execution, underlining its utility in decentralized operations.
Governance: The governance system of Autonolas is designed for transparency and community involvement. It features a governance module, specific contracts like veOLAS and GovernorOLAS, and mechanisms including timelock (with incentives for longer-term locks) and a community multisig. These elements collectively enable a decentralized governance process.
The protocol team has provided extensive high quality documentation both at the audit repo and at the docs webpage. Alongisde some content found on Youtube, this is enough contextualization to begin the security research on the codebase.
I have opted not to build diagrams as the team already provided high-quality overviews:
The Whitepaper is an 80-page document that covers the audit scope and much more about Autonolas.
These are the sources utilized for this security research outside of what is contained at the repository:
Source | Link | Brief description |
---|---|---|
Protocol | Autonolas Protocol - Autonolas Developer Documentation | Protocol overview |
Whitepaper | Whitepaper v1.0 (autonolas.network) | Whitepaper - very complete. Not read on its entirety as many of the concepts it explains are not in-scope. |
thefett - Autonolas Deep Dive | protocol deep dives w/ thefett - autonolas (youtube.com) | A youtube video of David Minarsch deep diving on Autonolas and its features |
Autonolas and IPFS | Decentralized Off-chain Backends: How Autonolas utilizes IPFS across its stack - David Minarsch (youtube.com) | A youtube video of David Minarsch explaining how IPFS is utilized - notice how this isn't directly in the scope of the audit but helps to get context in the features. |
The security research was done on three steps: context-gathering, codebase-evaluation and report-generation.
Context-gathering: involved reading all documentation available and reviewing all sources provided at the protocol overview. The main intention during this phase is to develop a mental-model of flows and reach clarity on the protocol's concepts. This helps with the research as threat-modeling can be adjusted to the protocol and its target audience operational risks.
Codebase-evaluation: consisted of 3 steps. Automated diagramming, line-by-line review and semantical analysis.
Report-generation: involved gathering the observations made on the previous phases and pre-sorting them into the following categories: "ask-the-sponsor", "easy-proof-of-concept", "hard-proof-of-concept", "false-positive". After the pre-sort, questions are asked and the reports are written. Finally the analysis report is written and the research is wrapped up.
Aspect | Strengths | Weaknesses | Grade |
---|---|---|---|
Documentation | Clear natspec comments that help to quickly understand methods. The tokenomics repo also contains a PDF that thoroughly describes the business-logic and the contracts from a high-level and from a technical standpoint | A | |
Code-clarity | Most of the variables utilized are self-descriptive. <br><br>Functions properly state their use-cases at their names. The developers utilize a lot of comments to describe the steps taken. <br><br>The definition of a public global constants at the TokenomicsConstants abstract contract is a very interesting design choice as it enables all inheriting contract to utilize those without much developer overhead. | The excessive amount of acronyms utilized at Tokenomics:checkpoint increases the effort required to understand it.<br><br>The usage of tp.unitPoints[0] and tp.unitPoints[1] to differentiate components from agents at the Tokenomics contract makes the developer experience not very intuitive and should be a point of concern during further developments. | B |
Complexity | The codebase manages to avoid complexity at almost its entirety.<br><br>The features are implemented by composition of small code snippets.<br><br>Inline-assembly is utilized in very small snippets - which represents a good balance of optimization and safety. | Even though the Tokenomics:checkpoint function has to take many parameters and states into account, it could use some refactoring to reduce the risks of missing state transitions. Consider checking out the extra comments.<br><br> | B |
extra comments The checkpoint function could be broken down into several smaller functions that can help the developers see more clearly whether a state is being properly transitioned or not.
I've taken this function and commented how it could be refactored into separate smaller functions that could be called in the following sequence:
By separating the state transitions into 11 smaller blocks, the developers are less likely to commit errors.
Aspect | Strengths | Weaknesses | Grade |
---|---|---|---|
Documentation | Similarly to the tokenomics repo, the Autonolas team has provided top-notch information to enable other developers get context. The governance repo contains documents that not only explain the contracts but also the bonding mechanism, the bridging mechanics and previous found vulnerabilities. That combined with well-written natspec and developer comments makes up for solid documentation. | A | |
Code-clarity | Variables and functions have descriptive naming.<br>Natspec comments express context with clarity.<br>Developer comments provide a step-by-step approach to functions.<br>Public variables in CAPSLOCK to avoid magic values.<br><br>The acronyms utilized at veOLAS:checkpoint are intuitive | A | |
Complexity | The contracts inherit battle-tested libraries from open-zeppelin and implement most* Gnosis' Guard interfaces correctly.<br><br>Most functions are straightforward and don't implement more features than their naming suggests. | veOLAS:checkpoint is a very big method and it is easy to get lost. Could use some refactoring to help developers not lose track of state transitions. | B |
Aspect | Strengths | Weaknesses | Grade |
---|---|---|---|
Documentation | Similarly to the repos previously described, the documentation was amazing. | A | |
Code-clarity | Variables and functions are descriptive.<br><br>There's no magic number usage.<br><br>Some functions have certain complexity but are accompained by thorough developer comments. | A | |
Complexity | The inheritance patterns are clearly defined and easy to disguinsh.<br>UnitRegistry inherits GenericRegistry. AgentRegistry and ComponentRegistry inherit UnitRegistry.<br>RegistriesManager inherits GenericManager.<br><br>The repo also has contracts that are Gnosis Safe-based multisigs and follow the defined interfaces. | The usage of inline assembly instead of abi decoding can lead to oversights. | A- |
The registries repo was truly pleasurable to look for bugs as the folder structuring, the design patterns and the logic was well set up. |
I have opted not to include the lockbox analysis as it is not a domain (solang) I have good understanding of.
There are several functions that are owner-determined and could be used to induce end-user losses. This is an expected side-effect of managed-protocols. The product's interactions are permissionless provided the protocol's core functionalities are set up.
The protocol has plans to implement Community-Owned multisigs and the contracts deployer is a Timelock governed by the DAO.
The DonatorBlacklist contract inhibits addresses blacklisted by the protocol from donating ETH. This is easily circumvented by transferring the funds to a second address then donating.
There is no clear definition on the parties that should be blacklisted, so it is difficult to suggest mitigation alternatives. Consider defining the blacklist persona further while keeping in mind some of the following elements: OFAC sanctioned addresses Mixer services-related addresses Public-exploit addresses Business-determined addresses
The usage of nline assembly bypasses many of Solidity's safety mechanisms. This can lead to issues in data handling, increased difficulty in debugging, and potential exploitation by malicious actors if any vulnerabilities are present. Moreover, the reduced readability and increased complexity make future maintenance and updates more challenging and error-prone.
The following example is taken from the HomeMediator contract:
// solhint-disable-next-line no-inline-assembly assembly { // First 20 bytes is the address (160 bits) i := add(i, 20) target := mload(add(data, i)) // Offset the data by 12 bytes of value (96 bits) i := add(i, 12) value := mload(add(data, i)) // Offset the data by 4 bytes of payload length (32 bits) i := add(i, 4) payloadLength := mload(add(data, i)) }
Consider utilizing abi.decode for complex data-handling operations.
The RegistriesManager create function enables any developer to create/register a new component or agent to an unit owner of the caller's choice. The issue with that is the mempool is public and malicious parties could opt to front-run the transaction and steal credit for the component/agent contribution by calling the same transaction but with it's own address as the unit owner.
As the dependencies data are stored off-chain, the ability of permissionlessly calling [create](2023-12-autonolas/registries/contracts/RegistriesManager.sol at main 路 code-423n4/2023-12-autonolas (github.com)) with any parameters should suffice as proof of concept.
function create( 聽 聽 聽 聽 IRegistry.UnitType unitType, 聽 聽 聽 聽 address unitOwner, 聽 聽 聽 聽 bytes32 unitHash, 聽 聽 聽 聽 uint32[] memory dependencies 聽 聽 ) external returns (uint256 unitId) 聽 聽 { 聽 聽 聽 聽 // Check if the creation is paused 聽 聽 聽 聽 if (paused) { 聽 聽 聽 聽 聽 聽 revert Paused(); 聽 聽 聽 聽 } 聽 聽 聽 聽 if (unitType == IRegistry.UnitType.Component) { 聽 聽 聽 聽 聽 聽 unitId = IRegistry(componentRegistry).create(unitOwner, unitHash, dependencies); 聽 聽 聽 聽 } else { 聽 聽 聽 聽 聽 聽 unitId = IRegistry(agentRegistry).create(unitOwner, unitHash, dependencies); 聽 聽 聽 聽 } 聽 聽 }
The issue is contribution theft and cannot be mitigated by in-contract mechanics. Utilize a relay such as flashbots for component/agent creation. Extensively warn developers to properly place licensing and credits at their dependencies and components before pushing a transaction to create the component/agent. Consider the possibility of disputes arising in the future.
The checkTransaction function at the GuardCM contract checks if it has authorized arguments and is important to protect the Gnosis Safe community multisig. The function works by reverting in case any unwanted parameter is passed. The issue, however, lies at the first if condition (that guards the whole checks ran at the function):
if (paused == 1) {
This means if the contract GuardCM contract is paused (defined by paused == 2), checkTransaction does not check anything and will allow the conditions that were expected to cause reversion not to revert.
This is confusing and can induce mistakes. The "pause" keyword may lead one to believe the whole multisig is paused and the Guard would revert every single call.
Place the following code snippet at the GuardCM.js test, after the "Pause the guard" test:
it("Pause the guard and it doesn't revert if to argument is the multisig's address", async function () { // Try to pause the guard not by the timelock or the multisig await expect( guard.pause() ).to.be.revertedWithCustomError(guard, "ManagerOnly"); // Pause the guard by the timelock const payload = guard.interface.encodeFunctionData("pause", []); await timelock.execute(guard.address, payload); expect(await guard.paused()).to.equal(2); // put random txData to checkTransaction // put the multisig address as the to argument. let txData = await timelock.interface.encodeFunctionData("schedule", [l1BridgeMediators[0], 0, gnosisPayload, Bytes32Zero, Bytes32Zero, 0]); await guard.checkTransaction(multisig.address, 0, txData, 0, 0, 0, 0, AddressZero, AddressZero, "0x", AddressZero); });
Run the test with:
npx hardhat test test/GuardCM.js --grep "Pause the guard and it doesn't revert if to argument is the multisig's address"
Notice how the GuardCM contract doesn't revert.
Make sure to revert if the paused variable is equal 2 during checkTransaction calls. If the intention is to pause the Safe's calls from being guarded, consider setting it's guard to the zero address during pause periods then setting it back to Guard implementations when unpausing.
55 hours
Thanks for taking the time to read this report. Thanks to the Autonolas team for the opportunity and specifically to Kupermind for patiently answering all my questions.
55 hours
#0 - c4-pre-sort
2024-01-10T12:27:23Z
alex-ppg marked the issue as sufficient quality report
#1 - c4-judge
2024-01-18T20:50:07Z
dmvt marked the issue as grade-b
#2 - joaovwfreire
2024-01-22T17:31:23Z
Dear Judge, A-05 is a duplicate of #440 as it explains how a GuardCM pause can enable the multi-sig to become the target of any transaction as the contract won't revert anymore.
Cheers!