UNPKG

@settlemint/solidity-attestation-service

Version:
1 lines 3.33 MB
{"id":"2d7992b4cccf2a47ae464259a6d29f74","_format":"hh-sol-build-info-1","solcVersion":"0.8.27","solcLongVersion":"0.8.27+commit.40a35a09","input":{"language":"Solidity","sources":{"@ethereum-attestation-service/eas-contracts/contracts/Common.sol":{"content":"// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.0;\n\n// A representation of an empty/uninitialized UID.\nbytes32 constant EMPTY_UID = 0;\n\n// A zero expiration represents an non-expiring attestation.\nuint64 constant NO_EXPIRATION_TIME = 0;\n\nerror AccessDenied();\nerror DeadlineExpired();\nerror InvalidEAS();\nerror InvalidLength();\nerror InvalidSignature();\nerror NotFound();\n\n/// @notice A struct representing ECDSA signature data.\nstruct Signature {\n uint8 v; // The recovery ID.\n bytes32 r; // The x-coordinate of the nonce R.\n bytes32 s; // The signature data.\n}\n\n/// @notice A struct representing a single attestation.\nstruct Attestation {\n bytes32 uid; // A unique identifier of the attestation.\n bytes32 schema; // The unique identifier of the schema.\n uint64 time; // The time when the attestation was created (Unix timestamp).\n uint64 expirationTime; // The time when the attestation expires (Unix timestamp).\n uint64 revocationTime; // The time when the attestation was revoked (Unix timestamp).\n bytes32 refUID; // The UID of the related attestation.\n address recipient; // The recipient of the attestation.\n address attester; // The attester/sender of the attestation.\n bool revocable; // Whether the attestation is revocable.\n bytes data; // Custom attestation data.\n}\n\n/// @notice A helper function to work with unchecked iterators in loops.\nfunction uncheckedInc(uint256 i) pure returns (uint256 j) {\n unchecked {\n j = i + 1;\n }\n}\n"},"@ethereum-attestation-service/eas-contracts/contracts/EAS.sol":{"content":"// SPDX-License-Identifier: MIT\n\npragma solidity 0.8.27;\n\nimport { Address } from \"@openzeppelin/contracts/utils/Address.sol\";\n\nimport { EIP1271Verifier } from \"./eip1271/EIP1271Verifier.sol\";\n\nimport { ISchemaResolver } from \"./resolver/ISchemaResolver.sol\";\n\n// prettier-ignore\nimport {\n AccessDenied,\n EMPTY_UID,\n InvalidLength,\n NotFound,\n NO_EXPIRATION_TIME,\n uncheckedInc\n} from \"./Common.sol\";\n\n// prettier-ignore\nimport {\n Attestation,\n AttestationRequest,\n AttestationRequestData,\n DelegatedAttestationRequest,\n DelegatedRevocationRequest,\n IEAS,\n MultiAttestationRequest,\n MultiDelegatedAttestationRequest,\n MultiDelegatedRevocationRequest,\n MultiRevocationRequest,\n RevocationRequest,\n RevocationRequestData\n} from \"./IEAS.sol\";\n\nimport { Semver } from \"./Semver.sol\";\nimport { ISchemaRegistry, SchemaRecord } from \"./ISchemaRegistry.sol\";\n\n/// @title EAS\n/// @notice The Ethereum Attestation Service protocol.\ncontract EAS is IEAS, Semver, EIP1271Verifier {\n using Address for address payable;\n\n error AlreadyRevoked();\n error AlreadyRevokedOffchain();\n error AlreadyTimestamped();\n error InsufficientValue();\n error InvalidAttestation();\n error InvalidAttestations();\n error InvalidExpirationTime();\n error InvalidOffset();\n error InvalidRegistry();\n error InvalidRevocation();\n error InvalidRevocations();\n error InvalidSchema();\n error InvalidVerifier();\n error Irrevocable();\n error NotPayable();\n error WrongSchema();\n\n /// @notice A struct representing an internal attestation result.\n struct AttestationsResult {\n uint256 usedValue; // Total ETH amount that was sent to resolvers.\n bytes32[] uids; // UIDs of the new attestations.\n }\n\n // The global schema registry.\n ISchemaRegistry private immutable _schemaRegistry;\n\n // The global mapping between attestations and their UIDs.\n mapping(bytes32 uid => Attestation attestation) private _db;\n\n // The global mapping between data and their timestamps.\n mapping(bytes32 data => uint64 timestamp) private _timestamps;\n\n // The global mapping between data and their revocation timestamps.\n mapping(address revoker => mapping(bytes32 data => uint64 timestamp) timestamps) private _revocationsOffchain;\n\n /// @dev Creates a new EAS instance.\n /// @param registry The address of the global schema registry.\n constructor(ISchemaRegistry registry) Semver(1, 3, 0) EIP1271Verifier(\"EAS\", \"1.3.0\") {\n if (address(registry) == address(0)) {\n revert InvalidRegistry();\n }\n\n _schemaRegistry = registry;\n }\n\n /// @inheritdoc IEAS\n function getSchemaRegistry() external view returns (ISchemaRegistry) {\n return _schemaRegistry;\n }\n\n /// @inheritdoc IEAS\n function attest(AttestationRequest calldata request) external payable returns (bytes32) {\n AttestationRequestData[] memory data = new AttestationRequestData[](1);\n data[0] = request.data;\n\n return _attest(request.schema, data, msg.sender, msg.value, true).uids[0];\n }\n\n /// @inheritdoc IEAS\n function attestByDelegation(\n DelegatedAttestationRequest calldata delegatedRequest\n ) external payable returns (bytes32) {\n _verifyAttest(delegatedRequest);\n\n AttestationRequestData[] memory data = new AttestationRequestData[](1);\n data[0] = delegatedRequest.data;\n\n return _attest(delegatedRequest.schema, data, delegatedRequest.attester, msg.value, true).uids[0];\n }\n\n /// @inheritdoc IEAS\n function multiAttest(MultiAttestationRequest[] calldata multiRequests) external payable returns (bytes32[] memory) {\n // Since a multi-attest call is going to make multiple attestations for multiple schemas, we'd need to collect\n // all the returned UIDs into a single list.\n uint256 length = multiRequests.length;\n bytes32[][] memory totalUIDs = new bytes32[][](length);\n uint256 totalUIDCount = 0;\n\n // We are keeping track of the total available ETH amount that can be sent to resolvers and will keep deducting\n // from it to verify that there isn't any attempt to send too much ETH to resolvers. Please note that unless\n // some ETH was stuck in the contract by accident (which shouldn't happen in normal conditions), it won't be\n // possible to send too much ETH anyway.\n uint256 availableValue = msg.value;\n\n for (uint256 i = 0; i < length; i = uncheckedInc(i)) {\n // The last batch is handled slightly differently: if the total available ETH wasn't spent in full and there\n // is a remainder - it will be refunded back to the attester (something that we can only verify during the\n // last and final batch).\n bool last;\n unchecked {\n last = i == length - 1;\n }\n\n // Process the current batch of attestations.\n MultiAttestationRequest calldata multiRequest = multiRequests[i];\n\n // Ensure that data isn't empty.\n if (multiRequest.data.length == 0) {\n revert InvalidLength();\n }\n\n AttestationsResult memory res = _attest(\n multiRequest.schema,\n multiRequest.data,\n msg.sender,\n availableValue,\n last\n );\n\n // Ensure to deduct the ETH that was forwarded to the resolver during the processing of this batch.\n availableValue -= res.usedValue;\n\n // Collect UIDs (and merge them later).\n totalUIDs[i] = res.uids;\n unchecked {\n totalUIDCount += res.uids.length;\n }\n }\n\n // Merge all the collected UIDs and return them as a flatten array.\n return _mergeUIDs(totalUIDs, totalUIDCount);\n }\n\n /// @inheritdoc IEAS\n function multiAttestByDelegation(\n MultiDelegatedAttestationRequest[] calldata multiDelegatedRequests\n ) external payable returns (bytes32[] memory) {\n // Since a multi-attest call is going to make multiple attestations for multiple schemas, we'd need to collect\n // all the returned UIDs into a single list.\n uint256 length = multiDelegatedRequests.length;\n bytes32[][] memory totalUIDs = new bytes32[][](length);\n uint256 totalUIDCount = 0;\n\n // We are keeping track of the total available ETH amount that can be sent to resolvers and will keep deducting\n // from it to verify that there isn't any attempt to send too much ETH to resolvers. Please note that unless\n // some ETH was stuck in the contract by accident (which shouldn't happen in normal conditions), it won't be\n // possible to send too much ETH anyway.\n uint256 availableValue = msg.value;\n\n for (uint256 i = 0; i < length; i = uncheckedInc(i)) {\n // The last batch is handled slightly differently: if the total available ETH wasn't spent in full and there\n // is a remainder - it will be refunded back to the attester (something that we can only verify during the\n // last and final batch).\n bool last;\n unchecked {\n last = i == length - 1;\n }\n\n MultiDelegatedAttestationRequest calldata multiDelegatedRequest = multiDelegatedRequests[i];\n AttestationRequestData[] calldata data = multiDelegatedRequest.data;\n\n // Ensure that no inputs are missing.\n uint256 dataLength = data.length;\n if (dataLength == 0 || dataLength != multiDelegatedRequest.signatures.length) {\n revert InvalidLength();\n }\n\n // Verify signatures. Please note that the signatures are assumed to be signed with increasing nonces.\n for (uint256 j = 0; j < dataLength; j = uncheckedInc(j)) {\n _verifyAttest(\n DelegatedAttestationRequest({\n schema: multiDelegatedRequest.schema,\n data: data[j],\n signature: multiDelegatedRequest.signatures[j],\n attester: multiDelegatedRequest.attester,\n deadline: multiDelegatedRequest.deadline\n })\n );\n }\n\n // Process the current batch of attestations.\n AttestationsResult memory res = _attest(\n multiDelegatedRequest.schema,\n data,\n multiDelegatedRequest.attester,\n availableValue,\n last\n );\n\n // Ensure to deduct the ETH that was forwarded to the resolver during the processing of this batch.\n availableValue -= res.usedValue;\n\n // Collect UIDs (and merge them later).\n totalUIDs[i] = res.uids;\n unchecked {\n totalUIDCount += res.uids.length;\n }\n }\n\n // Merge all the collected UIDs and return them as a flatten array.\n return _mergeUIDs(totalUIDs, totalUIDCount);\n }\n\n /// @inheritdoc IEAS\n function revoke(RevocationRequest calldata request) external payable {\n RevocationRequestData[] memory data = new RevocationRequestData[](1);\n data[0] = request.data;\n\n _revoke(request.schema, data, msg.sender, msg.value, true);\n }\n\n /// @inheritdoc IEAS\n function revokeByDelegation(DelegatedRevocationRequest calldata delegatedRequest) external payable {\n _verifyRevoke(delegatedRequest);\n\n RevocationRequestData[] memory data = new RevocationRequestData[](1);\n data[0] = delegatedRequest.data;\n\n _revoke(delegatedRequest.schema, data, delegatedRequest.revoker, msg.value, true);\n }\n\n /// @inheritdoc IEAS\n function multiRevoke(MultiRevocationRequest[] calldata multiRequests) external payable {\n // We are keeping track of the total available ETH amount that can be sent to resolvers and will keep deducting\n // from it to verify that there isn't any attempt to send too much ETH to resolvers. Please note that unless\n // some ETH was stuck in the contract by accident (which shouldn't happen in normal conditions), it won't be\n // possible to send too much ETH anyway.\n uint256 availableValue = msg.value;\n\n uint256 length = multiRequests.length;\n for (uint256 i = 0; i < length; i = uncheckedInc(i)) {\n // The last batch is handled slightly differently: if the total available ETH wasn't spent in full and there\n // is a remainder - it will be refunded back to the attester (something that we can only verify during the\n // last and final batch).\n bool last;\n unchecked {\n last = i == length - 1;\n }\n\n MultiRevocationRequest calldata multiRequest = multiRequests[i];\n\n // Ensure to deduct the ETH that was forwarded to the resolver during the processing of this batch.\n availableValue -= _revoke(multiRequest.schema, multiRequest.data, msg.sender, availableValue, last);\n }\n }\n\n /// @inheritdoc IEAS\n function multiRevokeByDelegation(\n MultiDelegatedRevocationRequest[] calldata multiDelegatedRequests\n ) external payable {\n // We are keeping track of the total available ETH amount that can be sent to resolvers and will keep deducting\n // from it to verify that there isn't any attempt to send too much ETH to resolvers. Please note that unless\n // some ETH was stuck in the contract by accident (which shouldn't happen in normal conditions), it won't be\n // possible to send too much ETH anyway.\n uint256 availableValue = msg.value;\n\n uint256 length = multiDelegatedRequests.length;\n for (uint256 i = 0; i < length; i = uncheckedInc(i)) {\n // The last batch is handled slightly differently: if the total available ETH wasn't spent in full and there\n // is a remainder - it will be refunded back to the attester (something that we can only verify during the\n // last and final batch).\n bool last;\n unchecked {\n last = i == length - 1;\n }\n\n MultiDelegatedRevocationRequest memory multiDelegatedRequest = multiDelegatedRequests[i];\n RevocationRequestData[] memory data = multiDelegatedRequest.data;\n\n // Ensure that no inputs are missing.\n uint256 dataLength = data.length;\n if (dataLength == 0 || dataLength != multiDelegatedRequest.signatures.length) {\n revert InvalidLength();\n }\n\n // Verify signatures. Please note that the signatures are assumed to be signed with increasing nonces.\n for (uint256 j = 0; j < dataLength; j = uncheckedInc(j)) {\n _verifyRevoke(\n DelegatedRevocationRequest({\n schema: multiDelegatedRequest.schema,\n data: data[j],\n signature: multiDelegatedRequest.signatures[j],\n revoker: multiDelegatedRequest.revoker,\n deadline: multiDelegatedRequest.deadline\n })\n );\n }\n\n // Ensure to deduct the ETH that was forwarded to the resolver during the processing of this batch.\n availableValue -= _revoke(\n multiDelegatedRequest.schema,\n data,\n multiDelegatedRequest.revoker,\n availableValue,\n last\n );\n }\n }\n\n /// @inheritdoc IEAS\n function timestamp(bytes32 data) external returns (uint64) {\n uint64 time = _time();\n\n _timestamp(data, time);\n\n return time;\n }\n\n /// @inheritdoc IEAS\n function revokeOffchain(bytes32 data) external returns (uint64) {\n uint64 time = _time();\n\n _revokeOffchain(msg.sender, data, time);\n\n return time;\n }\n\n /// @inheritdoc IEAS\n function multiRevokeOffchain(bytes32[] calldata data) external returns (uint64) {\n uint64 time = _time();\n\n uint256 length = data.length;\n for (uint256 i = 0; i < length; i = uncheckedInc(i)) {\n _revokeOffchain(msg.sender, data[i], time);\n }\n\n return time;\n }\n\n /// @inheritdoc IEAS\n function multiTimestamp(bytes32[] calldata data) external returns (uint64) {\n uint64 time = _time();\n\n uint256 length = data.length;\n for (uint256 i = 0; i < length; i = uncheckedInc(i)) {\n _timestamp(data[i], time);\n }\n\n return time;\n }\n\n /// @inheritdoc IEAS\n function getAttestation(bytes32 uid) external view returns (Attestation memory) {\n return _db[uid];\n }\n\n /// @inheritdoc IEAS\n function isAttestationValid(bytes32 uid) public view returns (bool) {\n return _db[uid].uid != EMPTY_UID;\n }\n\n /// @inheritdoc IEAS\n function getTimestamp(bytes32 data) external view returns (uint64) {\n return _timestamps[data];\n }\n\n /// @inheritdoc IEAS\n function getRevokeOffchain(address revoker, bytes32 data) external view returns (uint64) {\n return _revocationsOffchain[revoker][data];\n }\n\n /// @dev Attests to a specific schema.\n /// @param schemaUID The unique identifier of the schema to attest to.\n /// @param data The arguments of the attestation requests.\n /// @param attester The attesting account.\n /// @param availableValue The total available ETH amount that can be sent to the resolver.\n /// @param last Whether this is the last attestations/revocations set.\n /// @return The UID of the new attestations and the total sent ETH amount.\n function _attest(\n bytes32 schemaUID,\n AttestationRequestData[] memory data,\n address attester,\n uint256 availableValue,\n bool last\n ) private returns (AttestationsResult memory) {\n uint256 length = data.length;\n\n AttestationsResult memory res;\n res.uids = new bytes32[](length);\n\n // Ensure that we aren't attempting to attest to a non-existing schema.\n SchemaRecord memory schemaRecord = _schemaRegistry.getSchema(schemaUID);\n if (schemaRecord.uid == EMPTY_UID) {\n revert InvalidSchema();\n }\n\n Attestation[] memory attestations = new Attestation[](length);\n uint256[] memory values = new uint256[](length);\n\n for (uint256 i = 0; i < length; i = uncheckedInc(i)) {\n AttestationRequestData memory request = data[i];\n\n // Ensure that either no expiration time was set or that it was set in the future.\n if (request.expirationTime != NO_EXPIRATION_TIME && request.expirationTime <= _time()) {\n revert InvalidExpirationTime();\n }\n\n // Ensure that we aren't trying to make a revocable attestation for a non-revocable schema.\n if (!schemaRecord.revocable && request.revocable) {\n revert Irrevocable();\n }\n\n Attestation memory attestation = Attestation({\n uid: EMPTY_UID,\n schema: schemaUID,\n refUID: request.refUID,\n time: _time(),\n expirationTime: request.expirationTime,\n revocationTime: 0,\n recipient: request.recipient,\n attester: attester,\n revocable: request.revocable,\n data: request.data\n });\n\n // Look for the first non-existing UID (and use a bump seed/nonce in the rare case of a conflict).\n bytes32 uid;\n uint32 bump = 0;\n while (true) {\n uid = _getUID(attestation, bump);\n if (_db[uid].uid == EMPTY_UID) {\n break;\n }\n\n unchecked {\n ++bump;\n }\n }\n attestation.uid = uid;\n\n _db[uid] = attestation;\n\n if (request.refUID != EMPTY_UID) {\n // Ensure that we aren't trying to attest to a non-existing referenced UID.\n if (!isAttestationValid(request.refUID)) {\n revert NotFound();\n }\n }\n\n attestations[i] = attestation;\n values[i] = request.value;\n\n res.uids[i] = uid;\n\n emit Attested(request.recipient, attester, uid, schemaUID);\n }\n\n res.usedValue = _resolveAttestations(schemaRecord, attestations, values, false, availableValue, last);\n\n return res;\n }\n\n /// @dev Revokes an existing attestation to a specific schema.\n /// @param schemaUID The unique identifier of the schema to attest to.\n /// @param data The arguments of the revocation requests.\n /// @param revoker The revoking account.\n /// @param availableValue The total available ETH amount that can be sent to the resolver.\n /// @param last Whether this is the last attestations/revocations set.\n /// @return Returns the total sent ETH amount.\n function _revoke(\n bytes32 schemaUID,\n RevocationRequestData[] memory data,\n address revoker,\n uint256 availableValue,\n bool last\n ) private returns (uint256) {\n // Ensure that a non-existing schema ID wasn't passed by accident.\n SchemaRecord memory schemaRecord = _schemaRegistry.getSchema(schemaUID);\n if (schemaRecord.uid == EMPTY_UID) {\n revert InvalidSchema();\n }\n\n uint256 length = data.length;\n Attestation[] memory attestations = new Attestation[](length);\n uint256[] memory values = new uint256[](length);\n\n for (uint256 i = 0; i < length; i = uncheckedInc(i)) {\n RevocationRequestData memory request = data[i];\n\n Attestation storage attestation = _db[request.uid];\n\n // Ensure that we aren't attempting to revoke a non-existing attestation.\n if (attestation.uid == EMPTY_UID) {\n revert NotFound();\n }\n\n // Ensure that a wrong schema ID wasn't passed by accident.\n if (attestation.schema != schemaUID) {\n revert InvalidSchema();\n }\n\n // Allow only original attesters to revoke their attestations.\n if (attestation.attester != revoker) {\n revert AccessDenied();\n }\n\n // Please note that also checking of the schema itself is revocable is unnecessary, since it's not possible to\n // make revocable attestations to an irrevocable schema.\n if (!attestation.revocable) {\n revert Irrevocable();\n }\n\n // Ensure that we aren't trying to revoke the same attestation twice.\n if (attestation.revocationTime != 0) {\n revert AlreadyRevoked();\n }\n attestation.revocationTime = _time();\n\n attestations[i] = attestation;\n values[i] = request.value;\n\n emit Revoked(attestations[i].recipient, revoker, request.uid, schemaUID);\n }\n\n return _resolveAttestations(schemaRecord, attestations, values, true, availableValue, last);\n }\n\n /// @dev Resolves a new attestation or a revocation of an existing attestation.\n /// @param schemaRecord The schema of the attestation.\n /// @param attestation The data of the attestation to make/revoke.\n /// @param value An explicit ETH amount to send to the resolver.\n /// @param isRevocation Whether to resolve an attestation or its revocation.\n /// @param availableValue The total available ETH amount that can be sent to the resolver.\n /// @param last Whether this is the last attestations/revocations set.\n /// @return Returns the total sent ETH amount.\n function _resolveAttestation(\n SchemaRecord memory schemaRecord,\n Attestation memory attestation,\n uint256 value,\n bool isRevocation,\n uint256 availableValue,\n bool last\n ) private returns (uint256) {\n ISchemaResolver resolver = schemaRecord.resolver;\n if (address(resolver) == address(0)) {\n // Ensure that we don't accept payments if there is no resolver.\n if (value != 0) {\n revert NotPayable();\n }\n\n if (last) {\n _refund(availableValue);\n }\n\n return 0;\n }\n\n // Ensure that we don't accept payments which can't be forwarded to the resolver.\n if (value != 0) {\n if (!resolver.isPayable()) {\n revert NotPayable();\n }\n\n // Ensure that the attester/revoker doesn't try to spend more than available.\n if (value > availableValue) {\n revert InsufficientValue();\n }\n\n // Ensure to deduct the sent value explicitly.\n unchecked {\n availableValue -= value;\n }\n }\n\n if (isRevocation) {\n if (!resolver.revoke{ value: value }(attestation)) {\n revert InvalidRevocation();\n }\n } else if (!resolver.attest{ value: value }(attestation)) {\n revert InvalidAttestation();\n }\n\n if (last) {\n _refund(availableValue);\n }\n\n return value;\n }\n\n /// @dev Resolves multiple attestations or revocations of existing attestations.\n /// @param schemaRecord The schema of the attestation.\n /// @param attestations The data of the attestations to make/revoke.\n /// @param values Explicit ETH amounts to send to the resolver.\n /// @param isRevocation Whether to resolve an attestation or its revocation.\n /// @param availableValue The total available ETH amount that can be sent to the resolver.\n /// @param last Whether this is the last attestations/revocations set.\n /// @return Returns the total sent ETH amount.\n function _resolveAttestations(\n SchemaRecord memory schemaRecord,\n Attestation[] memory attestations,\n uint256[] memory values,\n bool isRevocation,\n uint256 availableValue,\n bool last\n ) private returns (uint256) {\n uint256 length = attestations.length;\n if (length == 1) {\n return _resolveAttestation(schemaRecord, attestations[0], values[0], isRevocation, availableValue, last);\n }\n\n ISchemaResolver resolver = schemaRecord.resolver;\n if (address(resolver) == address(0)) {\n // Ensure that we don't accept payments if there is no resolver.\n for (uint256 i = 0; i < length; i = uncheckedInc(i)) {\n if (values[i] != 0) {\n revert NotPayable();\n }\n }\n\n if (last) {\n _refund(availableValue);\n }\n\n return 0;\n }\n\n uint256 totalUsedValue = 0;\n bool isResolverPayable = resolver.isPayable();\n\n for (uint256 i = 0; i < length; i = uncheckedInc(i)) {\n uint256 value = values[i];\n\n // Ensure that we don't accept payments which can't be forwarded to the resolver.\n if (value == 0) {\n continue;\n }\n\n if (!isResolverPayable) {\n revert NotPayable();\n }\n\n // Ensure that the attester/revoker doesn't try to spend more than available.\n if (value > availableValue) {\n revert InsufficientValue();\n }\n\n // Ensure to deduct the sent value explicitly and add it to the total used value by the batch.\n unchecked {\n availableValue -= value;\n totalUsedValue += value;\n }\n }\n\n if (isRevocation) {\n if (!resolver.multiRevoke{ value: totalUsedValue }(attestations, values)) {\n revert InvalidRevocations();\n }\n } else if (!resolver.multiAttest{ value: totalUsedValue }(attestations, values)) {\n revert InvalidAttestations();\n }\n\n if (last) {\n _refund(availableValue);\n }\n\n return totalUsedValue;\n }\n\n /// @dev Calculates a UID for a given attestation.\n /// @param attestation The input attestation.\n /// @param bump A bump value to use in case of a UID conflict.\n /// @return Attestation UID.\n function _getUID(Attestation memory attestation, uint32 bump) private pure returns (bytes32) {\n return\n keccak256(\n abi.encodePacked(\n attestation.schema,\n attestation.recipient,\n attestation.attester,\n attestation.time,\n attestation.expirationTime,\n attestation.revocable,\n attestation.refUID,\n attestation.data,\n bump\n )\n );\n }\n\n /// @dev Refunds remaining ETH amount to the attester.\n /// @param remainingValue The remaining ETH amount that was not sent to the resolver.\n function _refund(uint256 remainingValue) private {\n if (remainingValue > 0) {\n // Using a regular transfer here might revert, for some non-EOA attesters, due to exceeding of the 2300\n // gas limit which is why we're using call instead (via sendValue), which the 2300 gas limit does not\n // apply for.\n payable(msg.sender).sendValue(remainingValue);\n }\n }\n\n /// @dev Timestamps the specified bytes32 data.\n /// @param data The data to timestamp.\n /// @param time The timestamp.\n function _timestamp(bytes32 data, uint64 time) private {\n if (_timestamps[data] != 0) {\n revert AlreadyTimestamped();\n }\n\n _timestamps[data] = time;\n\n emit Timestamped(data, time);\n }\n\n /// @dev Revokes the specified bytes32 data.\n /// @param revoker The revoking account.\n /// @param data The data to revoke.\n /// @param time The timestamp the data was revoked with.\n function _revokeOffchain(address revoker, bytes32 data, uint64 time) private {\n mapping(bytes32 data => uint64 timestamp) storage revocations = _revocationsOffchain[revoker];\n\n if (revocations[data] != 0) {\n revert AlreadyRevokedOffchain();\n }\n\n revocations[data] = time;\n\n emit RevokedOffchain(revoker, data, time);\n }\n\n /// @dev Merges lists of UIDs.\n /// @param uidLists The provided lists of UIDs.\n /// @param uidCount Total UID count.\n /// @return A merged and flatten list of all the UIDs.\n function _mergeUIDs(bytes32[][] memory uidLists, uint256 uidCount) private pure returns (bytes32[] memory) {\n bytes32[] memory uids = new bytes32[](uidCount);\n\n uint256 currentIndex = 0;\n uint256 uidListLength = uidLists.length;\n for (uint256 i = 0; i < uidListLength; i = uncheckedInc(i)) {\n bytes32[] memory currentUIDs = uidLists[i];\n uint256 currentUIDsLength = currentUIDs.length;\n for (uint256 j = 0; j < currentUIDsLength; j = uncheckedInc(j)) {\n uids[currentIndex] = currentUIDs[j];\n\n unchecked {\n ++currentIndex;\n }\n }\n }\n\n return uids;\n }\n}\n"},"@ethereum-attestation-service/eas-contracts/contracts/eip1271/EIP1271Verifier.sol":{"content":"// SPDX-License-Identifier: MIT\n\npragma solidity 0.8.27;\n\nimport { Address } from \"@openzeppelin/contracts/utils/Address.sol\";\nimport { EIP712 } from \"@openzeppelin/contracts/utils/cryptography/EIP712.sol\";\nimport { SignatureChecker } from \"@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol\";\nimport { DeadlineExpired, NO_EXPIRATION_TIME, Signature, InvalidSignature } from \"./../Common.sol\";\n\n// prettier-ignore\nimport {\n AttestationRequestData,\n DelegatedAttestationRequest,\n DelegatedRevocationRequest,\n RevocationRequestData\n} from \"../IEAS.sol\";\n\n/// @title EIP1271Verifier\n/// @notice EIP1271Verifier typed signatures verifier for EAS delegated attestations.\nabstract contract EIP1271Verifier is EIP712 {\n using Address for address;\n\n error InvalidNonce();\n\n // The hash of the data type used to relay calls to the attest function. It's the value of\n // keccak256(\"Attest(address attester,bytes32 schema,address recipient,uint64 expirationTime,bool revocable,bytes32 refUID,bytes data,uint256 value,uint256 nonce,uint64 deadline)\").\n bytes32 private constant ATTEST_TYPEHASH = 0xfeb2925a02bae3dae48d424a0437a2b6ac939aa9230ddc55a1a76f065d988076;\n\n // The hash of the data type used to relay calls to the revoke function. It's the value of\n // keccak256(\"Revoke(address revoker,bytes32 schema,bytes32 uid,uint256 value,uint256 nonce,uint64 deadline)\").\n bytes32 private constant REVOKE_TYPEHASH = 0xb5d556f07587ec0f08cf386545cc4362c702a001650c2058002615ee5c9d1e75;\n\n // The user readable name of the signing domain.\n string private _name;\n\n // Replay protection nonces.\n mapping(address attester => uint256 nonce) private _nonces;\n\n /// @notice Emitted when users invalidate nonces by increasing their nonces to (higher) new values.\n /// @param oldNonce The previous nonce.\n /// @param newNonce The new value.\n event NonceIncreased(uint256 oldNonce, uint256 newNonce);\n\n /// @dev Creates a new EIP1271Verifier instance.\n /// @param version The current major version of the signing domain\n constructor(string memory name, string memory version) EIP712(name, version) {\n _name = name;\n }\n\n /// @notice Returns the domain separator used in the encoding of the signatures for attest, and revoke.\n /// @return The domain separator used in the encoding of the signatures for attest, and revoke.\n function getDomainSeparator() external view returns (bytes32) {\n return _domainSeparatorV4();\n }\n\n /// @notice Returns the current nonce per-account.\n /// @param account The requested account.\n /// @return The current nonce.\n function getNonce(address account) external view returns (uint256) {\n return _nonces[account];\n }\n\n /// @notice Returns the EIP712 type hash for the attest function.\n /// @return The EIP712 type hash for the attest function.\n function getAttestTypeHash() external pure returns (bytes32) {\n return ATTEST_TYPEHASH;\n }\n\n /// @notice Returns the EIP712 type hash for the revoke function.\n /// @return The EIP712 type hash for the revoke function.\n function getRevokeTypeHash() external pure returns (bytes32) {\n return REVOKE_TYPEHASH;\n }\n\n /// @notice Returns the EIP712 name.\n /// @return The EIP712 name.\n function getName() external view returns (string memory) {\n return _name;\n }\n\n /// @notice Provides users an option to invalidate nonces by increasing their nonces to (higher) new values.\n /// @param newNonce The (higher) new value.\n function increaseNonce(uint256 newNonce) external {\n uint256 oldNonce = _nonces[msg.sender];\n if (newNonce <= oldNonce) {\n revert InvalidNonce();\n }\n\n _nonces[msg.sender] = newNonce;\n\n emit NonceIncreased({ oldNonce: oldNonce, newNonce: newNonce });\n }\n\n /// @dev Verifies delegated attestation request.\n /// @param request The arguments of the delegated attestation request.\n function _verifyAttest(DelegatedAttestationRequest memory request) internal {\n if (request.deadline != NO_EXPIRATION_TIME && request.deadline < _time()) {\n revert DeadlineExpired();\n }\n\n AttestationRequestData memory data = request.data;\n Signature memory signature = request.signature;\n\n bytes32 hash = _hashTypedDataV4(\n keccak256(\n abi.encode(\n ATTEST_TYPEHASH,\n request.attester,\n request.schema,\n data.recipient,\n data.expirationTime,\n data.revocable,\n data.refUID,\n keccak256(data.data),\n data.value,\n _nonces[request.attester]++,\n request.deadline\n )\n )\n );\n if (\n !SignatureChecker.isValidSignatureNow(\n request.attester,\n hash,\n abi.encodePacked(signature.r, signature.s, signature.v)\n )\n ) {\n revert InvalidSignature();\n }\n }\n\n /// @dev Verifies delegated revocation request.\n /// @param request The arguments of the delegated revocation request.\n function _verifyRevoke(DelegatedRevocationRequest memory request) internal {\n if (request.deadline != NO_EXPIRATION_TIME && request.deadline < _time()) {\n revert DeadlineExpired();\n }\n\n RevocationRequestData memory data = request.data;\n Signature memory signature = request.signature;\n\n bytes32 hash = _hashTypedDataV4(\n keccak256(\n abi.encode(\n REVOKE_TYPEHASH,\n request.revoker,\n request.schema,\n data.uid,\n data.value,\n _nonces[request.revoker]++,\n request.deadline\n )\n )\n );\n if (\n !SignatureChecker.isValidSignatureNow(\n request.revoker,\n hash,\n abi.encodePacked(signature.r, signature.s, signature.v)\n )\n ) {\n revert InvalidSignature();\n }\n }\n\n /// @dev Returns the current's block timestamp. This method is overridden during tests and used to simulate the\n /// current block time.\n function _time() internal view virtual returns (uint64) {\n return uint64(block.timestamp);\n }\n}\n"},"@ethereum-attestation-service/eas-contracts/contracts/IEAS.sol":{"content":"// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.0;\n\nimport { ISchemaRegistry } from \"./ISchemaRegistry.sol\";\nimport { ISemver } from \"./ISemver.sol\";\nimport { Attestation, Signature } from \"./Common.sol\";\n\n/// @notice A struct representing the arguments of the attestation request.\nstruct AttestationRequestData {\n address recipient; // The recipient of the attestation.\n uint64 expirationTime; // The time when the attestation expires (Unix timestamp).\n bool revocable; // Whether the attestation is revocable.\n bytes32 refUID; // The UID of the related attestation.\n bytes data; // Custom attestation data.\n uint256 value; // An explicit ETH amount to send to the resolver. This is important to prevent accidental user errors.\n}\n\n/// @notice A struct representing the full arguments of the attestation request.\nstruct AttestationRequest {\n bytes32 schema; // The unique identifier of the schema.\n AttestationRequestData data; // The arguments of the attestation request.\n}\n\n/// @notice A struct representing the full arguments of the full delegated attestation request.\nstruct DelegatedAttestationRequest {\n bytes32 schema; // The unique identifier of the schema.\n AttestationRequestData data; // The arguments of the attestation request.\n Signature signature; // The ECDSA signature data.\n address attester; // The attesting account.\n uint64 deadline; // The deadline of the signature/request.\n}\n\n/// @notice A struct representing the full arguments of the multi attestation request.\nstruct MultiAttestationRequest {\n bytes32 schema; // The unique identifier of the schema.\n AttestationRequestData[] data; // The arguments of the attestation request.\n}\n\n/// @notice A struct representing the full arguments of the delegated multi attestation request.\nstruct MultiDelegatedAttestationRequest {\n bytes32 schema; // The unique identifier of the schema.\n AttestationRequestData[] data; // The arguments of the attestation requests.\n Signature[] signatures; // The ECDSA signatures data. Please note that the signatures are assumed to be signed with increasing nonces.\n address attester; // The attesting account.\n uint64 deadline; // The deadline of the signature/request.\n}\n\n/// @notice A struct representing the arguments of the revocation request.\nstruct RevocationRequestData {\n bytes32 uid; // The UID of the attestation to revoke.\n uint256 value; // An explicit ETH amount to send to the resolver. This is important to prevent accidental user errors.\n}\n\n/// @notice A struct representing the full arguments of the revocation request.\nstruct RevocationRequest {\n bytes32 schema; // The unique identifier of the schema.\n RevocationRequestData data; // The arguments of the revocation request.\n}\n\n/// @notice A struct representing the arguments of the full delegated revocation request.\nstruct DelegatedRevocationRequest {\n bytes32 schema; // The unique identifier of the schema.\n RevocationRequestData data; // The arguments of the revocation request.\n Signature signature; // The ECDSA signature data.\n address revoker; // The revoking account.\n uint64 deadline; // The deadline of the signature/request.\n}\n\n/// @notice A struct representing the full arguments of the multi revocation request.\nstruct MultiRevocationRequest {\n bytes32 schema; // The unique identifier of the schema.\n RevocationRequestData[] data; // The arguments of the revocation request.\n}\n\n/// @notice A struct representing the full arguments of the delegated multi revocation request.\nstruct MultiDelegatedRevocationRequest {\n bytes32 schema; // The unique identifier of the schema.\n RevocationRequestData[] data; // The arguments of the revocation requests.\n Signature[] signatures; // The ECDSA signatures data. Please note that the signatures are assumed to be signed with increasing nonces.\n address revoker; // The revoking account.\n uint64 deadline; // The deadline of the signature/request.\n}\n\n/// @title IEAS\n/// @notice EAS - Ethereum Attestation Service interface.\ninterface IEAS is ISemver {\n /// @notice Emitted when an attestation has been made.\n /// @param recipient The recipient of the attestation.\n /// @param attester The attesting account.\n /// @param uid The UID of the new attestation.\n /// @param schemaUID The UID of the schema.\n event Attested(address indexed recipient, address indexed attester, bytes32 uid, bytes32 indexed schemaUID);\n\n /// @notice Emitted when an attestation has been revoked.\n /// @param recipient The recipient of the attestation.\n /// @param attester The attesting account.\n /// @param schemaUID The UID of the schema.\n /// @param uid The UID the revoked attestation.\n event Revoked(address indexed recipient, address indexed attester, bytes32 uid, bytes32 indexed schemaUID);\n\n /// @notice Emitted when a data has been timestamped.\n /// @param data The data.\n /// @param timestamp The timestamp.\n event Timestamped(bytes32 indexed data, uint64 indexed timestamp);\n\n /// @notice Emitted when a data has been revoked.\n /// @param revoker The address of the revoker.\n /// @param data The data.\n /// @param timestamp The timestamp.\n event RevokedOffchain(address indexed revoker, bytes32 indexed data, uint64 indexed timestamp);\n\n /// @notice Returns the address of the global schema registry.\n /// @return The address of the global schema registry.\n function getSchemaRegistry() external view returns (ISchemaRegistry);\n\n /// @notice Attests to a specific schema.\n /// @param request The arguments of the attestation request.\n /// @return The UID of the new attestation.\n ///\n /// Example:\n /// attest({\n /// schema: \"0facc36681cbe2456019c1b0d1e7bedd6d1d40f6f324bf3dd3a4cef2999200a0\",\n /// data: {\n /// recipient: \"0xdEADBeAFdeAdbEafdeadbeafDeAdbEAFdeadbeaf\",\n /// expirationTime: 0,\n /// revocable: true,\n /// refUID: \"0x0000000000000000000000000000000000000000000000000000000000000000\",\n /// data: \"0xF00D\",\n /// value: 0\n /// }\n /// })\n function attest(AttestationRequest calldata request) external payable returns (bytes32);\n\n /// @notice Attests to a specific schema via the provided ECDSA signature.\n /// @param delegatedRequest The arguments of the delegated attestation request.\n /// @return The UID of the new attestation.\n ///\n /// Example:\n /// attestByDelegation({\n /// schema: '0x8e72f5bc0a8d4be6aa98360baa889040c50a0e51f32dbf0baa5199bd93472ebc',\n /// data: {\n /// recipient: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266',\n /// expirationTime: 1673891048,\n /// revocable: true,\n /// refUID: '0x0000000000000000000000000000000000000000000000000000000000000000',\n /// data: '0x1234',\n /// value: 0\n /// },\n /// signature: {\n /// v: 28,\n /// r: '0x148c...b25b',\n /// s: '0x5a72...be22'\n /// },\n /// attester: '0xc5E8740aD971409492b1A63Db8d83025e0Fc427e',\n /// deadline: 1673891048\n /// })\n function attestByDelegation(\n DelegatedAttestationRequest calldata delegatedRequest\n ) external payable returns (bytes32);\n\n /// @notice Attests to multiple schemas.\n /// @param multiRequests The arguments of the multi attestation requests. The requests should be grouped by distinct\n /// schema ids to benefit from the best batching optimization.\n /// @return The UIDs of the new attestations.\n ///\n /// Example:\n /// multiAttest([{\n /// schema: '0x33e9094830a5cba5554d1954310e4fbed2ef5f859ec1404619adea4207f391fd',\n /// data: [{\n /// recipient: '0xdEADBeAFdeAdbEafdeadbeafDeAdbEAFdeadbeaf',\n /// expirationTime: 1673891048,\n /// revocable: true,\n /// refUID: '0x0000000000000000000000000000000000000000000000000000000000000000',\n /// data: '0x1234',\n /// value: 1000\n /// },\n /// {\n /// recipient: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266',\n /// expirationTime: 0,\n /// revocable: false,\n /// refUID: '0x480df4a039efc31b11bfdf491b383ca138b6bde160988222a2a3509c02cee174',\n /// data: '0x00',\n /// value: 0\n /// }],\n /// },\n /// {\n /// schema: '0x5ac273ce41e3c8bfa383efe7c03e54c5f0bff29c9f11ef6ffa930fc84ca32425',\n /// data: [{\n /// recipient: '0xdEADBeAFdeAdbEafdeadbeafDeAdbEAFdeadbeaf',\n /// expirationTime: 0,\n /// revocable: true,\n /// refUID: '0x75bf2ed8dca25a8190c50c52db136664de25b2449535839008ccfdab469b214f',\n /// data: '0x12345678',\n /// value: 0\n /// },\n /// }])\n function multiAttest(MultiAttestationRequest[] calldata multiRequests) external payable returns (bytes32[] memory);\n\n /// @notice Attests to multiple schemas using via provided ECDSA signatures.\n /// @param multiDelegatedRequests The arguments of the delegated multi attestation requests. The requests should be\n /// grouped by distinct schema ids to benefit from the best batching optimization.\n /// @return The UIDs of the new attestations.\n ///\n /// Example:\n /// multiAttestByDelegation([{\n /// schema: '0x8e72f5bc0a8d4be6aa98360baa889040c50a0e51f32dbf0baa5199bd93472ebc',\n /// data: [{\n /// recipient: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266',\n /// expirationTime: 1673891048,\n /// revocable: true,\n /// refUID: '0x0000000000000000000000000000000000000000000000000000000000000000',\n /// data: '0x1234',\n /// value: 0\n /// },\n /// {\n /// recipient: '0xdEADBeAFdeAdbEafdeadbeafDeAdbEAFdeadbeaf',\n /// expirationTime: 0,\n /// revocable: false,\n /// refUID: '0x0000000000000000000000000000000000000000000000000000000000000000',\n /// data: '0x00',\n /// value: 0\n /// }],\n /// signatures: [{\n /// v: 28,\n /// r: '0x148c...b25b',\n /// s: '0x5a72...be22'\n /// },\n /// {\n /// v: 28,\n /// r: '0x487s...67b