Skip to content

Commit

Permalink
feat: add replace attestation feature (#196)
Browse files Browse the repository at this point in the history
Co-authored-by: Alain Nicolas <[email protected]>
  • Loading branch information
0xEillo and alainncls authored Sep 15, 2023
1 parent f173c0c commit 4aac392
Show file tree
Hide file tree
Showing 9 changed files with 283 additions and 130 deletions.
62 changes: 45 additions & 17 deletions src/AttestationRegistry.sol
Original file line number Diff line number Diff line change
Expand Up @@ -34,17 +34,19 @@ contract AttestationRegistry is OwnableUpgradeable {
error AttestationSubjectFieldEmpty();
/// @notice Error thrown when an attestation data field is empty
error AttestationDataFieldEmpty();
/// @notice Error thrown when an attempt is made to bulk replace with mismatched parameter array lengths
error ArrayLengthMismatch();
/// @notice Error thrown when an attempt is made to revoke an attestation that was already revoked
error AlreadyRevoked();
/// @notice Error thrown when an attempt is made to revoke an attestation based on a non-revocable schema
error AttestationNotRevocable();
/// @notice Error thrown when attempting to bulk revoke attestations without the same number of replacing attestations
error BulkRevocationInvalidParams();

/// @notice Event emitted when an attestation is registered
event AttestationRegistered(bytes32 indexed attestationId);
/// @notice Event emitted when an attestation is replaced
event AttestationReplaced(bytes32 attestationId, bytes32 replacedBy);
/// @notice Event emitted when an attestation is revoked
event AttestationRevoked(bytes32 attestationId, bytes32 replacedBy);
event AttestationRevoked(bytes32 attestationId);
/// @notice Event emitted when the version number is incremented
event VersionUpdated(uint16 version);

Expand Down Expand Up @@ -147,11 +149,42 @@ contract AttestationRegistry is OwnableUpgradeable {
}

/**
* @notice Revokes an attestation for given identifier and can replace it by an other one
* @param attestationId the attestation ID to revoke
* @param replacedBy the replacing attestation ID (leave empty to just revoke)
* @notice Replaces an attestation for the given identifier and replaces it with a new attestation
* @param attestationId the ID of the attestation to replace
* @param attestationPayload the attestation payload to create the new attestation and register it
* @param attester the account address issuing the attestation
*/
function replace(bytes32 attestationId, AttestationPayload calldata attestationPayload, address attester) public {
attest(attestationPayload, attester);
revoke(attestationId);
bytes32 replacedBy = bytes32(abi.encode(attestationIdCounter));
attestations[attestationId].replacedBy = replacedBy;

emit AttestationReplaced(attestationId, replacedBy);
}

/**
* @notice Replaces attestations for given identifiers and replaces them with new attestations
* @param attestationIds the list of IDs of the attestations to replace
* @param attestationPayloads the list of attestation payloads to create the new attestations and register them
* @param attester the account address issuing the attestation
*/
function revoke(bytes32 attestationId, bytes32 replacedBy) public {
function bulkReplace(
bytes32[] calldata attestationIds,
AttestationPayload[] calldata attestationPayloads,
address attester
) public {
if (attestationIds.length != attestationPayloads.length) revert ArrayLengthMismatch();
for (uint256 i = 0; i < attestationIds.length; i++) {
replace(attestationIds[i], attestationPayloads[i], attester);
}
}

/**
* @notice Revokes an attestation for a given identifier
* @param attestationId the ID of the attestation to revoke
*/
function revoke(bytes32 attestationId) public {
if (!isRegistered(attestationId)) revert AttestationNotAttested();
if (attestations[attestationId].revoked) revert AlreadyRevoked();
if (msg.sender != attestations[attestationId].portal) revert OnlyAttestingPortal();
Expand All @@ -160,21 +193,16 @@ contract AttestationRegistry is OwnableUpgradeable {
attestations[attestationId].revoked = true;
attestations[attestationId].revocationDate = uint64(block.timestamp);

if (isRegistered(replacedBy)) attestations[attestationId].replacedBy = replacedBy;

emit AttestationRevoked(attestationId, replacedBy);
emit AttestationRevoked(attestationId);
}

/**
* @notice Bulk revokes attestations for given identifiers and can replace them by new ones
* @param attestationIds the attestations IDs to revoke
* @param replacedBy the replacing attestations IDs (leave an ID empty to just revoke)
* @notice Bulk revokes a list of attestations for the given identifiers
* @param attestationIds the IDs of the attestations to revoke
*/
function bulkRevoke(bytes32[] memory attestationIds, bytes32[] memory replacedBy) external {
if (attestationIds.length != replacedBy.length) revert BulkRevocationInvalidParams();

function bulkRevoke(bytes32[] memory attestationIds) external {
for (uint256 i = 0; i < attestationIds.length; i++) {
revoke(attestationIds[i], replacedBy[i]);
revoke(attestationIds[i]);
}
}

Expand Down
4 changes: 2 additions & 2 deletions src/example/EASPortal.sol
Original file line number Diff line number Diff line change
Expand Up @@ -74,11 +74,11 @@ contract EASPortal is AbstractPortal {
}
}

function revoke(bytes32 /*attestationId*/, bytes32 /*replacedBy*/) public pure override {
function _onRevoke(bytes32 /*attestationId*/) internal pure override {
revert NoRevocation();
}

function bulkRevoke(bytes32[] memory /*attestationIds*/, bytes32[] memory /*replacedBy*/) public pure override {
function _onBulkRevoke(bytes32[] memory /*attestationIds*/) internal pure override {
revert NoBulkRevocation();
}

Expand Down
97 changes: 70 additions & 27 deletions src/interface/AbstractPortal.sol
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,7 @@ abstract contract AbstractPortal is Initializable, IERC165Upgradeable {
* @param validationPayloads the payloads to validate via the modules to issue the attestations
* @dev Runs all modules for the portal and registers the attestation using AttestationRegistry
*/
function attest(
AttestationPayload memory attestationPayload,
bytes[] memory validationPayloads
) public payable virtual {
function attest(AttestationPayload memory attestationPayload, bytes[] memory validationPayloads) public payable {
moduleRegistry.runModules(modules, attestationPayload, validationPayloads, msg.value);

_onAttest(attestationPayload);
Expand All @@ -66,40 +63,71 @@ abstract contract AbstractPortal is Initializable, IERC165Upgradeable {
function bulkAttest(
AttestationPayload[] memory attestationsPayloads,
bytes[][] memory validationPayloads
) public payable virtual {
// Run all modules for all payloads
) public payable {
moduleRegistry.bulkRunModules(modules, attestationsPayloads, validationPayloads);

_onBulkAttest(attestationsPayloads, validationPayloads);

// Register attestations using the attestation registry
attestationRegistry.bulkAttest(attestationsPayloads, _getAttester());
}

/**
* @notice Revokes the attestation for the given identifier and can replace it by a new one
* @param attestationId the attestation ID to revoke
* @param replacedBy the replacing attestation ID (leave empty to just revoke)
* @notice Replaces the attestation for the given identifier and replaces it with a new attestation
* @param attestationId the ID of the attestation to replace
* @param attestationPayload the attestation payload to create the new attestation and register it
* @param validationPayloads the payloads to validate via the modules to issue the attestation
* @dev Runs all modules for the portal and registers the attestation using AttestationRegistry
*/
function replace(
bytes32 attestationId,
AttestationPayload memory attestationPayload,
bytes[] memory validationPayloads
) public payable {
moduleRegistry.runModules(modules, attestationPayload, validationPayloads, msg.value);

_onReplace(attestationId, attestationPayload);

attestationRegistry.replace(attestationId, attestationPayload, _getAttester());
}

/**
* @notice Bulk replaces the attestation for the given identifiers and replaces them with new attestations
* @param attestationIds the list of IDs of the attestations to replace
* @param attestationsPayloads the list of attestation payloads to create the new attestations and register them
* @param validationPayloads the payloads to validate via the modules to issue the attestations
*/
function bulkReplace(
bytes32[] memory attestationIds,
AttestationPayload[] memory attestationsPayloads,
bytes[][] memory validationPayloads
) public payable {
moduleRegistry.bulkRunModules(modules, attestationsPayloads, validationPayloads);

_onBulkReplace(attestationIds, attestationsPayloads, validationPayloads);

attestationRegistry.bulkReplace(attestationIds, attestationsPayloads, _getAttester());
}

/**
* @notice Revokes an attestation for the given identifier
* @param attestationId the ID of the attestation to revoke
* @dev By default, revocation is only possible by the portal owner
* We strongly encourage implementing such a rule in your Portal if you intend on overriding this method
*/
function revoke(bytes32 attestationId, bytes32 replacedBy) public virtual {
if (msg.sender != portalRegistry.getPortalByAddress(address(this)).ownerAddress) revert OnlyPortalOwner();
function revoke(bytes32 attestationId) public {
_onRevoke(attestationId);

_onRevoke(attestationId, replacedBy);

attestationRegistry.revoke(attestationId, replacedBy);
attestationRegistry.revoke(attestationId);
}

/**
* @notice Bulk revokes attestations for given identifiers and can replace them by new ones
* @param attestationIds the attestations IDs to revoke
* @param replacedBy the replacing attestations IDs (leave an ID empty to just revoke)
* @notice Bulk revokes a list of attestations for the given identifiers
* @param attestationIds the IDs of the attestations to revoke
*/
function bulkRevoke(bytes32[] memory attestationIds, bytes32[] memory replacedBy) public virtual {
_onBulkRevoke(attestationIds, replacedBy);
function bulkRevoke(bytes32[] memory attestationIds) public {
_onBulkRevoke(attestationIds);

attestationRegistry.bulkRevoke(attestationIds, replacedBy);
attestationRegistry.bulkRevoke(attestationIds);
}

/**
Expand Down Expand Up @@ -133,6 +161,13 @@ abstract contract AbstractPortal is Initializable, IERC165Upgradeable {
*/
function _onAttest(AttestationPayload memory attestationPayload) internal virtual {}

/**
* @notice Optional method run when an attestation is replaced
* @param attestationId the ID of the attestation being replaced
* @param attestationPayload the attestation payload to create attestation and register it
*/
function _onReplace(bytes32 attestationId, AttestationPayload memory attestationPayload) internal virtual {}

/**
* @notice Optional method run when attesting a batch of payloads
* @param attestationsPayloads the payloads to attest
Expand All @@ -143,17 +178,25 @@ abstract contract AbstractPortal is Initializable, IERC165Upgradeable {
bytes[][] memory validationPayloads
) internal virtual {}

function _onBulkReplace(
bytes32[] memory attestationIds,
AttestationPayload[] memory attestationsPayloads,
bytes[][] memory validationPayloads
) internal virtual {}

/**
* @notice Optional method run when an attestation is revoked or replaced
* @param attestationId the attestation ID to revoke
* @param replacedBy the replacing attestation ID
* @dev IMPORTANT NOTE: By default, revocation is only possible by the portal owner
*/
function _onRevoke(bytes32 attestationId, bytes32 replacedBy) internal virtual {}
function _onRevoke(bytes32 /*attestationId*/) internal virtual {
if (msg.sender != portalRegistry.getPortalByAddress(address(this)).ownerAddress) revert OnlyPortalOwner();
}

/**
* @notice Optional method run when a batch of attestations are revoked or replaced
* @param attestationIds the attestations IDs to revoke
* @param replacedBy the replacing attestations IDs
* @dev IMPORTANT NOTE: By default, revocation is only possible by the portal owner
*/
function _onBulkRevoke(bytes32[] memory attestationIds, bytes32[] memory replacedBy) internal virtual {}
function _onBulkRevoke(bytes32[] memory /*attestationIds*/) internal virtual {
if (msg.sender != portalRegistry.getPortalByAddress(address(this)).ownerAddress) revert OnlyPortalOwner();
}
}
1 change: 0 additions & 1 deletion src/portal/DefaultPortal.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
pragma solidity 0.8.21;

import { AbstractPortal } from "../interface/AbstractPortal.sol";
import { AttestationPayload } from "../types/Structs.sol";

/**
* @title Default Portal
Expand Down
Loading

0 comments on commit 4aac392

Please sign in to comment.