diff --git a/README.md b/README.md index 212b1eb..430545a 100644 --- a/README.md +++ b/README.md @@ -84,11 +84,10 @@ The [Ethereum Foundation](https://ethereum.foundation/) funded this effort as pa Individuals or data teams that are involved in active feedback for this initiative and/or opened up their labeled databases. - [Carl Cervone](https://github.com/ccerv1) & [Raymond Cheng](https://github.com/ryscheng) | [Open Source Observer](https://github.com/opensource-observer) +- [Hildobby](https://x.com/hildobby_) | [Dragonfly](https://x.com/dragonfly_xyz) - [Michael Silberling](https://github.com/MSilb7) | [OP Labs](https://www.oplabs.co/) -- [Storm](https://github.com/sslivkoff) & [samczsun](https://github.com/samczsun/) | [Paradigm](https://www.paradigm.xyz/) -- [Dune](https://dune.com/) -- [Allium](https://www.allium.so/) +- [Storm](https://github.com/sslivkoff) & [Samczsun](https://github.com/samczsun/) | [Paradigm](https://www.paradigm.xyz/) +- Simon Brown | [Consensys](https://consensys.io/) +- [Ethereum Attestation Service](https://attest.org/) - [Blockscout](https://www.blockscout.com/) -- [Artemis](https://www.artemis.xyz/) -- [Routescan](https://routescan.io/) - [Guild](https://guild.xyz/) diff --git a/data_entry/EAS/Common.sol b/data_entry/EAS/Common.sol new file mode 100644 index 0000000..613831e --- /dev/null +++ b/data_entry/EAS/Common.sol @@ -0,0 +1,44 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +// A representation of an empty/uninitialized UID. +bytes32 constant EMPTY_UID = 0; + +// A zero expiration represents an non-expiring attestation. +uint64 constant NO_EXPIRATION_TIME = 0; + +error AccessDenied(); +error DeadlineExpired(); +error InvalidEAS(); +error InvalidLength(); +error InvalidSignature(); +error NotFound(); + +/// @notice A struct representing ECDSA signature data. +struct Signature { + uint8 v; // The recovery ID. + bytes32 r; // The x-coordinate of the nonce R. + bytes32 s; // The signature data. +} + +/// @notice A struct representing a single attestation. +struct Attestation { + bytes32 uid; // A unique identifier of the attestation. + bytes32 schema; // The unique identifier of the schema. + uint64 time; // The time when the attestation was created (Unix timestamp). + uint64 expirationTime; // The time when the attestation expires (Unix timestamp). + uint64 revocationTime; // The time when the attestation was revoked (Unix timestamp). + bytes32 refUID; // The UID of the related attestation. + address recipient; // The recipient of the attestation. + address attester; // The attester/sender of the attestation. + bool revocable; // Whether the attestation is revocable. + bytes data; // Custom attestation data. +} + +/// @notice A helper function to work with unchecked iterators in loops. +function uncheckedInc(uint256 i) pure returns (uint256 j) { + unchecked { + j = i + 1; + } +} \ No newline at end of file diff --git a/data_entry/EAS/EAS.sol b/data_entry/EAS/EAS.sol new file mode 100644 index 0000000..c37b993 --- /dev/null +++ b/data_entry/EAS/EAS.sol @@ -0,0 +1,777 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.27; + +import { Address } from "@openzeppelin/contracts/utils/Address.sol"; + +import { EIP1271Verifier } from "./eip1271/EIP1271Verifier.sol"; + +import { ISchemaResolver } from "./ISchemaResolver.sol"; + +// prettier-ignore +import { + AccessDenied, + EMPTY_UID, + InvalidLength, + NotFound, + NO_EXPIRATION_TIME, + uncheckedInc +} from "./Common.sol"; + +// prettier-ignore +import { + Attestation, + AttestationRequest, + AttestationRequestData, + DelegatedAttestationRequest, + DelegatedRevocationRequest, + IEAS, + MultiAttestationRequest, + MultiDelegatedAttestationRequest, + MultiDelegatedRevocationRequest, + MultiRevocationRequest, + RevocationRequest, + RevocationRequestData +} from "./IEAS.sol"; + +import { Semver } from "./Semver.sol"; +import { ISchemaRegistry, SchemaRecord } from "./ISchemaRegistry.sol"; + +/// @title EAS +/// @notice The Ethereum Attestation Service protocol. +contract EAS is IEAS, Semver, EIP1271Verifier { + using Address for address payable; + + error AlreadyRevoked(); + error AlreadyRevokedOffchain(); + error AlreadyTimestamped(); + error InsufficientValue(); + error InvalidAttestation(); + error InvalidAttestations(); + error InvalidExpirationTime(); + error InvalidOffset(); + error InvalidRegistry(); + error InvalidRevocation(); + error InvalidRevocations(); + error InvalidSchema(); + error InvalidVerifier(); + error Irrevocable(); + error NotPayable(); + error WrongSchema(); + + /// @notice A struct representing an internal attestation result. + struct AttestationsResult { + uint256 usedValue; // Total ETH amount that was sent to resolvers. + bytes32[] uids; // UIDs of the new attestations. + } + + // The global schema registry. + ISchemaRegistry private immutable _schemaRegistry; + + // The global mapping between attestations and their UIDs. + mapping(bytes32 uid => Attestation attestation) private _db; + + // The global mapping between data and their timestamps. + mapping(bytes32 data => uint64 timestamp) private _timestamps; + + // The global mapping between data and their revocation timestamps. + mapping(address revoker => mapping(bytes32 data => uint64 timestamp) timestamps) private _revocationsOffchain; + + /// @dev Creates a new EAS instance. + /// @param registry The address of the global schema registry. + constructor(ISchemaRegistry registry) Semver(1, 3, 0) EIP1271Verifier("EAS", "1.3.0") { + if (address(registry) == address(0)) { + revert InvalidRegistry(); + } + + _schemaRegistry = registry; + } + + /// @inheritdoc IEAS + function getSchemaRegistry() external view returns (ISchemaRegistry) { + return _schemaRegistry; + } + + /// @inheritdoc IEAS + function attest(AttestationRequest calldata request) external payable returns (bytes32) { + AttestationRequestData[] memory data = new AttestationRequestData[](1); + data[0] = request.data; + + return _attest(request.schema, data, msg.sender, msg.value, true).uids[0]; + } + + /// @inheritdoc IEAS + function attestByDelegation( + DelegatedAttestationRequest calldata delegatedRequest + ) external payable returns (bytes32) { + _verifyAttest(delegatedRequest); + + AttestationRequestData[] memory data = new AttestationRequestData[](1); + data[0] = delegatedRequest.data; + + return _attest(delegatedRequest.schema, data, delegatedRequest.attester, msg.value, true).uids[0]; + } + + /// @inheritdoc IEAS + function multiAttest(MultiAttestationRequest[] calldata multiRequests) external payable returns (bytes32[] memory) { + // Since a multi-attest call is going to make multiple attestations for multiple schemas, we'd need to collect + // all the returned UIDs into a single list. + uint256 length = multiRequests.length; + bytes32[][] memory totalUIDs = new bytes32[][](length); + uint256 totalUIDCount = 0; + + // We are keeping track of the total available ETH amount that can be sent to resolvers and will keep deducting + // from it to verify that there isn't any attempt to send too much ETH to resolvers. Please note that unless + // some ETH was stuck in the contract by accident (which shouldn't happen in normal conditions), it won't be + // possible to send too much ETH anyway. + uint256 availableValue = msg.value; + + for (uint256 i = 0; i < length; i = uncheckedInc(i)) { + // The last batch is handled slightly differently: if the total available ETH wasn't spent in full and there + // is a remainder - it will be refunded back to the attester (something that we can only verify during the + // last and final batch). + bool last; + unchecked { + last = i == length - 1; + } + + // Process the current batch of attestations. + MultiAttestationRequest calldata multiRequest = multiRequests[i]; + + // Ensure that data isn't empty. + if (multiRequest.data.length == 0) { + revert InvalidLength(); + } + + AttestationsResult memory res = _attest( + multiRequest.schema, + multiRequest.data, + msg.sender, + availableValue, + last + ); + + // Ensure to deduct the ETH that was forwarded to the resolver during the processing of this batch. + availableValue -= res.usedValue; + + // Collect UIDs (and merge them later). + totalUIDs[i] = res.uids; + unchecked { + totalUIDCount += res.uids.length; + } + } + + // Merge all the collected UIDs and return them as a flatten array. + return _mergeUIDs(totalUIDs, totalUIDCount); + } + + /// @inheritdoc IEAS + function multiAttestByDelegation( + MultiDelegatedAttestationRequest[] calldata multiDelegatedRequests + ) external payable returns (bytes32[] memory) { + // Since a multi-attest call is going to make multiple attestations for multiple schemas, we'd need to collect + // all the returned UIDs into a single list. + uint256 length = multiDelegatedRequests.length; + bytes32[][] memory totalUIDs = new bytes32[][](length); + uint256 totalUIDCount = 0; + + // We are keeping track of the total available ETH amount that can be sent to resolvers and will keep deducting + // from it to verify that there isn't any attempt to send too much ETH to resolvers. Please note that unless + // some ETH was stuck in the contract by accident (which shouldn't happen in normal conditions), it won't be + // possible to send too much ETH anyway. + uint256 availableValue = msg.value; + + for (uint256 i = 0; i < length; i = uncheckedInc(i)) { + // The last batch is handled slightly differently: if the total available ETH wasn't spent in full and there + // is a remainder - it will be refunded back to the attester (something that we can only verify during the + // last and final batch). + bool last; + unchecked { + last = i == length - 1; + } + + MultiDelegatedAttestationRequest calldata multiDelegatedRequest = multiDelegatedRequests[i]; + AttestationRequestData[] calldata data = multiDelegatedRequest.data; + + // Ensure that no inputs are missing. + uint256 dataLength = data.length; + if (dataLength == 0 || dataLength != multiDelegatedRequest.signatures.length) { + revert InvalidLength(); + } + + // Verify signatures. Please note that the signatures are assumed to be signed with increasing nonces. + for (uint256 j = 0; j < dataLength; j = uncheckedInc(j)) { + _verifyAttest( + DelegatedAttestationRequest({ + schema: multiDelegatedRequest.schema, + data: data[j], + signature: multiDelegatedRequest.signatures[j], + attester: multiDelegatedRequest.attester, + deadline: multiDelegatedRequest.deadline + }) + ); + } + + // Process the current batch of attestations. + AttestationsResult memory res = _attest( + multiDelegatedRequest.schema, + data, + multiDelegatedRequest.attester, + availableValue, + last + ); + + // Ensure to deduct the ETH that was forwarded to the resolver during the processing of this batch. + availableValue -= res.usedValue; + + // Collect UIDs (and merge them later). + totalUIDs[i] = res.uids; + unchecked { + totalUIDCount += res.uids.length; + } + } + + // Merge all the collected UIDs and return them as a flatten array. + return _mergeUIDs(totalUIDs, totalUIDCount); + } + + /// @inheritdoc IEAS + function revoke(RevocationRequest calldata request) external payable { + RevocationRequestData[] memory data = new RevocationRequestData[](1); + data[0] = request.data; + + _revoke(request.schema, data, msg.sender, msg.value, true); + } + + /// @inheritdoc IEAS + function revokeByDelegation(DelegatedRevocationRequest calldata delegatedRequest) external payable { + _verifyRevoke(delegatedRequest); + + RevocationRequestData[] memory data = new RevocationRequestData[](1); + data[0] = delegatedRequest.data; + + _revoke(delegatedRequest.schema, data, delegatedRequest.revoker, msg.value, true); + } + + /// @inheritdoc IEAS + function multiRevoke(MultiRevocationRequest[] calldata multiRequests) external payable { + // We are keeping track of the total available ETH amount that can be sent to resolvers and will keep deducting + // from it to verify that there isn't any attempt to send too much ETH to resolvers. Please note that unless + // some ETH was stuck in the contract by accident (which shouldn't happen in normal conditions), it won't be + // possible to send too much ETH anyway. + uint256 availableValue = msg.value; + + uint256 length = multiRequests.length; + for (uint256 i = 0; i < length; i = uncheckedInc(i)) { + // The last batch is handled slightly differently: if the total available ETH wasn't spent in full and there + // is a remainder - it will be refunded back to the attester (something that we can only verify during the + // last and final batch). + bool last; + unchecked { + last = i == length - 1; + } + + MultiRevocationRequest calldata multiRequest = multiRequests[i]; + + // Ensure to deduct the ETH that was forwarded to the resolver during the processing of this batch. + availableValue -= _revoke(multiRequest.schema, multiRequest.data, msg.sender, availableValue, last); + } + } + + /// @inheritdoc IEAS + function multiRevokeByDelegation( + MultiDelegatedRevocationRequest[] calldata multiDelegatedRequests + ) external payable { + // We are keeping track of the total available ETH amount that can be sent to resolvers and will keep deducting + // from it to verify that there isn't any attempt to send too much ETH to resolvers. Please note that unless + // some ETH was stuck in the contract by accident (which shouldn't happen in normal conditions), it won't be + // possible to send too much ETH anyway. + uint256 availableValue = msg.value; + + uint256 length = multiDelegatedRequests.length; + for (uint256 i = 0; i < length; i = uncheckedInc(i)) { + // The last batch is handled slightly differently: if the total available ETH wasn't spent in full and there + // is a remainder - it will be refunded back to the attester (something that we can only verify during the + // last and final batch). + bool last; + unchecked { + last = i == length - 1; + } + + MultiDelegatedRevocationRequest memory multiDelegatedRequest = multiDelegatedRequests[i]; + RevocationRequestData[] memory data = multiDelegatedRequest.data; + + // Ensure that no inputs are missing. + uint256 dataLength = data.length; + if (dataLength == 0 || dataLength != multiDelegatedRequest.signatures.length) { + revert InvalidLength(); + } + + // Verify signatures. Please note that the signatures are assumed to be signed with increasing nonces. + for (uint256 j = 0; j < dataLength; j = uncheckedInc(j)) { + _verifyRevoke( + DelegatedRevocationRequest({ + schema: multiDelegatedRequest.schema, + data: data[j], + signature: multiDelegatedRequest.signatures[j], + revoker: multiDelegatedRequest.revoker, + deadline: multiDelegatedRequest.deadline + }) + ); + } + + // Ensure to deduct the ETH that was forwarded to the resolver during the processing of this batch. + availableValue -= _revoke( + multiDelegatedRequest.schema, + data, + multiDelegatedRequest.revoker, + availableValue, + last + ); + } + } + + /// @inheritdoc IEAS + function timestamp(bytes32 data) external returns (uint64) { + uint64 time = _time(); + + _timestamp(data, time); + + return time; + } + + /// @inheritdoc IEAS + function revokeOffchain(bytes32 data) external returns (uint64) { + uint64 time = _time(); + + _revokeOffchain(msg.sender, data, time); + + return time; + } + + /// @inheritdoc IEAS + function multiRevokeOffchain(bytes32[] calldata data) external returns (uint64) { + uint64 time = _time(); + + uint256 length = data.length; + for (uint256 i = 0; i < length; i = uncheckedInc(i)) { + _revokeOffchain(msg.sender, data[i], time); + } + + return time; + } + + /// @inheritdoc IEAS + function multiTimestamp(bytes32[] calldata data) external returns (uint64) { + uint64 time = _time(); + + uint256 length = data.length; + for (uint256 i = 0; i < length; i = uncheckedInc(i)) { + _timestamp(data[i], time); + } + + return time; + } + + /// @inheritdoc IEAS + function getAttestation(bytes32 uid) external view returns (Attestation memory) { + return _db[uid]; + } + + /// @inheritdoc IEAS + function isAttestationValid(bytes32 uid) public view returns (bool) { + return _db[uid].uid != EMPTY_UID; + } + + /// @inheritdoc IEAS + function getTimestamp(bytes32 data) external view returns (uint64) { + return _timestamps[data]; + } + + /// @inheritdoc IEAS + function getRevokeOffchain(address revoker, bytes32 data) external view returns (uint64) { + return _revocationsOffchain[revoker][data]; + } + + /// @dev Attests to a specific schema. + /// @param schemaUID The unique identifier of the schema to attest to. + /// @param data The arguments of the attestation requests. + /// @param attester The attesting account. + /// @param availableValue The total available ETH amount that can be sent to the resolver. + /// @param last Whether this is the last attestations/revocations set. + /// @return The UID of the new attestations and the total sent ETH amount. + function _attest( + bytes32 schemaUID, + AttestationRequestData[] memory data, + address attester, + uint256 availableValue, + bool last + ) private returns (AttestationsResult memory) { + uint256 length = data.length; + + AttestationsResult memory res; + res.uids = new bytes32[](length); + + // Ensure that we aren't attempting to attest to a non-existing schema. + SchemaRecord memory schemaRecord = _schemaRegistry.getSchema(schemaUID); + if (schemaRecord.uid == EMPTY_UID) { + revert InvalidSchema(); + } + + Attestation[] memory attestations = new Attestation[](length); + uint256[] memory values = new uint256[](length); + + for (uint256 i = 0; i < length; i = uncheckedInc(i)) { + AttestationRequestData memory request = data[i]; + + // Ensure that either no expiration time was set or that it was set in the future. + if (request.expirationTime != NO_EXPIRATION_TIME && request.expirationTime <= _time()) { + revert InvalidExpirationTime(); + } + + // Ensure that we aren't trying to make a revocable attestation for a non-revocable schema. + if (!schemaRecord.revocable && request.revocable) { + revert Irrevocable(); + } + + Attestation memory attestation = Attestation({ + uid: EMPTY_UID, + schema: schemaUID, + refUID: request.refUID, + time: _time(), + expirationTime: request.expirationTime, + revocationTime: 0, + recipient: request.recipient, + attester: attester, + revocable: request.revocable, + data: request.data + }); + + // Look for the first non-existing UID (and use a bump seed/nonce in the rare case of a conflict). + bytes32 uid; + uint32 bump = 0; + while (true) { + uid = _getUID(attestation, bump); + if (_db[uid].uid == EMPTY_UID) { + break; + } + + unchecked { + ++bump; + } + } + attestation.uid = uid; + + _db[uid] = attestation; + + if (request.refUID != EMPTY_UID) { + // Ensure that we aren't trying to attest to a non-existing referenced UID. + if (!isAttestationValid(request.refUID)) { + revert NotFound(); + } + } + + attestations[i] = attestation; + values[i] = request.value; + + res.uids[i] = uid; + + emit Attested(request.recipient, attester, uid, schemaUID); + } + + res.usedValue = _resolveAttestations(schemaRecord, attestations, values, false, availableValue, last); + + return res; + } + + /// @dev Revokes an existing attestation to a specific schema. + /// @param schemaUID The unique identifier of the schema to attest to. + /// @param data The arguments of the revocation requests. + /// @param revoker The revoking account. + /// @param availableValue The total available ETH amount that can be sent to the resolver. + /// @param last Whether this is the last attestations/revocations set. + /// @return Returns the total sent ETH amount. + function _revoke( + bytes32 schemaUID, + RevocationRequestData[] memory data, + address revoker, + uint256 availableValue, + bool last + ) private returns (uint256) { + // Ensure that a non-existing schema ID wasn't passed by accident. + SchemaRecord memory schemaRecord = _schemaRegistry.getSchema(schemaUID); + if (schemaRecord.uid == EMPTY_UID) { + revert InvalidSchema(); + } + + uint256 length = data.length; + Attestation[] memory attestations = new Attestation[](length); + uint256[] memory values = new uint256[](length); + + for (uint256 i = 0; i < length; i = uncheckedInc(i)) { + RevocationRequestData memory request = data[i]; + + Attestation storage attestation = _db[request.uid]; + + // Ensure that we aren't attempting to revoke a non-existing attestation. + if (attestation.uid == EMPTY_UID) { + revert NotFound(); + } + + // Ensure that a wrong schema ID wasn't passed by accident. + if (attestation.schema != schemaUID) { + revert InvalidSchema(); + } + + // Allow only original attesters to revoke their attestations. + if (attestation.attester != revoker) { + revert AccessDenied(); + } + + // Please note that also checking of the schema itself is revocable is unnecessary, since it's not possible to + // make revocable attestations to an irrevocable schema. + if (!attestation.revocable) { + revert Irrevocable(); + } + + // Ensure that we aren't trying to revoke the same attestation twice. + if (attestation.revocationTime != 0) { + revert AlreadyRevoked(); + } + attestation.revocationTime = _time(); + + attestations[i] = attestation; + values[i] = request.value; + + emit Revoked(attestations[i].recipient, revoker, request.uid, schemaUID); + } + + return _resolveAttestations(schemaRecord, attestations, values, true, availableValue, last); + } + + /// @dev Resolves a new attestation or a revocation of an existing attestation. + /// @param schemaRecord The schema of the attestation. + /// @param attestation The data of the attestation to make/revoke. + /// @param value An explicit ETH amount to send to the resolver. + /// @param isRevocation Whether to resolve an attestation or its revocation. + /// @param availableValue The total available ETH amount that can be sent to the resolver. + /// @param last Whether this is the last attestations/revocations set. + /// @return Returns the total sent ETH amount. + function _resolveAttestation( + SchemaRecord memory schemaRecord, + Attestation memory attestation, + uint256 value, + bool isRevocation, + uint256 availableValue, + bool last + ) private returns (uint256) { + ISchemaResolver resolver = schemaRecord.resolver; + if (address(resolver) == address(0)) { + // Ensure that we don't accept payments if there is no resolver. + if (value != 0) { + revert NotPayable(); + } + + if (last) { + _refund(availableValue); + } + + return 0; + } + + // Ensure that we don't accept payments which can't be forwarded to the resolver. + if (value != 0) { + if (!resolver.isPayable()) { + revert NotPayable(); + } + + // Ensure that the attester/revoker doesn't try to spend more than available. + if (value > availableValue) { + revert InsufficientValue(); + } + + // Ensure to deduct the sent value explicitly. + unchecked { + availableValue -= value; + } + } + + if (isRevocation) { + if (!resolver.revoke{ value: value }(attestation)) { + revert InvalidRevocation(); + } + } else if (!resolver.attest{ value: value }(attestation)) { + revert InvalidAttestation(); + } + + if (last) { + _refund(availableValue); + } + + return value; + } + + /// @dev Resolves multiple attestations or revocations of existing attestations. + /// @param schemaRecord The schema of the attestation. + /// @param attestations The data of the attestations to make/revoke. + /// @param values Explicit ETH amounts to send to the resolver. + /// @param isRevocation Whether to resolve an attestation or its revocation. + /// @param availableValue The total available ETH amount that can be sent to the resolver. + /// @param last Whether this is the last attestations/revocations set. + /// @return Returns the total sent ETH amount. + function _resolveAttestations( + SchemaRecord memory schemaRecord, + Attestation[] memory attestations, + uint256[] memory values, + bool isRevocation, + uint256 availableValue, + bool last + ) private returns (uint256) { + uint256 length = attestations.length; + if (length == 1) { + return _resolveAttestation(schemaRecord, attestations[0], values[0], isRevocation, availableValue, last); + } + + ISchemaResolver resolver = schemaRecord.resolver; + if (address(resolver) == address(0)) { + // Ensure that we don't accept payments if there is no resolver. + for (uint256 i = 0; i < length; i = uncheckedInc(i)) { + if (values[i] != 0) { + revert NotPayable(); + } + } + + if (last) { + _refund(availableValue); + } + + return 0; + } + + uint256 totalUsedValue = 0; + bool isResolverPayable = resolver.isPayable(); + + for (uint256 i = 0; i < length; i = uncheckedInc(i)) { + uint256 value = values[i]; + + // Ensure that we don't accept payments which can't be forwarded to the resolver. + if (value == 0) { + continue; + } + + if (!isResolverPayable) { + revert NotPayable(); + } + + // Ensure that the attester/revoker doesn't try to spend more than available. + if (value > availableValue) { + revert InsufficientValue(); + } + + // Ensure to deduct the sent value explicitly and add it to the total used value by the batch. + unchecked { + availableValue -= value; + totalUsedValue += value; + } + } + + if (isRevocation) { + if (!resolver.multiRevoke{ value: totalUsedValue }(attestations, values)) { + revert InvalidRevocations(); + } + } else if (!resolver.multiAttest{ value: totalUsedValue }(attestations, values)) { + revert InvalidAttestations(); + } + + if (last) { + _refund(availableValue); + } + + return totalUsedValue; + } + + /// @dev Calculates a UID for a given attestation. + /// @param attestation The input attestation. + /// @param bump A bump value to use in case of a UID conflict. + /// @return Attestation UID. + function _getUID(Attestation memory attestation, uint32 bump) private pure returns (bytes32) { + return + keccak256( + abi.encodePacked( + attestation.schema, + attestation.recipient, + attestation.attester, + attestation.time, + attestation.expirationTime, + attestation.revocable, + attestation.refUID, + attestation.data, + bump + ) + ); + } + + /// @dev Refunds remaining ETH amount to the attester. + /// @param remainingValue The remaining ETH amount that was not sent to the resolver. + function _refund(uint256 remainingValue) private { + if (remainingValue > 0) { + // Using a regular transfer here might revert, for some non-EOA attesters, due to exceeding of the 2300 + // gas limit which is why we're using call instead (via sendValue), which the 2300 gas limit does not + // apply for. + payable(msg.sender).sendValue(remainingValue); + } + } + + /// @dev Timestamps the specified bytes32 data. + /// @param data The data to timestamp. + /// @param time The timestamp. + function _timestamp(bytes32 data, uint64 time) private { + if (_timestamps[data] != 0) { + revert AlreadyTimestamped(); + } + + _timestamps[data] = time; + + emit Timestamped(data, time); + } + + /// @dev Revokes the specified bytes32 data. + /// @param revoker The revoking account. + /// @param data The data to revoke. + /// @param time The timestamp the data was revoked with. + function _revokeOffchain(address revoker, bytes32 data, uint64 time) private { + mapping(bytes32 data => uint64 timestamp) storage revocations = _revocationsOffchain[revoker]; + + if (revocations[data] != 0) { + revert AlreadyRevokedOffchain(); + } + + revocations[data] = time; + + emit RevokedOffchain(revoker, data, time); + } + + /// @dev Merges lists of UIDs. + /// @param uidLists The provided lists of UIDs. + /// @param uidCount Total UID count. + /// @return A merged and flatten list of all the UIDs. + function _mergeUIDs(bytes32[][] memory uidLists, uint256 uidCount) private pure returns (bytes32[] memory) { + bytes32[] memory uids = new bytes32[](uidCount); + + uint256 currentIndex = 0; + uint256 uidListLength = uidLists.length; + for (uint256 i = 0; i < uidListLength; i = uncheckedInc(i)) { + bytes32[] memory currentUIDs = uidLists[i]; + uint256 currentUIDsLength = currentUIDs.length; + for (uint256 j = 0; j < currentUIDsLength; j = uncheckedInc(j)) { + uids[currentIndex] = currentUIDs[j]; + + unchecked { + ++currentIndex; + } + } + } + + return uids; + } +} \ No newline at end of file diff --git a/data_entry/EAS/IEAS.sol b/data_entry/EAS/IEAS.sol new file mode 100644 index 0000000..830f0f5 --- /dev/null +++ b/data_entry/EAS/IEAS.sol @@ -0,0 +1,370 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +import { ISchemaRegistry } from "./ISchemaRegistry.sol"; +import { ISemver } from "./ISemver.sol"; +import { Attestation, Signature } from "./Common.sol"; + +/// @notice A struct representing the arguments of the attestation request. +struct AttestationRequestData { + address recipient; // The recipient of the attestation. + uint64 expirationTime; // The time when the attestation expires (Unix timestamp). + bool revocable; // Whether the attestation is revocable. + bytes32 refUID; // The UID of the related attestation. + bytes data; // Custom attestation data. + uint256 value; // An explicit ETH amount to send to the resolver. This is important to prevent accidental user errors. +} + +/// @notice A struct representing the full arguments of the attestation request. +struct AttestationRequest { + bytes32 schema; // The unique identifier of the schema. + AttestationRequestData data; // The arguments of the attestation request. +} + +/// @notice A struct representing the full arguments of the full delegated attestation request. +struct DelegatedAttestationRequest { + bytes32 schema; // The unique identifier of the schema. + AttestationRequestData data; // The arguments of the attestation request. + Signature signature; // The ECDSA signature data. + address attester; // The attesting account. + uint64 deadline; // The deadline of the signature/request. +} + +/// @notice A struct representing the full arguments of the multi attestation request. +struct MultiAttestationRequest { + bytes32 schema; // The unique identifier of the schema. + AttestationRequestData[] data; // The arguments of the attestation request. +} + +/// @notice A struct representing the full arguments of the delegated multi attestation request. +struct MultiDelegatedAttestationRequest { + bytes32 schema; // The unique identifier of the schema. + AttestationRequestData[] data; // The arguments of the attestation requests. + Signature[] signatures; // The ECDSA signatures data. Please note that the signatures are assumed to be signed with increasing nonces. + address attester; // The attesting account. + uint64 deadline; // The deadline of the signature/request. +} + +/// @notice A struct representing the arguments of the revocation request. +struct RevocationRequestData { + bytes32 uid; // The UID of the attestation to revoke. + uint256 value; // An explicit ETH amount to send to the resolver. This is important to prevent accidental user errors. +} + +/// @notice A struct representing the full arguments of the revocation request. +struct RevocationRequest { + bytes32 schema; // The unique identifier of the schema. + RevocationRequestData data; // The arguments of the revocation request. +} + +/// @notice A struct representing the arguments of the full delegated revocation request. +struct DelegatedRevocationRequest { + bytes32 schema; // The unique identifier of the schema. + RevocationRequestData data; // The arguments of the revocation request. + Signature signature; // The ECDSA signature data. + address revoker; // The revoking account. + uint64 deadline; // The deadline of the signature/request. +} + +/// @notice A struct representing the full arguments of the multi revocation request. +struct MultiRevocationRequest { + bytes32 schema; // The unique identifier of the schema. + RevocationRequestData[] data; // The arguments of the revocation request. +} + +/// @notice A struct representing the full arguments of the delegated multi revocation request. +struct MultiDelegatedRevocationRequest { + bytes32 schema; // The unique identifier of the schema. + RevocationRequestData[] data; // The arguments of the revocation requests. + Signature[] signatures; // The ECDSA signatures data. Please note that the signatures are assumed to be signed with increasing nonces. + address revoker; // The revoking account. + uint64 deadline; // The deadline of the signature/request. +} + +/// @title IEAS +/// @notice EAS - Ethereum Attestation Service interface. +interface IEAS is ISemver { + /// @notice Emitted when an attestation has been made. + /// @param recipient The recipient of the attestation. + /// @param attester The attesting account. + /// @param uid The UID of the new attestation. + /// @param schemaUID The UID of the schema. + event Attested(address indexed recipient, address indexed attester, bytes32 uid, bytes32 indexed schemaUID); + + /// @notice Emitted when an attestation has been revoked. + /// @param recipient The recipient of the attestation. + /// @param attester The attesting account. + /// @param schemaUID The UID of the schema. + /// @param uid The UID the revoked attestation. + event Revoked(address indexed recipient, address indexed attester, bytes32 uid, bytes32 indexed schemaUID); + + /// @notice Emitted when a data has been timestamped. + /// @param data The data. + /// @param timestamp The timestamp. + event Timestamped(bytes32 indexed data, uint64 indexed timestamp); + + /// @notice Emitted when a data has been revoked. + /// @param revoker The address of the revoker. + /// @param data The data. + /// @param timestamp The timestamp. + event RevokedOffchain(address indexed revoker, bytes32 indexed data, uint64 indexed timestamp); + + /// @notice Returns the address of the global schema registry. + /// @return The address of the global schema registry. + function getSchemaRegistry() external view returns (ISchemaRegistry); + + /// @notice Attests to a specific schema. + /// @param request The arguments of the attestation request. + /// @return The UID of the new attestation. + /// + /// Example: + /// attest({ + /// schema: "0facc36681cbe2456019c1b0d1e7bedd6d1d40f6f324bf3dd3a4cef2999200a0", + /// data: { + /// recipient: "0xdEADBeAFdeAdbEafdeadbeafDeAdbEAFdeadbeaf", + /// expirationTime: 0, + /// revocable: true, + /// refUID: "0x0000000000000000000000000000000000000000000000000000000000000000", + /// data: "0xF00D", + /// value: 0 + /// } + /// }) + function attest(AttestationRequest calldata request) external payable returns (bytes32); + + /// @notice Attests to a specific schema via the provided ECDSA signature. + /// @param delegatedRequest The arguments of the delegated attestation request. + /// @return The UID of the new attestation. + /// + /// Example: + /// attestByDelegation({ + /// schema: '0x8e72f5bc0a8d4be6aa98360baa889040c50a0e51f32dbf0baa5199bd93472ebc', + /// data: { + /// recipient: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266', + /// expirationTime: 1673891048, + /// revocable: true, + /// refUID: '0x0000000000000000000000000000000000000000000000000000000000000000', + /// data: '0x1234', + /// value: 0 + /// }, + /// signature: { + /// v: 28, + /// r: '0x148c...b25b', + /// s: '0x5a72...be22' + /// }, + /// attester: '0xc5E8740aD971409492b1A63Db8d83025e0Fc427e', + /// deadline: 1673891048 + /// }) + function attestByDelegation( + DelegatedAttestationRequest calldata delegatedRequest + ) external payable returns (bytes32); + + /// @notice Attests to multiple schemas. + /// @param multiRequests The arguments of the multi attestation requests. The requests should be grouped by distinct + /// schema ids to benefit from the best batching optimization. + /// @return The UIDs of the new attestations. + /// + /// Example: + /// multiAttest([{ + /// schema: '0x33e9094830a5cba5554d1954310e4fbed2ef5f859ec1404619adea4207f391fd', + /// data: [{ + /// recipient: '0xdEADBeAFdeAdbEafdeadbeafDeAdbEAFdeadbeaf', + /// expirationTime: 1673891048, + /// revocable: true, + /// refUID: '0x0000000000000000000000000000000000000000000000000000000000000000', + /// data: '0x1234', + /// value: 1000 + /// }, + /// { + /// recipient: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266', + /// expirationTime: 0, + /// revocable: false, + /// refUID: '0x480df4a039efc31b11bfdf491b383ca138b6bde160988222a2a3509c02cee174', + /// data: '0x00', + /// value: 0 + /// }], + /// }, + /// { + /// schema: '0x5ac273ce41e3c8bfa383efe7c03e54c5f0bff29c9f11ef6ffa930fc84ca32425', + /// data: [{ + /// recipient: '0xdEADBeAFdeAdbEafdeadbeafDeAdbEAFdeadbeaf', + /// expirationTime: 0, + /// revocable: true, + /// refUID: '0x75bf2ed8dca25a8190c50c52db136664de25b2449535839008ccfdab469b214f', + /// data: '0x12345678', + /// value: 0 + /// }, + /// }]) + function multiAttest(MultiAttestationRequest[] calldata multiRequests) external payable returns (bytes32[] memory); + + /// @notice Attests to multiple schemas using via provided ECDSA signatures. + /// @param multiDelegatedRequests The arguments of the delegated multi attestation requests. The requests should be + /// grouped by distinct schema ids to benefit from the best batching optimization. + /// @return The UIDs of the new attestations. + /// + /// Example: + /// multiAttestByDelegation([{ + /// schema: '0x8e72f5bc0a8d4be6aa98360baa889040c50a0e51f32dbf0baa5199bd93472ebc', + /// data: [{ + /// recipient: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266', + /// expirationTime: 1673891048, + /// revocable: true, + /// refUID: '0x0000000000000000000000000000000000000000000000000000000000000000', + /// data: '0x1234', + /// value: 0 + /// }, + /// { + /// recipient: '0xdEADBeAFdeAdbEafdeadbeafDeAdbEAFdeadbeaf', + /// expirationTime: 0, + /// revocable: false, + /// refUID: '0x0000000000000000000000000000000000000000000000000000000000000000', + /// data: '0x00', + /// value: 0 + /// }], + /// signatures: [{ + /// v: 28, + /// r: '0x148c...b25b', + /// s: '0x5a72...be22' + /// }, + /// { + /// v: 28, + /// r: '0x487s...67bb', + /// s: '0x12ad...2366' + /// }], + /// attester: '0x1D86495b2A7B524D747d2839b3C645Bed32e8CF4', + /// deadline: 1673891048 + /// }]) + function multiAttestByDelegation( + MultiDelegatedAttestationRequest[] calldata multiDelegatedRequests + ) external payable returns (bytes32[] memory); + + /// @notice Revokes an existing attestation to a specific schema. + /// @param request The arguments of the revocation request. + /// + /// Example: + /// revoke({ + /// schema: '0x8e72f5bc0a8d4be6aa98360baa889040c50a0e51f32dbf0baa5199bd93472ebc', + /// data: { + /// uid: '0x101032e487642ee04ee17049f99a70590c735b8614079fc9275f9dd57c00966d', + /// value: 0 + /// } + /// }) + function revoke(RevocationRequest calldata request) external payable; + + /// @notice Revokes an existing attestation to a specific schema via the provided ECDSA signature. + /// @param delegatedRequest The arguments of the delegated revocation request. + /// + /// Example: + /// revokeByDelegation({ + /// schema: '0x8e72f5bc0a8d4be6aa98360baa889040c50a0e51f32dbf0baa5199bd93472ebc', + /// data: { + /// uid: '0xcbbc12102578c642a0f7b34fe7111e41afa25683b6cd7b5a14caf90fa14d24ba', + /// value: 0 + /// }, + /// signature: { + /// v: 27, + /// r: '0xb593...7142', + /// s: '0x0f5b...2cce' + /// }, + /// revoker: '0x244934dd3e31bE2c81f84ECf0b3E6329F5381992', + /// deadline: 1673891048 + /// }) + function revokeByDelegation(DelegatedRevocationRequest calldata delegatedRequest) external payable; + + /// @notice Revokes existing attestations to multiple schemas. + /// @param multiRequests The arguments of the multi revocation requests. The requests should be grouped by distinct + /// schema ids to benefit from the best batching optimization. + /// + /// Example: + /// multiRevoke([{ + /// schema: '0x8e72f5bc0a8d4be6aa98360baa889040c50a0e51f32dbf0baa5199bd93472ebc', + /// data: [{ + /// uid: '0x211296a1ca0d7f9f2cfebf0daaa575bea9b20e968d81aef4e743d699c6ac4b25', + /// value: 1000 + /// }, + /// { + /// uid: '0xe160ac1bd3606a287b4d53d5d1d6da5895f65b4b4bab6d93aaf5046e48167ade', + /// value: 0 + /// }], + /// }, + /// { + /// schema: '0x5ac273ce41e3c8bfa383efe7c03e54c5f0bff29c9f11ef6ffa930fc84ca32425', + /// data: [{ + /// uid: '0x053d42abce1fd7c8fcddfae21845ad34dae287b2c326220b03ba241bc5a8f019', + /// value: 0 + /// }, + /// }]) + function multiRevoke(MultiRevocationRequest[] calldata multiRequests) external payable; + + /// @notice Revokes existing attestations to multiple schemas via provided ECDSA signatures. + /// @param multiDelegatedRequests The arguments of the delegated multi revocation attestation requests. The requests + /// should be grouped by distinct schema ids to benefit from the best batching optimization. + /// + /// Example: + /// multiRevokeByDelegation([{ + /// schema: '0x8e72f5bc0a8d4be6aa98360baa889040c50a0e51f32dbf0baa5199bd93472ebc', + /// data: [{ + /// uid: '0x211296a1ca0d7f9f2cfebf0daaa575bea9b20e968d81aef4e743d699c6ac4b25', + /// value: 1000 + /// }, + /// { + /// uid: '0xe160ac1bd3606a287b4d53d5d1d6da5895f65b4b4bab6d93aaf5046e48167ade', + /// value: 0 + /// }], + /// signatures: [{ + /// v: 28, + /// r: '0x148c...b25b', + /// s: '0x5a72...be22' + /// }, + /// { + /// v: 28, + /// r: '0x487s...67bb', + /// s: '0x12ad...2366' + /// }], + /// revoker: '0x244934dd3e31bE2c81f84ECf0b3E6329F5381992', + /// deadline: 1673891048 + /// }]) + function multiRevokeByDelegation( + MultiDelegatedRevocationRequest[] calldata multiDelegatedRequests + ) external payable; + + /// @notice Timestamps the specified bytes32 data. + /// @param data The data to timestamp. + /// @return The timestamp the data was timestamped with. + function timestamp(bytes32 data) external returns (uint64); + + /// @notice Timestamps the specified multiple bytes32 data. + /// @param data The data to timestamp. + /// @return The timestamp the data was timestamped with. + function multiTimestamp(bytes32[] calldata data) external returns (uint64); + + /// @notice Revokes the specified bytes32 data. + /// @param data The data to timestamp. + /// @return The timestamp the data was revoked with. + function revokeOffchain(bytes32 data) external returns (uint64); + + /// @notice Revokes the specified multiple bytes32 data. + /// @param data The data to timestamp. + /// @return The timestamp the data was revoked with. + function multiRevokeOffchain(bytes32[] calldata data) external returns (uint64); + + /// @notice Returns an existing attestation by UID. + /// @param uid The UID of the attestation to retrieve. + /// @return The attestation data members. + function getAttestation(bytes32 uid) external view returns (Attestation memory); + + /// @notice Checks whether an attestation exists. + /// @param uid The UID of the attestation to retrieve. + /// @return Whether an attestation exists. + function isAttestationValid(bytes32 uid) external view returns (bool); + + /// @notice Returns the timestamp that the specified data was timestamped with. + /// @param data The data to query. + /// @return The timestamp the data was timestamped with. + function getTimestamp(bytes32 data) external view returns (uint64); + + /// @notice Returns the timestamp that the specified data was timestamped with. + /// @param data The data to query. + /// @return The timestamp the data was timestamped with. + function getRevokeOffchain(address revoker, bytes32 data) external view returns (uint64); +} \ No newline at end of file diff --git a/data_entry/EAS/ISchemaRegistry.sol b/data_entry/EAS/ISchemaRegistry.sol new file mode 100644 index 0000000..756caf2 --- /dev/null +++ b/data_entry/EAS/ISchemaRegistry.sol @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +import { ISemver } from "./ISemver.sol"; + +import { ISchemaResolver } from "./ISchemaResolver.sol"; + +/// @notice A struct representing a record for a submitted schema. +struct SchemaRecord { + bytes32 uid; // The unique identifier of the schema. + ISchemaResolver resolver; // Optional schema resolver. + bool revocable; // Whether the schema allows revocations explicitly. + string schema; // Custom specification of the schema (e.g., an ABI). +} + +/// @title ISchemaRegistry +/// @notice The interface of global attestation schemas for the Ethereum Attestation Service protocol. +interface ISchemaRegistry is ISemver { + /// @notice Emitted when a new schema has been registered + /// @param uid The schema UID. + /// @param registerer The address of the account used to register the schema. + /// @param schema The schema data. + event Registered(bytes32 indexed uid, address indexed registerer, SchemaRecord schema); + + /// @notice Submits and reserves a new schema + /// @param schema The schema data schema. + /// @param resolver An optional schema resolver. + /// @param revocable Whether the schema allows revocations explicitly. + /// @return The UID of the new schema. + function register(string calldata schema, ISchemaResolver resolver, bool revocable) external returns (bytes32); + + /// @notice Returns an existing schema by UID + /// @param uid The UID of the schema to retrieve. + /// @return The schema data members. + function getSchema(bytes32 uid) external view returns (SchemaRecord memory); +} \ No newline at end of file diff --git a/data_entry/EAS/ISchemaResolver.sol b/data_entry/EAS/ISchemaResolver.sol new file mode 100644 index 0000000..8358899 --- /dev/null +++ b/data_entry/EAS/ISchemaResolver.sol @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +import { Attestation } from "./Common.sol"; +import { ISemver } from "./ISemver.sol"; + +/// @title ISchemaResolver +/// @notice The interface of an optional schema resolver. +interface ISchemaResolver is ISemver { + /// @notice Checks if the resolver can be sent ETH. + /// @return Whether the resolver supports ETH transfers. + function isPayable() external pure returns (bool); + + /// @notice Processes an attestation and verifies whether it's valid. + /// @param attestation The new attestation. + /// @return Whether the attestation is valid. + function attest(Attestation calldata attestation) external payable returns (bool); + + /// @notice Processes multiple attestations and verifies whether they are valid. + /// @param attestations The new attestations. + /// @param values Explicit ETH amounts which were sent with each attestation. + /// @return Whether all the attestations are valid. + function multiAttest( + Attestation[] calldata attestations, + uint256[] calldata values + ) external payable returns (bool); + + /// @notice Processes an attestation revocation and verifies if it can be revoked. + /// @param attestation The existing attestation to be revoked. + /// @return Whether the attestation can be revoked. + function revoke(Attestation calldata attestation) external payable returns (bool); + + /// @notice Processes revocation of multiple attestation and verifies they can be revoked. + /// @param attestations The existing attestations to be revoked. + /// @param values Explicit ETH amounts which were sent with each revocation. + /// @return Whether the attestations can be revoked. + function multiRevoke( + Attestation[] calldata attestations, + uint256[] calldata values + ) external payable returns (bool); +} \ No newline at end of file diff --git a/data_entry/EAS/ISemver.sol b/data_entry/EAS/ISemver.sol new file mode 100644 index 0000000..8dadb22 --- /dev/null +++ b/data_entry/EAS/ISemver.sol @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +/// @title ISemver +/// @notice A semver interface. +interface ISemver { + /// @notice Returns the full semver contract version. + /// @return Semver contract version as a string. + function version() external view returns (string memory); +} \ No newline at end of file diff --git a/data_entry/EAS/SchemaResolver.sol b/data_entry/EAS/SchemaResolver.sol new file mode 100644 index 0000000..5b5c94c --- /dev/null +++ b/data_entry/EAS/SchemaResolver.sol @@ -0,0 +1,156 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.4; + +import { AccessDenied, InvalidEAS, InvalidLength, uncheckedInc } from "./Common.sol"; +import { IEAS, Attestation } from "./IEAS.sol"; +import { Semver } from "./Semver.sol"; +import { ISchemaResolver } from "./ISchemaResolver.sol"; + +/// @title SchemaResolver +/// @notice The base schema resolver contract. +abstract contract SchemaResolver is ISchemaResolver, Semver { + error InsufficientValue(); + error NotPayable(); + + // The global EAS contract. + IEAS internal immutable _eas; + + /// @dev Creates a new resolver. + /// @param eas The address of the global EAS contract. + constructor(IEAS eas) Semver(1, 3, 0) { + if (address(eas) == address(0)) { + revert InvalidEAS(); + } + + _eas = eas; + } + + /// @dev Ensures that only the EAS contract can make this call. + modifier onlyEAS() { + _onlyEAS(); + + _; + } + + /// @inheritdoc ISchemaResolver + function isPayable() public pure virtual returns (bool) { + return false; + } + + /// @dev ETH callback. + receive() external payable virtual { + if (!isPayable()) { + revert NotPayable(); + } + } + + /// @inheritdoc ISchemaResolver + function attest(Attestation calldata attestation) external payable onlyEAS returns (bool) { + return onAttest(attestation, msg.value); + } + + /// @inheritdoc ISchemaResolver + function multiAttest( + Attestation[] calldata attestations, + uint256[] calldata values + ) external payable onlyEAS returns (bool) { + uint256 length = attestations.length; + if (length != values.length) { + revert InvalidLength(); + } + + // We are keeping track of the remaining ETH amount that can be sent to resolvers and will keep deducting + // from it to verify that there isn't any attempt to send too much ETH to resolvers. Please note that unless + // some ETH was stuck in the contract by accident (which shouldn't happen in normal conditions), it won't be + // possible to send too much ETH anyway. + uint256 remainingValue = msg.value; + + for (uint256 i = 0; i < length; i = uncheckedInc(i)) { + // Ensure that the attester/revoker doesn't try to spend more than available. + uint256 value = values[i]; + if (value > remainingValue) { + revert InsufficientValue(); + } + + // Forward the attestation to the underlying resolver and return false in case it isn't approved. + if (!onAttest(attestations[i], value)) { + return false; + } + + unchecked { + // Subtract the ETH amount, that was provided to this attestation, from the global remaining ETH amount. + remainingValue -= value; + } + } + + return true; + } + + /// @inheritdoc ISchemaResolver + function revoke(Attestation calldata attestation) external payable onlyEAS returns (bool) { + return onRevoke(attestation, msg.value); + } + + /// @inheritdoc ISchemaResolver + function multiRevoke( + Attestation[] calldata attestations, + uint256[] calldata values + ) external payable onlyEAS returns (bool) { + uint256 length = attestations.length; + if (length != values.length) { + revert InvalidLength(); + } + + // We are keeping track of the remaining ETH amount that can be sent to resolvers and will keep deducting + // from it to verify that there isn't any attempt to send too much ETH to resolvers. Please note that unless + // some ETH was stuck in the contract by accident (which shouldn't happen in normal conditions), it won't be + // possible to send too much ETH anyway. + uint256 remainingValue = msg.value; + + for (uint256 i = 0; i < length; i = uncheckedInc(i)) { + // Ensure that the attester/revoker doesn't try to spend more than available. + uint256 value = values[i]; + if (value > remainingValue) { + revert InsufficientValue(); + } + + // Forward the revocation to the underlying resolver and return false in case it isn't approved. + if (!onRevoke(attestations[i], value)) { + return false; + } + + unchecked { + // Subtract the ETH amount, that was provided to this attestation, from the global remaining ETH amount. + remainingValue -= value; + } + } + + return true; + } + + /// @notice A resolver callback that should be implemented by child contracts. + /// @param attestation The new attestation. + /// @param value An explicit ETH amount that was sent to the resolver. Please note that this value is verified in + /// both attest() and multiAttest() callbacks EAS-only callbacks and that in case of multi attestations, it'll + /// usually hold that msg.value != value, since msg.value aggregated the sent ETH amounts for all the + /// attestations in the batch. + /// @return Whether the attestation is valid. + function onAttest(Attestation calldata attestation, uint256 value) internal virtual returns (bool); + + /// @notice Processes an attestation revocation and verifies if it can be revoked. + /// @param attestation The existing attestation to be revoked. + /// @param value An explicit ETH amount that was sent to the resolver. Please note that this value is verified in + /// both revoke() and multiRevoke() callbacks EAS-only callbacks and that in case of multi attestations, it'll + /// usually hold that msg.value != value, since msg.value aggregated the sent ETH amounts for all the + /// attestations in the batch. + /// @return Whether the attestation can be revoked. + function onRevoke(Attestation calldata attestation, uint256 value) internal virtual returns (bool); + + /// @dev Ensures that only the EAS contract can make this call. + function _onlyEAS() private view { + if (msg.sender != address(_eas)) { + revert AccessDenied(); + } + } +} \ No newline at end of file diff --git a/data_entry/EAS/Semver.sol b/data_entry/EAS/Semver.sol new file mode 100644 index 0000000..28b2a23 --- /dev/null +++ b/data_entry/EAS/Semver.sol @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.4; + +import { Strings } from "@openzeppelin/contracts/utils/Strings.sol"; + +import { ISemver } from "./ISemver.sol"; + +/// @title Semver +/// @notice A simple contract for managing contract versions. +contract Semver is ISemver { + // Contract's major version number. + uint256 private immutable _major; + + // Contract's minor version number. + uint256 private immutable _minor; + + // Contract's patch version number. + uint256 private immutable _patch; + + /// @dev Create a new Semver instance. + /// @param major Major version number. + /// @param minor Minor version number. + /// @param patch Patch version number. + constructor(uint256 major, uint256 minor, uint256 patch) { + _major = major; + _minor = minor; + _patch = patch; + } + + /// @notice Returns the full semver contract version. + /// @return Semver contract version as a string. + function version() external view returns (string memory) { + return + string( + abi.encodePacked(Strings.toString(_major), ".", Strings.toString(_minor), ".", Strings.toString(_patch)) + ); + } +} \ No newline at end of file diff --git a/data_entry/EAS/artifacts/ISchemaResolver.json b/data_entry/EAS/artifacts/ISchemaResolver.json new file mode 100644 index 0000000..4f6fdbc --- /dev/null +++ b/data_entry/EAS/artifacts/ISchemaResolver.json @@ -0,0 +1,382 @@ +{ + "deploy": { + "VM:-": { + "linkReferences": {}, + "autoDeployLib": true + }, + "main:1": { + "linkReferences": {}, + "autoDeployLib": true + }, + "ropsten:3": { + "linkReferences": {}, + "autoDeployLib": true + }, + "rinkeby:4": { + "linkReferences": {}, + "autoDeployLib": true + }, + "kovan:42": { + "linkReferences": {}, + "autoDeployLib": true + }, + "goerli:5": { + "linkReferences": {}, + "autoDeployLib": true + }, + "Custom": { + "linkReferences": {}, + "autoDeployLib": true + } + }, + "data": { + "bytecode": { + "functionDebugData": {}, + "generatedSources": [], + "linkReferences": {}, + "object": "", + "opcodes": "", + "sourceMap": "" + }, + "deployedBytecode": { + "functionDebugData": {}, + "generatedSources": [], + "immutableReferences": {}, + "linkReferences": {}, + "object": "", + "opcodes": "", + "sourceMap": "" + }, + "gasEstimates": null, + "methodIdentifiers": { + "attest((bytes32,bytes32,uint64,uint64,uint64,bytes32,address,address,bool,bytes))": "e60c3505", + "isPayable()": "ce46e046", + "multiAttest((bytes32,bytes32,uint64,uint64,uint64,bytes32,address,address,bool,bytes)[],uint256[])": "91db0b7e", + "multiRevoke((bytes32,bytes32,uint64,uint64,uint64,bytes32,address,address,bool,bytes)[],uint256[])": "88e5b2d9", + "revoke((bytes32,bytes32,uint64,uint64,uint64,bytes32,address,address,bool,bytes))": "e49617e1", + "version()": "54fd4d50" + } + }, + "abi": [ + { + "inputs": [ + { + "components": [ + { + "internalType": "bytes32", + "name": "uid", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "schema", + "type": "bytes32" + }, + { + "internalType": "uint64", + "name": "time", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "expirationTime", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "revocationTime", + "type": "uint64" + }, + { + "internalType": "bytes32", + "name": "refUID", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "address", + "name": "attester", + "type": "address" + }, + { + "internalType": "bool", + "name": "revocable", + "type": "bool" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "internalType": "struct Attestation", + "name": "attestation", + "type": "tuple" + } + ], + "name": "attest", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [], + "name": "isPayable", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "bytes32", + "name": "uid", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "schema", + "type": "bytes32" + }, + { + "internalType": "uint64", + "name": "time", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "expirationTime", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "revocationTime", + "type": "uint64" + }, + { + "internalType": "bytes32", + "name": "refUID", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "address", + "name": "attester", + "type": "address" + }, + { + "internalType": "bool", + "name": "revocable", + "type": "bool" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "internalType": "struct Attestation[]", + "name": "attestations", + "type": "tuple[]" + }, + { + "internalType": "uint256[]", + "name": "values", + "type": "uint256[]" + } + ], + "name": "multiAttest", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "bytes32", + "name": "uid", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "schema", + "type": "bytes32" + }, + { + "internalType": "uint64", + "name": "time", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "expirationTime", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "revocationTime", + "type": "uint64" + }, + { + "internalType": "bytes32", + "name": "refUID", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "address", + "name": "attester", + "type": "address" + }, + { + "internalType": "bool", + "name": "revocable", + "type": "bool" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "internalType": "struct Attestation[]", + "name": "attestations", + "type": "tuple[]" + }, + { + "internalType": "uint256[]", + "name": "values", + "type": "uint256[]" + } + ], + "name": "multiRevoke", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "bytes32", + "name": "uid", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "schema", + "type": "bytes32" + }, + { + "internalType": "uint64", + "name": "time", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "expirationTime", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "revocationTime", + "type": "uint64" + }, + { + "internalType": "bytes32", + "name": "refUID", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "address", + "name": "attester", + "type": "address" + }, + { + "internalType": "bool", + "name": "revocable", + "type": "bool" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "internalType": "struct Attestation", + "name": "attestation", + "type": "tuple" + } + ], + "name": "revoke", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [], + "name": "version", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + } + ] +} \ No newline at end of file diff --git a/data_entry/EAS/artifacts/ISchemaResolver_metadata.json b/data_entry/EAS/artifacts/ISchemaResolver_metadata.json new file mode 100644 index 0000000..4cfad5b --- /dev/null +++ b/data_entry/EAS/artifacts/ISchemaResolver_metadata.json @@ -0,0 +1,448 @@ +{ + "compiler": { + "version": "0.8.27+commit.40a35a09" + }, + "language": "Solidity", + "output": { + "abi": [ + { + "inputs": [ + { + "components": [ + { + "internalType": "bytes32", + "name": "uid", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "schema", + "type": "bytes32" + }, + { + "internalType": "uint64", + "name": "time", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "expirationTime", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "revocationTime", + "type": "uint64" + }, + { + "internalType": "bytes32", + "name": "refUID", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "address", + "name": "attester", + "type": "address" + }, + { + "internalType": "bool", + "name": "revocable", + "type": "bool" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "internalType": "struct Attestation", + "name": "attestation", + "type": "tuple" + } + ], + "name": "attest", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [], + "name": "isPayable", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "bytes32", + "name": "uid", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "schema", + "type": "bytes32" + }, + { + "internalType": "uint64", + "name": "time", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "expirationTime", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "revocationTime", + "type": "uint64" + }, + { + "internalType": "bytes32", + "name": "refUID", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "address", + "name": "attester", + "type": "address" + }, + { + "internalType": "bool", + "name": "revocable", + "type": "bool" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "internalType": "struct Attestation[]", + "name": "attestations", + "type": "tuple[]" + }, + { + "internalType": "uint256[]", + "name": "values", + "type": "uint256[]" + } + ], + "name": "multiAttest", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "bytes32", + "name": "uid", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "schema", + "type": "bytes32" + }, + { + "internalType": "uint64", + "name": "time", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "expirationTime", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "revocationTime", + "type": "uint64" + }, + { + "internalType": "bytes32", + "name": "refUID", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "address", + "name": "attester", + "type": "address" + }, + { + "internalType": "bool", + "name": "revocable", + "type": "bool" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "internalType": "struct Attestation[]", + "name": "attestations", + "type": "tuple[]" + }, + { + "internalType": "uint256[]", + "name": "values", + "type": "uint256[]" + } + ], + "name": "multiRevoke", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "bytes32", + "name": "uid", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "schema", + "type": "bytes32" + }, + { + "internalType": "uint64", + "name": "time", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "expirationTime", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "revocationTime", + "type": "uint64" + }, + { + "internalType": "bytes32", + "name": "refUID", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "address", + "name": "attester", + "type": "address" + }, + { + "internalType": "bool", + "name": "revocable", + "type": "bool" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "internalType": "struct Attestation", + "name": "attestation", + "type": "tuple" + } + ], + "name": "revoke", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [], + "name": "version", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + } + ], + "devdoc": { + "kind": "dev", + "methods": { + "attest((bytes32,bytes32,uint64,uint64,uint64,bytes32,address,address,bool,bytes))": { + "params": { + "attestation": "The new attestation." + }, + "returns": { + "_0": "Whether the attestation is valid." + } + }, + "isPayable()": { + "returns": { + "_0": "Whether the resolver supports ETH transfers." + } + }, + "multiAttest((bytes32,bytes32,uint64,uint64,uint64,bytes32,address,address,bool,bytes)[],uint256[])": { + "params": { + "attestations": "The new attestations.", + "values": "Explicit ETH amounts which were sent with each attestation." + }, + "returns": { + "_0": "Whether all the attestations are valid." + } + }, + "multiRevoke((bytes32,bytes32,uint64,uint64,uint64,bytes32,address,address,bool,bytes)[],uint256[])": { + "params": { + "attestations": "The existing attestations to be revoked.", + "values": "Explicit ETH amounts which were sent with each revocation." + }, + "returns": { + "_0": "Whether the attestations can be revoked." + } + }, + "revoke((bytes32,bytes32,uint64,uint64,uint64,bytes32,address,address,bool,bytes))": { + "params": { + "attestation": "The existing attestation to be revoked." + }, + "returns": { + "_0": "Whether the attestation can be revoked." + } + }, + "version()": { + "returns": { + "_0": "Semver contract version as a string." + } + } + }, + "title": "ISchemaResolver", + "version": 1 + }, + "userdoc": { + "kind": "user", + "methods": { + "attest((bytes32,bytes32,uint64,uint64,uint64,bytes32,address,address,bool,bytes))": { + "notice": "Processes an attestation and verifies whether it's valid." + }, + "isPayable()": { + "notice": "Checks if the resolver can be sent ETH." + }, + "multiAttest((bytes32,bytes32,uint64,uint64,uint64,bytes32,address,address,bool,bytes)[],uint256[])": { + "notice": "Processes multiple attestations and verifies whether they are valid." + }, + "multiRevoke((bytes32,bytes32,uint64,uint64,uint64,bytes32,address,address,bool,bytes)[],uint256[])": { + "notice": "Processes revocation of multiple attestation and verifies they can be revoked." + }, + "revoke((bytes32,bytes32,uint64,uint64,uint64,bytes32,address,address,bool,bytes))": { + "notice": "Processes an attestation revocation and verifies if it can be revoked." + }, + "version()": { + "notice": "Returns the full semver contract version." + } + }, + "notice": "The interface of an optional schema resolver.", + "version": 1 + } + }, + "settings": { + "compilationTarget": { + "EAS/ISchemaResolver.sol": "ISchemaResolver" + }, + "evmVersion": "cancun", + "libraries": {}, + "metadata": { + "bytecodeHash": "ipfs" + }, + "optimizer": { + "enabled": false, + "runs": 200 + }, + "remappings": [] + }, + "sources": { + "EAS/Common.sol": { + "keccak256": "0x9007deeeb757cfd4c504887f0c82b49f43fe92de13f4ffd9400f457d8c5276c6", + "license": "MIT", + "urls": [ + "bzz-raw://949de4842085918b42d71c50f837b468e77871fc1417f480d2a6618f75eeb57d", + "dweb:/ipfs/QmbyYxXLvryZBHdNKijCmZzwsgU4bnTXszyA38F5Xdg5h1" + ] + }, + "EAS/ISchemaResolver.sol": { + "keccak256": "0x4df78a9ec7244d6f75d58b01c223e865316e16a071eb9d36d28adddcb784d787", + "license": "MIT", + "urls": [ + "bzz-raw://0399b3b62b3f95f12a6f09c397249dbf7f517fc6a2a0bc1be1cd0cc962d85888", + "dweb:/ipfs/QmYozmYWNnntXQaiVigovQRJuVoxHg78btYxZrLwMDQfx8" + ] + }, + "EAS/ISemver.sol": { + "keccak256": "0x6c6073e407395170dfc58d74be5e4cba5d2f6680d2c9aeade07e591dd7c6d5bf", + "license": "MIT", + "urls": [ + "bzz-raw://40f57462c6e329fcf5960e75e9f9a405429f416fec1debcfd4c79c2f10c04dd4", + "dweb:/ipfs/QmehZy36QoFsBKMMbCo5Jmta5y3uU3so4e4C14MHzx2VDp" + ] + } + }, + "version": 1 +} \ No newline at end of file diff --git a/data_entry/EAS/artifacts/build-info/b3243e0e0ce278cb57851439b44f680d.json b/data_entry/EAS/artifacts/build-info/b3243e0e0ce278cb57851439b44f680d.json new file mode 100644 index 0000000..2b2a1d5 --- /dev/null +++ b/data_entry/EAS/artifacts/build-info/b3243e0e0ce278cb57851439b44f680d.json @@ -0,0 +1,841 @@ +{ + "id": "b3243e0e0ce278cb57851439b44f680d", + "_format": "hh-sol-build-info-1", + "solcVersion": "0.8.27", + "solcLongVersion": "0.8.27+commit.40a35a09", + "input": { + "language": "Solidity", + "sources": { + "EAS/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}" + } + }, + "settings": { + "optimizer": { + "enabled": false, + "runs": 200 + }, + "outputSelection": { + "*": { + "": [ + "ast" + ], + "*": [ + "abi", + "metadata", + "devdoc", + "userdoc", + "storageLayout", + "evm.legacyAssembly", + "evm.bytecode", + "evm.deployedBytecode", + "evm.methodIdentifiers", + "evm.gasEstimates", + "evm.assembly" + ] + } + }, + "remappings": [] + } + }, + "output": { + "sources": { + "EAS/Common.sol": { + "ast": { + "absolutePath": "EAS/Common.sol", + "exportedSymbols": { + "AccessDenied": [ + 9 + ], + "Attestation": [ + 49 + ], + "DeadlineExpired": [ + 11 + ], + "EMPTY_UID": [ + 4 + ], + "InvalidEAS": [ + 13 + ], + "InvalidLength": [ + 15 + ], + "InvalidSignature": [ + 17 + ], + "NO_EXPIRATION_TIME": [ + 7 + ], + "NotFound": [ + 19 + ], + "Signature": [ + 27 + ], + "uncheckedInc": [ + 65 + ] + }, + "id": 66, + "license": "MIT", + "nodeType": "SourceUnit", + "nodes": [ + { + "id": 1, + "literals": [ + "solidity", + "^", + "0.8", + ".0" + ], + "nodeType": "PragmaDirective", + "src": "33:23:0" + }, + { + "constant": true, + "id": 4, + "mutability": "constant", + "name": "EMPTY_UID", + "nameLocation": "126:9:0", + "nodeType": "VariableDeclaration", + "scope": 66, + "src": "109:30:0", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + }, + "typeName": { + "id": 2, + "name": "bytes32", + "nodeType": "ElementaryTypeName", + "src": "109:7:0", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + }, + "value": { + "hexValue": "30", + "id": 3, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "number", + "lValueRequested": false, + "nodeType": "Literal", + "src": "138:1:0", + "typeDescriptions": { + "typeIdentifier": "t_rational_0_by_1", + "typeString": "int_const 0" + }, + "value": "0" + }, + "visibility": "internal" + }, + { + "constant": true, + "id": 7, + "mutability": "constant", + "name": "NO_EXPIRATION_TIME", + "nameLocation": "219:18:0", + "nodeType": "VariableDeclaration", + "scope": 66, + "src": "203:38:0", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint64", + "typeString": "uint64" + }, + "typeName": { + "id": 5, + "name": "uint64", + "nodeType": "ElementaryTypeName", + "src": "203:6:0", + "typeDescriptions": { + "typeIdentifier": "t_uint64", + "typeString": "uint64" + } + }, + "value": { + "hexValue": "30", + "id": 6, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "number", + "lValueRequested": false, + "nodeType": "Literal", + "src": "240:1:0", + "typeDescriptions": { + "typeIdentifier": "t_rational_0_by_1", + "typeString": "int_const 0" + }, + "value": "0" + }, + "visibility": "internal" + }, + { + "errorSelector": "4ca88867", + "id": 9, + "name": "AccessDenied", + "nameLocation": "250:12:0", + "nodeType": "ErrorDefinition", + "parameters": { + "id": 8, + "nodeType": "ParameterList", + "parameters": [], + "src": "262:2:0" + }, + "src": "244:21:0" + }, + { + "errorSelector": "1ab7da6b", + "id": 11, + "name": "DeadlineExpired", + "nameLocation": "272:15:0", + "nodeType": "ErrorDefinition", + "parameters": { + "id": 10, + "nodeType": "ParameterList", + "parameters": [], + "src": "287:2:0" + }, + "src": "266:24:0" + }, + { + "errorSelector": "83780ffe", + "id": 13, + "name": "InvalidEAS", + "nameLocation": "297:10:0", + "nodeType": "ErrorDefinition", + "parameters": { + "id": 12, + "nodeType": "ParameterList", + "parameters": [], + "src": "307:2:0" + }, + "src": "291:19:0" + }, + { + "errorSelector": "947d5a84", + "id": 15, + "name": "InvalidLength", + "nameLocation": "317:13:0", + "nodeType": "ErrorDefinition", + "parameters": { + "id": 14, + "nodeType": "ParameterList", + "parameters": [], + "src": "330:2:0" + }, + "src": "311:22:0" + }, + { + "errorSelector": "8baa579f", + "id": 17, + "name": "InvalidSignature", + "nameLocation": "340:16:0", + "nodeType": "ErrorDefinition", + "parameters": { + "id": 16, + "nodeType": "ParameterList", + "parameters": [], + "src": "356:2:0" + }, + "src": "334:25:0" + }, + { + "errorSelector": "c5723b51", + "id": 19, + "name": "NotFound", + "nameLocation": "366:8:0", + "nodeType": "ErrorDefinition", + "parameters": { + "id": 18, + "nodeType": "ParameterList", + "parameters": [], + "src": "374:2:0" + }, + "src": "360:17:0" + }, + { + "canonicalName": "Signature", + "documentation": { + "id": 20, + "nodeType": "StructuredDocumentation", + "src": "379:56:0", + "text": "@notice A struct representing ECDSA signature data." + }, + "id": 27, + "members": [ + { + "constant": false, + "id": 22, + "mutability": "mutable", + "name": "v", + "nameLocation": "464:1:0", + "nodeType": "VariableDeclaration", + "scope": 27, + "src": "458:7:0", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint8", + "typeString": "uint8" + }, + "typeName": { + "id": 21, + "name": "uint8", + "nodeType": "ElementaryTypeName", + "src": "458:5:0", + "typeDescriptions": { + "typeIdentifier": "t_uint8", + "typeString": "uint8" + } + }, + "visibility": "internal" + }, + { + "constant": false, + "id": 24, + "mutability": "mutable", + "name": "r", + "nameLocation": "499:1:0", + "nodeType": "VariableDeclaration", + "scope": 27, + "src": "491:9:0", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + }, + "typeName": { + "id": 23, + "name": "bytes32", + "nodeType": "ElementaryTypeName", + "src": "491:7:0", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + }, + "visibility": "internal" + }, + { + "constant": false, + "id": 26, + "mutability": "mutable", + "name": "s", + "nameLocation": "550:1:0", + "nodeType": "VariableDeclaration", + "scope": 27, + "src": "542:9:0", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + }, + "typeName": { + "id": 25, + "name": "bytes32", + "nodeType": "ElementaryTypeName", + "src": "542:7:0", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + }, + "visibility": "internal" + } + ], + "name": "Signature", + "nameLocation": "442:9:0", + "nodeType": "StructDefinition", + "scope": 66, + "src": "435:142:0", + "visibility": "public" + }, + { + "canonicalName": "Attestation", + "documentation": { + "id": 28, + "nodeType": "StructuredDocumentation", + "src": "579:56:0", + "text": "@notice A struct representing a single attestation." + }, + "id": 49, + "members": [ + { + "constant": false, + "id": 30, + "mutability": "mutable", + "name": "uid", + "nameLocation": "668:3:0", + "nodeType": "VariableDeclaration", + "scope": 49, + "src": "660:11:0", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + }, + "typeName": { + "id": 29, + "name": "bytes32", + "nodeType": "ElementaryTypeName", + "src": "660:7:0", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + }, + "visibility": "internal" + }, + { + "constant": false, + "id": 32, + "mutability": "mutable", + "name": "schema", + "nameLocation": "728:6:0", + "nodeType": "VariableDeclaration", + "scope": 49, + "src": "720:14:0", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + }, + "typeName": { + "id": 31, + "name": "bytes32", + "nodeType": "ElementaryTypeName", + "src": "720:7:0", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + }, + "visibility": "internal" + }, + { + "constant": false, + "id": 34, + "mutability": "mutable", + "name": "time", + "nameLocation": "787:4:0", + "nodeType": "VariableDeclaration", + "scope": 49, + "src": "780:11:0", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint64", + "typeString": "uint64" + }, + "typeName": { + "id": 33, + "name": "uint64", + "nodeType": "ElementaryTypeName", + "src": "780:6:0", + "typeDescriptions": { + "typeIdentifier": "t_uint64", + "typeString": "uint64" + } + }, + "visibility": "internal" + }, + { + "constant": false, + "id": 36, + "mutability": "mutable", + "name": "expirationTime", + "nameLocation": "867:14:0", + "nodeType": "VariableDeclaration", + "scope": 49, + "src": "860:21:0", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint64", + "typeString": "uint64" + }, + "typeName": { + "id": 35, + "name": "uint64", + "nodeType": "ElementaryTypeName", + "src": "860:6:0", + "typeDescriptions": { + "typeIdentifier": "t_uint64", + "typeString": "uint64" + } + }, + "visibility": "internal" + }, + { + "constant": false, + "id": 38, + "mutability": "mutable", + "name": "revocationTime", + "nameLocation": "953:14:0", + "nodeType": "VariableDeclaration", + "scope": 49, + "src": "946:21:0", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint64", + "typeString": "uint64" + }, + "typeName": { + "id": 37, + "name": "uint64", + "nodeType": "ElementaryTypeName", + "src": "946:6:0", + "typeDescriptions": { + "typeIdentifier": "t_uint64", + "typeString": "uint64" + } + }, + "visibility": "internal" + }, + { + "constant": false, + "id": 40, + "mutability": "mutable", + "name": "refUID", + "nameLocation": "1044:6:0", + "nodeType": "VariableDeclaration", + "scope": 49, + "src": "1036:14:0", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + }, + "typeName": { + "id": 39, + "name": "bytes32", + "nodeType": "ElementaryTypeName", + "src": "1036:7:0", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + }, + "visibility": "internal" + }, + { + "constant": false, + "id": 42, + "mutability": "mutable", + "name": "recipient", + "nameLocation": "1103:9:0", + "nodeType": "VariableDeclaration", + "scope": 49, + "src": "1095:17:0", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 41, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "1095:7:0", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "visibility": "internal" + }, + { + "constant": false, + "id": 44, + "mutability": "mutable", + "name": "attester", + "nameLocation": "1163:8:0", + "nodeType": "VariableDeclaration", + "scope": 49, + "src": "1155:16:0", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 43, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "1155:7:0", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "visibility": "internal" + }, + { + "constant": false, + "id": 46, + "mutability": "mutable", + "name": "revocable", + "nameLocation": "1225:9:0", + "nodeType": "VariableDeclaration", + "scope": 49, + "src": "1220:14:0", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + "typeName": { + "id": 45, + "name": "bool", + "nodeType": "ElementaryTypeName", + "src": "1220:4:0", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "visibility": "internal" + }, + { + "constant": false, + "id": 48, + "mutability": "mutable", + "name": "data", + "nameLocation": "1287:4:0", + "nodeType": "VariableDeclaration", + "scope": 49, + "src": "1281:10:0", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_bytes_storage_ptr", + "typeString": "bytes" + }, + "typeName": { + "id": 47, + "name": "bytes", + "nodeType": "ElementaryTypeName", + "src": "1281:5:0", + "typeDescriptions": { + "typeIdentifier": "t_bytes_storage_ptr", + "typeString": "bytes" + } + }, + "visibility": "internal" + } + ], + "name": "Attestation", + "nameLocation": "642:11:0", + "nodeType": "StructDefinition", + "scope": 66, + "src": "635:687:0", + "visibility": "public" + }, + { + "body": { + "id": 64, + "nodeType": "Block", + "src": "1455:44:0", + "statements": [ + { + "id": 63, + "nodeType": "UncheckedBlock", + "src": "1461:36:0", + "statements": [ + { + "expression": { + "id": 61, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "id": 57, + "name": "j", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 55, + "src": "1481:1:0", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "commonType": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "id": 60, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftExpression": { + "id": 58, + "name": "i", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 52, + "src": "1485:1:0", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "nodeType": "BinaryOperation", + "operator": "+", + "rightExpression": { + "hexValue": "31", + "id": 59, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "number", + "lValueRequested": false, + "nodeType": "Literal", + "src": "1489:1:0", + "typeDescriptions": { + "typeIdentifier": "t_rational_1_by_1", + "typeString": "int_const 1" + }, + "value": "1" + }, + "src": "1485:5:0", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "src": "1481:9:0", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "id": 62, + "nodeType": "ExpressionStatement", + "src": "1481:9:0" + } + ] + } + ] + }, + "documentation": { + "id": 50, + "nodeType": "StructuredDocumentation", + "src": "1324:73:0", + "text": "@notice A helper function to work with unchecked iterators in loops." + }, + "id": 65, + "implemented": true, + "kind": "freeFunction", + "modifiers": [], + "name": "uncheckedInc", + "nameLocation": "1406:12:0", + "nodeType": "FunctionDefinition", + "parameters": { + "id": 53, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 52, + "mutability": "mutable", + "name": "i", + "nameLocation": "1427:1:0", + "nodeType": "VariableDeclaration", + "scope": 65, + "src": "1419:9:0", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 51, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "1419:7:0", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "visibility": "internal" + } + ], + "src": "1418:11:0" + }, + "returnParameters": { + "id": 56, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 55, + "mutability": "mutable", + "name": "j", + "nameLocation": "1452:1:0", + "nodeType": "VariableDeclaration", + "scope": 65, + "src": "1444:9:0", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 54, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "1444:7:0", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "visibility": "internal" + } + ], + "src": "1443:11:0" + }, + "scope": 66, + "src": "1397:102:0", + "stateMutability": "pure", + "virtual": false, + "visibility": "internal" + } + ], + "src": "33:1466:0" + }, + "id": 0 + } + } + } +} \ No newline at end of file diff --git a/data_entry/EAS/eip1271/EIP1271Verifier.sol b/data_entry/EAS/eip1271/EIP1271Verifier.sol new file mode 100644 index 0000000..8b21c54 --- /dev/null +++ b/data_entry/EAS/eip1271/EIP1271Verifier.sol @@ -0,0 +1,171 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.27; + +import { Address } from "@openzeppelin/contracts/utils/Address.sol"; +import { EIP712 } from "@openzeppelin/contracts/utils/cryptography/EIP712.sol"; +import { SignatureChecker } from "@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol"; +import { DeadlineExpired, NO_EXPIRATION_TIME, Signature, InvalidSignature } from "./../Common.sol"; + +// prettier-ignore +import { + AttestationRequestData, + DelegatedAttestationRequest, + DelegatedRevocationRequest, + RevocationRequestData +} from "../IEAS.sol"; + +/// @title EIP1271Verifier +/// @notice EIP1271Verifier typed signatures verifier for EAS delegated attestations. +abstract contract EIP1271Verifier is EIP712 { + using Address for address; + + error InvalidNonce(); + + // The hash of the data type used to relay calls to the attest function. It's the value of + // keccak256("Attest(address attester,bytes32 schema,address recipient,uint64 expirationTime,bool revocable,bytes32 refUID,bytes data,uint256 value,uint256 nonce,uint64 deadline)"). + bytes32 private constant ATTEST_TYPEHASH = 0xfeb2925a02bae3dae48d424a0437a2b6ac939aa9230ddc55a1a76f065d988076; + + // The hash of the data type used to relay calls to the revoke function. It's the value of + // keccak256("Revoke(address revoker,bytes32 schema,bytes32 uid,uint256 value,uint256 nonce,uint64 deadline)"). + bytes32 private constant REVOKE_TYPEHASH = 0xb5d556f07587ec0f08cf386545cc4362c702a001650c2058002615ee5c9d1e75; + + // The user readable name of the signing domain. + string private _name; + + // Replay protection nonces. + mapping(address attester => uint256 nonce) private _nonces; + + /// @notice Emitted when users invalidate nonces by increasing their nonces to (higher) new values. + /// @param oldNonce The previous nonce. + /// @param newNonce The new value. + event NonceIncreased(uint256 oldNonce, uint256 newNonce); + + /// @dev Creates a new EIP1271Verifier instance. + /// @param version The current major version of the signing domain + constructor(string memory name, string memory version) EIP712(name, version) { + _name = name; + } + + /// @notice Returns the domain separator used in the encoding of the signatures for attest, and revoke. + /// @return The domain separator used in the encoding of the signatures for attest, and revoke. + function getDomainSeparator() external view returns (bytes32) { + return _domainSeparatorV4(); + } + + /// @notice Returns the current nonce per-account. + /// @param account The requested account. + /// @return The current nonce. + function getNonce(address account) external view returns (uint256) { + return _nonces[account]; + } + + /// @notice Returns the EIP712 type hash for the attest function. + /// @return The EIP712 type hash for the attest function. + function getAttestTypeHash() external pure returns (bytes32) { + return ATTEST_TYPEHASH; + } + + /// @notice Returns the EIP712 type hash for the revoke function. + /// @return The EIP712 type hash for the revoke function. + function getRevokeTypeHash() external pure returns (bytes32) { + return REVOKE_TYPEHASH; + } + + /// @notice Returns the EIP712 name. + /// @return The EIP712 name. + function getName() external view returns (string memory) { + return _name; + } + + /// @notice Provides users an option to invalidate nonces by increasing their nonces to (higher) new values. + /// @param newNonce The (higher) new value. + function increaseNonce(uint256 newNonce) external { + uint256 oldNonce = _nonces[msg.sender]; + if (newNonce <= oldNonce) { + revert InvalidNonce(); + } + + _nonces[msg.sender] = newNonce; + + emit NonceIncreased({ oldNonce: oldNonce, newNonce: newNonce }); + } + + /// @dev Verifies delegated attestation request. + /// @param request The arguments of the delegated attestation request. + function _verifyAttest(DelegatedAttestationRequest memory request) internal { + if (request.deadline != NO_EXPIRATION_TIME && request.deadline < _time()) { + revert DeadlineExpired(); + } + + AttestationRequestData memory data = request.data; + Signature memory signature = request.signature; + + bytes32 hash = _hashTypedDataV4( + keccak256( + abi.encode( + ATTEST_TYPEHASH, + request.attester, + request.schema, + data.recipient, + data.expirationTime, + data.revocable, + data.refUID, + keccak256(data.data), + data.value, + _nonces[request.attester]++, + request.deadline + ) + ) + ); + if ( + !SignatureChecker.isValidSignatureNow( + request.attester, + hash, + abi.encodePacked(signature.r, signature.s, signature.v) + ) + ) { + revert InvalidSignature(); + } + } + + /// @dev Verifies delegated revocation request. + /// @param request The arguments of the delegated revocation request. + function _verifyRevoke(DelegatedRevocationRequest memory request) internal { + if (request.deadline != NO_EXPIRATION_TIME && request.deadline < _time()) { + revert DeadlineExpired(); + } + + RevocationRequestData memory data = request.data; + Signature memory signature = request.signature; + + bytes32 hash = _hashTypedDataV4( + keccak256( + abi.encode( + REVOKE_TYPEHASH, + request.revoker, + request.schema, + data.uid, + data.value, + _nonces[request.revoker]++, + request.deadline + ) + ) + ); + if ( + !SignatureChecker.isValidSignatureNow( + request.revoker, + hash, + abi.encodePacked(signature.r, signature.s, signature.v) + ) + ) { + revert InvalidSignature(); + } + } + + /// @dev Returns the current's block timestamp. This method is overridden during tests and used to simulate the + /// current block time. + function _time() internal view virtual returns (uint64) { + return uint64(block.timestamp); + } +} \ No newline at end of file diff --git a/data_entry/EAS_schema_versioning.yml b/data_entry/EAS_schema_versioning.yml new file mode 100644 index 0000000..d1b7f45 --- /dev/null +++ b/data_entry/EAS_schema_versioning.yml @@ -0,0 +1,35 @@ +version_history: + - version: "0.1.0" + date: "2024-12-05" + schemas: + - chain: "Optimism" + chain_id: "10" + schema_hash: "0xacb55ee4f0b0bf4987ba23f863e41e78ea1863e1c5aa881c520d16dae76a0c7b" + custom_resolver: "0xedfea228da3bdb0efd231707b2ee588224ee0886" + schema: | + { + "chain_id": "uint256", + "is_owner": "bool", + "is_eoa": "uint8", + "is_contract": "uint8", + "is_factory_contract": "uint8", + "is_proxy": "uint8", + "is_safe_contract": "uint8", + "name": "string", + "deployment_tx": "string", + "deployer_address": "address", + "owner_project": "string", + "deployment_date": "uint256", + "erc_type": "uint8[]", + "erc20_symbol": "string", + "erc20_decimals": "uint8", + "erc721_name": "string", + "erc721_symbol": "string", + "erc1155_name": "string", + "erc1155_symbol": "string", + "usage_category": "string", + "version": "uint8", + "audit": "string", + "contract_monitored": "string", + "source_code_verified": "string" + } diff --git a/data_entry/README.md b/data_entry/README.md new file mode 100644 index 0000000..2e21a9b --- /dev/null +++ b/data_entry/README.md @@ -0,0 +1,52 @@ +# OLI Data Entry / Contract Labeling + +** OLI DATA ENTRY IS CURRENTLY IN BETA - USE AT OWN RISK ** + +OLI Data Entry focuses specifically on the process of data entry. While the broader OLI framework defines how labeled addresses are stored, OLI Data Entry provides the tools and guidelines necessary for labeling EVM smart contracts as part of a collaborative community effort. + +Using the [Ethereum Attestation Service (EAS)](https://attest.org/), this repository provides schemas and workflow for creating and retrieving contract labels. The goal is to establish a single, decentralized point of data entry, ensuring an open and fair system where: + +- **Labelers** only need to label their contracts once, making their labels universally accessible to everyone. +- **Data Consumers** can reliably and transparently access this information without duplicating efforts. + +With OLI Data Entry, we address the labeling challenge by providing a straightforward, fair and universally accessible solution, enabling everyone to benefit from crowdsourced contract labels. + +## Workflow + +### 1. **Data Entry (Creating Attestations)** + +Users can label a contract by attesting to the [correct schema](data_entry/EAS_schema_versioning.yml): + +1. **Visit the Attestation Front-End:** +While our custom white label front end is under development, please use the EAS Attestation dApp [here](https://optimism.easscan.org/attestation/attestWithSchema/0xacb55ee4f0b0bf4987ba23f863e41e78ea1863e1c5aa881c520d16dae76a0c7b) and make sure to use the [newest schema](data_entry/EAS_schema_versioning.yml). + +2. **Connect Your Wallet:** +Ensure your wallet is connected and you are on the correct network. + +### 3. **Create an Attestation** + +- **Recipient**: Enter the address to be labeled. + +- **Chain ID**: Provide the chain ID corresponding to the address you want to label. + +- **Ownership Proof**: If you are the owner of the contract you wish to label, toggle this option to "True". Else use "False. + - This feature works only if you are on the chain where the contract is deployed. + - Your address must be the owner of the contract, verified using the OpenZeppelin Ownable extension. + +- **OLI-Compliant Labels**: Provide as much detail as possible for each label in the attestation. Leaving fields blank, especially if they are redundant, is acceptable. + + - **True/False Fields**: + For fields such as `is_eoa`, `is_contract`, `is_factory_contract`, `is_proxy`, and `is_safe_contract`, use the following values: + - `0`: No value. + - `1`: True. + - `2`: False. + + - Refer to [tag_definitions.yml](tags/tag_definitions.yml) for a detailed explanation of each tag. + +4. **Sign and Submit Onchain or Offchain:** + - Select your preferred submission method: onchain or offchain. + - Use your wallet to sign the attestation. + +### 2. **Data Retrieval** + +coming soon \ No newline at end of file diff --git a/data_entry/ownable-resolver/.prettierrc.json b/data_entry/ownable-resolver/.prettierrc.json new file mode 100644 index 0000000..b2a56f2 --- /dev/null +++ b/data_entry/ownable-resolver/.prettierrc.json @@ -0,0 +1,38 @@ +{ + "overrides": [ + { + "files": "*.sol", + "options": { + "printWidth": 80, + "tabWidth": 4, + "useTabs": false, + "singleQuote": false, + "bracketSpacing": false + } + }, + { + "files": "*.yml", + "options": {} + }, + { + "files": "*.yaml", + "options": {} + }, + { + "files": "*.toml", + "options": {} + }, + { + "files": "*.json", + "options": {} + }, + { + "files": "*.js", + "options": {} + }, + { + "files": "*.ts", + "options": {} + } + ] +} diff --git a/data_entry/ownable-resolver/resolver.sol b/data_entry/ownable-resolver/resolver.sol new file mode 100644 index 0000000..f98c4e9 --- /dev/null +++ b/data_entry/ownable-resolver/resolver.sol @@ -0,0 +1,84 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import { SchemaResolver } from "EAS/SchemaResolver.sol"; +import { IEAS, Attestation } from "EAS/IEAS.sol"; +import "@openzeppelin/contracts/access/Ownable.sol"; + +/** + * @title A schema resolver that checks whether the sender is the owner of the contract behind attestation.recipient. + */ +contract OwnerRecipientResolver is SchemaResolver { + + constructor(IEAS eas) SchemaResolver(eas) {} + + function onAttest(Attestation calldata attestation, uint256 /*value*/) internal view override returns (bool) { + // Return false if the attestation.recipient field is empty (zero address) + if (attestation.recipient == address(0)) { + return false; + } + + // First we check if a OwnableCheck should be performed + bool ownableCheck = extractOwnableCheckFromData(attestation.data); + + // If OwnableCheck false, allow attestation + if (!ownableCheck) { + return true; + } + + // First we make sure the attestation.recipient is a contract, else return false + if (attestation.recipient.code.length == 0) { + return false; + } + + // Second we make sure the input chain_id matches chainId + uint256 currentChainId; + assembly { + currentChainId := chainid() + } + + uint256 expectedChainId = extractChainIdFromData(attestation.data); + if (currentChainId != expectedChainId) { + return false; + } + + // Third we cast the recipient address to Ownable + Ownable ownableContract = Ownable(attestation.recipient); + + try ownableContract.owner() returns (address owner) { + bool isOwner = attestation.attester == owner; + return isOwner; + } catch { + return false; + } + } + + function extractChainIdFromData(bytes memory data) internal pure returns (uint256) { + require(data.length >= 32, "Data too short"); // Make sure we have enough data to read from position 3 + + // The chain ID is at the first 32-byte slot in the data + uint256 extractedValue; + assembly { + extractedValue := mload(add(data, 32)) + } + + return extractedValue; + } + + function extractOwnableCheckFromData(bytes memory data) internal pure returns (bool) { + require(data.length >= 64, "Data too short"); + + // Extract the OwnerCheck from position 2, but we're only interested in the last bit + uint256 extractedValue; + assembly { + extractedValue := mload(add(data, 64)) // Load the first 64 bytes + extractedValue := and(extractedValue, 0x1) // Mask only the last bit + } + + return extractedValue == 1; + } + + function onRevoke(Attestation calldata /*attestation*/, uint256 /*value*/) internal pure override returns (bool) { + return true; + } +} \ No newline at end of file diff --git a/tags/tag_definitions.yml b/tags/tag_definitions.yml index 2b30d57..287e841 100644 --- a/tags/tag_definitions.yml +++ b/tags/tag_definitions.yml @@ -1,4 +1,4 @@ -version: 1.0.0 +version: 0.1.0 tags: - tag_id: oli.name name: Name