github.com/ethereum-optimism/optimism@v1.7.2/packages/contracts-bedrock/src/EAS/EAS.sol (about)

     1  // SPDX-License-Identifier: MIT
     2  pragma solidity 0.8.19;
     3  
     4  import { Address } from "@openzeppelin/contracts/utils/Address.sol";
     5  import { ISemver } from "src/universal/ISemver.sol";
     6  import { Predeploys } from "src/libraries/Predeploys.sol";
     7  import { EIP1271Verifier } from "src/EAS/eip1271/EIP1271Verifier.sol";
     8  import { ISchemaResolver } from "src/EAS/resolver/ISchemaResolver.sol";
     9  
    10  import {
    11      AccessDenied,
    12      EMPTY_UID,
    13      Signature,
    14      InvalidLength,
    15      MAX_GAP,
    16      NotFound,
    17      NO_EXPIRATION_TIME,
    18      uncheckedInc
    19  } from "src/EAS/Common.sol";
    20  
    21  import {
    22      Attestation,
    23      AttestationRequest,
    24      AttestationRequestData,
    25      DelegatedAttestationRequest,
    26      DelegatedRevocationRequest,
    27      IEAS,
    28      MultiAttestationRequest,
    29      MultiDelegatedAttestationRequest,
    30      MultiDelegatedRevocationRequest,
    31      MultiRevocationRequest,
    32      RevocationRequest,
    33      RevocationRequestData
    34  } from "src/EAS/IEAS.sol";
    35  
    36  import { ISchemaRegistry, SchemaRecord } from "src/EAS/ISchemaRegistry.sol";
    37  
    38  struct AttestationsResult {
    39      uint256 usedValue; // Total ETH amount that was sent to resolvers.
    40      bytes32[] uids; // UIDs of the new attestations.
    41  }
    42  
    43  /// @custom:proxied
    44  /// @custom:predeploy 0x4200000000000000000000000000000000000021
    45  /// @title EAS
    46  /// @notice The Ethereum Attestation Service protocol.
    47  contract EAS is IEAS, ISemver, EIP1271Verifier {
    48      using Address for address payable;
    49  
    50      error AlreadyRevoked();
    51      error AlreadyRevokedOffchain();
    52      error AlreadyTimestamped();
    53      error InsufficientValue();
    54      error InvalidAttestation();
    55      error InvalidAttestations();
    56      error InvalidExpirationTime();
    57      error InvalidOffset();
    58      error InvalidRegistry();
    59      error InvalidRevocation();
    60      error InvalidRevocations();
    61      error InvalidSchema();
    62      error InvalidVerifier();
    63      error Irrevocable();
    64      error NotPayable();
    65      error WrongSchema();
    66  
    67      // The global schema registry.
    68      ISchemaRegistry private constant _schemaRegistry = ISchemaRegistry(Predeploys.SCHEMA_REGISTRY);
    69  
    70      // The global mapping between attestations and their UIDs.
    71      mapping(bytes32 uid => Attestation attestation) private _db;
    72  
    73      // The global mapping between data and their timestamps.
    74      mapping(bytes32 data => uint64 timestamp) private _timestamps;
    75  
    76      // The global mapping between data and their revocation timestamps.
    77      mapping(address revoker => mapping(bytes32 data => uint64 timestamp)) private _revocationsOffchain;
    78  
    79      // Upgrade forward-compatibility storage gap
    80      uint256[MAX_GAP - 3] private __gap;
    81  
    82      /// @notice Semantic version.
    83      /// @custom:semver 1.4.0
    84      string public constant version = "1.4.0";
    85  
    86      /// @dev Creates a new EAS instance.
    87      constructor() EIP1271Verifier("EAS", "1.3.0") { }
    88  
    89      /// @inheritdoc IEAS
    90      function getSchemaRegistry() external pure returns (ISchemaRegistry) {
    91          return _schemaRegistry;
    92      }
    93  
    94      /// @inheritdoc IEAS
    95      function attest(AttestationRequest calldata request) external payable returns (bytes32) {
    96          AttestationRequestData[] memory data = new AttestationRequestData[](1);
    97          data[0] = request.data;
    98  
    99          return _attest(request.schema, data, msg.sender, msg.value, true).uids[0];
   100      }
   101  
   102      /// @inheritdoc IEAS
   103      function attestByDelegation(DelegatedAttestationRequest calldata delegatedRequest)
   104          external
   105          payable
   106          returns (bytes32)
   107      {
   108          _verifyAttest(delegatedRequest);
   109  
   110          AttestationRequestData[] memory data = new AttestationRequestData[](1);
   111          data[0] = delegatedRequest.data;
   112          return _attest(delegatedRequest.schema, data, delegatedRequest.attester, msg.value, true).uids[0];
   113      }
   114  
   115      /// @inheritdoc IEAS
   116      function multiAttest(MultiAttestationRequest[] calldata multiRequests)
   117          external
   118          payable
   119          returns (bytes32[] memory)
   120      {
   121          // Since a multi-attest call is going to make multiple attestations for multiple schemas, we'd need to collect
   122          // all the returned UIDs into a single list.
   123          uint256 length = multiRequests.length;
   124          bytes32[][] memory totalUids = new bytes32[][](length);
   125          uint256 totalUidsCount = 0;
   126  
   127          // We are keeping track of the total available ETH amount that can be sent to resolvers and will keep deducting
   128          // from it to verify that there isn't any attempt to send too much ETH to resolvers. Please note that unless
   129          // some ETH was stuck in the contract by accident (which shouldn't happen in normal conditions), it won't be
   130          // possible to send too much ETH anyway.
   131          uint256 availableValue = msg.value;
   132  
   133          for (uint256 i = 0; i < length; i = uncheckedInc(i)) {
   134              // The last batch is handled slightly differently: if the total available ETH wasn't spent in full and there
   135              // is a remainder - it will be refunded back to the attester (something that we can only verify during the
   136              // last and final batch).
   137              bool last;
   138              unchecked {
   139                  last = i == length - 1;
   140              }
   141  
   142              // Process the current batch of attestations.
   143              MultiAttestationRequest calldata multiRequest = multiRequests[i];
   144  
   145              // Ensure that data isn't empty.
   146              if (multiRequest.data.length == 0) {
   147                  revert InvalidLength();
   148              }
   149  
   150              AttestationsResult memory res =
   151                  _attest(multiRequest.schema, multiRequest.data, msg.sender, availableValue, last);
   152  
   153              // Ensure to deduct the ETH that was forwarded to the resolver during the processing of this batch.
   154              availableValue -= res.usedValue;
   155  
   156              // Collect UIDs (and merge them later).
   157              totalUids[i] = res.uids;
   158              unchecked {
   159                  totalUidsCount += res.uids.length;
   160              }
   161          }
   162  
   163          // Merge all the collected UIDs and return them as a flatten array.
   164          return _mergeUIDs(totalUids, totalUidsCount);
   165      }
   166  
   167      /// @inheritdoc IEAS
   168      function multiAttestByDelegation(MultiDelegatedAttestationRequest[] calldata multiDelegatedRequests)
   169          external
   170          payable
   171          returns (bytes32[] memory)
   172      {
   173          // Since a multi-attest call is going to make multiple attestations for multiple schemas, we'd need to collect
   174          // all the returned UIDs into a single list.
   175          uint256 length = multiDelegatedRequests.length;
   176          bytes32[][] memory totalUids = new bytes32[][](length);
   177          uint256 totalUidsCount = 0;
   178  
   179          // We are keeping track of the total available ETH amount that can be sent to resolvers and will keep deducting
   180          // from it to verify that there isn't any attempt to send too much ETH to resolvers. Please note that unless
   181          // some ETH was stuck in the contract by accident (which shouldn't happen in normal conditions), it won't be
   182          // possible to send too much ETH anyway.
   183          uint256 availableValue = msg.value;
   184  
   185          for (uint256 i = 0; i < length; i = uncheckedInc(i)) {
   186              // The last batch is handled slightly differently: if the total available ETH wasn't spent in full and there
   187              // is a remainder - it will be refunded back to the attester (something that we can only verify during the
   188              // last and final batch).
   189              bool last;
   190              unchecked {
   191                  last = i == length - 1;
   192              }
   193  
   194              MultiDelegatedAttestationRequest calldata multiDelegatedRequest = multiDelegatedRequests[i];
   195              AttestationRequestData[] calldata data = multiDelegatedRequest.data;
   196  
   197              // Ensure that no inputs are missing.
   198              uint256 dataLength = data.length;
   199              if (dataLength == 0 || dataLength != multiDelegatedRequest.signatures.length) {
   200                  revert InvalidLength();
   201              }
   202  
   203              // Verify signatures. Please note that the signatures are assumed to be signed with increasing nonces.
   204              for (uint256 j = 0; j < dataLength; j = uncheckedInc(j)) {
   205                  _verifyAttest(
   206                      DelegatedAttestationRequest({
   207                          schema: multiDelegatedRequest.schema,
   208                          data: data[j],
   209                          signature: multiDelegatedRequest.signatures[j],
   210                          attester: multiDelegatedRequest.attester,
   211                          deadline: multiDelegatedRequest.deadline
   212                      })
   213                  );
   214              }
   215  
   216              // Process the current batch of attestations.
   217              AttestationsResult memory res =
   218                  _attest(multiDelegatedRequest.schema, data, multiDelegatedRequest.attester, availableValue, last);
   219  
   220              // Ensure to deduct the ETH that was forwarded to the resolver during the processing of this batch.
   221              availableValue -= res.usedValue;
   222  
   223              // Collect UIDs (and merge them later).
   224              totalUids[i] = res.uids;
   225              unchecked {
   226                  totalUidsCount += res.uids.length;
   227              }
   228          }
   229  
   230          // Merge all the collected UIDs and return them as a flatten array.
   231          return _mergeUIDs(totalUids, totalUidsCount);
   232      }
   233  
   234      /// @inheritdoc IEAS
   235      function revoke(RevocationRequest calldata request) external payable {
   236          RevocationRequestData[] memory data = new RevocationRequestData[](1);
   237          data[0] = request.data;
   238  
   239          _revoke(request.schema, data, msg.sender, msg.value, true);
   240      }
   241  
   242      /// @inheritdoc IEAS
   243      function revokeByDelegation(DelegatedRevocationRequest calldata delegatedRequest) external payable {
   244          _verifyRevoke(delegatedRequest);
   245  
   246          RevocationRequestData[] memory data = new RevocationRequestData[](1);
   247          data[0] = delegatedRequest.data;
   248  
   249          _revoke(delegatedRequest.schema, data, delegatedRequest.revoker, msg.value, true);
   250      }
   251  
   252      /// @inheritdoc IEAS
   253      function multiRevoke(MultiRevocationRequest[] calldata multiRequests) external payable {
   254          // We are keeping track of the total available ETH amount that can be sent to resolvers and will keep deducting
   255          // from it to verify that there isn't any attempt to send too much ETH to resolvers. Please note that unless
   256          // some ETH was stuck in the contract by accident (which shouldn't happen in normal conditions), it won't be
   257          // possible to send too much ETH anyway.
   258          uint256 availableValue = msg.value;
   259  
   260          uint256 length = multiRequests.length;
   261          for (uint256 i = 0; i < length; i = uncheckedInc(i)) {
   262              // The last batch is handled slightly differently: if the total available ETH wasn't spent in full and there
   263              // is a remainder - it will be refunded back to the attester (something that we can only verify during the
   264              // last and final batch).
   265              bool last;
   266              unchecked {
   267                  last = i == length - 1;
   268              }
   269  
   270              MultiRevocationRequest calldata multiRequest = multiRequests[i];
   271  
   272              // Ensure to deduct the ETH that was forwarded to the resolver during the processing of this batch.
   273              availableValue -= _revoke(multiRequest.schema, multiRequest.data, msg.sender, availableValue, last);
   274          }
   275      }
   276  
   277      /// @inheritdoc IEAS
   278      function multiRevokeByDelegation(MultiDelegatedRevocationRequest[] calldata multiDelegatedRequests)
   279          external
   280          payable
   281      {
   282          // We are keeping track of the total available ETH amount that can be sent to resolvers and will keep deducting
   283          // from it to verify that there isn't any attempt to send too much ETH to resolvers. Please note that unless
   284          // some ETH was stuck in the contract by accident (which shouldn't happen in normal conditions), it won't be
   285          // possible to send too much ETH anyway.
   286          uint256 availableValue = msg.value;
   287  
   288          uint256 length = multiDelegatedRequests.length;
   289          for (uint256 i = 0; i < length; i = uncheckedInc(i)) {
   290              // The last batch is handled slightly differently: if the total available ETH wasn't spent in full and there
   291              // is a remainder - it will be refunded back to the attester (something that we can only verify during the
   292              // last and final batch).
   293              bool last;
   294              unchecked {
   295                  last = i == length - 1;
   296              }
   297  
   298              MultiDelegatedRevocationRequest memory multiDelegatedRequest = multiDelegatedRequests[i];
   299              RevocationRequestData[] memory data = multiDelegatedRequest.data;
   300  
   301              // Ensure that no inputs are missing.
   302              uint256 dataLength = data.length;
   303              if (dataLength == 0 || dataLength != multiDelegatedRequest.signatures.length) {
   304                  revert InvalidLength();
   305              }
   306  
   307              // Verify signatures. Please note that the signatures are assumed to be signed with increasing nonces.
   308              for (uint256 j = 0; j < dataLength; j = uncheckedInc(j)) {
   309                  _verifyRevoke(
   310                      DelegatedRevocationRequest({
   311                          schema: multiDelegatedRequest.schema,
   312                          data: data[j],
   313                          signature: multiDelegatedRequest.signatures[j],
   314                          revoker: multiDelegatedRequest.revoker,
   315                          deadline: multiDelegatedRequest.deadline
   316                      })
   317                  );
   318              }
   319  
   320              // Ensure to deduct the ETH that was forwarded to the resolver during the processing of this batch.
   321              availableValue -=
   322                  _revoke(multiDelegatedRequest.schema, data, multiDelegatedRequest.revoker, availableValue, last);
   323          }
   324      }
   325  
   326      /// @inheritdoc IEAS
   327      function timestamp(bytes32 data) external returns (uint64) {
   328          uint64 time = _time();
   329          _timestamp(data, time);
   330          return time;
   331      }
   332  
   333      /// @inheritdoc IEAS
   334      function revokeOffchain(bytes32 data) external returns (uint64) {
   335          uint64 time = _time();
   336          _revokeOffchain(msg.sender, data, time);
   337          return time;
   338      }
   339  
   340      /// @inheritdoc IEAS
   341      function multiRevokeOffchain(bytes32[] calldata data) external returns (uint64) {
   342          uint64 time = _time();
   343  
   344          uint256 length = data.length;
   345          for (uint256 i = 0; i < length; i = uncheckedInc(i)) {
   346              _revokeOffchain(msg.sender, data[i], time);
   347          }
   348  
   349          return time;
   350      }
   351  
   352      /// @inheritdoc IEAS
   353      function multiTimestamp(bytes32[] calldata data) external returns (uint64) {
   354          uint64 time = _time();
   355  
   356          uint256 length = data.length;
   357          for (uint256 i = 0; i < length; i = uncheckedInc(i)) {
   358              _timestamp(data[i], time);
   359          }
   360  
   361          return time;
   362      }
   363  
   364      /// @inheritdoc IEAS
   365      function getAttestation(bytes32 uid) external view returns (Attestation memory) {
   366          return _db[uid];
   367      }
   368  
   369      /// @inheritdoc IEAS
   370      function isAttestationValid(bytes32 uid) public view returns (bool) {
   371          return _db[uid].uid != EMPTY_UID;
   372      }
   373  
   374      /// @inheritdoc IEAS
   375      function getTimestamp(bytes32 data) external view returns (uint64) {
   376          return _timestamps[data];
   377      }
   378  
   379      /// @inheritdoc IEAS
   380      function getRevokeOffchain(address revoker, bytes32 data) external view returns (uint64) {
   381          return _revocationsOffchain[revoker][data];
   382      }
   383  
   384      /// @dev Attests to a specific schema.
   385      /// @param schemaUID The unique identifier of the schema to attest to.
   386      /// @param data The arguments of the attestation requests.
   387      /// @param attester The attesting account.
   388      /// @param availableValue The total available ETH amount that can be sent to the resolver.
   389      /// @param last Whether this is the last attestations/revocations set.
   390      /// @return The UID of the new attestations and the total sent ETH amount.
   391      function _attest(
   392          bytes32 schemaUID,
   393          AttestationRequestData[] memory data,
   394          address attester,
   395          uint256 availableValue,
   396          bool last
   397      )
   398          private
   399          returns (AttestationsResult memory)
   400      {
   401          uint256 length = data.length;
   402  
   403          AttestationsResult memory res;
   404          res.uids = new bytes32[](length);
   405  
   406          // Ensure that we aren't attempting to attest to a non-existing schema.
   407          SchemaRecord memory schemaRecord = _schemaRegistry.getSchema(schemaUID);
   408          if (schemaRecord.uid == EMPTY_UID) {
   409              revert InvalidSchema();
   410          }
   411  
   412          Attestation[] memory attestations = new Attestation[](length);
   413          uint256[] memory values = new uint256[](length);
   414  
   415          for (uint256 i = 0; i < length; i = uncheckedInc(i)) {
   416              AttestationRequestData memory request = data[i];
   417  
   418              // Ensure that either no expiration time was set or that it was set in the future.
   419              if (request.expirationTime != NO_EXPIRATION_TIME && request.expirationTime <= _time()) {
   420                  revert InvalidExpirationTime();
   421              }
   422  
   423              // Ensure that we aren't trying to make a revocable attestation for a non-revocable schema.
   424              if (!schemaRecord.revocable && request.revocable) {
   425                  revert Irrevocable();
   426              }
   427  
   428              Attestation memory attestation = Attestation({
   429                  uid: EMPTY_UID,
   430                  schema: schemaUID,
   431                  refUID: request.refUID,
   432                  time: _time(),
   433                  expirationTime: request.expirationTime,
   434                  revocationTime: 0,
   435                  recipient: request.recipient,
   436                  attester: attester,
   437                  revocable: request.revocable,
   438                  data: request.data
   439              });
   440  
   441              // Look for the first non-existing UID (and use a bump seed/nonce in the rare case of a conflict).
   442              bytes32 uid;
   443              uint32 bump = 0;
   444              while (true) {
   445                  uid = _getUID(attestation, bump);
   446                  if (_db[uid].uid == EMPTY_UID) {
   447                      break;
   448                  }
   449  
   450                  unchecked {
   451                      ++bump;
   452                  }
   453              }
   454              attestation.uid = uid;
   455  
   456              _db[uid] = attestation;
   457  
   458              if (request.refUID != EMPTY_UID) {
   459                  // Ensure that we aren't trying to attest to a non-existing referenced UID.
   460                  if (!isAttestationValid(request.refUID)) {
   461                      revert NotFound();
   462                  }
   463              }
   464  
   465              attestations[i] = attestation;
   466              values[i] = request.value;
   467  
   468              res.uids[i] = uid;
   469  
   470              emit Attested(request.recipient, attester, uid, schemaUID);
   471          }
   472  
   473          res.usedValue = _resolveAttestations(schemaRecord, attestations, values, false, availableValue, last);
   474  
   475          return res;
   476      }
   477  
   478      /// @dev Revokes an existing attestation to a specific schema.
   479      /// @param schemaUID The unique identifier of the schema to attest to.
   480      /// @param data The arguments of the revocation requests.
   481      /// @param revoker The revoking account.
   482      /// @param availableValue The total available ETH amount that can be sent to the resolver.
   483      /// @param last Whether this is the last attestations/revocations set.
   484      /// @return Returns the total sent ETH amount.
   485      function _revoke(
   486          bytes32 schemaUID,
   487          RevocationRequestData[] memory data,
   488          address revoker,
   489          uint256 availableValue,
   490          bool last
   491      )
   492          private
   493          returns (uint256)
   494      {
   495          // Ensure that a non-existing schema ID wasn't passed by accident.
   496          SchemaRecord memory schemaRecord = _schemaRegistry.getSchema(schemaUID);
   497          if (schemaRecord.uid == EMPTY_UID) {
   498              revert InvalidSchema();
   499          }
   500  
   501          uint256 length = data.length;
   502          Attestation[] memory attestations = new Attestation[](length);
   503          uint256[] memory values = new uint256[](length);
   504  
   505          for (uint256 i = 0; i < length; i = uncheckedInc(i)) {
   506              RevocationRequestData memory request = data[i];
   507  
   508              Attestation storage attestation = _db[request.uid];
   509  
   510              // Ensure that we aren't attempting to revoke a non-existing attestation.
   511              if (attestation.uid == EMPTY_UID) {
   512                  revert NotFound();
   513              }
   514  
   515              // Ensure that a wrong schema ID wasn't passed by accident.
   516              if (attestation.schema != schemaUID) {
   517                  revert InvalidSchema();
   518              }
   519  
   520              // Allow only original attesters to revoke their attestations.
   521              if (attestation.attester != revoker) {
   522                  revert AccessDenied();
   523              }
   524  
   525              // Please note that also checking of the schema itself is revocable is unnecessary, since it's not possible
   526              // to
   527              // make revocable attestations to an irrevocable schema.
   528              if (!attestation.revocable) {
   529                  revert Irrevocable();
   530              }
   531  
   532              // Ensure that we aren't trying to revoke the same attestation twice.
   533              if (attestation.revocationTime != 0) {
   534                  revert AlreadyRevoked();
   535              }
   536              attestation.revocationTime = _time();
   537  
   538              attestations[i] = attestation;
   539              values[i] = request.value;
   540  
   541              emit Revoked(attestations[i].recipient, revoker, request.uid, schemaUID);
   542          }
   543  
   544          return _resolveAttestations(schemaRecord, attestations, values, true, availableValue, last);
   545      }
   546  
   547      /// @dev Resolves a new attestation or a revocation of an existing attestation.
   548      /// @param schemaRecord The schema of the attestation.
   549      /// @param attestation The data of the attestation to make/revoke.
   550      /// @param value An explicit ETH amount to send to the resolver.
   551      /// @param isRevocation Whether to resolve an attestation or its revocation.
   552      /// @param availableValue The total available ETH amount that can be sent to the resolver.
   553      /// @param last Whether this is the last attestations/revocations set.
   554      /// @return Returns the total sent ETH amount.
   555      function _resolveAttestation(
   556          SchemaRecord memory schemaRecord,
   557          Attestation memory attestation,
   558          uint256 value,
   559          bool isRevocation,
   560          uint256 availableValue,
   561          bool last
   562      )
   563          private
   564          returns (uint256)
   565      {
   566          ISchemaResolver resolver = schemaRecord.resolver;
   567          if (address(resolver) == address(0)) {
   568              // Ensure that we don't accept payments if there is no resolver.
   569              if (value != 0) {
   570                  revert NotPayable();
   571              }
   572  
   573              if (last) {
   574                  _refund(availableValue);
   575              }
   576  
   577              return 0;
   578          }
   579  
   580          // Ensure that we don't accept payments which can't be forwarded to the resolver.
   581          if (value != 0) {
   582              if (!resolver.isPayable()) {
   583                  revert NotPayable();
   584              }
   585  
   586              // Ensure that the attester/revoker doesn't try to spend more than available.
   587              if (value > availableValue) {
   588                  revert InsufficientValue();
   589              }
   590  
   591              // Ensure to deduct the sent value explicitly.
   592              unchecked {
   593                  availableValue -= value;
   594              }
   595          }
   596  
   597          if (isRevocation) {
   598              if (!resolver.revoke{ value: value }(attestation)) {
   599                  revert InvalidRevocation();
   600              }
   601          } else if (!resolver.attest{ value: value }(attestation)) {
   602              revert InvalidAttestation();
   603          }
   604  
   605          if (last) {
   606              _refund(availableValue);
   607          }
   608  
   609          return value;
   610      }
   611  
   612      /// @dev Resolves multiple attestations or revocations of existing attestations.
   613      /// @param schemaRecord The schema of the attestation.
   614      /// @param attestations The data of the attestations to make/revoke.
   615      /// @param values Explicit ETH amounts to send to the resolver.
   616      /// @param isRevocation Whether to resolve an attestation or its revocation.
   617      /// @param availableValue The total available ETH amount that can be sent to the resolver.
   618      /// @param last Whether this is the last attestations/revocations set.
   619      /// @return Returns the total sent ETH amount.
   620      function _resolveAttestations(
   621          SchemaRecord memory schemaRecord,
   622          Attestation[] memory attestations,
   623          uint256[] memory values,
   624          bool isRevocation,
   625          uint256 availableValue,
   626          bool last
   627      )
   628          private
   629          returns (uint256)
   630      {
   631          uint256 length = attestations.length;
   632          if (length == 1) {
   633              return _resolveAttestation(schemaRecord, attestations[0], values[0], isRevocation, availableValue, last);
   634          }
   635  
   636          ISchemaResolver resolver = schemaRecord.resolver;
   637          if (address(resolver) == address(0)) {
   638              // Ensure that we don't accept payments if there is no resolver.
   639              for (uint256 i = 0; i < length; i = uncheckedInc(i)) {
   640                  if (values[i] != 0) {
   641                      revert NotPayable();
   642                  }
   643              }
   644  
   645              if (last) {
   646                  _refund(availableValue);
   647              }
   648  
   649              return 0;
   650          }
   651  
   652          uint256 totalUsedValue = 0;
   653          bool isResolverPayable = resolver.isPayable();
   654  
   655          for (uint256 i = 0; i < length; i = uncheckedInc(i)) {
   656              uint256 value = values[i];
   657  
   658              // Ensure that we don't accept payments which can't be forwarded to the resolver.
   659              if (value == 0) {
   660                  continue;
   661              }
   662  
   663              if (!isResolverPayable) {
   664                  revert NotPayable();
   665              }
   666  
   667              // Ensure that the attester/revoker doesn't try to spend more than available.
   668              if (value > availableValue) {
   669                  revert InsufficientValue();
   670              }
   671  
   672              // Ensure to deduct the sent value explicitly and add it to the total used value by the batch.
   673              unchecked {
   674                  availableValue -= value;
   675                  totalUsedValue += value;
   676              }
   677          }
   678  
   679          if (isRevocation) {
   680              if (!resolver.multiRevoke{ value: totalUsedValue }(attestations, values)) {
   681                  revert InvalidRevocations();
   682              }
   683          } else if (!resolver.multiAttest{ value: totalUsedValue }(attestations, values)) {
   684              revert InvalidAttestations();
   685          }
   686  
   687          if (last) {
   688              _refund(availableValue);
   689          }
   690  
   691          return totalUsedValue;
   692      }
   693  
   694      /// @dev Calculates a UID for a given attestation.
   695      /// @param attestation The input attestation.
   696      /// @param bump A bump value to use in case of a UID conflict.
   697      /// @return Attestation UID.
   698      function _getUID(Attestation memory attestation, uint32 bump) private pure returns (bytes32) {
   699          return keccak256(
   700              abi.encodePacked(
   701                  attestation.schema,
   702                  attestation.recipient,
   703                  attestation.attester,
   704                  attestation.time,
   705                  attestation.expirationTime,
   706                  attestation.revocable,
   707                  attestation.refUID,
   708                  attestation.data,
   709                  bump
   710              )
   711          );
   712      }
   713  
   714      /// @dev Refunds remaining ETH amount to the attester.
   715      /// @param remainingValue The remaining ETH amount that was not sent to the resolver.
   716      function _refund(uint256 remainingValue) private {
   717          if (remainingValue > 0) {
   718              // Using a regular transfer here might revert, for some non-EOA attesters, due to exceeding of the 2300
   719              // gas limit which is why we're using call instead (via sendValue), which the 2300 gas limit does not
   720              // apply for.
   721              payable(msg.sender).sendValue(remainingValue);
   722          }
   723      }
   724  
   725      /// @dev Timestamps the specified bytes32 data.
   726      /// @param data The data to timestamp.
   727      /// @param time The timestamp.
   728      function _timestamp(bytes32 data, uint64 time) private {
   729          if (_timestamps[data] != 0) {
   730              revert AlreadyTimestamped();
   731          }
   732  
   733          _timestamps[data] = time;
   734  
   735          emit Timestamped(data, time);
   736      }
   737  
   738      /// @dev Revokes the specified bytes32 data.
   739      /// @param revoker The revoking account.
   740      /// @param data The data to revoke.
   741      /// @param time The timestamp the data was revoked with.
   742      function _revokeOffchain(address revoker, bytes32 data, uint64 time) private {
   743          mapping(bytes32 data => uint64 timestamp) storage revocations = _revocationsOffchain[revoker];
   744  
   745          if (revocations[data] != 0) {
   746              revert AlreadyRevokedOffchain();
   747          }
   748  
   749          revocations[data] = time;
   750  
   751          emit RevokedOffchain(revoker, data, time);
   752      }
   753  
   754      /// @dev Merges lists of UIDs.
   755      /// @param uidLists The provided lists of UIDs.
   756      /// @param uidsCount Total UIDs count.
   757      /// @return A merged and flatten list of all the UIDs.
   758      function _mergeUIDs(bytes32[][] memory uidLists, uint256 uidsCount) private pure returns (bytes32[] memory) {
   759          bytes32[] memory uids = new bytes32[](uidsCount);
   760  
   761          uint256 currentIndex = 0;
   762          uint256 uidListLength = uidLists.length;
   763          for (uint256 i = 0; i < uidListLength; i = uncheckedInc(i)) {
   764              bytes32[] memory currentUids = uidLists[i];
   765              uint256 currentUidsLength = currentUids.length;
   766              for (uint256 j = 0; j < currentUidsLength; j = uncheckedInc(j)) {
   767                  uids[currentIndex] = currentUids[j];
   768  
   769                  unchecked {
   770                      ++currentIndex;
   771                  }
   772              }
   773          }
   774  
   775          return uids;
   776      }
   777  }