In RAAC, there are critical roles such as Minter, Collector, Executor, Oracle, Manager, and Deployer. These actors hold high-privilege functions that significantly contribute to the protocol's stability and security. If any of these roles are assigned to an EOA, the protocol may be at risk, as delegating crucial functions to a malicious actor due to a private key leak is a common attack vector, especially with the rise of unvetted tooling and codebases.
Let us examine the RAACToken
contract located in 2025-02-raac/contracts/core/tokens/RAACToken.sol
. We will specifically focus on two major roles in this contract: RAACToken::initialOwner
and RAACToken::minter
. The owner has the ability to perform core functionalities within this contract, and so does the minter. This is evident from the additional modifier on the functions:
Similarly, for the minter:
Even though there is a check to ensure that the addresses of RAACToken::initialOwner
and RAACToken::minter
are not 0
, this check alone is insufficient, especially with the increasing risk of private key leaks. It is advisable to add another verification step to ensure that the addresses provided are not EOAs but rather deployer contracts. The vulnerability lies in RAACToken::constructor
and RAACToken::setMinter
.
RAACToken::constructor
RAACToken::setMinter
If a private key leak occurs due to a malicious script, the account may be compromised.
The compromised account may fall into the hands of a malicious actor, and since a proxy contract was not used, recovery becomes nearly impossible.
The malicious actor can now execute critical functions like RAACToken::setMinter
.
Below is proof of concept demonstrating this vulnerability:
Add foundry setup to the project by:
Installing hardhat-foundry: npm i --save-dev @nomicfoundation/hardhat-foundry
Add require("@nomicfoundation/hardhat-foundry");
to the top of your hardhat.config.js file.
Run cp .env.example .env
for environment variables used
Run npx hardhat init-foundry in your terminal. This will generate a foundry.toml file based on your Hardhat project’s existing configuration, and will install the forge-std library
Run forge soldeer init
for simple dependencies management, specifically forge-std
. It will generate soldeer.lock, remappings.txt, and a dependencies folder with all the dependencies
We are testing the contract ```RAACToken`` access control. Create a folder test
in the tokens folder contracts/core/tokens/
such that contracts/core/tokens/test/
and create a file inside it RAACToken.t.sol
Add these contents to the file RAACToken.t.sol
⚠️ Important: I had to change these lines of code on the file
test/unit/libraries/ReserveLibraryMock.sol
because they were getting on the way due to arguments errors
The FakeDeployer
contract is simply an empty contract ensuring that its code.length
is non-zero, unlike an EOA
Run the test through this command forge test --mt test_SetMinter_WithEOA_ShouldSucceed -vvv
⚠️ Important: The test might fail because I added another function to handle the revert test. You can simply copy the code into the
RAACToken
contract
With the above changes, the test should successfully run and pass for setting a minter.
The first test confirms that the minter role (and all other critical roles) in RAAC, can be assigned to an EOA, which is not recommended and cannot be overemphasized. This introduces a single point of failure when an entire business hinges on the security of one private key. The test IRAACToken::test_SetMinter_WithEOA_ShouldSucceed
passes, indicating that an EOA can become a minter. This risk extends to the owner role, posing significant threats to the protocol. With a console log, we observe the output of the code as shown below
Foundry for testing and runtime (through the hardhat-foundry npm package)
Soldeer for dependency management.
RAAC should utilize a proxy contract or multi-signature wallet for critical roles. While a dedicated development environment could help, it does not fully mitigate the risk,due to human errors and forgetfulness. Implementing a proxy pattern is recommended. Additional checks should be included in the constructor for setting the initial owner to not accept an externally owned account controlled by a private key. Below is the recommended fix:
This ensures that only smart contracts can be assigned to critical roles, greatly reducing the risk of a full contract compromise.
Tests 2 and 3 prove a successful implement these changes
the above test TestRAACToken::test_SetMinterV2_WithEOA_ShouldRevert
should pass if there is a revert and it does as can be seen below
Test 3 TestRAACToken::test_SetMinterV2_WithContract_ShouldSucceed
however passes because we have set a contract address as the minter, protecting against potential primary key leak
The test passes
In summary, these are some of the recommendations
Use the proxy pattern for ease of upgradeability and security (best)
Use dedicated development environment (not independent though of the deployer contract)
Add checks to ensure that an EOA has not been given critical roles
Please read the CodeHawks documentation to know which submissions are valid. If you disagree, provide a coded PoC and explain the real likelihood and the detailed impact on the mainnet without any supposition (if, it could, etc) to prove your point.
There is no real proof, concrete root cause, specific impact, or enough details in those submissions. Examples include: "It could happen" without specifying when, "If this impossible case happens," "Unexpected behavior," etc. Make a Proof of Concept (PoC) using external functions and realistic parameters. Do not test only the internal function where you think you found something.
Please read the CodeHawks documentation to know which submissions are valid. If you disagree, provide a coded PoC and explain the real likelihood and the detailed impact on the mainnet without any supposition (if, it could, etc) to prove your point.
There is no real proof, concrete root cause, specific impact, or enough details in those submissions. Examples include: "It could happen" without specifying when, "If this impossible case happens," "Unexpected behavior," etc. Make a Proof of Concept (PoC) using external functions and realistic parameters. Do not test only the internal function where you think you found something.
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
Appeals are being carefully reviewed by our judges.