CertiK reviewed Quadrata (Quadrata Passport Audit 2) on 23 Sep 2022: 12 findings, 0 critical; 6 marked fixed, 6 acknowledged.
Report hosted on skynet.certik.com.
Trustblock does not review code.
Statuses are the security firm’s own, from its report.
| Severity | Open | Acknowledged | Fixed |
|---|---|---|---|
| Critical | 0 | 0 | 0 |
| High | 0 | 2 | 1 |
| Medium | 0 | 2 | 1 |
| Low | 0 | 2 | 4 |
In the contract `QuadGovernance`, the role `GOVERNANCE_ROLE` has authority over functions. Any compromise to the `GOVERNANCE_ROLE` account may allow the hacker to take advantage of this authority and - set QuadPassport treasury wallet to withdraw the protocol fees through `setTreasury()` - set QuadPassport contract address through `setPassportContractAddress()` - begin transfer of `GOVERNANCE_ROLE` through `updateGovernanceInPassport()` - accept transfer of `GOVERNANCE_ROLE` through `acceptGovernanceInPassport()` - set the eligibility status for a passport token ID through `setEligibleTokenId()` - set the eligibility status for an attribute type through `setEligibleAttribute()` - set the eligibility status for an attribute type grouped by DID through `setEligibleAttributeByDID()` - set the price for querying a single attribute after owning a passport through `setAttributePriceFixed()` - set the business attribute price for querying a single attribute after owning a passport through `setBusinessAttributePriceFixed()` - set the revenue split percentage between issuers and Quadrata Protocol through `setRevSplitIssuer()` - add a new issuer or update treasury through `addIssuer()` - delete issuer through `deleteIssuer()` - deactivate/activate issuer through `setIssuerStatus()` - set which attributes an issuer can attest to through `setIssuerAttributePermission()` - authorize to upgrade QuadGovernance contract through `_authorizeUpgrade()` In the contract `AccessControlUpgradeable`, each role has an associated `adminRole` account and the `adminRole` account has the authority over functions. Any compromise to the `adminRole` accounts may allow the hacker to take advantage of this and - grant a specific `role` to any arbitrary addresses through `grantRole()` - revoke a specific `role` from `account` through `revokeRole()` In the contract `QuadPassport`, the role `pendingGovernance` has authority over function `acceptGovernance()`. Any compromise to the `GOVERNANCE_ROLE` account may allow the hacker to take advantage of this authority and - accept the role `governance` In the contract `QuadPassport`, the role `GOVERNANCE_ROLE` has authority over functions. Any compromise to the `GOVERNANCE_ROLE` account may allow the hacker to take advantage of this authority and - withdraw revenue to an issuer's treasury through `withdraw()` - authorize to upgrade QuadPassport contract through `_authorizeUpgrade()` - migrate passports from an older contract through `migrate()` In the contract `QuadPassport`, the role `READER_ROLE` has authority over function `attributes()`. Any compromise to the `READER_ROLE` account may allow the hacker to take advantage of this authority and - get attribute information about a passport holder In the contract `QuadPassport`, the role `ISSUER_ROLE` has authority over functions. Any compromise to the `ISSUER_ROLE` account may allow the hacker to take advantage of this authority and - burn an account's Quadrata passport or delete passport attributes through `burnPassportIssuer()` - update or set a new attribute for your passport through `setAttributesIssuer()` In the contract `QuadReader`, the role `GOVERNANCE_ROLE` has authority over functions. Any compromise to the `GOVERNANCE_ROLE` account may allow the hacker to take advantage of this authority and - authorize to upgrade QuadReader contract through `_authorizeUpgrade()` - withdraw revenue to an issuer's treasury or the Quadrata treasury through `withdraw()`
`QuadPassport`, `QuadSoulbound`, `QuadReader` and `QuadGovernance` are upgradeable contracts, the owner can upgrade the contract without the community's commitment. If an attacker compromises the account, he can change the implementation of the contract and drain tokens from the contract.
The `QuadSoulbound` contract does not implement all ERC1155 functions, but the `supportsInterface()` function implies it supports ERC1155 interface. For interface checking, the `QuadSoulbound` contract should inherit from ERC165 contract and add super contract interface checking. Otherwise the interface checking functionality will malfunction in many cases. See https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.4.2/contracts/utils/introspection/ERC165.sol
An issuer can use a signature to modify the passport attribute of any eligible id since the issuer signature does not contain the `_config.tokenId` field. For security concerns, a signature should include `_config.attrTypes`, too.
Functions `setEligibleAttribute()` and `setEligibleAttributeByDID()` in contract `QuadGovernance` can update the eligibility of any attribute, misuses of these functions may cause issues. For example: 1. Function `QuadPassport._validateDid()` would be invalid if `ATTRIBUTE_DID` is not eligible, which may cause the function call of `QuadPassport._setAttributesInternal()` fail. 2. Changing the eligibility of an attribute after a passport's attribute was set may cause the latter to be invalid.
Function `QuadGovernance.acceptGovernanceInPassport()`: ```solidity function acceptGovernanceInPassport() external { require(hasRole(GOVERNANCE_ROLE, _msgSender()), "INVALID_ADMIN"); _passport.acceptGovernance(); } ``` Function `QuadPassport.acceptGovernance()`: ```solidity function acceptGovernance() external override { require(_msgSender() == pendingGovernance, "ONLY_PENDING_GOVERNANCE_CONTRACT"); address oldGov = address(governance); governance = IQuadGovernance(pendingGovernance); pendingGovernance = address(0); emit GovernanceUpdated(oldGov, address(governance)); } ``` The address `QuadPassport.pendingGovernance` has two ways to be the `governance` of contract `QuadPassport`: 1. Get the `GOVERNANCE_ROLE` in the current governance and call `QuadGovernance.acceptGovernanceInPassport()`. 2. Call `QuadPassport.acceptGovernance()` (without the `GOVERNANCE_ROLE`) The first way needs `QuadPassport.pendingGovernance` to get `GOVERNANCE_ROLE`, while the second way does not.
The report names a repository or files. Open the report to see its scope.
Repository and files the report names (14)
1,044 projects reviewed · 1,375 reports · latest May 2025
See CertiK’s auditsSomething wrong on this page? A missing report, a wrong date, a finding that was fixed since. Tell us and we check it against its source.
Tell us