Skip to content
$1,000,000 in security audit grants are live now, Apply here →

Security review · April 2025

Attester and Resolver

for Cyfrin

Cyfrin engaged Guardian to review the security of their Cyfrin's EAS attester and custom resolver (Certifications). From the 9th of April to the 11th of April, a team of 3 auditors reviewed the source code in scope.

Published
Review window
April 9 to 11, 2025
Language
Solidity
Chains
Arbitrum, Polygon, Scroll, ZKsync, Celo, Blast
Sector
Governance
  • 0 Critical
  • 0 High
  • 2 Medium
  • 6 Low
  • 0 Informational

4 resolved · 4 acknowledged

Scope

Overview

Cyfrin engaged Guardian to review the security of their Cyfrin's EAS attester and custom resolver (Certifications). From the 9th of April to the 11th of April, a team of 3 auditors reviewed the source code in scope.

Findings 8

  1. M-01 Medium Attestor Lacks Payable Function Logical Error Resolved
    Location
    CyfrinAttester.sol

    Description

    The Ethereum Attestation Service (EAS) allows users to make attestations and send ETH if the resolver is expected to be payable. If the amount of ETH sent exceeds the value set in the attestation, EAS refunds the remaining amount to the sender, as shown in the EAS contract code.

    To support cases where the remaining amount is refunded to the Cyfrin Attester, Cyfrin added a withdrawEth method that allows the admin to withdraw accumulated ETH. However, the current Cyfrin Attester contract does not include a receive() payable function or a fallback function to accept ETH.

    As a result, if a refund is attempted, the transaction would revert. While the current resolver used by Cyfrin (Certifications) is not payable, Cyfrin may support different schemas with various resolvers in the future. In such cases, the attester might receive ETH refunds if Cyfrin deploys a payable resolver for those future schemas and attestations.

    If we understand correctly, this is the reason Cyfrin included the withdrawEth function in the attester. If this issue goes unfixed, then for payable resolvers, Cyfrin would need to deploy a new attester with the ability to receive ETH, creating multiple on-chain identities for Cyfrin—which is not desirable.

    Recommendation

    Consider adding a receive() payable function or a fallback function to the Cyfrin attestor to handle incoming ETH properly.

    Resolution

    Cyfrin Team: The issue was resolved in PR#14.

  2. M-02 Medium Selective Rejection Of Low-Score Certifications Gaming Resolved
    Location
    Certification.sol: 67-68

    Description

    Cyfrin leverages an attestation-resolver pattern to record student scores onchain, with values ranging from s_minimumScore to s_maximumScore. These scores are intended to serve as a transparent and trustable metric for talent evaluation by Cyfrin and third parties.

    In the current implementation, the system uses safeMint to issue soulbound NFTs. This invokes the onERC721Received hook on the recipient's contract, giving the recipient (student) a chance to inspect the incoming certificate.

    A student can program this hook to conditionally revert the minting transaction if the score is below a self-imposed threshold. This enables them to:

    • Accept only high-score certificates
    • Reject lower-score certificates without consequences

    As a result, students can selectively curate their onchain reputation, misrepresenting their actual performance. This behavior undermines the credibility and completeness of the certification system. Evaluators relying on these onchain records might be misled, assuming that a student only received high scores, when in fact, lower scores were intentionally blocked from being recorded.

    Note: With the introduction of EIP-7702, even EOAs can include temporary smart contract logic during a transaction. This means any student, including those using EOAs can now curate their onchain reputation in a misleading way.

    Recommendation

    Consider replacing safeMint with a _mint . Since these certificates are intended to be soulbound and non-transferable, there's no need to check for receiver compatibility for handling of NFTs.

    Resolution

    Cyfrin Team: The issue was resolved in PR#14. 9

  3. L-01 Low Redundant Delegation Functions In CyfrinAttester Superfluous Code Resolved
    Location
    CyfrinAttester.sol

    Description

    In the current implementation, CyfrinAttester includes delegation-specific functions like attestByDelegation , revokeByDelegation , multiAttestByDelegation and multiRevokeByDelegation.

    However, these actions are intended to be executed through calls made to EAS, with signature validation handled via a call to isValidSignature on CyfrinAttester.

    Since EAS itself handles delegated execution, and CyfrinAttester only needs to expose isValidSignature, there is no need to implement attestByDelegation, revokeByDelegation, etc. inside CyfrinAttester.

    These delegated functions are currently guarded by onlyRole(ATTESTER_ROLE). However, if a caller already has the ATTESTER_ROLE, they can simply call attest() or revoke() directly, making the delegation route pointless for them.

    Recommendation

    Consider removing delegated action functions (attestByDelegation, revokeByDelegation, etc.) from CyfrinAttester.

    Resolution

    Cyfrin Team: The issue was resolved in PR#14.

  4. L-02 Low Pause Behavior For certificate(tokenId) And isExpired(tokenId) Validation Acknowledged
    Location
    Certification.sol: 168-169

    Description

    Functions like certificate(tokenId) and isExpired(tokenId) are designed for external readers — e.g., third-party apps, scoreboards, or evaluators — to query a student’s score and certificate status.

    However, these functions currently do not check if the resolver is paused, and continue to return data even when the contract is paused.

    Recommendation

    • If the intent of Pausable is only to stop new attestations/revocations, then the current behavior is

    fine.

    • But if the intent is to fully disable the use of the resolver, including read access (e.g., during a

    vulnerability, upgrade, or dispute period), then consider adding whenNotPaused to reader functions as well.

    Resolution

    Cyfrin Team: Acknowledged.

  5. L-03 Low Unused withdrawETH And withdrawERC20 In Resolver Superfluous Code Resolved
    Location
    Certifications.sol

    Description

    The CertificationResolver contract includes two withdrawal functions:

    • withdrawETH()
    • withdrawERC20(address token)

    However, the contract is non-payable and not designed to receive ETH or hold ERC20 tokens as part of its functional logic.

    Recommendation

    Consider removing withdrawETH and withdrawERC20 from the CertificationResolver. If keeping these as defensive mechanisms, clarify their intention in NatSpec comments and ensure they are appropriately access-controlled.

    Resolution

    Cyfrin Team: The issue was resolved in PR#14.

  6. L-04 Low isValidSignature Does Not Support Contract-Based Attesters Validation Acknowledged
    Location
    CyfrinAttester.sol: 157-158

    Description

    If an attester role is assigned to a smart contract wallet (e.g., Gnosis Safe, kernel-based modular wallet, etc.), ECDSA recovery will fail.

    These contracts do not produce ECDSA-compatible signatures and instead follow the ERC-1271 standard for contract-based signature verification.

    This means:

    Delegated attestations signed by contract-based attesters will be invalid, even if they are authorized attesters.

    Recommendation

    If only EOAs are ever meant to hold the ATTESTER_ROLE, no change is needed. However, if it is expected to support smart contract wallets as attesters, update isValidSignature to handle nested ERC-1271 validation.

    Resolution

    Cyfrin Team: Acknowledged.

  7. L-05 Low Future-Proofing Cyfrin's Attester Contract Informational Acknowledged
    Location
    Global

    Description

    This is regarding Cyfrin’s request to understand the implications of future-proofing their attestor contract. Based on our review, Cyfrin’s attester includes functions for attest, revoke, multiAttest, and multiRevoke, and supports delegations via isValidSignature.

    Once the issue reported in M-01 is resolved, the attester will function correctly regardless of whether the resolver is payable. One potential concern is the EAS contract itself. EAS has different versions across various deployments and may continue to deploy new versions in the future.

    If EAS changes the function signature of any method or introduces new features, the attester, in its current form, will not be compatible with the new EAS deployment.

    If Cyfrin intends to maintain a single attester contract with a consistent identity in perpetuity, consider making the attester upgradable to adapt to future EAS changes.

    Similarly, for the resolver, once an attestation is made, the resolver address is fixed. If Cyfrin wishes to modify the resolver logic in the future, consider making the resolver upgradable as well.

    Recommendation

    Please note that we don’t necessarily recommend upgradability, as it’s a design choice for the protocol. Both approaches have their merits, and we will review whichever option you select.

    Resolution

    Cyfrin Team: Acknowledged.

  8. L-06 Low Base URI Not Set On Deployment Deployment Acknowledged
    Location
    deploy_certification.ts: 32

    Description

    The deployment script for Certification.sol does sets an empty string for baseURI. This will lead to an empty string being stored as the NFT’s Base URI on deployment of the protocol, since _setBaseURI() is called in the constructor.

    Recommendation

    Change the deployment script to set the Base URI on deployment.

    Resolution

    Cyfrin Team: Acknowledged.

More from Cyfrin

  1. BattleChain

    19 findings1 high 19 findings: 1 high, 1 medium, 5 low, 12 informational

Put your code through the same review.

This review started with a conversation about scope. Tell us what you are building and we will plan yours with you.

Get a quote