Platform: Code4rena
Start Date: 13/10/2023
Pot Size: $31,250 USDC
Total HM: 4
Participants: 51
Period: 7 days
Judge: 0xsomeone
Id: 295
League: ETH
Rank: 24/51
Findings: 1
Award: $113.54
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: niroh
Also found by: 0xDetermination, 0xSmartContract, 0xbrett8571, 0xdice91, 0xweb3boy, Bauchibred, Bube, DadeKuma, JCK, K42, LinKenji, Myd, SAAJ, ZanyBonzy, albahaca, castle_chain, catellatech, digitizeworx, emerald7017, fouzantanveer, hunter_w3b, invitedtea, m4ttm, rahul, xiao
113.5407 USDC - $113.54
Brahma Console v2 is an orchestration layer designed to enhance the DeFi experience on smart contract wallets.
The Brahma contest consists of 16 contracts: TypeHashHelper.sol
, SafeHelper.sol
, TransactionValidator.sol
, SafeModeratorOverridable.sol
, SafeEnabler.sol
, SafeModerator.sol
, Constants.sol
, ConsoleFallbackHandler.sol
, AddressProvider.sol
, PolicyValidator.sol
, PolicyRegistry.sol
, ExecutorRegistry.sol
, WalletRegistry.sol
, AddressProviderService.sol
, SafeDeployer.sol
and ExecutorPlugin.sol
.
The contract TypeHashHelper.sol
is a library that provides helper functions to build EIP712 struct and type hashes. EIP712 is a standard for hashing and signing of data in Ethereum. The library defines two structs: Transaction
and Validation
. The Transaction
struct represents the details of a transaction, including operation type, addresses involved, value, nonce, and data. The Validation
struct represents the validation details, including expiry epoch, transaction struct hash, and policy hash.
The library also defines two constant hashes: TRANSACTION_PARAMS_TYPEHASH
and VALIDATION_PARAMS_TYPEHASH
. These are EIP712 type hashes for transaction and validation data respectively. The library provides two functions: _buildTransactionStructHash
and _buildValidationStructHash
. These functions take a Transaction
or Validation
struct as input and return a keccak256 hash of the struct data encoded with the corresponding type hash.
The contract SafeHelper.sol
is also a library that provides helper functions to interact with a Gnosis Safe
, a smart contract wallet that enables multi-signature transactions. The library includes functions to:
_executeOnSafe
)_generateSingleThresholdSignature
)_packMultisendTxns
)_getGuard
)_getFallbackHandler
)_parseOperationEnum
)The TransactionValidator.sol
contract is used to validate transactions before and after execution. The contract includes several functions for validating transactions, including validatePreTransactionOverridable
, validatePostTransactionOverridable
, validatePreTransaction
, validatePostTransaction
, validatePreExecutorTransaction
, and validatePostExecutorTransaction
. These functions are used to validate transactions before and after they are executed, for both console accounts and subaccounts.
The contract also includes several internal helper functions, such as _isConsoleBeingOverriden
, _checkSubAccountSecurityConfig
, and _validatePolicySignature
. These functions are used to check the security configuration of a subaccount, validate policy signatures, and determine if a console is being overridden.
The fourth contract SafeModeratorOverridable.sol
is a guard contract that validates transactions and only allows transactions that abide by certain policies. It is designed to work with the Gnosis Safe
. The checkTransaction
function is called before a transaction is executed. It uses the TransactionValidator
contract to validate the transaction parameters. If the transaction does not meet the validation criteria, it will likely be rejected. The checkAfterExecution
function is called after a transaction is executed. It also uses the TransactionValidator
contract to validate the transaction based on its hash, success status, and the sender. The checkModuleTransaction
function is designed to provide compatibility with Safe 1.5 guard over module.
The contract SafeEnabler.sol
is designed to enable modules and guards for a Gnosis Safe
. The contract has two main functions: enableModule
and setGuard
. The enableModule
function allows a module to be added to the Safe
. It checks that the module address is not null or the sentinel, and that the module has not been added before. If these conditions are met, the module is added and an EnabledModule
event is emitted. The setGuard
function sets the guard for a Safe. It uses inline assembly to directly manipulate storage at a specific slot, bypassing the usual Solidity storage layout. After the guard is set, a ChangedGuard
event is emitted. The _onlyDelegateCall
function is a modifier that checks if the current call is a delegatecall. If it is not, it reverts the transaction. This is used to ensure that the enableModule
and setGuard
functions can only be called via delegatecall. The contract lacks access control mechanisms, meaning any address can call the enableModule
and setGuard
functions via delegatecall. This could potentially be a security risk if not handled correctly in the calling contract. Also, the contract does not check if all input arguments are valid or non-zero addresses.
The SafeModerator.sol
contract implements the IGuard
interface and extends the AddressProviderService
contract. The SafeModerator
contract is designed to validate transactions and only allow those that abide by certain policies. The checkTransaction
function is called before a Safe
transaction is executed. It validates the transaction parameters using the TransactionValidator
contract's validatePreTransaction
function. The checkAfterExecution
function is called after a Safe
transaction is executed. It validates the transaction's success and the transaction hash using the TransactionValidator
contract's validatePostTransaction
function.
The contract Constants.sol
contains a series of constant values. These constants are the keccak256 hashes of various strings, presumably used as identifiers or keys in other parts of the system. The contract is divided into three sections: REGISTRIES, CORE, and ROLES. Each section contains constants related to different aspects of the system:
The ConsoleFallbackHandler.sol
contract is a fallback handler that provides compatibility between pre 1.3.0 and 1.3.0+ Safe
contracts. It ensures that actions performed are policy abiding. It imports and extends several other contracts and interfaces, including AddressProviderService
, DefaultCallbackHandler
, and ISignatureValidator
.
The contract has several constants and a constructor that sets the _addressProvider
address. It also has several functions, including isValidSignature
, getMessageHash
, getMessageHashForSafe
, getModules
, and simulate
. The isValidSignature
function checks if a signature is valid for the provided data. It uses the PolicyValidator
to ensure that the intent of the message hash signed by the owners of the safe complies with its committed policy. The getMessageHash
and getMessageHashForSafe
functions return the hash of a message that can be signed by owners. The getModules
function returns an array of the first 10 modules. The simulate
function performs a delegatecall on a target contract in the context of self. It internally reverts execution to avoid side effects, catches the revert, and returns the encoded result as bytes.
The contract AddressProvider.sol
is designed to serve as a single source of truth for resolving addresses of core components and external contracts. It is part of a governance system where the governance address has the authority to set and update authorized addresses and registries.
The contract PolicyValidator.sol
is used to validate policy signatures for safe transactions. It imports several libraries and contracts, and extends AddressProviderService
and EIP712
. The contract has two main public functions: isPolicySignatureValid
. The first one is used to generate a digest and validate a signature against policies for a safe transaction. The second one is used to generate a digest and validate a signature against policies for module execution. The contract also has two internal helper functions: _decompileSignatures
and _domainNameAndVersion
. The first one is used to extract the validity signature from the overall safe transaction signature. The second one is used to get the EIP712 domain name and version. The contract uses the EIP712 standard for creating a structured data hash, which is then signed to ensure the integrity and authenticity of the data.
The contract PolicyRegistry.sol
is used to manage policy commits for accounts. The contract inherits from AddressProviderService
and uses WalletRegistry
for account management. The contract has a mapping commitments
that links Ethereum addresses to their respective policy commits, represented as bytes32
hashes.
The updatePolicy
function allows for updating the policy commit of an account. It first checks if the new policy commit is not zero. If it is, it reverts the transaction with a PolicyCommitInvalid
error. It then checks if the sender is authorized to update the policy commit. The sender can be authorized in three ways:
UnauthorizedPolicyUpdate
error. If the sender is authorized, the policy commit is updated with the _updatePolicy
function.The ExecutorRegistry.sol
contract is used to manage executors for sub-accounts. Executors are addresses that have certain permissions or capabilities in relation to a sub-account. The contract uses the OpenZeppelin library's EnumerableSet
to manage sets of addresses, and it inherits from AddressProviderService
. The contract has a mapping subAccountToExecutors
that maps a sub-account address to a set of executor addresses. The registerExecutor
function allows the owner of a sub-account to register an executor for that sub-account. It checks if the caller is the owner of the sub-account and if the executor is not already registered. If these conditions are met, it adds the executor to the set and emits a RegisterExecutor
event.
The deRegisterExecutor
function allows the owner of a sub-account to deregister an executor for that sub-account. It checks if the caller is the owner of the sub-account and if the executor is registered. If these conditions are met, it removes the executor from the set and emits a DeRegisterExecutor
event.
The isExecutor
function checks if an address is a registered executor for a given sub-account.
The getExecutorsForSubAccount
function returns all the executors for a given sub-account.
The contract WalletRegistry.sol
is used to manage wallet and sub-account addresses. The contract inherits from AddressProviderService
and uses it to get authorized addresses. The contract has three mappings:
subAccountToWallet
- maps a sub-account address to its owner wallet address.walletToSubAccountList
- maps a wallet address to a list of its sub-accounts.isWallet
- maps an address to a boolean indicating whether it's a wallet.
The contract has two main functions:registerWallet
- allows an address to register itself as a wallet. It checks if the sender is already registered as a wallet or as a sub-account and throws errors if so.registerSubAccount
- allows the safe deployer to register a sub-account for a wallet. It checks if the sub-account is already registered and throws an error if so.The AddressProviderService.sol
contract provides a base for other services to resolve services through AddressProvider
. It is designed to be inherited by other contracts and provides access to all contracts in the Console Ecosystem. The contract imports IAddressProviderService
interface, AddressProvider
contract, and Constants
contract. It also defines custom errors for invalid address provider, non-governance address, and invalid address. The constructor sets the addressProvider
to the provided address, but reverts if the provided address is zero. The _getRegistry
and _getAuthorizedAddress
functions are helper functions that return the registry address and authorized address from the addressProvider
respectively, given a keccak256 key. They also check if the returned address is not null. The _onlyGov
function checks if the msg.sender
is the governance address and reverts if it's not.
The contract SafeDeployer.sol
is used to deploy new console accounts and sub-accounts. It uses the Gnosis Safe
contracts for account creation. The contract has two main public functions: deployConsoleAccount
and deploySubAccount
. Both functions create a new Gnosis Safe
with a list of owners and a threshold. The deployConsoleAccount
function can optionally include a policy commit, while the deploySubAccount
function requires a policy commit. The contract uses the OpenZeppelin ReentrancyGuard
to prevent re-entrancy attacks. It also uses a mapping to keep track of the nonce for each owner's safe deployment.
The contract ExecutorPlugin.sol
is designed to execute transactions on safes with module permissions. The contract uses the OpenZeppelin ReentrancyGuard
to prevent reentrancy attacks. It also uses the EIP712
standard for creating a typed structure that can be signed and sent in a transaction, providing a secure way to verify the signer's intent. The contract defines a struct ExecutionRequest
that holds the details of a transaction execution request. It also maintains a mapping executorNonce
to keep track of nonces for each executor per account. The main function executeTransaction
takes an ExecutionRequest
as input, validates the request, executes the transaction as a module, and then validates the post-execution transaction. The contract also includes several internal helper functions to execute transactions, validate execution requests, and get the EIP712
domain name and version.
The entire project is well written, structured and documented. The contest was 7 days long, but I had a limited time of 4 days to work on it. The first day was spent reading the documentation and understanding the entire codebase. Over the next two days, I made a manually reviewed the contracts, studied the test cases and made comments on the code. On the fourth day I wrote the reports. The contest was interesting and helped me to improve my auditing skills.
30 hours
#0 - c4-pre-sort
2023-10-22T21:16:48Z
raymondfam marked the issue as sufficient quality report
#1 - alex-ppg
2023-10-27T13:24:42Z
The overall analysis is very good and details what role each module of the system is meant to fulfill in an expansive way. While solid, I would advise the Warden to attempt to make their text more digestible by separating some statements into bullet lists, using headers to split the overall report, and generally attempting to minimize the overall language used to make sure the report is concise in addition to being useful.
#2 - c4-judge
2023-10-27T13:24:47Z
alex-ppg marked the issue as grade-a