github.com/ethereum-optimism/optimism@v1.7.2/packages/contracts-bedrock/src/EAS/resolver/SchemaResolver.sol (about) 1 // SPDX-License-Identifier: MIT 2 pragma solidity 0.8.19; 3 4 import { IEAS, Attestation } from "src/EAS/IEAS.sol"; 5 import { AccessDenied, InvalidEAS, InvalidLength, uncheckedInc } from "src/EAS/Common.sol"; 6 7 import { ISchemaResolver } from "src/EAS/resolver/ISchemaResolver.sol"; 8 9 /// @title SchemaResolver 10 /// @notice The base schema resolver contract. 11 abstract contract SchemaResolver is ISchemaResolver { 12 error InsufficientValue(); 13 error NotPayable(); 14 15 // The global EAS contract. 16 IEAS internal immutable _eas; 17 18 /// @dev Creates a new resolver. 19 /// @param eas The address of the global EAS contract. 20 constructor(IEAS eas) { 21 if (address(eas) == address(0)) { 22 revert InvalidEAS(); 23 } 24 25 _eas = eas; 26 } 27 28 /// @dev Ensures that only the EAS contract can make this call. 29 modifier onlyEAS() { 30 _onlyEAS(); 31 32 _; 33 } 34 35 /// @inheritdoc ISchemaResolver 36 function isPayable() public pure virtual returns (bool) { 37 return false; 38 } 39 40 /// @dev ETH callback. 41 receive() external payable virtual { 42 if (!isPayable()) { 43 revert NotPayable(); 44 } 45 } 46 47 /// @inheritdoc ISchemaResolver 48 function attest(Attestation calldata attestation) external payable onlyEAS returns (bool) { 49 return onAttest(attestation, msg.value); 50 } 51 52 /// @inheritdoc ISchemaResolver 53 function multiAttest( 54 Attestation[] calldata attestations, 55 uint256[] calldata values 56 ) 57 external 58 payable 59 onlyEAS 60 returns (bool) 61 { 62 uint256 length = attestations.length; 63 if (length != values.length) { 64 revert InvalidLength(); 65 } 66 67 // We are keeping track of the remaining ETH amount that can be sent to resolvers and will keep deducting 68 // from it to verify that there isn't any attempt to send too much ETH to resolvers. Please note that unless 69 // some ETH was stuck in the contract by accident (which shouldn't happen in normal conditions), it won't be 70 // possible to send too much ETH anyway. 71 uint256 remainingValue = msg.value; 72 73 for (uint256 i = 0; i < length; i = uncheckedInc(i)) { 74 // Ensure that the attester/revoker doesn't try to spend more than available. 75 uint256 value = values[i]; 76 if (value > remainingValue) { 77 revert InsufficientValue(); 78 } 79 80 // Forward the attestation to the underlying resolver and return false in case it isn't approved. 81 if (!onAttest(attestations[i], value)) { 82 return false; 83 } 84 85 unchecked { 86 // Subtract the ETH amount, that was provided to this attestation, from the global remaining ETH amount. 87 remainingValue -= value; 88 } 89 } 90 91 return true; 92 } 93 94 /// @inheritdoc ISchemaResolver 95 function revoke(Attestation calldata attestation) external payable onlyEAS returns (bool) { 96 return onRevoke(attestation, msg.value); 97 } 98 99 /// @inheritdoc ISchemaResolver 100 function multiRevoke( 101 Attestation[] calldata attestations, 102 uint256[] calldata values 103 ) 104 external 105 payable 106 onlyEAS 107 returns (bool) 108 { 109 uint256 length = attestations.length; 110 if (length != values.length) { 111 revert InvalidLength(); 112 } 113 114 // We are keeping track of the remaining ETH amount that can be sent to resolvers and will keep deducting 115 // from it to verify that there isn't any attempt to send too much ETH to resolvers. Please note that unless 116 // some ETH was stuck in the contract by accident (which shouldn't happen in normal conditions), it won't be 117 // possible to send too much ETH anyway. 118 uint256 remainingValue = msg.value; 119 120 for (uint256 i = 0; i < length; i = uncheckedInc(i)) { 121 // Ensure that the attester/revoker doesn't try to spend more than available. 122 uint256 value = values[i]; 123 if (value > remainingValue) { 124 revert InsufficientValue(); 125 } 126 127 // Forward the revocation to the underlying resolver and return false in case it isn't approved. 128 if (!onRevoke(attestations[i], value)) { 129 return false; 130 } 131 132 unchecked { 133 // Subtract the ETH amount, that was provided to this attestation, from the global remaining ETH amount. 134 remainingValue -= value; 135 } 136 } 137 138 return true; 139 } 140 141 /// @notice A resolver callback that should be implemented by child contracts. 142 /// @param attestation The new attestation. 143 /// @param value An explicit ETH amount that was sent to the resolver. Please note that this value is verified in 144 /// both attest() and multiAttest() callbacks EAS-only callbacks and that in case of multi attestations, 145 /// it'll usually hold that msg.value != value, since msg.value aggregated the sent ETH amounts for all 146 /// the attestations in the batch. 147 /// @return Whether the attestation is valid. 148 function onAttest(Attestation calldata attestation, uint256 value) internal virtual returns (bool); 149 150 /// @notice Processes an attestation revocation and verifies if it can be revoked. 151 /// @param attestation The existing attestation to be revoked. 152 /// @param value An explicit ETH amount that was sent to the resolver. Please note that this value is verified in 153 /// both revoke() and multiRevoke() callbacks EAS-only callbacks and that in case of multi attestations, 154 /// it'll usually hold that msg.value != value, since msg.value aggregated the sent ETH amounts for all 155 /// the attestations in the batch. 156 /// @return Whether the attestation can be revoked. 157 function onRevoke(Attestation calldata attestation, uint256 value) internal virtual returns (bool); 158 159 /// @notice Ensures that only the EAS contract can make this call. 160 function _onlyEAS() private view { 161 if (msg.sender != address(_eas)) { 162 revert AccessDenied(); 163 } 164 } 165 }