Brahma - Bube's results

Brahma Console is a custody and DeFi execution environment.

General Information

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

Brahma

Findings Distribution

Researcher Performance

Rank: 24/51

Findings: 1

Award: $113.54

Analysis:
grade-a

🌟 Selected for report: 0

🚀 Solo Findings: 0

Awards

113.5407 USDC - $113.54

Labels

analysis-advanced
grade-a
sufficient quality report
A-16

External Links

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:

  • Execute a transaction on a safe (_executeOnSafe)
  • Generate a pre-validated signature for a safe transaction (_generateSingleThresholdSignature)
  • Pack multiple executables into a single bytes array compatible with Safe's MultiSend contract (_packMultisendTxns)
  • Get the guard for a safe (_getGuard)
  • Get the fallback handler for a safe (_getFallbackHandler)
  • Convert a CallType enum to an Operation enum (_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:

  1. REGISTRIES: Contains constants related to different registries in the system such as ExecutorRegistry, WalletRegistry, and PolicyRegistry.
  2. CORE: Contains constants related to core functionalities of the system such as ExecutorPlugin, ConsoleFallbackHandler, GnosisFallbackHandler, etc.
  3. ROLES: Contains constants related to different roles in the system such as Guardian and TrustedValidator.

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:

  1. If the account is uninitialized and the sender is the safe deployer.
  2. If the sender is the owner of the account and the account is a sub-account.
  3. If the sender is the account itself and the account is a registered wallet. If none of these conditions are met, the transaction is reverted with an 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:

  1. subAccountToWallet - maps a sub-account address to its owner wallet address.
  2. walletToSubAccountList - maps a wallet address to a list of its sub-accounts.
  3. isWallet - maps an address to a boolean indicating whether it's a wallet. The contract has two main functions:
  4. 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.
  5. 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.

Time spent:

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

AuditHub

A portfolio for auditors, a security profile for protocols, a hub for web3 security.

Built bymalatrax © 2024

Auditors

Browse

Contests

Browse

Get in touch

ContactTwitter