As of 3 Nov 2026, Quadrata has 3 audit reports by 2 security firms on Trustblock; the latest is by CertiK, dated 3 Jul 2023. Those reports list no open critical findings.
Record last updated . It holds audits, findings and contracts up to Nov 2026, and incidents up to Oct 2026. Trustblock does not review code.
Newest first
| Audit | Security firm | Date | Scope | Critical findings | Report |
|---|---|---|---|---|---|
| Quadrata Passport - Audit (Addendum) | Security firm: CertiK | Date: | Scope: Files named in the report | No open critical findings | Open · skynet.certik.com |
| Quadrata Lending | Security firm: Quantstamp | Date: | Scope: Files named in the report | No open critical findings | Open · certificate.quantstamp.com |
| Quadrata Passport Audit 2 | Security firm: CertiK | Date: | Scope: Files named in the report | No open critical findings | Open · skynet.certik.com |
Statuses are the security firm’s own, from each report.
| Severity | Open | Acknowledged | Fixed |
|---|---|---|---|
| Critical | 0 | 0 | 0 |
| High | 3 | 3 | 1 |
| Medium | 0 | 5 | 2 |
| Low | 3 | 6 | 11 |
In the contract `QuadGovernance` the role `DEFAULT_ADMIN_ROLE` is the admin of `GOVERNANCE_ROLE`. In the contract `QuadGovernance` the role `GOVERNANCE_ROLE` is the admin of `PAUSER_ROLE`, `ISSUER_ROLE` and `READER_ROLE`, and also has authority over the functions below. - function setTreasury() - function setPassportContractAddress() - function updateGovernanceInPassport() - function acceptGovernanceInPassport() - function setEligibleAttribute() - function setEligibleAttributeByDID() - function setPreapprovals() - function setAttributePriceFixed() - function setBusinessAttributePriceFixed() - function setRevSplitIssuer() - function addIssuer() - function deleteIssuer() - function setIssuerStatus() - function setIssuerAttributePermission() - function _authorizeUpgrade(): upgrade contract. In the contract `QuadGovernance` the role `OPERATOR_ROLE` has authority over the function below. - function setTokenURI() In the contract `QuadPassport` the role `GOVERNANCE_ROLE` of `QuadGovernance` contract has authority over the functions below. - function withdraw(): withdraw received fees to an issuer's treasury. - function setQuadReader(): set `reader` contract. - function migrateAttributes(): migrate attributes from `_attributes` to `_attributesv2`. - function _authorizeUpgrade(): upgrade contract. In the contract `QuadPassport` the role `PAUSER_ROLE` of `QuadGovernance` contract has authority over the functions below. - function pause() - function unpause() In the contract `QuadPassport` the role `ISSUER_ROLE` of `QuadGovernance` contract has authority to sign passport configuration data, which can be used to update passports. In the contract `QuadPassport` the role `READER_ROLE` of `QuadGovernance` contract has authority over the functions below. - function attribute(): get one attribute information about a passport. - function attributes(): get all attribute information about a passport. In the contract `QuadReader` the role `GOVERNANCE_ROLE` of `QuadGovernance` contract has authority over the functions below. - function withdraw() - function _authorizeUpgrade(): upgrade contract. `QuadPassport`, `QuadSoulbound`, `QuadReader` and `QuadGovernance` are upgradeable contracts, the owner can upgrade the contract without the community's commitment. Any compromise to the privileged account may allow the hacker to take advantage of this authority.
Open the reportIn 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()`
Open the report`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.
Open the reportThe `burnPassports()` and `burnPassportsIssuer()` functions are used to burn users' passports with all attributes. These functions iterate through the `eligibleAttributesArray` array, which contains eligible attributes. However, there may be other attributes that are true in the `_eligibleAttributesByDID` mapping that are not included in the `eligibleAttributesArray`. As a result, the `burnPassports()` and `burnPassportsIssuer()` functions may not burn all passports.
Open the reportThe function `_setAttributesVerify()` does not check whether a `signedMsg` has been used before. This means that a `signedMsg` can be reused, potentially allowing a user to recover a passport after it has been removed.
Open the reportThe current migration process involves removing the previous data right after the migration. However, this practice is not recommended as it can lead to the loss of important data in case of unexpected errors or issues.
Open the reportThe `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
Open the reportAn 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.
Open the reportThe `_issuerTreasury` map stores the issuer's treasury address. The `addIssuer()` function sets this address for the new issuer, but the `deleteIssuer()` function doesn't remove the deleted issuer's treasury address, which causes inconsistency in state.
Open the reportThe deleted issuer will be saved in the `_deletedIssuers` array. The `addIssuer()` function doesn't check whether the new issuer to be added is in the `_deletedIssuers` array. Therefore, the rejoined issuer may exist in both the `_issuers` and `_deletedIssuers` arrays at the same time.
Open the report```solidity function attribute( address _account, bytes32 _attribute ) public view override returns (Attribute memory) { require(msg.sender == address(reader) || IAccessControlUpgradeable(address(governance)).hasRole(READER_ROLE, _msgSender()), "INVALID_READER"); address[] memory issuers = governance.getIssuers(); for (uint256 i = 0; i < issuers.length; i++) { bytes32 attrKey = attributeKey(_account, _attribute, issuers[i]); Attribute memory attr = _attributesv2[attrKey]; if (attr.epoch != uint256(0)) { attr.issuer = issuers[i]; return attr; } } return Attribute({value: bytes32(0), epoch: uint256(0), issuer: address(0)}); } ``` The `attribute()` function in the contract is designed to return a single `Attribute` struct for the inputted `_account` and `_attribute`. However, if multiple issuers sign the same `_attribute` for the `_account`, there could be multiple `Attribute` structs, potentially leading to data inconsistency issues and confusion in the passport verification process.
Open the report```solidity contract QuadGovernanceStoreV2 is QuadGovernanceStore { address[] internal _deletedIssuers; mapping(address => bool) internal _preapprovals; } ``` ```solidity contract QuadGovernance is IQuadGovernance, AccessControlUpgradeable, UUPSUpgradeable, QuadGovernanceStoreV2 { ``` In the current design, a new contract `QuadGovernanceStoreV2` is created by inheriting from an existing contract `QuadGovernanceStore`, which is then inherited by the implementation contract `QuadGovernance`. This approach is not recommended because if a developer upgrades `QuadGovernance` in the future without looking at the previous version, it is possible that new state variables that conflict with existing storage slots will be introduced to the new `QuadGovernance` contract. Contract `QuadPassportStoreV2` shares the same issue.
Open the reportFunctions `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.
Open the reportFunction `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.
Open the reportThe reports name a repository or files. Open a report to see its scope.
Is this your project? Show this record on your own website. The label states what the record holds, links to this page and updates by itself. It is free and needs no sign-in.
Automatic: the three strongest facts of this project’s own record (its last audit and two more), highlighted. They update with the record. Picking a fact turns Automatic off.
<head><script src="https://widget.trustblock.run/project-widget.min.js" defer></script></head><body><divdata-tb-project-widgetdata-tb-project-slug="quadrata"></div></body>
Paste the code where the label should appear. It uses no API credit and updates by itself.
Something 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