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
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
-
M-01 Medium Attestor Lacks Payable Function Logical Error Resolved
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
withdrawEthmethod that allows the admin to withdraw accumulated ETH. However, the current Cyfrin Attester contract does not include areceive() payablefunction or afallbackfunction 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
withdrawEthfunction 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() payablefunction or afallbackfunction to the Cyfrin attestor to handle incoming ETH properly.Resolution
Cyfrin Team: The issue was resolved in PR#14.
-
M-02 Medium Selective Rejection Of Low-Score Certifications Gaming Resolved
Description
Cyfrin leverages an attestation-resolver pattern to record student scores onchain, with values ranging from
s_minimumScoretos_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
safeMintto issue soulbound NFTs. This invokes theonERC721Receivedhook 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
safeMintwith 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
-
L-01 Low Redundant Delegation Functions In CyfrinAttester Superfluous Code Resolved
Description
In the current implementation,
CyfrinAttesterincludes delegation-specific functions likeattestByDelegation,revokeByDelegation,multiAttestByDelegationandmultiRevokeByDelegation.However, these actions are intended to be executed through calls made to EAS, with signature validation handled via a call to
isValidSignatureonCyfrinAttester.Since EAS itself handles delegated execution, and
CyfrinAttesteronly needs to exposeisValidSignature, there is no need to implementattestByDelegation,revokeByDelegation, etc. insideCyfrinAttester.These delegated functions are currently guarded by
onlyRole(ATTESTER_ROLE). However, if a caller already has theATTESTER_ROLE, they can simply callattest()orrevoke()directly, making the delegation route pointless for them.Recommendation
Consider removing delegated action functions (
attestByDelegation,revokeByDelegation, etc.) fromCyfrinAttester.Resolution
Cyfrin Team: The issue was resolved in PR#14.
-
L-02 Low Pause Behavior For certificate(tokenId) And isExpired(tokenId) Validation Acknowledged
Description
Functions like
certificate(tokenId)andisExpired(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
Pausableis 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
whenNotPausedto reader functions as well.Resolution
Cyfrin Team: Acknowledged.
- If the intent of
-
L-03 Low Unused withdrawETH And withdrawERC20 In Resolver Superfluous Code Resolved
Description
The
CertificationResolvercontract includes two withdrawal functions:withdrawETH()withdrawERC20(address token)
However, the contract is non-payable and not designed to receive ETH or hold
ERC20tokens as part of its functional logic.Recommendation
Consider removing
withdrawETHandwithdrawERC20from theCertificationResolver. If keeping these as defensive mechanisms, clarify their intention inNatSpeccomments and ensure they are appropriately access-controlled.Resolution
Cyfrin Team: The issue was resolved in PR#14.
-
L-04 Low isValidSignature Does Not Support Contract-Based Attesters Validation Acknowledged
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-1271standard 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, updateisValidSignatureto handle nested ERC-1271 validation.Resolution
Cyfrin Team: Acknowledged.
-
L-05 Low Future-Proofing Cyfrin's Attester Contract Informational Acknowledged
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, andmultiRevoke, and supports delegations viaisValidSignature.Once the issue reported in
M-01is 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.
-
L-06 Low Base URI Not Set On Deployment Deployment Acknowledged
Description
The deployment script for
Certification.soldoes sets an empty string forbaseURI. 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.
No findings match.
More from Cyfrin
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.
