diff --git a/src/core/SP.sol b/src/core/SP.sol index 5908891..5fd2b48 100644 --- a/src/core/SP.sol +++ b/src/core/SP.sol @@ -11,17 +11,21 @@ import { SignatureChecker } from "@openzeppelin/contracts/utils/cryptography/Sig import { MessageHashUtils } from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol"; import { UUPSUpgradeable } from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; +import { PausableUpgradeable } from "@openzeppelin/contracts-upgradeable/utils/PausableUpgradeable.sol"; // solhint-disable var-name-mixedcase -contract SP is ISP, UUPSUpgradeable, OwnableUpgradeable { +contract SP is ISP, UUPSUpgradeable, OwnableUpgradeable, PausableUpgradeable { /// @custom:storage-location erc7201:ethsign.SP struct SPStorage { - mapping(uint256 => Schema) _schemaRegistry; - mapping(uint256 => Attestation) _attestationRegistry; + mapping(uint64 => Schema) _schemaRegistry; + mapping(uint64 => Attestation) _attestationRegistry; mapping(string => OffchainAttestation) _offchainAttestationRegistry; - uint256 schemaCounter; - uint256 attestationCounter; + uint64 schemaCounter; + uint64 attestationCounter; + uint64 initialSchemaCounter; + uint64 initialAttestationCounter; ISPGlobalHook globalHook; + address legacySP; } // keccak256(abi.encode(uint256(keccak256("ethsign.SP")) - 1)) & ~bytes32(uint256(0xff)) @@ -51,11 +55,26 @@ contract SP is ISP, UUPSUpgradeable, OwnableUpgradeable { } } - function initialize() public initializer { + function initialize(uint64 schemaCounter_, uint64 attestationCounter_) public initializer { SPStorage storage $ = _getSPStorage(); __Ownable_init(_msgSender()); - $.schemaCounter = 1; - $.attestationCounter = 1; + __Pausable_init_unchained(); + $.schemaCounter = schemaCounter_; + $.attestationCounter = attestationCounter_; + $.initialSchemaCounter = schemaCounter_; + $.initialAttestationCounter = attestationCounter_; + } + + function setGlobalHook(address hook) external onlyOwner { + _getSPStorage().globalHook = ISPGlobalHook(hook); + } + + function setLegacySP(address legacySP) external onlyOwner { + _getSPStorage().legacySP = legacySP; + } + + function setPause(bool pause) external onlyOwner { + pause ? _pause() : _unpause(); } function register( @@ -64,7 +83,7 @@ contract SP is ISP, UUPSUpgradeable, OwnableUpgradeable { ) external override - returns (uint256 schemaId) + returns (uint64 schemaId) { bool delegateMode = delegateSignature.length != 0; if (delegateMode) { @@ -82,7 +101,7 @@ contract SP is ISP, UUPSUpgradeable, OwnableUpgradeable { ) external override - returns (uint256[] memory schemaIds) + returns (uint64[] memory schemaIds) { bool delegateMode = delegateSignature.length != 0; address registrant = schemas[0].registrant; @@ -94,7 +113,7 @@ contract SP is ISP, UUPSUpgradeable, OwnableUpgradeable { revert SchemaWrongRegistrant(schemas[0].registrant, _msgSender()); } } - schemaIds = new uint256[](schemas.length); + schemaIds = new uint64[](schemas.length); for (uint256 i = 0; i < schemas.length; i++) { if (delegateMode && schemas[i].registrant != registrant) { revert SchemaWrongRegistrant(registrant, schemas[i].registrant); @@ -112,13 +131,13 @@ contract SP is ISP, UUPSUpgradeable, OwnableUpgradeable { ) external override - returns (uint256) + returns (uint64) { bool delegateMode = delegateSignature.length != 0; if (delegateMode) { __checkDelegationSignature(attestation.attester, getDelegatedAttestHash(attestation), delegateSignature); } - (uint256 schemaId, uint256 attestationId) = _attest(attestation, indexingKey, delegateMode); + (uint64 schemaId, uint64 attestationId) = _attest(attestation, indexingKey, delegateMode); ISPHook hook = __getResolverFromAttestationId(attestationId); if (address(hook) != address(0)) { hook.didReceiveAttestation(attestation.attester, schemaId, attestationId, extraData); @@ -135,19 +154,19 @@ contract SP is ISP, UUPSUpgradeable, OwnableUpgradeable { ) external override - returns (uint256[] memory attestationIds) + returns (uint64[] memory attestationIds) { bool delegateMode = delegateSignature.length != 0; address attester = attestations[0].attester; if (delegateMode) { __checkDelegationSignature(attester, getDelegatedAttestBatchHash(attestations), delegateSignature); } - attestationIds = new uint256[](attestations.length); + attestationIds = new uint64[](attestations.length); for (uint256 i = 0; i < attestations.length; i++) { if (delegateMode && attestations[i].attester != attester) { revert AttestationWrongAttester(attester, attestations[i].attester); } - (uint256 schemaId, uint256 attestationId) = _attest(attestations[i], indexingKeys[i], delegateMode); + (uint64 schemaId, uint64 attestationId) = _attest(attestations[i], indexingKeys[i], delegateMode); attestationIds[i] = attestationId; ISPHook hook = __getResolverFromAttestationId(attestationId); if (address(hook) != address(0)) { @@ -166,13 +185,13 @@ contract SP is ISP, UUPSUpgradeable, OwnableUpgradeable { ) external payable - returns (uint256) + returns (uint64) { bool delegateMode = delegateSignature.length != 0; if (delegateMode) { __checkDelegationSignature(attestation.attester, getDelegatedAttestHash(attestation), delegateSignature); } - (uint256 schemaId, uint256 attestationId) = _attest(attestation, indexingKey, delegateMode); + (uint64 schemaId, uint64 attestationId) = _attest(attestation, indexingKey, delegateMode); ISPHook hook = __getResolverFromAttestationId(attestationId); if (address(hook) != address(0)) { hook.didReceiveAttestation{ value: resolverFeesETH }( @@ -193,19 +212,19 @@ contract SP is ISP, UUPSUpgradeable, OwnableUpgradeable { external payable override - returns (uint256[] memory attestationIds) + returns (uint64[] memory attestationIds) { bool delegateMode = delegateSignature.length != 0; address attester = attestations[0].attester; if (delegateMode) { __checkDelegationSignature(attester, getDelegatedAttestBatchHash(attestations), delegateSignature); } - attestationIds = new uint256[](attestations.length); + attestationIds = new uint64[](attestations.length); for (uint256 i = 0; i < attestations.length; i++) { if (delegateMode && attestations[i].attester != attester) { revert AttestationWrongAttester(attester, attestations[i].attester); } - (uint256 schemaId, uint256 attestationId) = _attest(attestations[i], indexingKeys[i], delegateMode); + (uint64 schemaId, uint64 attestationId) = _attest(attestations[i], indexingKeys[i], delegateMode); attestationIds[i] = attestationId; ISPHook hook = __getResolverFromAttestationId(attestationId); if (address(hook) != address(0)) { @@ -227,13 +246,13 @@ contract SP is ISP, UUPSUpgradeable, OwnableUpgradeable { ) external override - returns (uint256) + returns (uint64) { bool delegateMode = delegateSignature.length != 0; if (delegateMode) { __checkDelegationSignature(attestation.attester, getDelegatedAttestHash(attestation), delegateSignature); } - (uint256 schemaId, uint256 attestationId) = _attest(attestation, indexingKey, delegateMode); + (uint64 schemaId, uint64 attestationId) = _attest(attestation, indexingKey, delegateMode); ISPHook hook = __getResolverFromAttestationId(attestationId); if (address(hook) != address(0)) { hook.didReceiveAttestation( @@ -259,7 +278,7 @@ contract SP is ISP, UUPSUpgradeable, OwnableUpgradeable { ) external override - returns (uint256[] memory attestationIds) + returns (uint64[] memory attestationIds) { bool delegateMode = delegateSignature.length != 0; // address attester = attestations[0].attester; @@ -268,12 +287,12 @@ contract SP is ISP, UUPSUpgradeable, OwnableUpgradeable { attestations[0].attester, getDelegatedAttestBatchHash(attestations), delegateSignature ); } - attestationIds = new uint256[](attestations.length); + attestationIds = new uint64[](attestations.length); for (uint256 i = 0; i < attestations.length; i++) { if (delegateMode && attestations[i].attester != attestations[0].attester) { revert AttestationWrongAttester(attestations[0].attester, attestations[i].attester); } - (uint256 schemaId, uint256 attestationId) = _attest(attestations[i], indexingKeys[i], delegateMode); + (uint64 schemaId, uint64 attestationId) = _attest(attestations[i], indexingKeys[i], delegateMode); attestationIds[i] = attestationId; ISPHook hook = __getResolverFromAttestationId(attestationId); if (address(hook) != address(0)) { @@ -331,7 +350,7 @@ contract SP is ISP, UUPSUpgradeable, OwnableUpgradeable { } function revoke( - uint256 attestationId, + uint64 attestationId, string calldata reason, bytes calldata delegateSignature, bytes calldata extraData @@ -346,7 +365,7 @@ contract SP is ISP, UUPSUpgradeable, OwnableUpgradeable { storageAttester, getDelegatedRevokeHash(attestationId, reason), delegateSignature ); } - uint256 schemaId = _revoke(attestationId, reason, delegateMode); + uint64 schemaId = _revoke(attestationId, reason, delegateMode); ISPHook hook = __getResolverFromAttestationId(attestationId); if (address(hook) != address(0)) { hook.didReceiveRevocation(storageAttester, schemaId, attestationId, extraData); @@ -355,7 +374,7 @@ contract SP is ISP, UUPSUpgradeable, OwnableUpgradeable { } function revokeBatch( - uint256[] memory attestationIds, + uint64[] memory attestationIds, string[] memory reasons, bytes memory delegateSignature, bytes memory extraData @@ -377,7 +396,7 @@ contract SP is ISP, UUPSUpgradeable, OwnableUpgradeable { if (delegateMode && storageAttester != currentAttester) { revert AttestationWrongAttester(storageAttester, currentAttester); } - uint256 schemaId = _revoke(attestationIds[i], reasons[i], delegateMode); + uint64 schemaId = _revoke(attestationIds[i], reasons[i], delegateMode); ISPHook hook = __getResolverFromAttestationId(attestationIds[i]); if (address(hook) != address(0)) { hook.didReceiveRevocation(storageAttester, schemaId, attestationIds[i], extraData); @@ -387,7 +406,7 @@ contract SP is ISP, UUPSUpgradeable, OwnableUpgradeable { } function revoke( - uint256 attestationId, + uint64 attestationId, string memory reason, uint256 resolverFeesETH, bytes memory delegateSignature, @@ -404,7 +423,7 @@ contract SP is ISP, UUPSUpgradeable, OwnableUpgradeable { storageAttester, getDelegatedRevokeHash(attestationId, reason), delegateSignature ); } - uint256 schemaId = _revoke(attestationId, reason, delegateMode); + uint64 schemaId = _revoke(attestationId, reason, delegateMode); ISPHook hook = __getResolverFromAttestationId(attestationId); if (address(hook) != address(0)) { hook.didReceiveRevocation{ value: resolverFeesETH }(storageAttester, schemaId, attestationId, extraData); @@ -413,7 +432,7 @@ contract SP is ISP, UUPSUpgradeable, OwnableUpgradeable { } function revokeBatch( - uint256[] memory attestationIds, + uint64[] memory attestationIds, string[] memory reasons, uint256[] memory resolverFeesETH, bytes memory delegateSignature, @@ -437,7 +456,7 @@ contract SP is ISP, UUPSUpgradeable, OwnableUpgradeable { if (delegateMode && storageAttester != currentAttester) { revert AttestationWrongAttester(storageAttester, currentAttester); } - uint256 schemaId = _revoke(attestationIds[i], reasons[i], delegateMode); + uint64 schemaId = _revoke(attestationIds[i], reasons[i], delegateMode); ISPHook hook = __getResolverFromAttestationId(attestationIds[i]); if (address(hook) != address(0)) { hook.didReceiveRevocation{ value: resolverFeesETH[i] }( @@ -449,7 +468,7 @@ contract SP is ISP, UUPSUpgradeable, OwnableUpgradeable { } function revoke( - uint256 attestationId, + uint64 attestationId, string memory reason, IERC20 resolverFeesERC20Token, uint256 resolverFeesERC20Amount, @@ -466,7 +485,7 @@ contract SP is ISP, UUPSUpgradeable, OwnableUpgradeable { storageAttester, getDelegatedRevokeHash(attestationId, reason), delegateSignature ); } - uint256 schemaId = _revoke(attestationId, reason, delegateMode); + uint64 schemaId = _revoke(attestationId, reason, delegateMode); ISPHook hook = __getResolverFromAttestationId(attestationId); if (address(hook) != address(0)) { hook.didReceiveRevocation( @@ -477,7 +496,7 @@ contract SP is ISP, UUPSUpgradeable, OwnableUpgradeable { } function revokeBatch( - uint256[] memory attestationIds, + uint64[] memory attestationIds, string[] memory reasons, IERC20[] memory resolverFeesERC20Tokens, uint256[] memory resolverFeesERC20Amount, @@ -501,7 +520,7 @@ contract SP is ISP, UUPSUpgradeable, OwnableUpgradeable { if (delegateMode && storageAttester != currentAttester) { revert AttestationWrongAttester(storageAttester, currentAttester); } - uint256 schemaId = _revoke(attestationIds[i], reasons[i], delegateMode); + uint64 schemaId = _revoke(attestationIds[i], reasons[i], delegateMode); ISPHook hook = __getResolverFromAttestationId(attestationIds[i]); if (address(hook) != address(0)) { hook.didReceiveRevocation( @@ -563,12 +582,16 @@ contract SP is ISP, UUPSUpgradeable, OwnableUpgradeable { _callGlobalHook(); } - function getSchema(uint256 schemaId) external view override returns (Schema memory) { - return _getSPStorage()._schemaRegistry[schemaId]; + function getSchema(uint64 schemaId) external view override returns (Schema memory) { + SPStorage storage $ = _getSPStorage(); + if (schemaId < $.initialSchemaCounter) revert LegacySPRequired($.legacySP); + return $._schemaRegistry[schemaId]; } - function getAttestation(uint256 attestationId) external view override returns (Attestation memory) { - return _getSPStorage()._attestationRegistry[attestationId]; + function getAttestation(uint64 attestationId) external view override returns (Attestation memory) { + SPStorage storage $ = _getSPStorage(); + if (attestationId < $.initialAttestationCounter) revert LegacySPRequired($.legacySP); + return $._attestationRegistry[attestationId]; } function getOffchainAttestation(string calldata offchainAttestationId) @@ -579,16 +602,16 @@ contract SP is ISP, UUPSUpgradeable, OwnableUpgradeable { return _getSPStorage()._offchainAttestationRegistry[offchainAttestationId]; } - function schemaCounter() external view override returns (uint256) { + function schemaCounter() external view override returns (uint64) { return _getSPStorage().schemaCounter; } - function attestationCounter() external view override returns (uint256) { + function attestationCounter() external view override returns (uint64) { return _getSPStorage().attestationCounter; } function version() external pure override returns (string memory) { - return "1.0.1"; + return "1.1.0"; } function getDelegatedRegisterHash(Schema memory schema) public pure override returns (bytes32) { @@ -625,7 +648,7 @@ contract SP is ISP, UUPSUpgradeable, OwnableUpgradeable { } function getDelegatedRevokeHash( - uint256 attestationId, + uint64 attestationId, string memory reason ) public @@ -637,7 +660,7 @@ contract SP is ISP, UUPSUpgradeable, OwnableUpgradeable { } function getDelegatedRevokeBatchHash( - uint256[] memory attestationIds, + uint64[] memory attestationIds, string[] memory reasons ) public @@ -675,9 +698,10 @@ contract SP is ISP, UUPSUpgradeable, OwnableUpgradeable { if (address($.globalHook) != address(0)) $.globalHook.callHook(_msgData()); } - function _register(Schema memory schema) internal returns (uint256 schemaId) { + function _register(Schema memory schema) internal whenNotPaused returns (uint64 schemaId) { SPStorage storage $ = _getSPStorage(); schemaId = $.schemaCounter++; + schema.timestamp = uint64(block.timestamp); $._schemaRegistry[schemaId] = schema; emit SchemaRegistered(schemaId); } @@ -688,10 +712,13 @@ contract SP is ISP, UUPSUpgradeable, OwnableUpgradeable { bool delegateMode ) internal - returns (uint256 schemaId, uint256 attestationId) + whenNotPaused + returns (uint64 schemaId, uint64 attestationId) { SPStorage storage $ = _getSPStorage(); attestationId = $.attestationCounter++; + attestation.attestTimestamp = uint64(block.timestamp); + attestation.revokeTimestamp = 0; // In delegation mode, the attester is already checked ahead of time. if (!delegateMode && attestation.attester != _msgSender()) { revert AttestationWrongAttester(attestation.attester, _msgSender()); @@ -720,7 +747,7 @@ contract SP is ISP, UUPSUpgradeable, OwnableUpgradeable { return (attestation.schemaId, attestationId); } - function _attestOffchain(string calldata offchainAttestationId, address attester) internal { + function _attestOffchain(string calldata offchainAttestationId, address attester) internal whenNotPaused { SPStorage storage $ = _getSPStorage(); OffchainAttestation storage attestation = $._offchainAttestationRegistry[offchainAttestationId]; if (__offchainAttestationExists(offchainAttestationId)) { @@ -732,12 +759,13 @@ contract SP is ISP, UUPSUpgradeable, OwnableUpgradeable { } function _revoke( - uint256 attestationId, + uint64 attestationId, string memory reason, bool delegateMode ) internal - returns (uint256 schemaId) + whenNotPaused + returns (uint64 schemaId) { SPStorage storage $ = _getSPStorage(); Attestation storage a = $._attestationRegistry[attestationId]; @@ -748,6 +776,7 @@ contract SP is ISP, UUPSUpgradeable, OwnableUpgradeable { if (!s.revocable) revert AttestationIrrevocable(a.schemaId, attestationId); if (a.revoked) revert AttestationAlreadyRevoked(attestationId); a.revoked = true; + a.revokeTimestamp = uint64(block.timestamp); emit AttestationRevoked(attestationId, reason); return a.schemaId; } @@ -758,6 +787,7 @@ contract SP is ISP, UUPSUpgradeable, OwnableUpgradeable { bool delegateMode ) internal + whenNotPaused { SPStorage storage $ = _getSPStorage(); OffchainAttestation storage attestation = $._offchainAttestationRegistry[offchainAttestationId]; @@ -794,19 +824,19 @@ contract SP is ISP, UUPSUpgradeable, OwnableUpgradeable { } } - function __getResolverFromAttestationId(uint256 attestationId) internal view returns (ISPHook) { + function __getResolverFromAttestationId(uint64 attestationId) internal view returns (ISPHook) { SPStorage storage $ = _getSPStorage(); Attestation memory a = $._attestationRegistry[attestationId]; Schema memory s = $._schemaRegistry[a.schemaId]; return s.hook; } - function __schemaExists(uint256 schemaId) internal view returns (bool) { + function __schemaExists(uint64 schemaId) internal view returns (bool) { SPStorage storage $ = _getSPStorage(); return schemaId < $.schemaCounter; } - function __attestationExists(uint256 attestationId) internal view returns (bool) { + function __attestationExists(uint64 attestationId) internal view returns (bool) { SPStorage storage $ = _getSPStorage(); return attestationId < $.attestationCounter; } diff --git a/src/interfaces/ISP.sol b/src/interfaces/ISP.sol index 949af7e..2f7e32f 100644 --- a/src/interfaces/ISP.sol +++ b/src/interfaces/ISP.sol @@ -11,16 +11,16 @@ import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; * @author Jack Xu @ EthSign */ interface ISP is IVersionable { - event SchemaRegistered(uint256 schemaId); - event AttestationMade(uint256 attestationId, string indexingKey); - event AttestationRevoked(uint256 attestationId, string reason); + event SchemaRegistered(uint64 schemaId); + event AttestationMade(uint64 attestationId, string indexingKey); + event AttestationRevoked(uint64 attestationId, string reason); event OffchainAttestationMade(string attestationId); event OffchainAttestationRevoked(string attestationId, string reason); /** * @dev 0x38f8c6c4 */ - error SchemaNonexistent(uint256 nonexistentSchemaId); + error SchemaNonexistent(uint64 nonexistentSchemaId); /** * @dev 0x71984561 */ @@ -28,19 +28,19 @@ interface ISP is IVersionable { /** * @dev 0x8ac42f49 */ - error AttestationIrrevocable(uint256 schemaId, uint256 offendingAttestationId); + error AttestationIrrevocable(uint64 schemaId, uint64 offendingAttestationId); /** * @dev 0x54681a13 */ - error AttestationNonexistent(uint256 nonexistentAttestationId); + error AttestationNonexistent(uint64 nonexistentAttestationId); /** * @dev 0xa65e02ed */ - error AttestationInvalidDuration(uint256 offendingAttestationId, uint64 maxDuration, uint64 inputDuration); + error AttestationInvalidDuration(uint64 offendingAttestationId, uint64 maxDuration, uint64 inputDuration); /** * @dev 0xd8c3da86 */ - error AttestationAlreadyRevoked(uint256 offendingAttestationId); + error AttestationAlreadyRevoked(uint64 offendingAttestationId); /** * @dev 0xa9ad2007 */ @@ -61,6 +61,10 @@ interface ISP is IVersionable { * @dev 0xfdf4e6f9 */ error InvalidDelegateSignature(); + /** + * @dev 0x5c34b9cc + */ + error LegacySPRequired(address legacySP); /** * @notice Registers a Schema. @@ -70,7 +74,7 @@ interface ISP is IVersionable { * otherwise. * @return schemaId The assigned ID of the registered schema. */ - function register(Schema memory schema, bytes calldata delegateSignature) external returns (uint256 schemaId); + function register(Schema memory schema, bytes calldata delegateSignature) external returns (uint64 schemaId); /** * @notice Makes an attestation. @@ -89,7 +93,7 @@ interface ISP is IVersionable { bytes calldata extraData ) external - returns (uint256 attestationId); + returns (uint64 attestationId); /** * @notice Makes an attestation where the schema hook expects ETH payment. @@ -111,7 +115,7 @@ interface ISP is IVersionable { ) external payable - returns (uint256 attestationId); + returns (uint64 attestationId); /** * @notice Makes an attestation where the schema hook expects ERC20 payment. @@ -134,7 +138,7 @@ interface ISP is IVersionable { bytes calldata extraData ) external - returns (uint256 attestationId); + returns (uint64 attestationId); /** * @notice Timestamps an off-chain data ID. @@ -161,7 +165,7 @@ interface ISP is IVersionable { * @param extraData This is forwarded to the resolver directly. */ function revoke( - uint256 attestationId, + uint64 attestationId, string calldata reason, bytes calldata delegateSignature, bytes calldata extraData @@ -178,7 +182,7 @@ interface ISP is IVersionable { * @param extraData This is forwarded to the resolver directly. */ function revoke( - uint256 attestationId, + uint64 attestationId, string calldata reason, uint256 resolverFeesETH, bytes calldata delegateSignature, @@ -198,7 +202,7 @@ interface ISP is IVersionable { * @param extraData This is forwarded to the resolver directly. */ function revoke( - uint256 attestationId, + uint64 attestationId, string calldata reason, IERC20 resolverFeesERC20Token, uint256 resolverFeesERC20Amount, @@ -229,7 +233,7 @@ interface ISP is IVersionable { bytes calldata delegateSignature ) external - returns (uint256[] calldata schemaIds); + returns (uint64[] calldata schemaIds); /** * @notice Batch attests. @@ -241,7 +245,7 @@ interface ISP is IVersionable { bytes calldata extraData ) external - returns (uint256[] calldata attestationIds); + returns (uint64[] calldata attestationIds); /** * @notice Batch attests where the schema hook expects ETH payment. @@ -255,7 +259,7 @@ interface ISP is IVersionable { ) external payable - returns (uint256[] calldata attestationIds); + returns (uint64[] calldata attestationIds); /** * @notice Batch attests where the schema hook expects ERC20 payment. @@ -269,7 +273,7 @@ interface ISP is IVersionable { bytes calldata extraData ) external - returns (uint256[] calldata attestationIds); + returns (uint64[] calldata attestationIds); /** * @notice Batch timestamps off-chain data IDs. @@ -285,7 +289,7 @@ interface ISP is IVersionable { * @notice Batch revokes revocable on-chain attestations. */ function revokeBatch( - uint256[] calldata attestationIds, + uint64[] calldata attestationIds, string[] calldata reasons, bytes calldata delegateSignature, bytes calldata extraData @@ -296,7 +300,7 @@ interface ISP is IVersionable { * @notice Batch revokes revocable on-chain attestations where the schema hook expects ETH payment. */ function revokeBatch( - uint256[] calldata attestationIds, + uint64[] calldata attestationIds, string[] calldata reasons, uint256[] calldata resolverFeesETH, bytes calldata delegateSignature, @@ -309,7 +313,7 @@ interface ISP is IVersionable { * @notice Batch revokes revocable on-chain attestations where the schema hook expects ERC20 payment. */ function revokeBatch( - uint256[] calldata attestationIds, + uint64[] calldata attestationIds, string[] calldata reasons, IERC20[] calldata resolverFeesERC20Tokens, uint256[] calldata resolverFeesERC20Amount, @@ -331,12 +335,12 @@ interface ISP is IVersionable { /** * @notice Returns the specified `Schema`. */ - function getSchema(uint256 schemaId) external view returns (Schema calldata); + function getSchema(uint64 schemaId) external view returns (Schema calldata); /** * @notice Returns the specified `Attestation`. */ - function getAttestation(uint256 attestationId) external view returns (Attestation calldata); + function getAttestation(uint64 attestationId) external view returns (Attestation calldata); /** * @notice Returns the specified `OffchainAttestation`. @@ -382,13 +386,13 @@ interface ISP is IVersionable { /** * @notice Returns the hash that will be used to authorize a delegated revocation. */ - function getDelegatedRevokeHash(uint256 attestationId, string memory reason) external pure returns (bytes32); + function getDelegatedRevokeHash(uint64 attestationId, string memory reason) external pure returns (bytes32); /** * @notice Returns the hash that will be used to authorize a delegated batch revocation. */ function getDelegatedRevokeBatchHash( - uint256[] memory attestationIds, + uint64[] memory attestationIds, string[] memory reasons ) external @@ -420,10 +424,10 @@ interface ISP is IVersionable { /** * @notice Returns the current schema counter. This is incremented for each `Schema` registered. */ - function schemaCounter() external view returns (uint256); + function schemaCounter() external view returns (uint64); /** * @notice Returns the current on-chain attestation counter. This is incremented for each `Attestation` made. */ - function attestationCounter() external view returns (uint256); + function attestationCounter() external view returns (uint64); } diff --git a/src/interfaces/ISPHook.sol b/src/interfaces/ISPHook.sol index 84c7962..0e1d50b 100644 --- a/src/interfaces/ISPHook.sol +++ b/src/interfaces/ISPHook.sol @@ -10,8 +10,8 @@ import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; interface ISPHook { function didReceiveAttestation( address attester, - uint256 schemaId, - uint256 attestationId, + uint64 schemaId, + uint64 attestationId, bytes calldata extraData ) external @@ -19,8 +19,8 @@ interface ISPHook { function didReceiveAttestation( address attester, - uint256 schemaId, - uint256 attestationId, + uint64 schemaId, + uint64 attestationId, IERC20 resolverFeeERC20Token, uint256 resolverFeeERC20Amount, bytes calldata extraData @@ -29,8 +29,8 @@ interface ISPHook { function didReceiveRevocation( address attester, - uint256 schemaId, - uint256 attestationId, + uint64 schemaId, + uint64 attestationId, bytes calldata extraData ) external @@ -38,8 +38,8 @@ interface ISPHook { function didReceiveRevocation( address attester, - uint256 schemaId, - uint256 attestationId, + uint64 schemaId, + uint64 attestationId, IERC20 resolverFeeERC20Token, uint256 resolverFeeERC20Amount, bytes calldata extraData diff --git a/src/mock/MockResolver.sol b/src/mock/MockResolver.sol index af19b58..193351b 100644 --- a/src/mock/MockResolver.sol +++ b/src/mock/MockResolver.sol @@ -8,14 +8,14 @@ import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/O contract MockResolverAdmin is OwnableUpgradeable { using SafeERC20 for IERC20; - mapping(uint256 schemaId => uint256 ethFees) public schemaAttestETHFees; - mapping(uint256 schemaId => mapping(IERC20 tokenAddress => uint256 tokenFees)) public schemaAttestTokenFees; - mapping(uint256 attestationId => uint256 ethFees) public attestationETHFees; - mapping(uint256 attestationId => mapping(IERC20 tokenAddress => uint256 tokenFees)) public attestationTokenFees; + mapping(uint64 schemaId => uint256 ethFees) public schemaAttestETHFees; + mapping(uint64 schemaId => mapping(IERC20 tokenAddress => uint256 tokenFees)) public schemaAttestTokenFees; + mapping(uint64 attestationId => uint256 ethFees) public attestationETHFees; + mapping(uint64 attestationId => mapping(IERC20 tokenAddress => uint256 tokenFees)) public attestationTokenFees; mapping(IERC20 tokenAddress => bool approved) public approvedTokens; - event ETHFeesReceived(uint256 attestationId, uint256 amount); - event TokenFeesReceived(uint256 attestationId, IERC20 token, uint256 amount); + event ETHFeesReceived(uint64 attestationId, uint256 amount); + event TokenFeesReceived(uint64 attestationId, IERC20 token, uint256 amount); error MismatchETHFee(); error InsufficientETHFee(); @@ -26,11 +26,11 @@ contract MockResolverAdmin is OwnableUpgradeable { __Ownable_init(_msgSender()); } - function setSchemaAttestETHFees(uint256 schemaId, uint256 fees) external onlyOwner { + function setSchemaAttestETHFees(uint64 schemaId, uint256 fees) external onlyOwner { schemaAttestETHFees[schemaId] = fees; } - function setSchemaAttestTokenFees(uint256 schemaId, IERC20 token, uint256 fees) external onlyOwner { + function setSchemaAttestTokenFees(uint64 schemaId, IERC20 token, uint256 fees) external onlyOwner { schemaAttestTokenFees[schemaId][token] = fees; } @@ -38,7 +38,7 @@ contract MockResolverAdmin is OwnableUpgradeable { approvedTokens[token] = approved; } - function _receiveEther(address attester, uint256 schemaId, uint256 attestationId) internal { + function _receiveEther(address attester, uint64 schemaId, uint64 attestationId) internal { uint256 fees = schemaAttestETHFees[schemaId] == 0 ? attestationETHFees[attestationId] : schemaAttestETHFees[schemaId]; if (msg.value != fees) revert InsufficientETHFee(); @@ -48,8 +48,8 @@ contract MockResolverAdmin is OwnableUpgradeable { function _receiveTokens( address attester, - uint256 schemaId, - uint256 attestationId, + uint64 schemaId, + uint64 attestationId, IERC20 resolverFeeERC20Token, uint256 resolverFeeERC20Amount ) @@ -68,8 +68,8 @@ contract MockResolverAdmin is OwnableUpgradeable { contract MockResolver is ISPHook, MockResolverAdmin { function didReceiveAttestation( address attester, - uint256 schemaId, - uint256 attestationId, + uint64 schemaId, + uint64 attestationId, bytes calldata ) external @@ -80,8 +80,8 @@ contract MockResolver is ISPHook, MockResolverAdmin { function didReceiveAttestation( address attester, - uint256 schemaId, - uint256 attestationId, + uint64 schemaId, + uint64 attestationId, IERC20 resolverFeeERC20Token, uint256 resolverFeeERC20Amount, bytes calldata @@ -94,8 +94,8 @@ contract MockResolver is ISPHook, MockResolverAdmin { function didReceiveRevocation( address attester, - uint256 schemaId, - uint256 attestationId, + uint64 schemaId, + uint64 attestationId, bytes calldata ) external @@ -107,8 +107,8 @@ contract MockResolver is ISPHook, MockResolverAdmin { function didReceiveRevocation( address attester, - uint256 schemaId, - uint256 attestationId, + uint64 schemaId, + uint64 attestationId, IERC20 resolverFeeERC20Token, uint256 resolverFeeERC20Amount, bytes calldata diff --git a/src/models/Attestation.sol b/src/models/Attestation.sol index 45915e3..3572b0f 100644 --- a/src/models/Attestation.sol +++ b/src/models/Attestation.sol @@ -22,11 +22,13 @@ import { DataLocation } from "./DataLocation.sol"; * to use `abi.encode`. */ struct Attestation { - uint256 schemaId; - DataLocation dataLocation; - uint256 linkedAttestationId; + uint64 schemaId; + uint64 linkedAttestationId; + uint64 attestTimestamp; + uint64 revokeTimestamp; address attester; uint64 validUntil; + DataLocation dataLocation; bool revoked; bytes[] recipients; bytes data; diff --git a/src/models/Schema.sol b/src/models/Schema.sol index d43249c..18ac53f 100644 --- a/src/models/Schema.sol +++ b/src/models/Schema.sol @@ -25,5 +25,6 @@ struct Schema { DataLocation dataLocation; uint64 maxValidFor; ISPHook hook; + uint64 timestamp; string data; } diff --git a/test/SP.test.sol b/test/SP.test.sol index 852beb0..f807a5e 100644 --- a/test/SP.test.sol +++ b/test/SP.test.sol @@ -20,18 +20,18 @@ contract SPTest is Test { address public prankRecipient0 = 0x003BBE6Da0EB4963856395829030FcE383a14C53; address public prankRecipient1 = 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045; - event SchemaRegistered(uint256 schemaId); - event AttestationMade(uint256 attestationId, string indexingKey); - event AttestationRevoked(uint256 attestationId, string reason); + event SchemaRegistered(uint64 schemaId); + event AttestationMade(uint64 attestationId, string indexingKey); + event AttestationRevoked(uint64 attestationId, string reason); event OffchainAttestationMade(string attestationId); event OffchainAttestationRevoked(string attestationId, string reason); - error SchemaNonexistent(uint256 nonexistentSchemaId); + error SchemaNonexistent(uint64 nonexistentSchemaId); error SchemaWrongRegistrant(address expected, address actual); - error AttestationIrrevocable(uint256 schemaId, uint256 offendingAttestationId); - error AttestationNonexistent(uint256 nonexistentAttestationId); - error AttestationInvalidDuration(uint256 offendingAttestationId, uint64 maxDuration, uint64 inputDuration); - error AttestationAlreadyRevoked(uint256 offendingAttestationId); + error AttestationIrrevocable(uint64 schemaId, uint64 offendingAttestationId); + error AttestationNonexistent(uint64 nonexistentAttestationId); + error AttestationInvalidDuration(uint64 offendingAttestationId, uint64 maxDuration, uint64 inputDuration); + error AttestationAlreadyRevoked(uint64 offendingAttestationId); error AttestationWrongAttester(address expected, address actual); error OffchainAttestationExists(string existingOffchainAttestationId); error OffchainAttestationNonexistent(string nonexistentOffchainAttestationId); @@ -40,7 +40,7 @@ contract SPTest is Test { function setUp() public { sp = new SP(); - SP(address(sp)).initialize(); + SP(address(sp)).initialize(1, 1); mockResolver = new MockResolver(); } @@ -49,11 +49,13 @@ contract SPTest is Test { function test_register() public { Schema[] memory schemas = _createMockSchemas(); // Register 2 different schemas, check events & storage - uint256 currentSchemaCounter = sp.schemaCounter(); + uint64 currentSchemaCounter = sp.schemaCounter(); vm.expectEmit(); emit SchemaRegistered(currentSchemaCounter++); emit SchemaRegistered(currentSchemaCounter++); - uint256[] memory schemaIds = sp.registerBatch(schemas, ""); + uint64 mockTimestamp = 12_345; + vm.warp(mockTimestamp); + uint64[] memory schemaIds = sp.registerBatch(schemas, ""); Schema memory schema0Expected = schemas[0]; Schema memory schema1Expected = schemas[1]; Schema memory schema0Actual = sp.getSchema(schemaIds[0]); @@ -62,21 +64,25 @@ contract SPTest is Test { assertEq(schema0Expected.revocable, schema0Actual.revocable); assertEq(address(schema0Expected.hook), address(schema0Actual.hook)); assertEq(schema0Expected.maxValidFor, schema0Actual.maxValidFor); + assertEq(mockTimestamp, schema0Actual.timestamp); assertEq(schema1Expected.data, schema1Actual.data); assertEq(schema1Expected.revocable, schema1Actual.revocable); assertEq(address(schema1Expected.hook), address(schema1Actual.hook)); assertEq(schema1Expected.maxValidFor, schema1Actual.maxValidFor); + assertEq(mockTimestamp, schema1Actual.timestamp); } function test_attest() public { + uint64 mockTimestamp = 12_345; + vm.warp(mockTimestamp); // Register 2 different schemas Schema[] memory schemas = _createMockSchemas(); - uint256[] memory schemaIds = sp.registerBatch(schemas, ""); + uint64[] memory schemaIds = sp.registerBatch(schemas, ""); // Create two normal attestations (Attestation[] memory attestations, string[] memory indexingKeys) = _createMockAttestations(schemaIds); // Modify the second one to trigger `AttestationInvalidDuration` - uint256 attestationId0 = sp.attestationCounter(); - attestations[1].validUntil = uint64(attestations[1].validUntil + schemas[1].maxValidFor + 1); + uint64 attestationId0 = sp.attestationCounter(); + attestations[1].validUntil = uint64(mockTimestamp + attestations[1].validUntil + schemas[1].maxValidFor + 1); vm.expectRevert( abi.encodeWithSelector( AttestationInvalidDuration.selector, @@ -95,7 +101,7 @@ contract SPTest is Test { sp.attestBatch(attestations, indexingKeys, "", ""); // Reset and trigger `AttestationNonexistent` for a linked attestation (attestations,) = _createMockAttestations(schemaIds); - uint256 nonexistentAttestationId = 100_000; + uint64 nonexistentAttestationId = 100_000; attestations[1].linkedAttestationId = nonexistentAttestationId; vm.expectRevert(abi.encodeWithSelector(AttestationNonexistent.selector, nonexistentAttestationId)); vm.prank(prankSender); @@ -123,22 +129,26 @@ contract SPTest is Test { Attestation memory attestation1Actual = sp.getAttestation(attestationId0 + 1); assertEq(attestation0Actual.attester, prankSender); assertEq(attestation0Actual.schemaId, attestations[0].schemaId); + assertEq(attestation0Actual.attestTimestamp, mockTimestamp); + assertEq(attestation0Actual.revokeTimestamp, 0); assertEq(attestation1Actual.attester, prankSender); assertEq(attestation1Actual.schemaId, attestations[1].schemaId); + assertEq(attestation1Actual.attestTimestamp, mockTimestamp); + assertEq(attestation1Actual.revokeTimestamp, 0); } function test_revokeFail() public { // Register 2 different schemas Schema[] memory schemas = _createMockSchemas(); - uint256[] memory schemaIds = sp.registerBatch(schemas, ""); + uint64[] memory schemaIds = sp.registerBatch(schemas, ""); // Make two normal attestations (Attestation[] memory attestations, string[] memory indexingKeys) = _createMockAttestations(schemaIds); vm.prank(prankSender); - uint256[] memory attestationIds = sp.attestBatch(attestations, indexingKeys, "", ""); + uint64[] memory attestationIds = sp.attestBatch(attestations, indexingKeys, "", ""); string[] memory reasons = _createMockReasons(); // Trigger `AttestationNonexistent` - uint256 originalAttestationid = attestationIds[0]; - uint256 fakeAttestationId = 10_000; + uint64 originalAttestationid = attestationIds[0]; + uint64 fakeAttestationId = 10_000; attestationIds[0] = fakeAttestationId; vm.expectRevert(abi.encodeWithSelector(AttestationNonexistent.selector, fakeAttestationId)); vm.prank(prankSender); @@ -154,14 +164,16 @@ contract SPTest is Test { } function test_revoke() public { + uint64 mockTimestamp = 12_345; + vm.warp(mockTimestamp); // Register 2 different schemas Schema[] memory schemas = _createMockSchemas(); schemas[1].revocable = true; - uint256[] memory schemaIds = sp.registerBatch(schemas, ""); + uint64[] memory schemaIds = sp.registerBatch(schemas, ""); // Make two normal attestations (Attestation[] memory attestations, string[] memory indexingKeys) = _createMockAttestations(schemaIds); vm.prank(prankSender); - uint256[] memory attestationIds = sp.attestBatch(attestations, indexingKeys, "", ""); + uint64[] memory attestationIds = sp.attestBatch(attestations, indexingKeys, "", ""); string[] memory reasons = _createMockReasons(); // Revoke normally vm.expectEmit(); @@ -169,6 +181,7 @@ contract SPTest is Test { emit AttestationRevoked(attestationIds[1], reasons[1]); vm.prank(prankSender); sp.revokeBatch(attestationIds, reasons, "", ""); + assertEq(sp.getAttestation(attestationIds[0]).revokeTimestamp, mockTimestamp); // Revoke again and trigger `AttestationAlreadyRevoked` vm.expectRevert(abi.encodeWithSelector(AttestationAlreadyRevoked.selector, attestationIds[0])); vm.prank(prankSender); @@ -241,8 +254,8 @@ contract SPTest is Test { function test_attest_delegated() public { // Register 2 different schemas Schema[] memory schemas = _createMockSchemas(); - uint256[] memory schemaIds = sp.registerBatch(schemas, ""); - uint256 attestationId0 = sp.attestationCounter(); + uint64[] memory schemaIds = sp.registerBatch(schemas, ""); + uint64 attestationId0 = sp.attestationCounter(); // Create two normal attestations (Attestation[] memory attestations, string[] memory indexingKeys) = _createMockAttestations(schemaIds); // Create ECDSA signature @@ -271,8 +284,8 @@ contract SPTest is Test { function test_attest_batch_delegated() public { // Register 2 different schemas Schema[] memory schemas = _createMockSchemas(); - uint256[] memory schemaIds = sp.registerBatch(schemas, ""); - uint256 attestationId0 = sp.attestationCounter(); + uint64[] memory schemaIds = sp.registerBatch(schemas, ""); + uint64 attestationId0 = sp.attestationCounter(); // Create two normal attestations (Attestation[] memory attestations, string[] memory indexingKeys) = _createMockAttestations(schemaIds); // Create ECDSA signature @@ -340,8 +353,8 @@ contract SPTest is Test { function test_revoke_delegated() public { // Register 2 different schemas Schema[] memory schemas = _createMockSchemas(); - uint256[] memory schemaIds = sp.registerBatch(schemas, ""); - uint256 attestationId0 = sp.attestationCounter(); + uint64[] memory schemaIds = sp.registerBatch(schemas, ""); + uint64 attestationId0 = sp.attestationCounter(); // Create two normal attestations (Attestation[] memory attestations, string[] memory indexingKeys) = _createMockAttestations(schemaIds); // Delegate attest normally @@ -365,8 +378,8 @@ contract SPTest is Test { // Register 2 different schemas Schema[] memory schemas = _createMockSchemas(); schemas[1].revocable = true; - uint256[] memory schemaIds = sp.registerBatch(schemas, ""); - uint256[] memory attestationIds = new uint256[](2); + uint64[] memory schemaIds = sp.registerBatch(schemas, ""); + uint64[] memory attestationIds = new uint64[](2); attestationIds[0] = sp.attestationCounter(); attestationIds[1] = attestationIds[0] + 1; // Create two normal attestations @@ -443,6 +456,7 @@ contract SPTest is Test { dataLocation: DataLocation.ONCHAIN, maxValidFor: 0, hook: mockResolver, + timestamp: 0, data: "stupid0" }); Schema memory schema1 = Schema({ @@ -451,6 +465,7 @@ contract SPTest is Test { dataLocation: DataLocation.ONCHAIN, maxValidFor: 100, hook: mockResolver, + timestamp: 0, data: "stupid1" }); Schema[] memory schemas = new Schema[](2); @@ -481,28 +496,32 @@ contract SPTest is Test { return attestationIds; } - function _createMockAttestations(uint256[] memory schemaIds) + function _createMockAttestations(uint64[] memory schemaIds) internal view returns (Attestation[] memory, string[] memory) { Attestation memory attestation0 = Attestation({ schemaId: schemaIds[0], - dataLocation: DataLocation.ONCHAIN, linkedAttestationId: 0, + attestTimestamp: 0, + revokeTimestamp: 0, data: "", attester: prankSender, validUntil: uint64(block.timestamp), + dataLocation: DataLocation.ONCHAIN, revoked: false, recipients: _createMockRecipients() }); Attestation memory attestation1 = Attestation({ schemaId: schemaIds[1], - dataLocation: DataLocation.ONCHAIN, linkedAttestationId: 0, + attestTimestamp: 0, + revokeTimestamp: 0, data: "", attester: prankSender, validUntil: uint64(block.timestamp), + dataLocation: DataLocation.ONCHAIN, revoked: false, recipients: _createMockRecipients() });