From adbb81db2ca5f10feb6bb0118d542cca31737d18 Mon Sep 17 00:00:00 2001 From: Alain Nicolas Date: Thu, 23 Nov 2023 23:50:39 +0100 Subject: [PATCH] feat: As a user, I want to differentiate the Attestations from each network --- .github/workflows/website-build.yml | 4 +- contracts/script/deploy/deployEverything.ts | 8 ++ contracts/script/updateChainPrefix.ts | 31 +++++ .../script/upgrade/forceUpgradeEverything.ts | 10 +- contracts/script/upgrade/upgradeEverything.ts | 10 +- contracts/script/utils.ts | 14 +++ contracts/src/AttestationRegistry.sol | 51 +++++++- contracts/test/AttestationRegistry.t.sol | 115 +++++++++++++++--- 8 files changed, 206 insertions(+), 37 deletions(-) create mode 100644 contracts/script/updateChainPrefix.ts create mode 100644 contracts/script/utils.ts diff --git a/.github/workflows/website-build.yml b/.github/workflows/website-build.yml index ababbd45..a4de6c61 100644 --- a/.github/workflows/website-build.yml +++ b/.github/workflows/website-build.yml @@ -1,4 +1,4 @@ -name: Deploy website +name: Build website on: pull_request: @@ -13,7 +13,7 @@ on: - release/* jobs: - website-deploy: + website-build: runs-on: ubuntu-latest defaults: diff --git a/contracts/script/deploy/deployEverything.ts b/contracts/script/deploy/deployEverything.ts index 236ceeee..219396ae 100644 --- a/contracts/script/deploy/deployEverything.ts +++ b/contracts/script/deploy/deployEverything.ts @@ -1,5 +1,6 @@ import { ethers, run, upgrades } from "hardhat"; import dotenv from "dotenv"; +import { getChainPrefix } from "../utils"; dotenv.config({ path: "../.env" }); @@ -135,6 +136,13 @@ async function main() { await attestationRegistry.updateRouter(routerProxyAddress); console.log("AttestationRegistry updated!"); + console.log("Updating AttestationRegistry with the chain prefix..."); + const network = await ethers.provider.getNetwork(); + const chainPrefix = getChainPrefix(network.chainId); + console.log(`Chain prefix for chain ID ${network.chainId} is ${chainPrefix}`); + await attestationRegistry.updateChainPrefix(chainPrefix); + console.log("AttestationRegistry updated!"); + console.log("Updating ModuleRegistry with the Router address..."); await moduleRegistry.updateRouter(routerProxyAddress); console.log("ModuleRegistry updated!"); diff --git a/contracts/script/updateChainPrefix.ts b/contracts/script/updateChainPrefix.ts new file mode 100644 index 00000000..81972998 --- /dev/null +++ b/contracts/script/updateChainPrefix.ts @@ -0,0 +1,31 @@ +import { ethers } from "hardhat"; +import dotenv from "dotenv"; +import { getChainPrefix } from "./utils"; + +dotenv.config({ path: "../.env" }); + +async function main() { + console.log("Updating AttestationRegistry with the chain prefix..."); + + const attestationProxyAddress = process.env.ATTESTATION_REGISTRY_ADDRESS; + if (!attestationProxyAddress) { + throw new Error("Attestation proxy address not found"); + } + + const attestationRegistry = await ethers.getContractAt("AttestationRegistry", attestationProxyAddress); + + const network = await ethers.provider.getNetwork(); + const chainPrefix = getChainPrefix(network.chainId); + console.log(`Chain prefix for chain ID ${network.chainId} is ${chainPrefix}`); + + await attestationRegistry.updateChainPrefix(chainPrefix); + + console.log("AttestationRegistry updated!"); +} + +// We recommend this pattern to be able to use async/await everywhere +// and properly handle errors. +main().catch((error) => { + console.error(error); + process.exitCode = 1; +}); diff --git a/contracts/script/upgrade/forceUpgradeEverything.ts b/contracts/script/upgrade/forceUpgradeEverything.ts index 7bcaf085..e2e5f141 100644 --- a/contracts/script/upgrade/forceUpgradeEverything.ts +++ b/contracts/script/upgrade/forceUpgradeEverything.ts @@ -11,27 +11,27 @@ async function main() { throw new Error("Router proxy address not found"); } - const attestationProxyAddress = process.env.ATTESTATION_REGISTRY_ADDRESS ?? ""; + const attestationProxyAddress = process.env.ATTESTATION_REGISTRY_ADDRESS; if (!attestationProxyAddress) { throw new Error("Attestation proxy address not found"); } - const moduleProxyAddress = process.env.MODULE_REGISTRY_ADDRESS ?? ""; + const moduleProxyAddress = process.env.MODULE_REGISTRY_ADDRESS; if (!moduleProxyAddress) { throw new Error("Module proxy address not found"); } - const portalProxyAddress = process.env.PORTAL_REGISTRY_ADDRESS ?? ""; + const portalProxyAddress = process.env.PORTAL_REGISTRY_ADDRESS; if (!portalProxyAddress) { throw new Error("Portal proxy address not found"); } - const schemaProxyAddress = process.env.SCHEMA_REGISTRY_ADDRESS ?? ""; + const schemaProxyAddress = process.env.SCHEMA_REGISTRY_ADDRESS; if (!schemaProxyAddress) { throw new Error("Schema proxy address not found"); } - const attestationReaderProxyAddress = process.env.ATTESTATION_READER_ADDRESS ?? ""; + const attestationReaderProxyAddress = process.env.ATTESTATION_READER_ADDRESS; if (!attestationReaderProxyAddress) { throw new Error("Attestation reader proxy address not found"); } diff --git a/contracts/script/upgrade/upgradeEverything.ts b/contracts/script/upgrade/upgradeEverything.ts index ad955222..d0599b4d 100644 --- a/contracts/script/upgrade/upgradeEverything.ts +++ b/contracts/script/upgrade/upgradeEverything.ts @@ -11,27 +11,27 @@ async function main() { throw new Error("Router proxy address not found"); } - const attestationProxyAddress = process.env.ATTESTATION_REGISTRY_ADDRESS ?? ""; + const attestationProxyAddress = process.env.ATTESTATION_REGISTRY_ADDRESS; if (!attestationProxyAddress) { throw new Error("Attestation proxy address not found"); } - const moduleProxyAddress = process.env.MODULE_REGISTRY_ADDRESS ?? ""; + const moduleProxyAddress = process.env.MODULE_REGISTRY_ADDRESS; if (!moduleProxyAddress) { throw new Error("Module proxy address not found"); } - const portalProxyAddress = process.env.PORTAL_REGISTRY_ADDRESS ?? ""; + const portalProxyAddress = process.env.PORTAL_REGISTRY_ADDRESS; if (!portalProxyAddress) { throw new Error("Portal proxy address not found"); } - const schemaProxyAddress = process.env.SCHEMA_REGISTRY_ADDRESS ?? ""; + const schemaProxyAddress = process.env.SCHEMA_REGISTRY_ADDRESS; if (!schemaProxyAddress) { throw new Error("Schema proxy address not found"); } - const attestationReaderProxyAddress = process.env.ATTESTATION_READER_ADDRESS ?? ""; + const attestationReaderProxyAddress = process.env.ATTESTATION_READER_ADDRESS; if (!attestationReaderProxyAddress) { throw new Error("Attestation reader proxy address not found"); } diff --git a/contracts/script/utils.ts b/contracts/script/utils.ts new file mode 100644 index 00000000..8bc7bc8f --- /dev/null +++ b/contracts/script/utils.ts @@ -0,0 +1,14 @@ +export const getChainPrefix = (chainId: bigint): bigint => { + switch (chainId) { + case 59140n: // Linea testnet + return 0n; + case 59144n: // Linea mainnet + return 0n; + case 421613n: // Arbitrum testnet + return 1000000000000000000000000000000000000000000000000000000000000n; + case 42161n: // Arbitrum mainnet + return 1000000000000000000000000000000000000000000000000000000000000n; + default: + throw new Error("Unknown network"); + } +}; diff --git a/contracts/src/AttestationRegistry.sol b/contracts/src/AttestationRegistry.sol index 8fd1b546..4e24c120 100644 --- a/contracts/src/AttestationRegistry.sol +++ b/contracts/src/AttestationRegistry.sol @@ -21,6 +21,8 @@ contract AttestationRegistry is OwnableUpgradeable { mapping(bytes32 attestationId => Attestation attestation) private attestations; + uint256 private chainPrefix; + /// @notice Error thrown when a non-portal tries to call a method that can only be called by a portal error OnlyPortal(); /// @notice Error thrown when an invalid Router address is given @@ -82,6 +84,14 @@ contract AttestationRegistry is OwnableUpgradeable { router = IRouter(_router); } + /** + * @notice Changes the chain prefix for the attestation IDs + * @dev Only the registry owner can call this method + */ + function updateChainPrefix(uint256 _chainPrefix) public onlyOwner { + chainPrefix = _chainPrefix; + } + /** * @notice Registers an attestation to the AttestationRegistry * @param attestationPayload the attestation payload to create attestation and register it @@ -98,7 +108,8 @@ contract AttestationRegistry is OwnableUpgradeable { if (attestationPayload.attestationData.length == 0) revert AttestationDataFieldEmpty(); // Auto increment attestation counter attestationIdCounter++; - bytes32 id = bytes32(abi.encode(attestationIdCounter)); + // Generate the full attestation ID, padded with the chain prefix + bytes32 id = generateAttestationId(attestationIdCounter); // Create attestation attestations[id] = Attestation( id, @@ -131,7 +142,8 @@ contract AttestationRegistry is OwnableUpgradeable { for (uint256 i = 0; i < attestationsPayloads.length; i = uncheckedInc256(i)) { // Auto increment attestation counter attestationIdCounter++; - bytes32 id = bytes32(abi.encode(attestationIdCounter)); + // Generate the full attestation ID, padded with the chain prefix + bytes32 id = generateAttestationId(attestationIdCounter); // Create attestation attestations[id] = Attestation( id, @@ -160,7 +172,7 @@ contract AttestationRegistry is OwnableUpgradeable { function replace(bytes32 attestationId, AttestationPayload calldata attestationPayload, address attester) public { attest(attestationPayload, attester); revoke(attestationId); - bytes32 replacedBy = bytes32(abi.encode(attestationIdCounter)); + bytes32 replacedBy = generateAttestationId(attestationIdCounter); attestations[attestationId].replacedBy = replacedBy; emit AttestationReplaced(attestationId, replacedBy); @@ -257,13 +269,21 @@ contract AttestationRegistry is OwnableUpgradeable { } /** - * @notice Gets the attestation id counter - * @return The attestationIdCounter + * @notice Gets the attestation counter + * @return The attestation counter */ function getAttestationIdCounter() public view returns (uint32) { return attestationIdCounter; } + /** + * @notice Gets the chain prefix used to generate the attestation IDs + * @return The chain prefix + */ + function getChainPrefix() public view returns (uint256) { + return chainPrefix; + } + /** * @notice Checks if an address owns a given attestation following ERC-1155 * @param account The address of the token holder @@ -271,7 +291,7 @@ contract AttestationRegistry is OwnableUpgradeable { * @return The _owner's balance of the attestations on a given attestation ID */ function balanceOf(address account, uint256 id) public view returns (uint256) { - bytes32 attestationId = bytes32(abi.encode(id)); + bytes32 attestationId = generateAttestationId(id); Attestation memory attestation = attestations[attestationId]; if (attestation.subject.length > 20 && keccak256(attestation.subject) == keccak256(abi.encode(account))) { return 1; @@ -296,4 +316,23 @@ contract AttestationRegistry is OwnableUpgradeable { } return result; } + + /** + * @notice Generate an attestation ID, prefixed by the Verax chain identifier + * @param id The attestation ID (coming after the chain prefix) + * @return The attestation ID + */ + function generateAttestationId(uint256 id) public view returns (bytes32) { + // Convert the basic attestation ID to a bytes32 value + bytes32 originalCounterValue = bytes32(abi.encode(id)); + + // Clear the first byte of the original bytes32 to make room for the chain ID + bytes32 clearedCounterValue = (originalCounterValue << 8) >> 8; + + // Shift the chain ID to the left by 240 bits to position it as the first byte + bytes32 shiftedChainPrefix = bytes32(chainPrefix) << 240; + + // Combine the shifted chain ID and the cleared counter value to form the attestation ID + return shiftedChainPrefix | clearedCounterValue; + } } diff --git a/contracts/test/AttestationRegistry.t.sol b/contracts/test/AttestationRegistry.t.sol index 25cbace5..bc1345bc 100644 --- a/contracts/test/AttestationRegistry.t.sol +++ b/contracts/test/AttestationRegistry.t.sol @@ -17,6 +17,7 @@ contract AttestationRegistryTest is Test { AttestationRegistry public attestationRegistry; address public portalRegistryAddress; address public schemaRegistryAddress; + uint256 public initialChainPrefix = 3; event Initialized(uint8 version); event AttestationRegistered(bytes32 indexed attestationId); @@ -37,6 +38,8 @@ contract AttestationRegistryTest is Test { schemaRegistryAddress = address(new SchemaRegistryMock()); vm.prank(address(0)); attestationRegistry.updateRouter(address(router)); + vm.prank(address(0)); + attestationRegistry.updateChainPrefix(initialChainPrefix); router.updatePortalRegistry(portalRegistryAddress); router.updateSchemaRegistry(schemaRegistryAddress); @@ -44,6 +47,28 @@ contract AttestationRegistryTest is Test { PortalRegistry(portalRegistryAddress).register(portal, "Name", "Description", true, "Owner name"); } + function test_setup() public { + // Check Router address + address routerAddress = address(attestationRegistry.router()); + assertEq(routerAddress, address(router)); + + // Check Chain Prefix value + uint256 chainPrefix = attestationRegistry.getChainPrefix(); + assertEq(chainPrefix, initialChainPrefix); + + // Check AttestationRegistry address + address testAttestationRegistry = address(router.getAttestationRegistry()); + assertEq(testAttestationRegistry, address(attestationRegistry)); + + // Check PortalRegistry address + address testPortalRegistry = address(router.getPortalRegistry()); + assertEq(testPortalRegistry, portalRegistryAddress); + + // Check SchemaRegistry address + address testSchemaRegistry = address(router.getSchemaRegistry()); + assertEq(testSchemaRegistry, schemaRegistryAddress); + } + function test_initialize_ContractAlreadyInitialized() public { vm.expectRevert("Initializable: contract is already initialized"); attestationRegistry.initialize(); @@ -58,6 +83,22 @@ contract AttestationRegistryTest is Test { assertEq(routerAddress, address(1)); } + function test_updateChainPrefix() public { + AttestationRegistry testAttestationRegistry = new AttestationRegistry(); + + vm.prank(address(0)); + testAttestationRegistry.updateChainPrefix(initialChainPrefix); + + uint256 chainPrefix = testAttestationRegistry.getChainPrefix(); + assertEq(chainPrefix, initialChainPrefix); + + vm.prank(address(0)); + testAttestationRegistry.updateChainPrefix(1000000000000000000000000000000000000000000000000000000000000); + + chainPrefix = testAttestationRegistry.getChainPrefix(); + assertEq(chainPrefix, 1000000000000000000000000000000000000000000000000000000000000); + } + function test_updateRouter_InvalidParameter() public { AttestationRegistry testAttestationRegistry = new AttestationRegistry(); @@ -165,9 +206,9 @@ contract AttestationRegistryTest is Test { payloadsToAttest[1] = attestationsPayloads[1]; vm.expectEmit(true, true, true, true); - emit AttestationRegistered(bytes32(abi.encode(1))); + emit AttestationRegistered(attestationRegistry.generateAttestationId(1)); vm.expectEmit(true, true, true, true); - emit AttestationRegistered(bytes32(abi.encode(2))); + emit AttestationRegistered(attestationRegistry.generateAttestationId(2)); vm.prank(address(0)); attestationRegistry.massImport(payloadsToAttest, portal); } @@ -183,9 +224,11 @@ contract AttestationRegistryTest is Test { vm.prank(portal); attestationRegistry.attest(attestationPayload, attester); + bytes32 attestationIdReplacing = attestationRegistry.generateAttestationId(2); + vm.prank(portal); vm.expectEmit(true, true, true, true); - emit AttestationReplaced(attestation.attestationId, bytes32(abi.encode(2))); + emit AttestationReplaced(attestation.attestationId, attestationIdReplacing); attestationRegistry.replace(attestation.attestationId, attestationPayload, attester); } @@ -244,7 +287,7 @@ contract AttestationRegistryTest is Test { bytes32[] memory attestationIds = new bytes32[](3); attestationIds[0] = attestation1.attestationId; attestationIds[1] = attestation2.attestationId; - attestationIds[2] = bytes32(abi.encode(3)); + attestationIds[2] = attestationRegistry.generateAttestationId((3)); vm.startPrank(portal); attestationRegistry.bulkAttest(payloadsToAttest, attester); @@ -262,10 +305,10 @@ contract AttestationRegistryTest is Test { vm.startPrank(portal, attester); attestationRegistry.attest(attestationPayload, attester); // Assert initial state is a valid attestation - bytes32 attestationId = bytes32(abi.encode(1)); + bytes32 attestationId = attestationRegistry.generateAttestationId((1)); Attestation memory registeredAttestation = attestationRegistry.getAttestation(attestationId); - assertEq(registeredAttestation.attestationId, bytes32(abi.encode(1))); + assertEq(registeredAttestation.attestationId, attestationRegistry.generateAttestationId(1)); assertFalse(registeredAttestation.revoked); assertEq(registeredAttestation.revocationDate, 0); assertEq(registeredAttestation.portal, portal); @@ -277,7 +320,9 @@ contract AttestationRegistryTest is Test { vm.stopPrank(); // Assert final state is a revoked attestation - Attestation memory revokedAttestation = attestationRegistry.getAttestation(bytes32(abi.encode(1))); + Attestation memory revokedAttestation = attestationRegistry.getAttestation( + attestationRegistry.generateAttestationId(1) + ); assertTrue(revokedAttestation.revoked); assertEq(revokedAttestation.revocationDate, block.timestamp); } @@ -291,19 +336,22 @@ contract AttestationRegistryTest is Test { vm.startPrank(portal, attester); attestationRegistry.attest(attestationPayload, attester); + bytes32 attestationIdToRevoke = attestationRegistry.generateAttestationId(1); + vm.expectEmit(); - emit AttestationRevoked(bytes32(abi.encode(1))); - attestationRegistry.revoke(bytes32(abi.encode(1))); + emit AttestationRevoked(attestationIdToRevoke); + attestationRegistry.revoke(attestationIdToRevoke); vm.expectRevert(AttestationRegistry.AlreadyRevoked.selector); - attestationRegistry.revoke(bytes32(abi.encode(1))); + attestationRegistry.revoke(attestationIdToRevoke); vm.stopPrank(); } function test_revoke_AttestationNotAttested() public { + bytes32 attestationIdToRevoke = attestationRegistry.generateAttestationId(1); vm.expectRevert(AttestationRegistry.AttestationNotAttested.selector); - attestationRegistry.revoke(bytes32(abi.encode(1))); + attestationRegistry.revoke(attestationIdToRevoke); } function test_revoke_OnlyAttestingPortal(AttestationPayload memory attestationPayload) public { @@ -316,9 +364,10 @@ contract AttestationRegistryTest is Test { vm.prank(portal); attestationRegistry.attest(attestationPayload, attester); + bytes32 attestationIdToRevoke = attestationRegistry.generateAttestationId(1); vm.expectRevert(AttestationRegistry.OnlyAttestingPortal.selector); vm.prank(user); - attestationRegistry.revoke(bytes32(abi.encode(1))); + attestationRegistry.revoke(attestationIdToRevoke); } function test_revoke_AttestationNotRevocable(AttestationPayload memory attestationPayload) public { @@ -341,8 +390,10 @@ contract AttestationRegistryTest is Test { attestationRegistry.attest(attestationPayload, attester); + bytes32 attestationIdToRevoke = attestationRegistry.generateAttestationId(1); + vm.expectRevert(AttestationRegistry.AttestationNotRevocable.selector); - attestationRegistry.revoke(bytes32(abi.encode(1))); + attestationRegistry.revoke(attestationIdToRevoke); vm.stopPrank(); } @@ -368,9 +419,9 @@ contract AttestationRegistryTest is Test { attestationRegistry.attest(attestationPayloads[1], attester); attestationRegistry.attest(attestationPayloads[2], attester); - bytes32 attestationId1 = bytes32(abi.encode(1)); - bytes32 attestationId2 = bytes32(abi.encode(2)); - bytes32 attestationId3 = bytes32(abi.encode(3)); + bytes32 attestationId1 = attestationRegistry.generateAttestationId((1)); + bytes32 attestationId2 = attestationRegistry.generateAttestationId((2)); + bytes32 attestationId3 = attestationRegistry.generateAttestationId((3)); // Assert initial state is a valid attestation Attestation memory registeredAttestation1 = attestationRegistry.getAttestation(attestationId1); @@ -430,7 +481,7 @@ contract AttestationRegistryTest is Test { function test_isRegistered(AttestationPayload memory attestationPayload) public { vm.assume(attestationPayload.subject.length != 0); vm.assume(attestationPayload.attestationData.length != 0); - bool isRegistered = attestationRegistry.isRegistered(bytes32(abi.encode(1))); + bool isRegistered = attestationRegistry.isRegistered(attestationRegistry.generateAttestationId(1)); assertFalse(isRegistered); SchemaRegistryMock schemaRegistryMock = SchemaRegistryMock(router.getSchemaRegistry()); attestationPayload.schemaId = schemaRegistryMock.getIdFromSchemaString("schemaString"); @@ -461,8 +512,9 @@ contract AttestationRegistryTest is Test { } function test_getAttestation_AttestationNotAttested() public { + bytes32 attestationIdNotAttested = attestationRegistry.generateAttestationId((1)); vm.expectRevert(AttestationRegistry.AttestationNotAttested.selector); - attestationRegistry.getAttestation(bytes32(abi.encode(1))); + attestationRegistry.getAttestation(attestationIdNotAttested); } function test_incrementVersionNumber() public { @@ -503,6 +555,21 @@ contract AttestationRegistryTest is Test { vm.stopPrank(); } + function test_balanceOf_notFound(AttestationPayload memory attestationPayload) public { + vm.assume(attestationPayload.subject.length != 0); + vm.assume(attestationPayload.attestationData.length != 0); + SchemaRegistryMock schemaRegistryMock = SchemaRegistryMock(router.getSchemaRegistry()); + attestationPayload.schemaId = schemaRegistryMock.getIdFromSchemaString("schemaString"); + schemaRegistryMock.createSchema("name", "description", "context", "schemaString"); + + vm.startPrank(portal); + attestationPayload.subject = abi.encode(address(1234)); + attestationRegistry.attest(attestationPayload, attester); + + uint256 balance = attestationRegistry.balanceOf(address(1), 1); + assertEq(balance, 0); + } + function test_balanceOf_subjectEncodedAddress(AttestationPayload memory attestationPayload) public { vm.assume(attestationPayload.subject.length != 0); vm.assume(attestationPayload.attestationData.length != 0); @@ -569,6 +636,14 @@ contract AttestationRegistryTest is Test { attestationRegistry.balanceOfBatch(owners, ids); } + function test_attestationRegistry() public { + bytes32 attestationId = attestationRegistry.generateAttestationId(0); + assertEq(attestationId, 0x0003000000000000000000000000000000000000000000000000000000000000); + + attestationId = attestationRegistry.generateAttestationId(10); + assertEq(attestationId, 0x000300000000000000000000000000000000000000000000000000000000000a); + } + function _createAttestation( AttestationPayload memory attestationPayload, uint256 id @@ -576,8 +651,9 @@ contract AttestationRegistryTest is Test { uint16 versionNumber = attestationRegistry.getVersionNumber(); SchemaRegistryMock schemaRegistryMock = SchemaRegistryMock(router.getSchemaRegistry()); schemaRegistryMock.createSchema("name", "description", "context", "schemaString"); + Attestation memory attestation = Attestation( - bytes32(abi.encode(id)), + attestationRegistry.generateAttestationId(id), attestationPayload.schemaId, bytes32(0), attester, @@ -590,6 +666,7 @@ contract AttestationRegistryTest is Test { attestationPayload.subject, attestationPayload.attestationData ); + return attestation; }