├── .env.example ├── .env.local ├── .env.prod ├── .env.upgrade ├── .gas-snapshot ├── .gitattributes ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature.md ├── PULL_REQUEST_TEMPLATE.md └── workflows │ └── ci.yml ├── .gitignore ├── .gitmodules ├── .lintstagedrc ├── .rusty-hook.toml ├── .solhint.json ├── .solhintignore ├── .vscode └── settings.json ├── CONTRIBUTING.md ├── Cargo.toml ├── Dockerfile.foundry ├── LICENSE.md ├── README.md ├── build.rs ├── docker-compose.yml ├── docs ├── audits │ └── 2023-11-05-cyfrin-farcaster-v1.0.pdf └── docs.md ├── foundry.toml ├── main.rs ├── script ├── DeployL1.s.sol ├── DeployL2.s.sol ├── DeployTierRegistry.s.sol ├── FnameResolver.s.sol ├── IdRegistry.s.sol ├── KeyRegistry.s.sol ├── LocalDeploy.s.sol ├── StorageRegistry.s.sol ├── UpgradeBundler.s.sol ├── UpgradeL2.s.sol └── abstract │ └── ImmutableCreate2Deployer.sol ├── slither.config.json ├── src ├── Bundler.sol ├── BundlerV1.sol ├── FnameResolver.sol ├── IdGateway.sol ├── IdRegistry.sol ├── KeyGateway.sol ├── KeyRegistry.sol ├── RecoveryProxy.sol ├── StorageRegistry.sol ├── TierRegistry.sol ├── abstract │ ├── EIP712.sol │ ├── Guardians.sol │ ├── Migration.sol │ ├── Nonces.sol │ └── Signatures.sol ├── interfaces │ ├── IBundler.sol │ ├── IBundlerV1.sol │ ├── IIdGateway.sol │ ├── IIdRegistry.sol │ ├── IKeyGateway.sol │ ├── IKeyRegistry.sol │ ├── IMetadataValidator.sol │ ├── IStorageRegistry.sol │ ├── ITierRegistry.sol │ ├── IdRegistryLike.sol │ └── abstract │ │ ├── IEIP712.sol │ │ ├── IGuardians.sol │ │ ├── IMigration.sol │ │ ├── INonces.sol │ │ └── ISignatures.sol ├── libraries │ ├── EnumerableKeySet.sol │ └── TransferHelper.sol └── validators │ └── SignedKeyRequestValidator.sol └── test ├── Bundler ├── Bundler.t.sol └── BundlerTestSuite.sol ├── BundlerV1 ├── BundlerV1.gas.t.sol ├── BundlerV1.t.sol └── BundlerV1TestSuite.sol ├── Deploy ├── AuthKeys.t.sol ├── DeployL1.t.sol ├── DeployL2.t.sol ├── DeployTierRegistry.t.sol ├── UpgradeBundler.t.sol └── UpgradeL2.t.sol ├── FnameResolver ├── FnameResolver.t.sol └── FnameResolverTestSuite.sol ├── IdGateway ├── IdGateway.gas.t.sol ├── IdGateway.owner.t.sol ├── IdGateway.t.sol └── IdGatewayTestSuite.sol ├── IdRegistry ├── IdRegistry.migration.t.sol ├── IdRegistry.owner.t.sol ├── IdRegistry.symbolic.t.sol ├── IdRegistry.t.sol ├── IdRegistryTestHelpers.sol └── IdRegistryTestSuite.sol ├── KeyGateway ├── KeyGateway.t.sol └── KeyGatewayTestSuite.sol ├── KeyRegistry ├── KeyRegistry.integration.t.sol ├── KeyRegistry.migration.t.sol ├── KeyRegistry.symbolic.t.sol ├── KeyRegistry.t.sol ├── KeyRegistryTestHelpers.sol ├── KeyRegistryTestSuite.sol └── utils │ └── KeyRegistryHarness.sol ├── RecoveryProxy ├── RecoveryProxy.t.sol └── RecoveryProxyTestSuite.sol ├── StorageRegistry ├── StorageRegistry.gas.t.sol ├── StorageRegistry.t.sol └── StorageRegistryTestSuite.sol ├── TestSuiteSetup.sol ├── TierRegistry ├── TierRegistry.t.sol └── TierRegistryTestSuite.sol ├── Utils.sol ├── abstract ├── EIP712 │ └── EIP712.t.sol ├── Guardians │ └── Guardians.symbolic.t.sol └── Migration │ └── Migration.symbolic.t.sol └── validators └── SignedKeyRequestValidator ├── SignedKeyRequestValidator.t.sol └── SignedKeyRequestValidatorTestSuite.sol /.env.example: -------------------------------------------------------------------------------- 1 | # Price feed addresses 2 | STORAGE_RENT_PRICE_FEED_ADDRESS=0x13e3ee699d1909e989722e753853ae30b17e08c5 3 | STORAGE_RENT_UPTIME_FEED_ADDRESS=0x371EAD81c9102C9BF4874A9075FFFf170F2Ee389 4 | 5 | # Storage rent params 6 | STORAGE_RENT_ROLE_ADMIN_ADDRESS=0x53c6dA835c777AD11159198FBe11f95E5eE6B692 7 | STORAGE_RENT_VAULT_ADDRESS=0x53c6dA835c777AD11159198FBe11f95E5eE6B692 8 | STORAGE_RENT_ADMIN_ADDRESS=0xD84E32224A249A575A09672Da9cb58C381C4837a 9 | STORAGE_RENT_OPERATOR_ADDRESS=0x0000000000000000000000000000000000000000 10 | STORAGE_RENT_TREASURER_ADDRESS=0x0000000000000000000000000000000000000000 11 | 12 | # ID registry params 13 | ID_REGISTRY_OWNER_ADDRESS=0x53c6dA835c777AD11159198FBe11f95E5eE6B692 14 | 15 | # Key registry params 16 | KEY_REGISTRY_OWNER_ADDRESS=0x2D93c2F74b2C4697f9ea85D0450148AA45D4D5a2 17 | 18 | # Metadata validator params 19 | METADATA_VALIDATOR_OWNER_ADDRESS=0x53c6dA835c777AD11159198FBe11f95E5eE6B692 20 | 21 | # Bundler params 22 | BUNDLER_TRUSTED_CALLER_ADDRESS=0x2D93c2F74b2C4697f9ea85D0450148AA45D4D5a2 23 | BUNDLER_OWNER_ADDRESS=0x53c6dA835c777AD11159198FBe11f95E5eE6B692 24 | 25 | # Recovery proxy params 26 | RECOVERY_PROXY_OWNER_ADDRESS=0xFFE52568Fb0E7038Ef289677288BB704E5c9E82e 27 | 28 | # Fname resolver params. 29 | FNAME_RESOLVER_SERVER_URL=https://fnames.farcaster.xyz/ccip/{sender}/{data}.json 30 | FNAME_RESOLVER_SIGNER_ADDRESS=0xBc5274eFc266311015793d89E9B591fa46294741 31 | FNAME_RESOLVER_OWNER_ADDRESS=0x138356f24c7A16BE48978dE277a468F6C16A19a5 32 | 33 | # Tier registry params 34 | TIER_REGISTRY_VAULT_ADDRESS=0x53c6dA835c777AD11159198FBe11f95E5eE6B692 35 | TIER_REGISTRY_OWNER_ADDRESS=0x53c6dA835c777AD11159198FBe11f95E5eE6B692 36 | TIER_REGISTRY_MIGRATOR_ADDRESS=0x53c6dA835c777AD11159198FBe11f95E5eE6B692 37 | 38 | # RPC endpoints 39 | ETH_MAINNET_RPC_URL= 40 | OP_MAINNET_RPC_URL= 41 | BASE_MAINNET_RPC_URL= 42 | 43 | # Salts 44 | STORAGE_RENT_CREATE2_SALT=0x6d2b70e39c6bc63763098e336323591eb77cd0c65360d99ba6ea4e0161f2b96c 45 | ID_REGISTRY_CREATE2_SALT=0x6d2b70e39c6bc63763098e336323591eb77cd0c63e0688f6d95afa008febf4d7 46 | KEY_REGISTRY_CREATE2_SALT=0x6d2b70e39c6bc63763098e336323591eb77cd0c62af4de6e1f0355029f357f47 47 | SIGNED_KEY_REQUEST_VALIDATOR_CREATE2_SALT=0x6d2b70e39c6bc63763098e336323591eb77cd0c6610c0841333604016684800c 48 | BUNDLER_CREATE2_SALT=0x6d2b70e39c6bc63763098e336323591eb77cd0c6e451fc0a34ec4c008c9a31fa 49 | RECOVERY_PROXY_CREATE2_SALT=0x6d2b70e39c6bc63763098e336323591eb77cd0c6110eaaca06f77900dac1cad3 50 | TIER_REGISTRY_CREATE2_SALT=0x6d2b70e39c6bc63763098e336323591eb77cd0c6110eaaca06f77900dac1cad3 51 | 52 | # Deployer address. 53 | DEPLOYER=0x6D2b70e39C6bc63763098e336323591eb77Cd0C6 54 | 55 | # Default migrator address. 56 | MIGRATOR_ADDRESS=0x2D93c2F74b2C4697f9ea85D0450148AA45D4D5a2 57 | -------------------------------------------------------------------------------- /.env.local: -------------------------------------------------------------------------------- 1 | # Price feed addresses 2 | # These defaults are the Optimism mainnet addresses. 3 | STORAGE_RENT_PRICE_FEED_ADDRESS=0x13e3ee699d1909e989722e753853ae30b17e08c5 4 | STORAGE_RENT_UPTIME_FEED_ADDRESS=0x371EAD81c9102C9BF4874A9075FFFf170F2Ee389 5 | 6 | # Storage rent params 7 | # Default address is Anvil test account 1 8 | STORAGE_RENT_ROLE_ADMIN_ADDRESS=0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 9 | STORAGE_RENT_VAULT_ADDRESS=0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 10 | STORAGE_RENT_ADMIN_ADDRESS=0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 11 | STORAGE_RENT_OPERATOR_ADDRESS=0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 12 | STORAGE_RENT_TREASURER_ADDRESS=0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 13 | 14 | # ID registry params 15 | # Default address is Anvil test account 1 16 | ID_REGISTRY_OWNER_ADDRESS=0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 17 | 18 | # Key registry params 19 | # Default address is Anvil test account 1 20 | KEY_REGISTRY_OWNER_ADDRESS=0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 21 | 22 | # Metadata validator params 23 | # Default address is Anvil test account 1 24 | METADATA_VALIDATOR_OWNER_ADDRESS=0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 25 | 26 | # Bundler params 27 | # Default addresses are Anvil test account 1 28 | BUNDLER_TRUSTED_CALLER_ADDRESS=0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 29 | BUNDLER_OWNER_ADDRESS=0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 30 | 31 | # RecoveryProxy params 32 | # Default addresses are Anvil test account 1 33 | RECOVERY_PROXY_OWNER_ADDRESS=0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 34 | 35 | # Fname resolver params. 36 | # Default owner is Anvil test account 1 37 | FNAME_RESOLVER_SERVER_URL=https://fnames.farcaster.xyz/ccip/{sender}/{data}.json 38 | FNAME_RESOLVER_SIGNER_ADDRESS=0xBc5274eFc266311015793d89E9B591fa46294741 39 | FNAME_RESOLVER_OWNER_ADDRESS=0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 40 | 41 | # Tier registry params 42 | TIER_REGISTRY_VAULT_ADDRESS=0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 43 | TIER_REGISTRY_OWNER_ADDRESS=0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 44 | TIER_REGISTRY_MIGRATOR_ADDRESS=0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 45 | 46 | # RPC endpoints 47 | ETH_MAINNET_RPC_URL= 48 | OP_MAINNET_RPC_URL= 49 | BASE_MAINNET_RPC_URL= 50 | 51 | STORAGE_RENT_CREATE2_SALT=0x0000000000000000000000000000000000000000000000000000000000000000 52 | ID_REGISTRY_CREATE2_SALT=0x0000000000000000000000000000000000000000000000000000000000000000 53 | KEY_REGISTRY_CREATE2_SALT=0x0000000000000000000000000000000000000000000000000000000000000000 54 | SIGNED_KEY_REQUEST_VALIDATOR_CREATE2_SALT=0x0000000000000000000000000000000000000000000000000000000000000000 55 | BUNDLER_CREATE2_SALT=0x0000000000000000000000000000000000000000000000000000000000000000 56 | RECOVERY_PROXY_CREATE2_SALT=0x0000000000000000000000000000000000000000000000000000000000000000 57 | TIER_REGISTRY_CREATE2_SALT=0x0000000000000000000000000000000000000000000000000000000000000000 58 | 59 | # Deployer address. 60 | # Default address is Anvil test account 1 61 | DEPLOYER=0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 62 | 63 | # Migrator address. 64 | # Default address is Anvil test account 1 65 | MIGRATOR_ADDRESS=0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 66 | -------------------------------------------------------------------------------- /.env.prod: -------------------------------------------------------------------------------- 1 | # Price feed addresses 2 | STORAGE_RENT_PRICE_FEED_ADDRESS=0x13e3ee699d1909e989722e753853ae30b17e08c5 3 | STORAGE_RENT_UPTIME_FEED_ADDRESS=0x371EAD81c9102C9BF4874A9075FFFf170F2Ee389 4 | 5 | # Storage rent params 6 | STORAGE_RENT_ROLE_ADMIN_ADDRESS=0x53c6dA835c777AD11159198FBe11f95E5eE6B692 7 | STORAGE_RENT_VAULT_ADDRESS=0x53c6dA835c777AD11159198FBe11f95E5eE6B692 8 | STORAGE_RENT_ADMIN_ADDRESS=0xD84E32224A249A575A09672Da9cb58C381C4837a 9 | STORAGE_RENT_OPERATOR_ADDRESS=0x0000000000000000000000000000000000000000 10 | STORAGE_RENT_TREASURER_ADDRESS=0x0000000000000000000000000000000000000000 11 | 12 | # ID registry params 13 | ID_REGISTRY_OWNER_ADDRESS=0x53c6dA835c777AD11159198FBe11f95E5eE6B692 14 | 15 | # Key registry params 16 | KEY_REGISTRY_OWNER_ADDRESS=0x2D93c2F74b2C4697f9ea85D0450148AA45D4D5a2 17 | 18 | # Metadata validator params 19 | METADATA_VALIDATOR_OWNER_ADDRESS=0x53c6dA835c777AD11159198FBe11f95E5eE6B692 20 | 21 | # Bundler params 22 | BUNDLER_TRUSTED_CALLER_ADDRESS=0x2D93c2F74b2C4697f9ea85D0450148AA45D4D5a2 23 | BUNDLER_OWNER_ADDRESS=0x53c6dA835c777AD11159198FBe11f95E5eE6B692 24 | 25 | # Recovery proxy params 26 | RECOVERY_PROXY_OWNER_ADDRESS=0xFFE52568Fb0E7038Ef289677288BB704E5c9E82e 27 | 28 | # Fname resolver params. 29 | FNAME_RESOLVER_SERVER_URL=https://fnames.farcaster.xyz/ccip/{sender}/{data}.json 30 | FNAME_RESOLVER_SIGNER_ADDRESS=0xBc5274eFc266311015793d89E9B591fa46294741 31 | FNAME_RESOLVER_OWNER_ADDRESS=0x138356f24c7A16BE48978dE277a468F6C16A19a5 32 | 33 | # Tier registry params 34 | TIER_REGISTRY_VAULT_ADDRESS=0x0BDcA19c9801bb484285362fD5dd0c94592c874C 35 | TIER_REGISTRY_OWNER_ADDRESS=0x53c6dA835c777AD11159198FBe11f95E5eE6B692 36 | TIER_REGISTRY_MIGRATOR_ADDRESS=0x2d93c2f74b2c4697f9ea85d0450148aa45d4d5a2 37 | 38 | # RPC endpoints 39 | ETH_MAINNET_RPC_URL= 40 | OP_MAINNET_RPC_URL= 41 | BASE_MAINNET_RPC_URL= 42 | 43 | # Salts 44 | STORAGE_RENT_CREATE2_SALT=0x6d2b70e39c6bc63763098e336323591eb77cd0c65360d99ba6ea4e0161f2b96c 45 | ID_REGISTRY_CREATE2_SALT=0x6d2b70e39c6bc63763098e336323591eb77cd0c63e0688f6d95afa008febf4d7 46 | KEY_REGISTRY_CREATE2_SALT=0x6d2b70e39c6bc63763098e336323591eb77cd0c62af4de6e1f0355029f357f47 47 | SIGNED_KEY_REQUEST_VALIDATOR_CREATE2_SALT=0x6d2b70e39c6bc63763098e336323591eb77cd0c6610c0841333604016684800c 48 | BUNDLER_CREATE2_SALT=0x6d2b70e39c6bc63763098e336323591eb77cd0c6e451fc0a34ec4c008c9a31fa 49 | RECOVERY_PROXY_CREATE2_SALT=0x6d2b70e39c6bc63763098e336323591eb77cd0c6110eaaca06f77900dac1cad3 50 | TIER_REGISTRY_CREATE2_SALT=0x299707e127cc77de01b9fd968bc0ff475f3c63425a6dbfc4658e47004603abdf 51 | 52 | # Deployer address. 53 | DEPLOYER=0x6D2b70e39C6bc63763098e336323591eb77Cd0C6 54 | 55 | # Migrator address. 56 | MIGRATOR_ADDRESS=0x2D93c2F74b2C4697f9ea85D0450148AA45D4D5a2 57 | -------------------------------------------------------------------------------- /.env.upgrade: -------------------------------------------------------------------------------- 1 | # Price feed addresses 2 | STORAGE_RENT_PRICE_FEED_ADDRESS=0x13e3ee699d1909e989722e753853ae30b17e08c5 3 | STORAGE_RENT_UPTIME_FEED_ADDRESS=0x371EAD81c9102C9BF4874A9075FFFf170F2Ee389 4 | 5 | # Storage rent params 6 | STORAGE_RENT_ROLE_ADMIN_ADDRESS=0x53c6dA835c777AD11159198FBe11f95E5eE6B692 7 | STORAGE_RENT_VAULT_ADDRESS=0x53c6dA835c777AD11159198FBe11f95E5eE6B692 8 | STORAGE_RENT_ADMIN_ADDRESS=0xD84E32224A249A575A09672Da9cb58C381C4837a 9 | STORAGE_RENT_OPERATOR_ADDRESS=0x0000000000000000000000000000000000000000 10 | STORAGE_RENT_TREASURER_ADDRESS=0x0000000000000000000000000000000000000000 11 | 12 | # ID registry params 13 | ID_REGISTRY_OWNER_ADDRESS=0x53c6dA835c777AD11159198FBe11f95E5eE6B692 14 | 15 | # Key registry params 16 | KEY_REGISTRY_OWNER_ADDRESS=0x53c6dA835c777AD11159198FBe11f95E5eE6B692 17 | 18 | # Metadata validator params 19 | METADATA_VALIDATOR_OWNER_ADDRESS=0x53c6dA835c777AD11159198FBe11f95E5eE6B692 20 | 21 | # Bundler params 22 | BUNDLER_TRUSTED_CALLER_ADDRESS= 23 | BUNDLER_OWNER_ADDRESS= 24 | 25 | # Recovery proxy params 26 | RECOVERY_PROXY_OWNER_ADDRESS=0xFFE52568Fb0E7038Ef289677288BB704E5c9E82e 27 | 28 | # Fname resolver params. 29 | FNAME_RESOLVER_SERVER_URL=https://fnames.farcaster.xyz/ccip/{sender}/{data}.json 30 | FNAME_RESOLVER_SIGNER_ADDRESS=0xBc5274eFc266311015793d89E9B591fa46294741 31 | FNAME_RESOLVER_OWNER_ADDRESS=0x138356f24c7A16BE48978dE277a468F6C16A19a5 32 | 33 | # Tier registry params 34 | TIER_REGISTRY_VAULT_ADDRESS=0x0BDcA19c9801bb484285362fD5dd0c94592c874C 35 | TIER_REGISTRY_OWNER_ADDRESS=0x53c6dA835c777AD11159198FBe11f95E5eE6B692 36 | TIER_REGISTRY_MIGRATOR_ADDRESS=0x2d93c2f74b2c4697f9ea85d0450148aa45d4d5a2 37 | 38 | # RPC endpoints 39 | ETH_MAINNET_RPC_URL= 40 | OP_MAINNET_RPC_URL= 41 | BASE_MAINNET_RPC_URL= 42 | 43 | # Salts 44 | STORAGE_RENT_CREATE2_SALT=0x0000000000000000000000000000000000000000000000000000000000000000 45 | ID_REGISTRY_CREATE2_SALT=0x299707e127cc77de01b9fd968bc0ff475f3c6342d7872c397cd084029fbf64dc 46 | ID_GATEWAY_CREATE2_SALT=0x299707e127cc77de01b9fd968bc0ff475f3c6342a1b2a1fd9db0df01f0373563 47 | KEY_REGISTRY_CREATE2_SALT=0x299707e127cc77de01b9fd968bc0ff475f3c6342aee9be2412b02b01eb294554 48 | KEY_GATEWAY_CREATE2_SALT=0x299707e127cc77de01b9fd968bc0ff475f3c6342229ded5ec3c3bd02e574f7be 49 | SIGNED_KEY_REQUEST_VALIDATOR_CREATE2_SALT=0x0000000000000000000000000000000000000000000000000000000000000000 50 | BUNDLER_CREATE2_SALT=0x299707e127cc77de01b9fd968bc0ff475f3c6342e9da01d98917640342e02a5c 51 | RECOVERY_PROXY_CREATE2_SALT=0x299707e127cc77de01b9fd968bc0ff475f3c63421958ea987b94fd038a490454 52 | TIER_REGISTRY_CREATE2_SALT=0x299707e127cc77de01b9fd968bc0ff475f3c63425a6dbfc4658e47004603abdf 53 | 54 | # Deployed contracts 55 | STORAGE_RENT_ADDRESS=0x00000000fcCe7f938e7aE6D3c335bD6a1a7c593D 56 | SIGNED_KEY_REQUEST_VALIDATOR_ADDRESS=0x00000000FC700472606ED4fA22623Acf62c60553 57 | 58 | # Deployer address. 59 | DEPLOYER=0x299707E127CC77DE01b9Fd968Bc0ff475f3C6342 60 | 61 | # Migrator address. 62 | MIGRATOR_ADDRESS=0x2D93c2F74b2C4697f9ea85D0450148AA45D4D5a2 63 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.sol linguist-language=Solidity 2 | .gas-snapshot linguist-language=Julia 3 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: "bug:" 5 | labels: ["bug"] 6 | assignees: 7 | - varunsrin 8 | --- 9 | 10 | **What is the bug?** 11 | A concise, high level description of the bug and how it affects you 12 | 13 | **How can it be reproduced? (optional)** 14 | Include steps, code samples, replits, screenshots and anything else that would be helpful to reproduce the problem. 15 | 16 | **Additional context (optional)** 17 | Add any other context about the problem here. 18 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature 3 | about: "Opening new feature requests " 4 | title: "feat: " 5 | labels: ["feat"] 6 | assignees: 7 | - varunsrin 8 | --- 9 | 10 | **What is the feature you would like to implement?** 11 | A concise, high level summary of the feature 12 | 13 | **Why is this feature important?** 14 | An argument for why this feature should be build and how it should be prioritized 15 | 16 | **Will the protocol spec need to be updated??** 17 | Call out the sections of the [protocol spec](https://github.com/farcasterxyz/protocol) that will need to be updated (e.g. Section 4.3 - Verifications, Section 3.1 - Identity Systems) 18 | 19 | **How should this feature be built? (optional)** 20 | A design for the implementation of the feature 21 | 22 | **Additional context** 23 | Add any other context or screenshots about the feature request here. 24 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## Motivation 2 | 3 | Describe why this issue should be fixed and link to any relevant design docs, issues or other relevant items. 4 | 5 | ## Change Summary 6 | 7 | Describe the changes being made in 1-2 concise sentences. 8 | 9 | ## Merge Checklist 10 | 11 | _Choose all relevant options below by adding an `x` now or at any time before submitting for review_ 12 | 13 | - [ ] The PR title adheres to the [conventional commits](https://www.conventionalcommits.org/en/v1.0.0/) standard 14 | - [ ] The PR has been tagged with change type label(s) (i.e. documentation, feature, bugfix, or chore) 15 | - [ ] The PR's changes adhere to all the requirements in the [contribution guidelines](https://github.com/farcasterxyz/contracts/blob/main/CONTRIBUTING.md#3-proposing-changes) 16 | - [ ] All [commits have been signed](https://github.com/farcasterxyz/contracts/blob/main/CONTRIBUTING.md#22-signing-commits) 17 | 18 | ## Additional Context 19 | 20 | If this is a relatively large or complex change, provide more details here that will help reviewers. 21 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | # CI is run on main because new branches can only access caches from master, not previous branches. 4 | # So building on master allows new PR's to get the cache from before. 5 | push: 6 | branches: [main] 7 | pull_request: 8 | branches: [main] 9 | 10 | env: 11 | FOUNDRY_PROFILE: ci 12 | ETH_MAINNET_RPC_URL: ${{ secrets.L1_MAINNET_RPC_URL }} 13 | OP_MAINNET_RPC_URL: ${{ secrets.L2_MAINNET_RPC_URL }} 14 | BASE_MAINNET_RPC_URL: ${{ secrets.BASE_MAINNET_RPC_URL }} 15 | 16 | jobs: 17 | build-image: 18 | timeout-minutes: 5 19 | runs-on: ubuntu-latest 20 | steps: 21 | - uses: actions/checkout@v3 22 | with: 23 | submodules: recursive 24 | 25 | - name: Install foundry 26 | uses: foundry-rs/foundry-toolchain@v1 27 | with: 28 | version: stable 29 | 30 | - name: Install Docker buildx 31 | uses: docker/setup-buildx-action@v2 32 | 33 | - name: Copy .env.local 34 | run: cp .env.local .env 35 | shell: bash 36 | 37 | - name: Build Docker images defined in Docker Compose file 38 | uses: docker/bake-action@v3 39 | with: 40 | load: true # Load images into local Docker engine after build 41 | 42 | - name: Run containers defined in Docker Compose 43 | shell: bash 44 | run: docker compose up --detach 45 | 46 | - name: Check that Anvil is running 47 | uses: nick-fields/retry@v2 48 | with: 49 | timeout_seconds: 30 50 | retry_wait_seconds: 10 51 | max_attempts: 10 52 | shell: bash 53 | command: '[ "$(cast balance 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266)" != 0 ]' # Default address 54 | on_retry_command: docker compose logs && docker compose ps && cast balance 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 55 | 56 | - name: Wait for contract to be deployed 57 | uses: nick-fields/retry@v2 58 | with: 59 | timeout_seconds: 30 60 | retry_wait_seconds: 10 61 | max_attempts: 10 62 | shell: bash 63 | command: | 64 | set -e -o pipefail 65 | docker compose logs | grep Bundler | awk '{ print $5 }' 66 | on_retry_command: docker compose logs 67 | 68 | - name: Get contract addresses 69 | run: | 70 | echo "ID_CONTRACT_ADDRESS=$(docker compose logs | grep IdRegistry | awk '{ print $5 }')" >> $GITHUB_ENV 71 | echo "KEY_CONTRACT_ADDRESS=$(docker compose logs | grep KeyRegistry | awk '{ print $5 }')" >> $GITHUB_ENV 72 | echo "STORAGE_CONTRACT_ADDRESS=$(docker compose logs | grep StorageRegistry | awk '{ print $5 }')" >> $GITHUB_ENV 73 | echo "BUNDLER_CONTRACT_ADDRESS=$(docker compose logs | grep Bundler | awk '{ print $5 }')" >> $GITHUB_ENV 74 | shell: bash 75 | 76 | - name: Confirm ID Registry contract was deployed 77 | shell: bash 78 | run: '[ $(cast call $ID_CONTRACT_ADDRESS "owner()") = 0x000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266 ]' 79 | 80 | - name: Confirm Key Registry contract was deployed 81 | shell: bash 82 | run: '[ $(cast call $KEY_CONTRACT_ADDRESS "owner()") = 0x000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266 ]' 83 | 84 | - name: Confirm Storage Registry contract was deployed 85 | shell: bash 86 | run: '[ $(cast call $STORAGE_CONTRACT_ADDRESS "paused()") = 0x0000000000000000000000000000000000000000000000000000000000000000 ]' 87 | 88 | - name: Confirm Bundler contract was deployed 89 | shell: bash 90 | run: '[ $(cast call $BUNDLER_CONTRACT_ADDRESS "VERSION()") = 0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000a323032332e31312e313500000000000000000000000000000000000000000000 ]' 91 | 92 | test: 93 | strategy: 94 | fail-fast: true 95 | 96 | timeout-minutes: 15 97 | runs-on: ubuntu-latest 98 | steps: 99 | - uses: actions/checkout@v3 100 | with: 101 | submodules: recursive 102 | 103 | - name: Install foundry 104 | uses: foundry-rs/foundry-toolchain@v1 105 | with: 106 | version: stable 107 | 108 | - name: Run forge build 109 | run: | 110 | forge --version 111 | forge build --sizes 112 | 113 | - name: Run forge fmt 114 | run: forge fmt --check 115 | 116 | - name: Run forge tests 117 | run: forge test -vvv 118 | 119 | - name: Check forge snapshots 120 | run: forge snapshot --check --match-contract Gas 121 | 122 | halmos: 123 | runs-on: ubuntu-latest 124 | container: 125 | image: ghcr.io/a16z/halmos:latest 126 | steps: 127 | - uses: actions/checkout@v3 128 | with: 129 | submodules: recursive 130 | 131 | - name: Install foundry 132 | uses: foundry-rs/foundry-toolchain@v1 133 | 134 | - name: Run halmos 135 | run: halmos --test-parallel --solver-parallel --storage-layout=generic --solver-timeout-assertion 0 136 | 137 | coverage: 138 | permissions: 139 | contents: read 140 | pull-requests: write 141 | runs-on: ubuntu-latest 142 | steps: 143 | - uses: actions/checkout@v3 144 | 145 | - name: Install Foundry 146 | uses: foundry-rs/foundry-toolchain@v1 147 | with: 148 | version: stable 149 | 150 | - name: Check code coverage 151 | run: forge coverage --report summary --report lcov 152 | 153 | # Ignores coverage results for the test and script directories. Note that because this 154 | # filtering applies to the lcov file, the summary table generated in the previous step will 155 | # still include all files and directories. 156 | # The `--rc lcov_branch_coverage=1` part keeps branch info in the filtered report, since lcov 157 | # defaults to removing branch info. 158 | - name: Filter directories 159 | run: | 160 | sudo apt update && sudo apt install -y lcov 161 | lcov --remove lcov.info 'test/*' 'script/*' 'src/libraries/*' --output-file lcov.info --rc lcov_branch_coverage=1 162 | 163 | # Post a detailed coverage report as a comment and deletes previous comments on each push. 164 | - name: Post coverage report 165 | if: github.event_name == 'pull_request' # This action fails when ran outside of a pull request. 166 | uses: romeovs/lcov-reporter-action@v0.3.1 167 | with: 168 | delete-old-comments: true 169 | lcov-file: ./lcov.info 170 | github-token: ${{ secrets.GITHUB_TOKEN }} # Adds a coverage summary comment to the PR. 171 | 172 | # Fail coverage if the specified coverage threshold is not met 173 | - name: Verify minimum coverage 174 | uses: zgosalvez/github-actions-report-lcov@v2 175 | with: 176 | coverage-files: ./lcov.info 177 | minimum-coverage: 94 178 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # directories 2 | .yarn/* 3 | !.yarn/patches 4 | !.yarn/releases 5 | !.yarn/plugins 6 | !.yarn/sdks 7 | !.yarn/versions 8 | **/cache 9 | **/node_modules 10 | **/out 11 | 12 | # files 13 | *.env 14 | *.log 15 | .DS_Store 16 | .pnp.* 17 | yarn-debug.log* 18 | yarn-error.log* 19 | 20 | # broadcasts 21 | broadcast/* 22 | 23 | # Rust 24 | # will have compiled files and executables 25 | debug/ 26 | target/ 27 | 28 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 29 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 30 | Cargo.lock 31 | 32 | # These are backup files generated by rustfmt 33 | **/*.rs.bk 34 | 35 | # MSVC Windows builds of rustc generate these, which store debugging information 36 | *.pdb -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "lib/forge-std"] 2 | path = lib/forge-std 3 | url = https://github.com/foundry-rs/forge-std 4 | branch = v1 5 | [submodule "lib/solmate"] 6 | path = lib/solmate 7 | url = https://github.com/transmissions11/solmate 8 | branch = v7 9 | [submodule "lib/chainlink-brownie-contracts"] 10 | path = lib/chainlink-brownie-contracts 11 | url = https://github.com/smartcontractkit/chainlink-brownie-contracts 12 | [submodule "lib/openzeppelin-contracts"] 13 | path = lib/openzeppelin-contracts 14 | url = https://github.com/openzeppelin/openzeppelin-contracts 15 | [submodule "lib/openzeppelin-latest"] 16 | path = lib/openzeppelin-latest 17 | url = https://github.com/OpenZeppelin/openzeppelin-contracts 18 | [submodule "lib/halmos-cheatcodes"] 19 | path = lib/halmos-cheatcodes 20 | url = https://github.com/a16z/halmos-cheatcodes 21 | -------------------------------------------------------------------------------- /.lintstagedrc: -------------------------------------------------------------------------------- 1 | { 2 | "*.sol": [ 3 | "forge fmt --check" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /.rusty-hook.toml: -------------------------------------------------------------------------------- 1 | [hooks] 2 | pre-commit = "forge fmt --check && forge snapshot --check --match-contract Gas && forge test -vvv" 3 | 4 | [logging] 5 | verbose = true 6 | -------------------------------------------------------------------------------- /.solhint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "solhint:recommended", 3 | "plugins": ["prettier"], 4 | "rules": { 5 | "code-complexity": ["error", 8], 6 | "compiler-version": ["error", ">=0.8.4"], 7 | "func-visibility": ["error", { "ignoreConstructors": true }], 8 | "max-line-length": ["error", 120], 9 | "not-rely-on-time": "off", 10 | "reason-string": ["warn", { "maxLength": 64 }] 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /.solhintignore: -------------------------------------------------------------------------------- 1 | # directories 2 | **/lib 3 | **/node_modules 4 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "search.exclude": { 3 | ".git": true, 4 | "out": true, 5 | "cache": true, 6 | "lib": true 7 | }, 8 | "editor.formatOnSave": true, 9 | "editor.rulers": [119], 10 | "[json]": { 11 | "editor.defaultFormatter": "dbaeumer.vscode-eslint" 12 | }, 13 | "[solidity]": { 14 | "editor.defaultFormatter": "JuanBlanco.solidity" 15 | }, 16 | "solidity.formatter": "forge", 17 | "cSpell.words": ["curr", "Fname", "Seedable", "Pausable", "UUPS"], 18 | "solidity.compileUsingRemoteVersion": "v0.8.19+commit.7dd6d404" 19 | } 20 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "farcasterxyz-contracts" 3 | version = "2.0.0" 4 | edition = "2021" 5 | publish = false 6 | build = "build.rs" 7 | 8 | [[bin]] 9 | name = "main" 10 | path = "main.rs" 11 | 12 | [dependencies] 13 | rusty-hook = "^0.11.2" 14 | -------------------------------------------------------------------------------- /Dockerfile.foundry: -------------------------------------------------------------------------------- 1 | # syntax=docker/dockerfile:1.4 2 | 3 | # Used for local development so that we have binaries compiled for the local 4 | # architecture, since Foundry only publishes Docker images for AMD, and we don't 5 | # want to have slow emulation on Apple Silicon. 6 | 7 | FROM ubuntu:latest 8 | 9 | RUN apt-get update -y && apt-get install -y bash curl git gzip netcat-openbsd 10 | 11 | RUN curl --proto '=https' --tlsv1.2 -L "https://foundry.paradigm.xyz" | bash 12 | ENV PATH="$PATH:/root/.foundry/bin" 13 | RUN foundryup --install stable 14 | 15 | ENV RUST_BACKTRACE=full 16 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2020-2024 Merkle Manufactory and others 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Contracts 2 | 3 | This repository contains all the contracts deployed by the [Farcaster protocol](https://github.com/farcasterxyz/protocol). The contracts are: 4 | 5 | 1. **[Id Registry](./src/IdRegistry.sol)** - tracks ownership of farcaster identities (fids). 6 | 2. **[Storage Registry](./src/StorageRegistry.sol)** - allocates storage to fids and collects rent. 7 | 3. **[Key Registry](./src/KeyRegistry.sol)** - tracks associations between fids and key pairs for signing messages. 8 | 4. **[Id Gateway](./src/IdGateway.sol)** - issues farcaster identities (fids) to new users. 9 | 5. **[Key Gateway](./src/KeyGateway.sol)** - adds new associations between fids and keys. 10 | 6. **[Bundler](./src/Bundler.sol)** - allows calling gateways and storage in a single transaction. 11 | 7. **[Signed Key Request Validator](./src/validators/SignedKeyRequestValidator.sol)** - validates key registry metadata. 12 | 8. **[Recovery Proxy](./src/RecoveryProxy.sol)** - proxy for recovery service operators to initiate fid recovery. 13 | 9. **[Fname Resolver](./src/FnameResolver.sol)** - validates Farcaster ENS names which were issued offchain. 14 | 15 | Read the [docs](docs/docs.md) for more details on how the contracts work. 16 | 17 | ## Deployments 18 | 19 | The [v3.1 contracts](https://github.com/farcasterxyz/contracts/releases/tag/v3.1.0) are deployed across both OP Mainnet and Ethereum Mainnet. 20 | 21 | ### OP Mainnet 22 | 23 | | Contract | Address | 24 | | ------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | 25 | | IdRegistry | [0x00000000fc6c5f01fc30151999387bb99a9f489b](https://optimistic.etherscan.io/address/0x00000000fc6c5f01fc30151999387bb99a9f489b) | 26 | | StorageRegistry | [0x00000000fcce7f938e7ae6d3c335bd6a1a7c593d](https://optimistic.etherscan.io/address/0x00000000fcce7f938e7ae6d3c335bd6a1a7c593d) | 27 | | KeyRegistry | [0x00000000fc1237824fb747abde0ff18990e59b7e](https://optimistic.etherscan.io/address/0x00000000fc1237824fb747abde0ff18990e59b7e) | 28 | | IdGateway | [0x00000000fc25870c6ed6b6c7e41fb078b7656f69](https://optimistic.etherscan.io/address/0x00000000fc25870c6ed6b6c7e41fb078b7656f69) | 29 | | KeyGateway | [0x00000000fc56947c7e7183f8ca4b62398caadf0b](https://optimistic.etherscan.io/address/0x00000000fc56947c7e7183f8ca4b62398caadf0b) | 30 | | Bundler | [0x00000000fc04c910a0b5fea33b03e0447ad0b0aa](https://optimistic.etherscan.io/address/0x00000000fc04c910a0b5fea33b03e0447ad0b0aa) | 31 | | SignedKeyRequestValidator | [0x00000000fc700472606ed4fa22623acf62c60553](https://optimistic.etherscan.io/address/0x00000000fc700472606ed4fa22623acf62c60553) | 32 | | RecoveryProxy | [0x00000000fcb080a4d6c39a9354da9eb9bc104cd7](https://optimistic.etherscan.io/address/0x00000000fcb080a4d6c39a9354da9eb9bc104cd7) | 33 | 34 | ### ETH Mainnet 35 | 36 | | Contract | Address | 37 | | ------------- | ---------------- | 38 | | FnameResolver | Not yet deployed | 39 | 40 | ## Audits 41 | 42 | The [v3.1 contracts](https://github.com/farcasterxyz/contracts/releases/tag/v3.1.0) contracts were reviewed by [0xMacro](https://0xmacro.com/) and [Cyfrin](https://www.cyfrin.io/). 43 | 44 | - [0xMacro Report A-3](https://0xmacro.com/library/audits/farcaster-3.html) 45 | - [Cyfrin Report](https://github.com/farcasterxyz/contracts/blob/fe24a79e8901e8f2479474b16e32f43b66455a1d/docs/audits/2023-11-05-cyfrin-farcaster-v1.0.pdf) 46 | 47 | The [v3.0 contracts](https://github.com/farcasterxyz/contracts/releases/tag/v3.0.0) contracts were reviewed by [0xMacro](https://0xmacro.com/): 48 | 49 | - [0xMacro Report A-1](https://0xmacro.com/library/audits/farcaster-1.html) 50 | - [0xMacro Report A-2](https://0xmacro.com/library/audits/farcaster-2.html) 51 | 52 | ## Contributing 53 | 54 | Please see the [contributing guidelines](CONTRIBUTING.md). 55 | -------------------------------------------------------------------------------- /build.rs: -------------------------------------------------------------------------------- 1 | use std::process::Command; 2 | 3 | // remove husky configuration from .git/config if it exists 4 | fn main() { 5 | Command::new("git") 6 | .arg("config") 7 | .arg("--unset") 8 | .arg("core.hooksPath") 9 | .status() 10 | .expect("core.hooksPath failed to reset. You should manually run git config --unset core.hooksPath"); 11 | } 12 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | x-build-defaults: &build-defaults 2 | build: 3 | dockerfile: Dockerfile.foundry 4 | context: . 5 | networks: 6 | - contracts_subnet 7 | 8 | x-anvil-defaults: &anvil-defaults 9 | <<: *build-defaults 10 | restart: on-failure 11 | healthcheck: 12 | test: ["CMD", "/usr/bin/nc", "-z", "localhost", "${PORT:-8545}"] 13 | interval: 1s 14 | timeout: 1s 15 | retries: 3 16 | 17 | x-deployer-defaults: &deployer-defaults 18 | <<: *build-defaults 19 | environment: 20 | - DEPLOYER 21 | volumes: 22 | - .:/app 23 | working_dir: /app 24 | 25 | services: 26 | l2-anvil: 27 | <<: *anvil-defaults 28 | command: | 29 | sh -c ' 30 | exec anvil --host 0.0.0.0 --port ${PORT:-8545} --rpc-url $$OP_MAINNET_RPC_URL --state /var/lib/anvil/state --retries 3 --timeout 10000 31 | ' 32 | environment: 33 | - OP_MAINNET_RPC_URL 34 | volumes: 35 | - l2-anvil-data:/var/lib/anvil 36 | - l2-anvil-cache:/root/.foundry/cache 37 | ports: 38 | - "${PORT:-8545}:${PORT:-8545}" 39 | 40 | l2-deployer: 41 | <<: *deployer-defaults 42 | depends_on: 43 | - l2-anvil 44 | environment: 45 | - DEPLOYER 46 | - ID_REGISTRY_OWNER_ADDRESS 47 | - KEY_REGISTRY_OWNER_ADDRESS 48 | - BUNDLER_OWNER_ADDRESS 49 | - RECOVERY_PROXY_OWNER_ADDRESS 50 | - STORAGE_RENT_PRICE_FEED_ADDRESS 51 | - STORAGE_RENT_UPTIME_FEED_ADDRESS 52 | - STORAGE_RENT_VAULT_ADDRESS 53 | - STORAGE_RENT_ROLE_ADMIN_ADDRESS 54 | - STORAGE_RENT_ADMIN_ADDRESS 55 | - STORAGE_RENT_OPERATOR_ADDRESS 56 | - STORAGE_RENT_TREASURER_ADDRESS 57 | - BUNDLER_TRUSTED_CALLER_ADDRESS 58 | - METADATA_VALIDATOR_OWNER_ADDRESS 59 | - MIGRATOR_ADDRESS 60 | entrypoint: | 61 | sh -c ' 62 | set -e 63 | git config --global --add safe.directory "*" 64 | export RPC_URL="http://l2-anvil:${PORT:-8545}" 65 | echo "Waiting for Anvil..." 66 | while ! nc -z l2-anvil "${PORT:-8545}"; do sleep 0.1; done 67 | echo "Anvil online" 68 | echo "Enabling impersonation" 69 | cast rpc anvil_autoImpersonateAccount true --rpc-url "$$RPC_URL" > /dev/null 70 | echo "Funding deployer" 71 | cast rpc anvil_setBalance "$$DEPLOYER" 0xde0b6b3a7640000000 --rpc-url "$$RPC_URL" > /dev/null 72 | echo "Deploying contract" 73 | forge install 74 | forge script -v script/DeployL2.s.sol --rpc-url "$$RPC_URL" --unlocked --broadcast --sender "$$DEPLOYER" 75 | echo "Disabling impersonation" 76 | cast rpc anvil_autoImpersonateAccount false --rpc-url "$$RPC_URL" > /dev/null 77 | echo "Deploy complete" 78 | ' 79 | 80 | l1-anvil: 81 | <<: *anvil-defaults 82 | command: | 83 | sh -c ' 84 | exec anvil --host 0.0.0.0 --port ${PORT:-8545} --rpc-url $$ETH_MAINNET_RPC_URL --state /var/lib/anvil/state --retries 3 --timeout 10000 85 | ' 86 | environment: 87 | - ETH_MAINNET_RPC_URL 88 | volumes: 89 | - l1-anvil-data:/var/lib/anvil 90 | - l1-anvil-cache:/root/.foundry/cache 91 | ports: 92 | - "${PORT:-8546}:${PORT:-8545}" 93 | 94 | l1-deployer: 95 | <<: *deployer-defaults 96 | depends_on: 97 | - l1-anvil 98 | environment: 99 | - DEPLOYER 100 | - FNAME_RESOLVER_SERVER_URL 101 | - FNAME_RESOLVER_SIGNER_ADDRESS 102 | - FNAME_RESOLVER_OWNER_ADDRESS 103 | entrypoint: | 104 | sh -c ' 105 | set -e 106 | git config --global --add safe.directory "*" 107 | export RPC_URL="http://l1-anvil:${PORT:-8545}" 108 | echo "Waiting for Anvil..." 109 | while ! nc -z l1-anvil "${PORT:-8545}"; do sleep 0.1; done 110 | echo "Anvil online" 111 | echo "Enabling impersonation" 112 | cast rpc anvil_autoImpersonateAccount true --rpc-url "$$RPC_URL" > /dev/null 113 | echo "Funding deployer" 114 | cast rpc anvil_setBalance "$$DEPLOYER" 0xde0b6b3a7640000 --rpc-url "$$RPC_URL" > /dev/null 115 | echo "Deploying contract" 116 | forge install 117 | forge script -v script/DeployL1.s.sol --rpc-url "$$RPC_URL" --unlocked --broadcast --sender "$$DEPLOYER" 118 | echo "Disabling impersonation" 119 | cast rpc anvil_autoImpersonateAccount false --rpc-url "$$RPC_URL" > /dev/null 120 | echo "Deploy complete" 121 | ' 122 | 123 | volumes: 124 | l1-anvil-cache: 125 | l1-anvil-data: 126 | l2-anvil-cache: 127 | l2-anvil-data: 128 | 129 | networks: 130 | # Allows us to share the services in this file with other Docker Compose files 131 | contracts_subnet: 132 | driver: bridge 133 | -------------------------------------------------------------------------------- /docs/audits/2023-11-05-cyfrin-farcaster-v1.0.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/farcasterxyz/contracts/0f49b0e8245a3a57a7b1fed89b7a0a8903d3cd45/docs/audits/2023-11-05-cyfrin-farcaster-v1.0.pdf -------------------------------------------------------------------------------- /foundry.toml: -------------------------------------------------------------------------------- 1 | [profile.default] 2 | solc_version = "0.8.29" 3 | evm_version = "cancun" 4 | optimizer_runs = 100_000 5 | fuzz = { runs = 512 } 6 | remappings = [ 7 | "solmate/=lib/solmate/", 8 | "openzeppelin/=lib/openzeppelin-contracts/", 9 | "openzeppelin-latest/=lib/openzeppelin-latest/", 10 | "chainlink/=lib/chainlink-brownie-contracts/contracts/src/" 11 | ] 12 | no_match_path = "test/Deploy/*" 13 | libs = ["node_modules", "lib"] 14 | bytecode_hash = 'none' 15 | 16 | [profile.ci] 17 | verbosity = 3 18 | fuzz = { runs = 2500 } 19 | no_match_path = "fake/" 20 | match_path = "test/*/**" 21 | 22 | [fmt] 23 | line_length = 120 24 | tab_width = 4 25 | quote_style = "double" 26 | bracket_spacing = false 27 | int_types = "long" 28 | multiline_func_header = "params_first" 29 | 30 | [rpc_endpoints] 31 | base_mainnet = "${BASE_MAINNET_RPC_URL}" 32 | op_mainnet = "${OP_MAINNET_RPC_URL}" 33 | eth_mainnet = "${ETH_MAINNET_RPC_URL}" 34 | -------------------------------------------------------------------------------- /main.rs: -------------------------------------------------------------------------------- 1 | // an empty cargo target 2 | // we use cargo to install git commit hooks via rusty-hook 3 | fn main() {} 4 | -------------------------------------------------------------------------------- /script/DeployL1.s.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.21; 3 | 4 | import {FnameResolver} from "../src/FnameResolver.sol"; 5 | import {console, ImmutableCreate2Deployer} from "./abstract/ImmutableCreate2Deployer.sol"; 6 | 7 | contract DeployL1 is ImmutableCreate2Deployer { 8 | bytes32 internal constant FNAME_RESOLVER_CREATE2_SALT = bytes32(0); 9 | 10 | struct DeploymentParams { 11 | string serverURL; 12 | address signer; 13 | address owner; 14 | address deployer; 15 | } 16 | 17 | struct Contracts { 18 | FnameResolver fnameResolver; 19 | } 20 | 21 | function run() public { 22 | runDeploy(loadDeploymentParams()); 23 | } 24 | 25 | function runDeploy( 26 | DeploymentParams memory params 27 | ) public returns (Contracts memory) { 28 | return runDeploy(params, true); 29 | } 30 | 31 | function runDeploy(DeploymentParams memory params, bool broadcast) public returns (Contracts memory) { 32 | address fnameResolver = register( 33 | "FnameResolver", 34 | FNAME_RESOLVER_CREATE2_SALT, 35 | type(FnameResolver).creationCode, 36 | abi.encode(params.serverURL, params.signer, params.owner) 37 | ); 38 | 39 | deploy(broadcast); 40 | 41 | return Contracts({fnameResolver: FnameResolver(fnameResolver)}); 42 | } 43 | 44 | function loadDeploymentParams() internal view returns (DeploymentParams memory) { 45 | return DeploymentParams({ 46 | serverURL: vm.envString("FNAME_RESOLVER_SERVER_URL"), 47 | signer: vm.envAddress("FNAME_RESOLVER_SIGNER_ADDRESS"), 48 | owner: vm.envAddress("FNAME_RESOLVER_OWNER_ADDRESS"), 49 | deployer: vm.envAddress("DEPLOYER") 50 | }); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /script/DeployTierRegistry.s.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.21; 3 | 4 | import {Test} from "forge-std/Test.sol"; 5 | import {TierRegistry} from "../src/TierRegistry.sol"; 6 | import {console, ImmutableCreate2Deployer} from "./abstract/ImmutableCreate2Deployer.sol"; 7 | 8 | contract DeployTierRegistry is ImmutableCreate2Deployer, Test { 9 | address public constant BASE_USDC = address(0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913); 10 | uint256 public constant MIN_DAYS = 30; 11 | uint256 public constant MAX_DAYS = 365; 12 | uint256 public constant PRICE_PER_DAY = 328768; 13 | 14 | struct Salts { 15 | bytes32 tierRegistry; 16 | } 17 | 18 | struct DeploymentParams { 19 | address deployer; 20 | address vault; 21 | address owner; 22 | address migrator; 23 | Salts salts; 24 | } 25 | 26 | struct Addresses { 27 | address tierRegistry; 28 | } 29 | 30 | struct Contracts { 31 | TierRegistry tierRegistry; 32 | } 33 | 34 | function run() public { 35 | runSetup(runDeploy(loadDeploymentParams())); 36 | } 37 | 38 | function runDeploy( 39 | DeploymentParams memory params 40 | ) public returns (Contracts memory) { 41 | return runDeploy(params, true); 42 | } 43 | 44 | function runDeploy(DeploymentParams memory params, bool broadcast) public returns (Contracts memory) { 45 | Addresses memory addrs; 46 | addrs.tierRegistry = register( 47 | "TierRegistry", 48 | params.salts.tierRegistry, 49 | type(TierRegistry).creationCode, 50 | abi.encode(params.migrator, params.deployer) 51 | ); 52 | deploy(broadcast); 53 | 54 | return Contracts({tierRegistry: TierRegistry(addrs.tierRegistry)}); 55 | } 56 | 57 | function runSetup(Contracts memory contracts, DeploymentParams memory params, bool broadcast) public { 58 | if (deploymentChanged()) { 59 | console.log("Running setup"); 60 | 61 | if (broadcast) vm.startBroadcast(); 62 | contracts.tierRegistry.setTier(1, BASE_USDC, MIN_DAYS, MAX_DAYS, PRICE_PER_DAY, params.vault); 63 | contracts.tierRegistry.transferOwnership(params.owner); 64 | if (broadcast) vm.stopBroadcast(); 65 | } else { 66 | console.log("No changes, skipping setup"); 67 | } 68 | } 69 | 70 | function runSetup( 71 | Contracts memory contracts 72 | ) public { 73 | DeploymentParams memory params = loadDeploymentParams(); 74 | runSetup(contracts, params, true); 75 | } 76 | 77 | function loadDeploymentParams() internal returns (DeploymentParams memory) { 78 | return DeploymentParams({ 79 | deployer: vm.envAddress("DEPLOYER"), 80 | vault: vm.envAddress("TIER_REGISTRY_VAULT_ADDRESS"), 81 | owner: vm.envAddress("TIER_REGISTRY_OWNER_ADDRESS"), 82 | migrator: vm.envAddress("TIER_REGISTRY_MIGRATOR_ADDRESS"), 83 | salts: Salts({tierRegistry: vm.envOr("TIER_REGISTRY_CREATE2_SALT", bytes32(0))}) 84 | }); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /script/FnameResolver.s.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.21; 3 | 4 | import "forge-std/Script.sol"; 5 | 6 | import {FnameResolver} from "../src/FnameResolver.sol"; 7 | import {ImmutableCreate2Deployer} from "./abstract/ImmutableCreate2Deployer.sol"; 8 | 9 | contract FnameResolverScript is ImmutableCreate2Deployer { 10 | function run() public { 11 | string memory serverURI = vm.envString("FNAME_RESOLVER_SERVER_URL"); 12 | address signer = vm.envAddress("FNAME_RESOLVER_SIGNER_ADDRESS"); 13 | address owner = vm.envAddress("FNAME_RESOLVER_OWNER_ADDRESS"); 14 | 15 | register("FnameResolver", type(FnameResolver).creationCode, abi.encode(serverURI, signer, owner)); 16 | deploy(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /script/IdRegistry.s.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.21; 3 | 4 | import {IdRegistry} from "../src/IdRegistry.sol"; 5 | import {ImmutableCreate2Deployer} from "./abstract/ImmutableCreate2Deployer.sol"; 6 | 7 | contract IdRegistryScript is ImmutableCreate2Deployer { 8 | function run() public { 9 | address initialOwner = vm.envAddress("ID_REGISTRY_OWNER_ADDRESS"); 10 | 11 | register("IdRegistry", type(IdRegistry).creationCode, abi.encode(initialOwner)); 12 | deploy(); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /script/KeyRegistry.s.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.21; 3 | 4 | import {KeyRegistry} from "../src/KeyRegistry.sol"; 5 | import {ImmutableCreate2Deployer} from "./abstract/ImmutableCreate2Deployer.sol"; 6 | 7 | contract IdRegistryScript is ImmutableCreate2Deployer { 8 | uint24 internal constant KEY_REGISTRY_MIGRATION_GRACE_PERIOD = 1 days; 9 | 10 | function run() public { 11 | address idRegistry = vm.envAddress("ID_REGISTRY_ADDRESS"); 12 | address initialOwner = vm.envAddress("KEY_REGISTRY_OWNER_ADDRESS"); 13 | 14 | register( 15 | "KeyRegistry", 16 | type(KeyRegistry).creationCode, 17 | abi.encode(idRegistry, KEY_REGISTRY_MIGRATION_GRACE_PERIOD, initialOwner) 18 | ); 19 | 20 | deploy(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /script/LocalDeploy.s.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.21; 3 | 4 | import "forge-std/Script.sol"; 5 | import "forge-std/console.sol"; 6 | import {AggregatorV3Interface} from "chainlink/v0.8/interfaces/AggregatorV3Interface.sol"; 7 | 8 | import {IdRegistry} from "../src/IdRegistry.sol"; 9 | import {StorageRegistry} from "../src/StorageRegistry.sol"; 10 | import {KeyRegistry} from "../src/KeyRegistry.sol"; 11 | import {MockPriceFeed, MockUptimeFeed, MockChainlinkFeed} from "../test/Utils.sol"; 12 | 13 | contract LocalDeploy is Script { 14 | uint256 internal constant INITIAL_RENTAL_PERIOD = 365 days; 15 | uint256 internal constant INITIAL_USD_UNIT_PRICE = 5e8; // $5 USD 16 | uint256 internal constant INITIAL_MAX_UNITS = 2_000_000; 17 | uint256 internal constant INITIAL_PRICE_FEED_CACHE_DURATION = 1 days; 18 | uint256 internal constant INITIAL_UPTIME_FEED_GRACE_PERIOD = 1 hours; 19 | 20 | bytes32 internal constant ID_REGISTRY_CREATE2_SALT = "fc"; 21 | bytes32 internal constant KEY_REGISTRY_CREATE2_SALT = "fc"; 22 | bytes32 internal constant STORAGE_RENT_CREATE2_SALT = "fc"; 23 | 24 | function run() public { 25 | _etchCreate2Deployer(); 26 | 27 | address initialIdRegistryOwner = vm.envAddress("ID_REGISTRY_OWNER_ADDRESS"); 28 | address initialKeyRegistryOwner = vm.envAddress("KEY_REGISTRY_OWNER_ADDRESS"); 29 | 30 | address vault = vm.envAddress("STORAGE_RENT_VAULT_ADDRESS"); 31 | address roleAdmin = vm.envAddress("STORAGE_RENT_ROLE_ADMIN_ADDRESS"); 32 | address admin = vm.envAddress("STORAGE_RENT_ADMIN_ADDRESS"); 33 | address operator = vm.envAddress("STORAGE_RENT_OPERATOR_ADDRESS"); 34 | address treasurer = vm.envAddress("STORAGE_RENT_TREASURER_ADDRESS"); 35 | address migrator = vm.envAddress("MIGRATOR_ADDRESS"); 36 | 37 | vm.startBroadcast(); 38 | (AggregatorV3Interface priceFeed, AggregatorV3Interface uptimeFeed) = _getOrDeployPriceFeeds(); 39 | IdRegistry idRegistry = new IdRegistry{salt: ID_REGISTRY_CREATE2_SALT}(migrator, initialIdRegistryOwner); 40 | KeyRegistry keyRegistry = new KeyRegistry{salt: KEY_REGISTRY_CREATE2_SALT}( 41 | address(idRegistry), migrator, initialKeyRegistryOwner, 1000 42 | ); 43 | StorageRegistry storageRegistry = new StorageRegistry{salt: STORAGE_RENT_CREATE2_SALT}( 44 | priceFeed, 45 | uptimeFeed, 46 | INITIAL_USD_UNIT_PRICE, 47 | INITIAL_MAX_UNITS, 48 | vault, 49 | roleAdmin, 50 | admin, 51 | operator, 52 | treasurer 53 | ); 54 | vm.stopBroadcast(); 55 | console.log("ID Registry: %s", address(idRegistry)); 56 | console.log("Key Registry: %s", address(keyRegistry)); 57 | console.log("Storage Rent: %s", address(storageRegistry)); 58 | } 59 | 60 | /* @dev Make an Anvil RPC call to deploy the same CREATE2 deployer Foundry uses on mainnet. */ 61 | function _etchCreate2Deployer() internal { 62 | if (block.chainid == 31337) { 63 | string[] memory command = new string[](5); 64 | command[0] = "cast"; 65 | command[1] = "rpc"; 66 | command[2] = "anvil_setCode"; 67 | command[3] = "0x4e59b44847b379578588920cA78FbF26c0B4956C"; 68 | command[4] = ( 69 | "0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" 70 | "e03601600081602082378035828234f58015156039578182fd5b8082525050506014600cf3" 71 | ); 72 | vm.ffi(command); 73 | } 74 | } 75 | 76 | /* @dev Warp block.timestamp forward 3600 seconds, beyond the uptime feed grace period. */ 77 | function _warpForward() internal { 78 | if (block.chainid == 31337) { 79 | string[] memory command = new string[](4); 80 | command[0] = "cast"; 81 | command[1] = "rpc"; 82 | command[2] = "evm_increaseTime"; 83 | command[3] = "0xe10"; 84 | vm.ffi(command); 85 | } 86 | } 87 | 88 | /* @dev Deploy mock price feeds if we're on Anvil, otherwise read their addresses from the environment. */ 89 | function _getOrDeployPriceFeeds() 90 | internal 91 | returns (AggregatorV3Interface priceFeed, AggregatorV3Interface uptimeFeed) 92 | { 93 | if (block.chainid == 31337) { 94 | MockPriceFeed _priceFeed = new MockPriceFeed{salt: bytes32(0)}(); 95 | MockUptimeFeed _uptimeFeed = new MockUptimeFeed{salt: bytes32(0)}(); 96 | _priceFeed.setRoundData( 97 | MockChainlinkFeed.RoundData({ 98 | roundId: 1, 99 | answer: 2000e8, 100 | startedAt: 0, 101 | timeStamp: block.timestamp, 102 | answeredInRound: 1 103 | }) 104 | ); 105 | _uptimeFeed.setRoundData( 106 | MockChainlinkFeed.RoundData({ 107 | roundId: 1, 108 | answer: 0, 109 | startedAt: 0, 110 | timeStamp: block.timestamp, 111 | answeredInRound: 1 112 | }) 113 | ); 114 | _warpForward(); 115 | priceFeed = _priceFeed; 116 | uptimeFeed = _uptimeFeed; 117 | } else { 118 | priceFeed = AggregatorV3Interface(vm.envAddress("STORAGE_RENT_PRICE_FEED_ADDRESS")); 119 | uptimeFeed = AggregatorV3Interface(vm.envAddress("STORAGE_RENT_UPTIME_FEED_ADDRESS")); 120 | } 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /script/StorageRegistry.s.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.21; 3 | 4 | import {StorageRegistry} from "../src/StorageRegistry.sol"; 5 | import {ImmutableCreate2Deployer} from "./abstract/ImmutableCreate2Deployer.sol"; 6 | 7 | contract StorageRegistryScript is ImmutableCreate2Deployer { 8 | uint256 internal constant INITIAL_RENTAL_PERIOD = 365 days; 9 | uint256 internal constant INITIAL_USD_UNIT_PRICE = 5e8; // $5 USD 10 | uint256 internal constant INITIAL_MAX_UNITS = 2_000_000; 11 | uint256 internal constant INITIAL_PRICE_FEED_CACHE_DURATION = 1 days; 12 | uint256 internal constant INITIAL_UPTIME_FEED_GRACE_PERIOD = 1 hours; 13 | 14 | function run() public { 15 | address priceFeed = vm.envAddress("STORAGE_RENT_PRICE_FEED_ADDRESS"); 16 | address uptimeFeed = vm.envAddress("STORAGE_RENT_UPTIME_FEED_ADDRESS"); 17 | address vault = vm.envAddress("STORAGE_RENT_VAULT_ADDRESS"); 18 | address roleAdmin = vm.envAddress("STORAGE_RENT_ROLE_ADMIN_ADDRESS"); 19 | address admin = vm.envAddress("STORAGE_RENT_ADMIN_ADDRESS"); 20 | address operator = vm.envAddress("STORAGE_RENT_OPERATOR_ADDRESS"); 21 | address treasurer = vm.envAddress("STORAGE_RENT_TREASURER_ADDRESS"); 22 | 23 | register( 24 | "StorageRegistry", 25 | type(StorageRegistry).creationCode, 26 | abi.encode( 27 | priceFeed, 28 | uptimeFeed, 29 | INITIAL_RENTAL_PERIOD, 30 | INITIAL_USD_UNIT_PRICE, 31 | INITIAL_MAX_UNITS, 32 | vault, 33 | roleAdmin, 34 | admin, 35 | operator, 36 | treasurer, 37 | INITIAL_PRICE_FEED_CACHE_DURATION, 38 | INITIAL_UPTIME_FEED_GRACE_PERIOD 39 | ) 40 | ); 41 | 42 | deploy(); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /script/UpgradeBundler.s.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.21; 3 | 4 | import {Test} from "forge-std/Test.sol"; 5 | import {StorageRegistry} from "../src/StorageRegistry.sol"; 6 | import {IdRegistry} from "../src/IdRegistry.sol"; 7 | import {IdGateway} from "../src/IdGateway.sol"; 8 | import {KeyRegistry, IKeyRegistry} from "../src/KeyRegistry.sol"; 9 | import {KeyGateway} from "../src/KeyGateway.sol"; 10 | import {SignedKeyRequestValidator} from "../src/validators/SignedKeyRequestValidator.sol"; 11 | import {Bundler, IBundler} from "../src/Bundler.sol"; 12 | import {RecoveryProxy} from "../src/RecoveryProxy.sol"; 13 | import {console, ImmutableCreate2Deployer} from "./abstract/ImmutableCreate2Deployer.sol"; 14 | 15 | contract UpgradeBundler is ImmutableCreate2Deployer, Test { 16 | struct Salts { 17 | bytes32 bundler; 18 | } 19 | 20 | struct DeploymentParams { 21 | Salts salts; 22 | } 23 | 24 | struct Addresses { 25 | address storageRegistry; 26 | address idRegistry; 27 | address idGateway; 28 | address keyRegistry; 29 | address keyGateway; 30 | address signedKeyRequestValidator; 31 | address bundler; 32 | address recoveryProxy; 33 | } 34 | 35 | struct Contracts { 36 | StorageRegistry storageRegistry; 37 | IdRegistry idRegistry; 38 | IdGateway idGateway; 39 | KeyRegistry keyRegistry; 40 | KeyGateway keyGateway; 41 | SignedKeyRequestValidator signedKeyRequestValidator; 42 | Bundler bundler; 43 | RecoveryProxy recoveryProxy; 44 | } 45 | 46 | function run() public { 47 | runSetup(runDeploy(loadDeploymentParams())); 48 | } 49 | 50 | function runDeploy( 51 | DeploymentParams memory params 52 | ) public returns (Contracts memory) { 53 | return runDeploy(params, true); 54 | } 55 | 56 | function runDeploy(DeploymentParams memory params, bool broadcast) public returns (Contracts memory) { 57 | Addresses memory addrs; 58 | 59 | // No changes 60 | addrs.storageRegistry = address(0x00000000fcCe7f938e7aE6D3c335bD6a1a7c593D); 61 | addrs.idRegistry = address(0x00000000Fc6c5F01Fc30151999387Bb99A9f489b); 62 | addrs.idGateway = payable(address(0x00000000Fc25870C6eD6b6c7E41Fb078b7656f69)); 63 | addrs.keyRegistry = address(0x00000000Fc1237824fb747aBDE0FF18990E59b7e); 64 | addrs.keyGateway = address(0x00000000fC56947c7E7183f8Ca4B62398CaAdf0B); 65 | addrs.signedKeyRequestValidator = address(0x00000000FC700472606ED4fA22623Acf62c60553); 66 | addrs.recoveryProxy = address(0x00000000FcB080a4D6c39a9354dA9EB9bC104cd7); 67 | 68 | // Deploy new Bundler 69 | addrs.bundler = register( 70 | "Bundler", params.salts.bundler, type(Bundler).creationCode, abi.encode(addrs.idGateway, addrs.keyGateway) 71 | ); 72 | deploy(broadcast); 73 | 74 | return Contracts({ 75 | storageRegistry: StorageRegistry(addrs.storageRegistry), 76 | idRegistry: IdRegistry(addrs.idRegistry), 77 | idGateway: IdGateway(payable(addrs.idGateway)), 78 | keyRegistry: KeyRegistry(addrs.keyRegistry), 79 | keyGateway: KeyGateway(payable(addrs.keyGateway)), 80 | signedKeyRequestValidator: SignedKeyRequestValidator(addrs.signedKeyRequestValidator), 81 | bundler: Bundler(payable(addrs.bundler)), 82 | recoveryProxy: RecoveryProxy(addrs.recoveryProxy) 83 | }); 84 | } 85 | 86 | function runSetup(Contracts memory contracts, DeploymentParams memory, bool) public { 87 | if (deploymentChanged()) { 88 | console.log("Running setup"); 89 | 90 | // Check bundler deploy parameters 91 | assertEq(address(contracts.bundler.idGateway()), address(contracts.idGateway)); 92 | assertEq(address(contracts.bundler.keyGateway()), address(contracts.keyGateway)); 93 | } else { 94 | console.log("No changes, skipping setup"); 95 | } 96 | } 97 | 98 | function runSetup( 99 | Contracts memory contracts 100 | ) public { 101 | DeploymentParams memory params = loadDeploymentParams(); 102 | runSetup(contracts, params, true); 103 | } 104 | 105 | function loadDeploymentParams() internal returns (DeploymentParams memory) { 106 | return DeploymentParams({salts: Salts({bundler: vm.envOr("BUNDLER_CREATE2_SALT", bytes32(0))})}); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /script/abstract/ImmutableCreate2Deployer.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.21; 3 | 4 | import "forge-std/Script.sol"; 5 | import "forge-std/console.sol"; 6 | import {Strings} from "openzeppelin/contracts/utils/Strings.sol"; 7 | 8 | interface ImmutableCreate2Factory { 9 | function hasBeenDeployed( 10 | address deploymentAddress 11 | ) external view returns (bool); 12 | 13 | function findCreate2Address( 14 | bytes32 salt, 15 | bytes calldata initializationCode 16 | ) external view returns (address deploymentAddress); 17 | 18 | function safeCreate2( 19 | bytes32 salt, 20 | bytes calldata initializationCode 21 | ) external payable returns (address deploymentAddress); 22 | } 23 | 24 | abstract contract ImmutableCreate2Deployer is Script { 25 | enum Status { 26 | UNKNOWN, 27 | FOUND, 28 | DEPLOYED 29 | } 30 | 31 | /** 32 | * @dev Deployment information for a contract. 33 | * 34 | * @param name Contract name 35 | * @param salt CREATE2 salt 36 | * @param creationCode Contract creationCode bytes 37 | * @param constructorArgs ABI-encoded constructor argument bytes 38 | * @param initCodeHash Contract initCode (creationCode + constructorArgs) hash 39 | * @param deploymentAddress Deterministic deployment address 40 | */ 41 | struct Deployment { 42 | string name; 43 | bytes32 salt; 44 | bytes creationCode; 45 | bytes constructorArgs; 46 | bytes32 initCodeHash; 47 | address deploymentAddress; 48 | Status status; 49 | } 50 | 51 | /// @dev Deterministic address of the cross-chain ImmutableCreate2Factory 52 | ImmutableCreate2Factory private constant IMMUTABLE_CREATE2_FACTORY = 53 | ImmutableCreate2Factory(0x0000000000FFe8B47B3e2130213B802212439497); 54 | 55 | /// @dev Default CREATE2 salt 56 | bytes32 private constant DEFAULT_SALT = bytes32(0); 57 | 58 | /// @dev Array of contract names, used to track contracts "registered" for later deployment. 59 | string[] internal names; 60 | 61 | /// @dev Mapping of contract name to deployment details. 62 | mapping(string name => Deployment deployment) internal contracts; 63 | 64 | /** 65 | * @dev "Register" a contract to be deployed by deploy(). 66 | * 67 | * @param name Contract name 68 | * @param creationCode Contract creationCode bytes 69 | */ 70 | function register(string memory name, bytes memory creationCode) internal returns (address) { 71 | return register(name, DEFAULT_SALT, creationCode, ""); 72 | } 73 | 74 | /** 75 | * @dev "Register" a contract to be deployed by deploy(). 76 | * 77 | * @param name Contract name 78 | * @param salt CREATE2 salt 79 | * @param creationCode Contract creationCode bytes 80 | */ 81 | function register(string memory name, bytes32 salt, bytes memory creationCode) internal returns (address) { 82 | return register(name, salt, creationCode, ""); 83 | } 84 | 85 | /** 86 | * @dev "Register" a contract to be deployed by deploy(). 87 | * 88 | * @param name Contract name 89 | * @param creationCode Contract creationCode bytes 90 | * @param constructorArgs ABI-encoded constructor argument bytes 91 | */ 92 | function register( 93 | string memory name, 94 | bytes memory creationCode, 95 | bytes memory constructorArgs 96 | ) internal returns (address) { 97 | return register(name, DEFAULT_SALT, creationCode, constructorArgs); 98 | } 99 | 100 | /** 101 | * @dev "Register" a contract to be deployed by deploy(). 102 | * 103 | * @param name Contract name 104 | * @param salt CREATE2 salt 105 | * @param creationCode Contract creationCode bytes 106 | * @param constructorArgs ABI-encoded constructor argument bytes 107 | */ 108 | function register( 109 | string memory name, 110 | bytes32 salt, 111 | bytes memory creationCode, 112 | bytes memory constructorArgs 113 | ) internal returns (address) { 114 | bytes memory initCode = bytes.concat(creationCode, constructorArgs); 115 | bytes32 initCodeHash = keccak256(initCode); 116 | address deploymentAddress = address( 117 | uint160( 118 | uint256(keccak256(abi.encodePacked(hex"ff", address(IMMUTABLE_CREATE2_FACTORY), salt, initCodeHash))) 119 | ) 120 | ); 121 | names.push(name); 122 | contracts[name] = Deployment({ 123 | name: name, 124 | salt: salt, 125 | creationCode: creationCode, 126 | constructorArgs: constructorArgs, 127 | initCodeHash: initCodeHash, 128 | deploymentAddress: deploymentAddress, 129 | status: Status.UNKNOWN 130 | }); 131 | return deploymentAddress; 132 | } 133 | 134 | /** 135 | * @dev Deploy all registered contracts. 136 | */ 137 | function deploy( 138 | bool broadcast 139 | ) internal { 140 | console.log(pad("State", 10), pad("Name", 27), pad("Address", 43), "Initcode hash"); 141 | for (uint256 i; i < names.length; i++) { 142 | _deploy(names[i], broadcast); 143 | } 144 | } 145 | 146 | function deploy() internal { 147 | deploy(true); 148 | } 149 | 150 | /** 151 | * @dev Deploy a registered contract by name. 152 | * 153 | * @param name Contract name 154 | */ 155 | function deploy(string memory name, bool broadcast) public { 156 | console.log(pad("State", 10), pad("Name", 17), pad("Address", 43), "Initcode hash"); 157 | _deploy(name, broadcast); 158 | } 159 | 160 | function deploy( 161 | string memory name 162 | ) internal { 163 | deploy(name, true); 164 | } 165 | 166 | function _deploy(string memory name, bool broadcast) internal { 167 | Deployment storage deployment = contracts[name]; 168 | if (!IMMUTABLE_CREATE2_FACTORY.hasBeenDeployed(deployment.deploymentAddress)) { 169 | if (broadcast) vm.broadcast(); 170 | deployment.deploymentAddress = IMMUTABLE_CREATE2_FACTORY.safeCreate2( 171 | deployment.salt, bytes.concat(deployment.creationCode, deployment.constructorArgs) 172 | ); 173 | deployment.status = Status.DEPLOYED; 174 | } else { 175 | deployment.status = Status.FOUND; 176 | } 177 | console.log( 178 | pad((deployment.status == Status.DEPLOYED) ? "Deploying" : "Found", 10), 179 | pad(deployment.name, 27), 180 | pad(Strings.toHexString(deployment.deploymentAddress), 43), 181 | Strings.toHexString(uint256(deployment.initCodeHash)) 182 | ); 183 | } 184 | 185 | function deploymentChanged() public view returns (bool) { 186 | for (uint256 i; i < names.length; i++) { 187 | Deployment storage deployment = contracts[names[i]]; 188 | if (deployment.status == Status.DEPLOYED) { 189 | return true; 190 | } 191 | } 192 | return false; 193 | } 194 | 195 | /** 196 | * @dev Pad string to given length. 197 | * 198 | * @param str string to pad 199 | * @param n length to pad to 200 | */ 201 | function pad(string memory str, uint256 n) internal pure returns (string memory) { 202 | string memory padded = str; 203 | while (bytes(padded).length < n) { 204 | padded = string.concat(padded, " "); 205 | } 206 | return padded; 207 | } 208 | } 209 | -------------------------------------------------------------------------------- /slither.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "exclude_informational": false, 3 | "exclude_low": false, 4 | "exclude_medium": false, 5 | "exclude_high": false, 6 | "disable_color": false, 7 | "filter_paths": "(test/|lib/forge-std/|script/)", 8 | "legacy_ast": false 9 | } 10 | -------------------------------------------------------------------------------- /src/Bundler.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.29; 3 | 4 | import {IBundler} from "./interfaces/IBundler.sol"; 5 | import {IIdGateway} from "./interfaces/IIdGateway.sol"; 6 | import {IKeyGateway} from "./interfaces/IKeyGateway.sol"; 7 | import {TransferHelper} from "./libraries/TransferHelper.sol"; 8 | 9 | /** 10 | * @title Farcaster Bundler 11 | * 12 | * @notice See https://github.com/farcasterxyz/contracts/blob/v3.1.0/docs/docs.md for an overview. 13 | * 14 | * @custom:security-contact security@merklemanufactory.com 15 | */ 16 | contract Bundler is IBundler { 17 | using TransferHelper for address; 18 | 19 | /*////////////////////////////////////////////////////////////// 20 | CONSTANTS 21 | //////////////////////////////////////////////////////////////*/ 22 | 23 | /** 24 | * @inheritdoc IBundler 25 | */ 26 | string public constant VERSION = "2025.06.16"; 27 | 28 | /*////////////////////////////////////////////////////////////// 29 | IMMUTABLES 30 | //////////////////////////////////////////////////////////////*/ 31 | 32 | /** 33 | * @inheritdoc IBundler 34 | */ 35 | IIdGateway public immutable idGateway; 36 | 37 | /** 38 | * @inheritdoc IBundler 39 | */ 40 | IKeyGateway public immutable keyGateway; 41 | 42 | /*////////////////////////////////////////////////////////////// 43 | CONSTRUCTOR 44 | //////////////////////////////////////////////////////////////*/ 45 | 46 | /** 47 | * @notice Configure the addresses of the IdGateway and KeyGateway contracts. 48 | * 49 | * @param _idGateway Address of the IdGateway contract 50 | * @param _keyGateway Address of the KeyGateway contract 51 | */ 52 | constructor(address _idGateway, address _keyGateway) { 53 | idGateway = IIdGateway(payable(_idGateway)); 54 | keyGateway = IKeyGateway(payable(_keyGateway)); 55 | } 56 | 57 | /** 58 | * @inheritdoc IBundler 59 | */ 60 | function price( 61 | uint256 extraStorage 62 | ) external view returns (uint256) { 63 | return idGateway.price(extraStorage); 64 | } 65 | 66 | /** 67 | * @inheritdoc IBundler 68 | */ 69 | function register( 70 | RegistrationParams calldata registerParams, 71 | SignerParams[] calldata signerParams, 72 | uint256 extraStorage 73 | ) external payable returns (uint256) { 74 | (uint256 fid, uint256 overpayment) = idGateway.registerFor{value: msg.value}( 75 | registerParams.to, registerParams.recovery, registerParams.deadline, registerParams.sig, extraStorage 76 | ); 77 | _addKeys(registerParams.to, signerParams); 78 | if (overpayment > 0) msg.sender.sendNative(overpayment); 79 | return fid; 80 | } 81 | 82 | /** 83 | * @inheritdoc IBundler 84 | */ 85 | function addKeys(address fidOwner, SignerParams[] calldata signerParams) external { 86 | _addKeys(fidOwner, signerParams); 87 | } 88 | 89 | function _addKeys(address fidOwner, SignerParams[] calldata signerParams) internal { 90 | uint256 signersLen = signerParams.length; 91 | for (uint256 i; i < signersLen; ++i) { 92 | SignerParams calldata signer = signerParams[i]; 93 | keyGateway.addFor( 94 | fidOwner, signer.keyType, signer.key, signer.metadataType, signer.metadata, signer.deadline, signer.sig 95 | ); 96 | } 97 | } 98 | 99 | receive() external payable { 100 | if (msg.sender != address(idGateway)) revert Unauthorized(); 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/BundlerV1.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.21; 3 | 4 | import {IBundlerV1} from "./interfaces/IBundlerV1.sol"; 5 | import {IIdGateway} from "./interfaces/IIdGateway.sol"; 6 | import {IKeyGateway} from "./interfaces/IKeyGateway.sol"; 7 | import {TransferHelper} from "./libraries/TransferHelper.sol"; 8 | 9 | /** 10 | * @title Farcaster Bundler 11 | * 12 | * @notice See https://github.com/farcasterxyz/contracts/blob/v3.1.0/docs/docs.md for an overview. 13 | * 14 | * @custom:security-contact security@merklemanufactory.com 15 | */ 16 | contract BundlerV1 is IBundlerV1 { 17 | using TransferHelper for address; 18 | 19 | /*////////////////////////////////////////////////////////////// 20 | CONSTANTS 21 | //////////////////////////////////////////////////////////////*/ 22 | 23 | /** 24 | * @inheritdoc IBundlerV1 25 | */ 26 | string public constant VERSION = "2023.11.15"; 27 | 28 | /*////////////////////////////////////////////////////////////// 29 | IMMUTABLES 30 | //////////////////////////////////////////////////////////////*/ 31 | 32 | /** 33 | * @inheritdoc IBundlerV1 34 | */ 35 | IIdGateway public immutable idGateway; 36 | 37 | /** 38 | * @inheritdoc IBundlerV1 39 | */ 40 | IKeyGateway public immutable keyGateway; 41 | 42 | /*////////////////////////////////////////////////////////////// 43 | CONSTRUCTOR 44 | //////////////////////////////////////////////////////////////*/ 45 | 46 | /** 47 | * @notice Configure the addresses of the IdGateway and KeyGateway contracts. 48 | * 49 | * @param _idGateway Address of the IdGateway contract 50 | * @param _keyGateway Address of the KeyGateway contract 51 | */ 52 | constructor(address _idGateway, address _keyGateway) { 53 | idGateway = IIdGateway(payable(_idGateway)); 54 | keyGateway = IKeyGateway(payable(_keyGateway)); 55 | } 56 | 57 | /** 58 | * @inheritdoc IBundlerV1 59 | */ 60 | function price( 61 | uint256 extraStorage 62 | ) external view returns (uint256) { 63 | return idGateway.price(extraStorage); 64 | } 65 | 66 | /** 67 | * @inheritdoc IBundlerV1 68 | */ 69 | function register( 70 | RegistrationParams calldata registerParams, 71 | SignerParams[] calldata signerParams, 72 | uint256 extraStorage 73 | ) external payable returns (uint256) { 74 | (uint256 fid, uint256 overpayment) = idGateway.registerFor{value: msg.value}( 75 | registerParams.to, registerParams.recovery, registerParams.deadline, registerParams.sig, extraStorage 76 | ); 77 | 78 | uint256 signersLen = signerParams.length; 79 | for (uint256 i; i < signersLen;) { 80 | SignerParams calldata signer = signerParams[i]; 81 | keyGateway.addFor( 82 | registerParams.to, 83 | signer.keyType, 84 | signer.key, 85 | signer.metadataType, 86 | signer.metadata, 87 | signer.deadline, 88 | signer.sig 89 | ); 90 | 91 | // Safety: i can be incremented unchecked since it is bound by signerParams.length. 92 | unchecked { 93 | ++i; 94 | } 95 | } 96 | if (overpayment > 0) msg.sender.sendNative(overpayment); 97 | return fid; 98 | } 99 | 100 | receive() external payable { 101 | if (msg.sender != address(idGateway)) revert Unauthorized(); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/IdGateway.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.21; 3 | 4 | import {IIdGateway} from "./interfaces/IIdGateway.sol"; 5 | import {IStorageRegistry} from "./interfaces/IStorageRegistry.sol"; 6 | import {IIdRegistry} from "./interfaces/IIdRegistry.sol"; 7 | import {Guardians} from "./abstract/Guardians.sol"; 8 | import {TransferHelper} from "./libraries/TransferHelper.sol"; 9 | import {EIP712} from "./abstract/EIP712.sol"; 10 | import {Nonces} from "./abstract/Nonces.sol"; 11 | import {Signatures} from "./abstract/Signatures.sol"; 12 | 13 | /** 14 | * @title Farcaster IdGateway 15 | * 16 | * @notice See https://github.com/farcasterxyz/contracts/blob/v3.1.0/docs/docs.md for an overview. 17 | * 18 | * @custom:security-contact security@merklemanufactory.com 19 | */ 20 | contract IdGateway is IIdGateway, Guardians, Signatures, EIP712, Nonces { 21 | using TransferHelper for address; 22 | 23 | /*////////////////////////////////////////////////////////////// 24 | CONSTANTS 25 | //////////////////////////////////////////////////////////////*/ 26 | 27 | /** 28 | * @inheritdoc IIdGateway 29 | */ 30 | string public constant VERSION = "2023.11.15"; 31 | 32 | /** 33 | * @inheritdoc IIdGateway 34 | */ 35 | bytes32 public constant REGISTER_TYPEHASH = 36 | keccak256("Register(address to,address recovery,uint256 nonce,uint256 deadline)"); 37 | 38 | /*////////////////////////////////////////////////////////////// 39 | IMMUTABLES 40 | //////////////////////////////////////////////////////////////*/ 41 | 42 | /** 43 | * @inheritdoc IIdGateway 44 | */ 45 | IIdRegistry public immutable idRegistry; 46 | 47 | /*////////////////////////////////////////////////////////////// 48 | STORAGE 49 | //////////////////////////////////////////////////////////////*/ 50 | 51 | /** 52 | * @inheritdoc IIdGateway 53 | */ 54 | IStorageRegistry public storageRegistry; 55 | 56 | /*////////////////////////////////////////////////////////////// 57 | CONSTRUCTOR 58 | //////////////////////////////////////////////////////////////*/ 59 | 60 | /** 61 | * @notice Configure IdRegistry and StorageRegistry addresses. 62 | * Set the owner of the contract to the provided _owner. 63 | * 64 | * @param _idRegistry IdRegistry address. 65 | * @param _storageRegistry StorageRegistry address. 66 | * @param _initialOwner Initial owner address. 67 | * 68 | */ 69 | constructor( 70 | address _idRegistry, 71 | address _storageRegistry, 72 | address _initialOwner 73 | ) Guardians(_initialOwner) EIP712("Farcaster IdGateway", "1") { 74 | idRegistry = IIdRegistry(_idRegistry); 75 | storageRegistry = IStorageRegistry(_storageRegistry); 76 | emit SetStorageRegistry(address(0), _storageRegistry); 77 | } 78 | 79 | /*////////////////////////////////////////////////////////////// 80 | PRICE VIEW 81 | //////////////////////////////////////////////////////////////*/ 82 | 83 | /** 84 | * @inheritdoc IIdGateway 85 | */ 86 | function price() external view returns (uint256) { 87 | return storageRegistry.unitPrice(); 88 | } 89 | 90 | /** 91 | * @inheritdoc IIdGateway 92 | */ 93 | function price( 94 | uint256 extraStorage 95 | ) external view returns (uint256) { 96 | return storageRegistry.price(1 + extraStorage); 97 | } 98 | 99 | /*////////////////////////////////////////////////////////////// 100 | REGISTRATION LOGIC 101 | //////////////////////////////////////////////////////////////*/ 102 | 103 | /** 104 | * @inheritdoc IIdGateway 105 | */ 106 | function register( 107 | address recovery 108 | ) external payable returns (uint256, uint256) { 109 | return register(recovery, 0); 110 | } 111 | 112 | function register( 113 | address recovery, 114 | uint256 extraStorage 115 | ) public payable whenNotPaused returns (uint256 fid, uint256 overpayment) { 116 | fid = idRegistry.register(msg.sender, recovery); 117 | overpayment = _rentStorage(fid, extraStorage, msg.value, msg.sender); 118 | } 119 | 120 | /** 121 | * @inheritdoc IIdGateway 122 | */ 123 | function registerFor( 124 | address to, 125 | address recovery, 126 | uint256 deadline, 127 | bytes calldata sig 128 | ) external payable returns (uint256, uint256) { 129 | return registerFor(to, recovery, deadline, sig, 0); 130 | } 131 | 132 | function registerFor( 133 | address to, 134 | address recovery, 135 | uint256 deadline, 136 | bytes calldata sig, 137 | uint256 extraStorage 138 | ) public payable whenNotPaused returns (uint256 fid, uint256 overpayment) { 139 | /* Revert if signature is invalid */ 140 | _verifyRegisterSig({to: to, recovery: recovery, deadline: deadline, sig: sig}); 141 | fid = idRegistry.register(to, recovery); 142 | overpayment = _rentStorage(fid, extraStorage, msg.value, msg.sender); 143 | } 144 | 145 | /*////////////////////////////////////////////////////////////// 146 | PERMISSIONED ACTIONS 147 | //////////////////////////////////////////////////////////////*/ 148 | 149 | /** 150 | * @inheritdoc IIdGateway 151 | */ 152 | function setStorageRegistry( 153 | address _storageRegistry 154 | ) external onlyOwner { 155 | emit SetStorageRegistry(address(storageRegistry), _storageRegistry); 156 | storageRegistry = IStorageRegistry(_storageRegistry); 157 | } 158 | 159 | /*////////////////////////////////////////////////////////////// 160 | SIGNATURE VERIFICATION HELPERS 161 | //////////////////////////////////////////////////////////////*/ 162 | 163 | function _verifyRegisterSig(address to, address recovery, uint256 deadline, bytes memory sig) internal { 164 | _verifySig( 165 | _hashTypedDataV4(keccak256(abi.encode(REGISTER_TYPEHASH, to, recovery, _useNonce(to), deadline))), 166 | to, 167 | deadline, 168 | sig 169 | ); 170 | } 171 | 172 | /*////////////////////////////////////////////////////////////// 173 | STORAGE RENTAL HELPERS 174 | //////////////////////////////////////////////////////////////*/ 175 | 176 | function _rentStorage( 177 | uint256 fid, 178 | uint256 extraUnits, 179 | uint256 payment, 180 | address payer 181 | ) internal returns (uint256 overpayment) { 182 | overpayment = storageRegistry.rent{value: payment}(fid, 1 + extraUnits); 183 | 184 | if (overpayment > 0) { 185 | payer.sendNative(overpayment); 186 | } 187 | } 188 | 189 | receive() external payable { 190 | if (msg.sender != address(storageRegistry)) revert Unauthorized(); 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /src/KeyGateway.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.21; 3 | 4 | import {IKeyGateway} from "./interfaces/IKeyGateway.sol"; 5 | import {IKeyRegistry} from "./interfaces/IKeyRegistry.sol"; 6 | import {EIP712} from "./abstract/EIP712.sol"; 7 | import {Nonces} from "./abstract/Nonces.sol"; 8 | import {Guardians} from "./abstract/Guardians.sol"; 9 | import {Signatures} from "./abstract/Signatures.sol"; 10 | 11 | /** 12 | * @title Farcaster KeyGateway 13 | * 14 | * @notice See https://github.com/farcasterxyz/contracts/blob/v3.1.0/docs/docs.md for an overview. 15 | * 16 | * @custom:security-contact security@merklemanufactory.com 17 | */ 18 | contract KeyGateway is IKeyGateway, Guardians, Signatures, EIP712, Nonces { 19 | /*////////////////////////////////////////////////////////////// 20 | CONSTANTS 21 | //////////////////////////////////////////////////////////////*/ 22 | 23 | /** 24 | * @inheritdoc IKeyGateway 25 | */ 26 | string public constant VERSION = "2023.11.15"; 27 | 28 | /** 29 | * @inheritdoc IKeyGateway 30 | */ 31 | bytes32 public constant ADD_TYPEHASH = keccak256( 32 | "Add(address owner,uint32 keyType,bytes key,uint8 metadataType,bytes metadata,uint256 nonce,uint256 deadline)" 33 | ); 34 | 35 | /*////////////////////////////////////////////////////////////// 36 | STORAGE 37 | //////////////////////////////////////////////////////////////*/ 38 | 39 | /** 40 | * @inheritdoc IKeyGateway 41 | */ 42 | IKeyRegistry public immutable keyRegistry; 43 | 44 | /*////////////////////////////////////////////////////////////// 45 | CONSTRUCTOR 46 | //////////////////////////////////////////////////////////////*/ 47 | 48 | /** 49 | * @notice Configure the address of the KeyRegistry contract. 50 | * Set the initial owner address. 51 | * 52 | * @param _keyRegistry Address of the KeyRegistry contract. 53 | * @param _initialOwner Address of the initial owner. 54 | */ 55 | constructor( 56 | address _keyRegistry, 57 | address _initialOwner 58 | ) Guardians(_initialOwner) EIP712("Farcaster KeyGateway", "1") { 59 | keyRegistry = IKeyRegistry(_keyRegistry); 60 | } 61 | 62 | /*////////////////////////////////////////////////////////////// 63 | REGISTRATION 64 | //////////////////////////////////////////////////////////////*/ 65 | 66 | /** 67 | * @inheritdoc IKeyGateway 68 | */ 69 | function add( 70 | uint32 keyType, 71 | bytes calldata key, 72 | uint8 metadataType, 73 | bytes calldata metadata 74 | ) external whenNotPaused { 75 | keyRegistry.add(msg.sender, keyType, key, metadataType, metadata); 76 | } 77 | 78 | /** 79 | * @inheritdoc IKeyGateway 80 | */ 81 | function addFor( 82 | address fidOwner, 83 | uint32 keyType, 84 | bytes calldata key, 85 | uint8 metadataType, 86 | bytes calldata metadata, 87 | uint256 deadline, 88 | bytes calldata sig 89 | ) external whenNotPaused { 90 | _verifyAddSig(fidOwner, keyType, key, metadataType, metadata, deadline, sig); 91 | keyRegistry.add(fidOwner, keyType, key, metadataType, metadata); 92 | } 93 | 94 | /*////////////////////////////////////////////////////////////// 95 | SIGNATURE VERIFICATION HELPERS 96 | //////////////////////////////////////////////////////////////*/ 97 | 98 | function _verifyAddSig( 99 | address fidOwner, 100 | uint32 keyType, 101 | bytes memory key, 102 | uint8 metadataType, 103 | bytes memory metadata, 104 | uint256 deadline, 105 | bytes memory sig 106 | ) internal { 107 | _verifySig( 108 | _hashTypedDataV4( 109 | keccak256( 110 | abi.encode( 111 | ADD_TYPEHASH, 112 | fidOwner, 113 | keyType, 114 | keccak256(key), 115 | metadataType, 116 | keccak256(metadata), 117 | _useNonce(fidOwner), 118 | deadline 119 | ) 120 | ) 121 | ), 122 | fidOwner, 123 | deadline, 124 | sig 125 | ); 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /src/RecoveryProxy.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.21; 3 | 4 | import {IIdRegistry} from "./interfaces/IIdRegistry.sol"; 5 | import {Ownable2Step} from "openzeppelin/contracts/access/Ownable2Step.sol"; 6 | 7 | /** 8 | * @title Farcaster RecoveryProxy 9 | * 10 | * @notice RecoveryProxy allows the recovery execution logic to be changed 11 | * without changing the recovery address. 12 | * 13 | * The proxy is set to the recovery address and it delegates 14 | * permissions to execute the recovery to its owner. The owner 15 | * can be changed at any time, for example from an EOA to a 2/3 16 | * multisig. This allows a recovery service operator to change the 17 | * recovery mechanisms in the future without requiring each user to 18 | * come online and execute a transaction. 19 | * 20 | * @custom:security-contact security@merklemanufactory.com 21 | */ 22 | contract RecoveryProxy is Ownable2Step { 23 | /*////////////////////////////////////////////////////////////// 24 | EVENTS 25 | //////////////////////////////////////////////////////////////*/ 26 | 27 | /** 28 | * @notice Emit an event when owner changes the IdRegistry 29 | * 30 | * @param oldIdRegistry The previous IIdRegistry 31 | * @param newIdRegistry The new IIdRegistry 32 | */ 33 | event SetIdRegistry(IIdRegistry oldIdRegistry, IIdRegistry newIdRegistry); 34 | 35 | /*////////////////////////////////////////////////////////////// 36 | IMMUTABLES 37 | //////////////////////////////////////////////////////////////*/ 38 | 39 | /** 40 | * @dev Address of the IdRegistry contract 41 | */ 42 | IIdRegistry public idRegistry; 43 | 44 | /*////////////////////////////////////////////////////////////// 45 | CONSTRUCTOR 46 | //////////////////////////////////////////////////////////////*/ 47 | 48 | /** 49 | * @notice Configure the address of the IdRegistry contract and 50 | * set the initial owner. 51 | * 52 | * @param _idRegistry Address of the IdRegistry contract 53 | * @param _initialOwner Initial owner address 54 | */ 55 | constructor(address _idRegistry, address _initialOwner) { 56 | idRegistry = IIdRegistry(_idRegistry); 57 | _transferOwnership(_initialOwner); 58 | } 59 | 60 | /** 61 | * @notice Recover an fid for a user who has set the RecoveryProxy as their recovery address. 62 | * Only owner. 63 | * 64 | * @param from The address that currently owns the fid. 65 | * @param to The address to transfer the fid to. 66 | * @param deadline Expiration timestamp of the signature. 67 | * @param sig EIP-712 Transfer signature signed by the to address. 68 | */ 69 | function recover(address from, address to, uint256 deadline, bytes calldata sig) external onlyOwner { 70 | idRegistry.recover(from, to, deadline, sig); 71 | } 72 | 73 | /** 74 | * @notice Set the IdRegistry address. 75 | * Only owner. 76 | * 77 | * @param _idRegistry IDRegistry contract address. 78 | */ 79 | function setIdRegistry( 80 | IIdRegistry _idRegistry 81 | ) external onlyOwner { 82 | emit SetIdRegistry(idRegistry, _idRegistry); 83 | idRegistry = _idRegistry; 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/abstract/EIP712.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.21; 3 | 4 | import {EIP712 as EIP712Base} from "openzeppelin/contracts/utils/cryptography/EIP712.sol"; 5 | 6 | import {IEIP712} from "../interfaces/abstract/IEIP712.sol"; 7 | 8 | abstract contract EIP712 is IEIP712, EIP712Base { 9 | constructor(string memory name, string memory version) EIP712Base(name, version) {} 10 | 11 | /*////////////////////////////////////////////////////////////// 12 | EIP-712 HELPERS 13 | //////////////////////////////////////////////////////////////*/ 14 | 15 | /** 16 | * @inheritdoc IEIP712 17 | */ 18 | function domainSeparatorV4() external view returns (bytes32) { 19 | return _domainSeparatorV4(); 20 | } 21 | 22 | /** 23 | * @inheritdoc IEIP712 24 | */ 25 | function hashTypedDataV4( 26 | bytes32 structHash 27 | ) external view returns (bytes32) { 28 | return _hashTypedDataV4(structHash); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/abstract/Guardians.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.21; 3 | 4 | import {Ownable2Step} from "openzeppelin/contracts/access/Ownable2Step.sol"; 5 | import {Pausable} from "openzeppelin/contracts/security/Pausable.sol"; 6 | 7 | import {IGuardians} from "../interfaces/abstract/IGuardians.sol"; 8 | 9 | abstract contract Guardians is IGuardians, Ownable2Step, Pausable { 10 | /** 11 | * @notice Mapping of addresses to guardian status. 12 | */ 13 | mapping(address guardian => bool isGuardian) public guardians; 14 | 15 | /*////////////////////////////////////////////////////////////// 16 | MODIFIERS 17 | //////////////////////////////////////////////////////////////*/ 18 | 19 | /** 20 | * @notice Allow only the owner or a guardian to call the 21 | * protected function. 22 | */ 23 | modifier onlyGuardian() { 24 | if (msg.sender != owner() && !guardians[msg.sender]) { 25 | revert OnlyGuardian(); 26 | } 27 | _; 28 | } 29 | 30 | /*////////////////////////////////////////////////////////////// 31 | CONSTRUCTOR 32 | //////////////////////////////////////////////////////////////*/ 33 | 34 | /** 35 | * @notice Set the initial owner address. 36 | * 37 | * @param _initialOwner Address of the contract owner. 38 | */ 39 | constructor( 40 | address _initialOwner 41 | ) { 42 | _transferOwnership(_initialOwner); 43 | } 44 | 45 | /*////////////////////////////////////////////////////////////// 46 | PERMISSIONED FUNCTIONS 47 | //////////////////////////////////////////////////////////////*/ 48 | 49 | /** 50 | * @inheritdoc IGuardians 51 | */ 52 | function addGuardian( 53 | address guardian 54 | ) external onlyOwner { 55 | guardians[guardian] = true; 56 | emit Add(guardian); 57 | } 58 | 59 | /** 60 | * @inheritdoc IGuardians 61 | */ 62 | function removeGuardian( 63 | address guardian 64 | ) external onlyOwner { 65 | guardians[guardian] = false; 66 | emit Remove(guardian); 67 | } 68 | 69 | /** 70 | * @inheritdoc IGuardians 71 | */ 72 | function pause() external onlyGuardian { 73 | _pause(); 74 | } 75 | 76 | /** 77 | * @inheritdoc IGuardians 78 | */ 79 | function unpause() external onlyOwner { 80 | _unpause(); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/abstract/Migration.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.21; 3 | 4 | import {Guardians} from "../abstract/Guardians.sol"; 5 | import {IMigration} from "../interfaces/abstract/IMigration.sol"; 6 | 7 | abstract contract Migration is IMigration, Guardians { 8 | /*////////////////////////////////////////////////////////////// 9 | IMMUTABLES 10 | //////////////////////////////////////////////////////////////*/ 11 | 12 | /** 13 | * @inheritdoc IMigration 14 | */ 15 | uint24 public immutable gracePeriod; 16 | 17 | /*////////////////////////////////////////////////////////////// 18 | STORAGE 19 | //////////////////////////////////////////////////////////////*/ 20 | 21 | /** 22 | * @inheritdoc IMigration 23 | */ 24 | address public migrator; 25 | 26 | /** 27 | * @inheritdoc IMigration 28 | */ 29 | uint40 public migratedAt; 30 | 31 | /*////////////////////////////////////////////////////////////// 32 | MODIFIERS 33 | //////////////////////////////////////////////////////////////*/ 34 | 35 | /** 36 | * @notice Allow only the migrator to call the protected function. 37 | * Revoke permissions after the migration period. 38 | */ 39 | modifier onlyMigrator() { 40 | if (msg.sender != migrator) revert OnlyMigrator(); 41 | if (isMigrated() && block.timestamp > migratedAt + gracePeriod) { 42 | revert PermissionRevoked(); 43 | } 44 | _requirePaused(); 45 | _; 46 | } 47 | 48 | /*////////////////////////////////////////////////////////////// 49 | CONSTRUCTOR 50 | //////////////////////////////////////////////////////////////*/ 51 | 52 | /** 53 | * @notice Set the grace period and migrator address. 54 | * Pauses contract at deployment time. 55 | * 56 | * @param _gracePeriod Migration grace period in seconds. 57 | * @param _initialOwner Initial owner address. Set as migrator. 58 | */ 59 | constructor(uint24 _gracePeriod, address _migrator, address _initialOwner) Guardians(_initialOwner) { 60 | gracePeriod = _gracePeriod; 61 | migrator = _migrator; 62 | emit SetMigrator(address(0), _migrator); 63 | _pause(); 64 | } 65 | 66 | /*////////////////////////////////////////////////////////////// 67 | VIEWS 68 | //////////////////////////////////////////////////////////////*/ 69 | 70 | /** 71 | * @inheritdoc IMigration 72 | */ 73 | function isMigrated() public view returns (bool) { 74 | return migratedAt != 0; 75 | } 76 | 77 | /*////////////////////////////////////////////////////////////// 78 | MIGRATION 79 | //////////////////////////////////////////////////////////////*/ 80 | 81 | /** 82 | * @inheritdoc IMigration 83 | */ 84 | function migrate() external { 85 | if (msg.sender != migrator) revert OnlyMigrator(); 86 | if (isMigrated()) revert AlreadyMigrated(); 87 | _requirePaused(); 88 | migratedAt = uint40(block.timestamp); 89 | emit Migrated(migratedAt); 90 | } 91 | 92 | /*////////////////////////////////////////////////////////////// 93 | SET MIGRATOR 94 | //////////////////////////////////////////////////////////////*/ 95 | 96 | /** 97 | * @inheritdoc IMigration 98 | */ 99 | function setMigrator( 100 | address _migrator 101 | ) public onlyOwner { 102 | if (isMigrated()) revert AlreadyMigrated(); 103 | _requirePaused(); 104 | emit SetMigrator(migrator, _migrator); 105 | migrator = _migrator; 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/abstract/Nonces.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.21; 3 | 4 | import {Nonces as NoncesBase} from "openzeppelin-latest/contracts/utils/Nonces.sol"; 5 | import {INonces} from "../interfaces/abstract/INonces.sol"; 6 | 7 | abstract contract Nonces is INonces, NoncesBase { 8 | /*////////////////////////////////////////////////////////////// 9 | NONCE MANAGEMENT 10 | //////////////////////////////////////////////////////////////*/ 11 | 12 | /** 13 | * @inheritdoc INonces 14 | */ 15 | function useNonce() external returns (uint256) { 16 | return _useNonce(msg.sender); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/abstract/Signatures.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.21; 3 | 4 | import {SignatureChecker} from "openzeppelin/contracts/utils/cryptography/SignatureChecker.sol"; 5 | import {ISignatures} from "../interfaces/abstract/ISignatures.sol"; 6 | 7 | abstract contract Signatures is ISignatures { 8 | /*////////////////////////////////////////////////////////////// 9 | SIGNATURE VERIFICATION HELPERS 10 | //////////////////////////////////////////////////////////////*/ 11 | 12 | function _verifySig(bytes32 digest, address signer, uint256 deadline, bytes memory sig) internal view { 13 | if (block.timestamp > deadline) revert SignatureExpired(); 14 | if (!SignatureChecker.isValidSignatureNow(signer, digest, sig)) { 15 | revert InvalidSignature(); 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/interfaces/IBundler.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.21; 3 | 4 | import {IIdGateway} from "./IIdGateway.sol"; 5 | import {IKeyGateway} from "./IKeyGateway.sol"; 6 | import {IStorageRegistry} from "./IStorageRegistry.sol"; 7 | 8 | interface IBundler { 9 | /*////////////////////////////////////////////////////////////// 10 | ERRORS 11 | //////////////////////////////////////////////////////////////*/ 12 | 13 | /// @dev Revert if the caller does not have the authority to perform the action. 14 | error Unauthorized(); 15 | 16 | /*////////////////////////////////////////////////////////////// 17 | STRUCTS 18 | //////////////////////////////////////////////////////////////*/ 19 | 20 | /// @notice Data needed to register an fid with signature. 21 | struct RegistrationParams { 22 | address to; 23 | address recovery; 24 | uint256 deadline; 25 | bytes sig; 26 | } 27 | 28 | /// @notice Data needed to add a signer with signature. 29 | struct SignerParams { 30 | uint32 keyType; 31 | bytes key; 32 | uint8 metadataType; 33 | bytes metadata; 34 | uint256 deadline; 35 | bytes sig; 36 | } 37 | 38 | /*////////////////////////////////////////////////////////////// 39 | CONSTANTS 40 | //////////////////////////////////////////////////////////////*/ 41 | 42 | /** 43 | * @notice Contract version specified in the Farcaster protocol version scheme. 44 | */ 45 | function VERSION() external view returns (string memory); 46 | 47 | /** 48 | * @dev Address of the IdGateway contract 49 | */ 50 | function idGateway() external view returns (IIdGateway); 51 | 52 | /** 53 | * @dev Address of the KeyGateway contract 54 | */ 55 | function keyGateway() external view returns (IKeyGateway); 56 | 57 | /*////////////////////////////////////////////////////////////// 58 | VIEWS 59 | //////////////////////////////////////////////////////////////*/ 60 | 61 | /** 62 | * @notice Calculate the total price of a registration. 63 | * 64 | * @param extraStorage Number of additional storage units to rent. All registrations include 1 65 | * storage unit, but additional storage can be rented at registration time. 66 | * 67 | * @return Total price in wei. 68 | * 69 | */ 70 | function price( 71 | uint256 extraStorage 72 | ) external view returns (uint256); 73 | 74 | /*////////////////////////////////////////////////////////////// 75 | REGISTRATION 76 | //////////////////////////////////////////////////////////////*/ 77 | 78 | /** 79 | * @notice Register an fid, add one or more signers, and rent storage in a single transaction. 80 | * 81 | * @param registerParams Struct containing register parameters: to, recovery, deadline, and signature. 82 | * @param signerParams Array of structs containing signer parameters: keyType, key, metadataType, 83 | * metadata, deadline, and signature. 84 | * @param extraStorage Number of additional storage units to rent. (fid registration includes 1 unit). 85 | * 86 | */ 87 | function register( 88 | RegistrationParams calldata registerParams, 89 | SignerParams[] calldata signerParams, 90 | uint256 extraStorage 91 | ) external payable returns (uint256 fid); 92 | 93 | /** 94 | * @notice Add multiple keys in a single transaction. 95 | * 96 | * @param fidOwner The fid owner address. 97 | * @param signerParams Array of structs containing signer parameters: keyType, key, metadataType, 98 | * metadata, deadline, and signature. 99 | * 100 | */ 101 | function addKeys(address fidOwner, SignerParams[] calldata signerParams) external; 102 | } 103 | -------------------------------------------------------------------------------- /src/interfaces/IBundlerV1.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.21; 3 | 4 | import {IIdGateway} from "./IIdGateway.sol"; 5 | import {IKeyGateway} from "./IKeyGateway.sol"; 6 | import {IStorageRegistry} from "./IStorageRegistry.sol"; 7 | 8 | interface IBundlerV1 { 9 | /*////////////////////////////////////////////////////////////// 10 | ERRORS 11 | //////////////////////////////////////////////////////////////*/ 12 | 13 | /// @dev Revert if the caller does not have the authority to perform the action. 14 | error Unauthorized(); 15 | 16 | /*////////////////////////////////////////////////////////////// 17 | STRUCTS 18 | //////////////////////////////////////////////////////////////*/ 19 | 20 | /// @notice Data needed to register an fid with signature. 21 | struct RegistrationParams { 22 | address to; 23 | address recovery; 24 | uint256 deadline; 25 | bytes sig; 26 | } 27 | 28 | /// @notice Data needed to add a signer with signature. 29 | struct SignerParams { 30 | uint32 keyType; 31 | bytes key; 32 | uint8 metadataType; 33 | bytes metadata; 34 | uint256 deadline; 35 | bytes sig; 36 | } 37 | 38 | /*////////////////////////////////////////////////////////////// 39 | CONSTANTS 40 | //////////////////////////////////////////////////////////////*/ 41 | 42 | /** 43 | * @notice Contract version specified in the Farcaster protocol version scheme. 44 | */ 45 | function VERSION() external view returns (string memory); 46 | 47 | /** 48 | * @dev Address of the IdGateway contract 49 | */ 50 | function idGateway() external view returns (IIdGateway); 51 | 52 | /** 53 | * @dev Address of the KeyGateway contract 54 | */ 55 | function keyGateway() external view returns (IKeyGateway); 56 | 57 | /*////////////////////////////////////////////////////////////// 58 | VIEWS 59 | //////////////////////////////////////////////////////////////*/ 60 | 61 | /** 62 | * @notice Calculate the total price of a registration. 63 | * 64 | * @param extraStorage Number of additional storage units to rent. All registrations include 1 65 | * storage unit, but additional storage can be rented at registration time. 66 | * 67 | * @return Total price in wei. 68 | * 69 | */ 70 | function price( 71 | uint256 extraStorage 72 | ) external view returns (uint256); 73 | 74 | /*////////////////////////////////////////////////////////////// 75 | REGISTRATION 76 | //////////////////////////////////////////////////////////////*/ 77 | 78 | /** 79 | * @notice Register an fid, add one or more signers, and rent storage in a single transaction. 80 | * 81 | * @param registerParams Struct containing register parameters: to, recovery, deadline, and signature. 82 | * @param signerParams Array of structs containing signer parameters: keyType, key, metadataType, 83 | * metadata, deadline, and signature. 84 | * @param extraStorage Number of additional storage units to rent. (fid registration includes 1 unit). 85 | * 86 | */ 87 | function register( 88 | RegistrationParams calldata registerParams, 89 | SignerParams[] calldata signerParams, 90 | uint256 extraStorage 91 | ) external payable returns (uint256 fid); 92 | } 93 | -------------------------------------------------------------------------------- /src/interfaces/IIdGateway.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.21; 3 | 4 | import {IStorageRegistry} from "./IStorageRegistry.sol"; 5 | import {IIdRegistry} from "./IIdRegistry.sol"; 6 | 7 | interface IIdGateway { 8 | /*////////////////////////////////////////////////////////////// 9 | ERRORS 10 | //////////////////////////////////////////////////////////////*/ 11 | 12 | /// @dev Revert if the caller does not have the authority to perform the action. 13 | error Unauthorized(); 14 | 15 | /*////////////////////////////////////////////////////////////// 16 | EVENTS 17 | //////////////////////////////////////////////////////////////*/ 18 | 19 | /** 20 | * @dev Emit an event when the admin sets a new StorageRegistry address. 21 | * 22 | * @param oldStorageRegistry The previous StorageRegistry address. 23 | * @param newStorageRegistry The new StorageRegistry address. 24 | */ 25 | event SetStorageRegistry(address oldStorageRegistry, address newStorageRegistry); 26 | 27 | /*////////////////////////////////////////////////////////////// 28 | CONSTANTS 29 | //////////////////////////////////////////////////////////////*/ 30 | 31 | /** 32 | * @notice Contract version specified in the Farcaster protocol version scheme. 33 | */ 34 | function VERSION() external view returns (string memory); 35 | 36 | /** 37 | * @notice EIP-712 typehash for Register signatures. 38 | */ 39 | function REGISTER_TYPEHASH() external view returns (bytes32); 40 | 41 | /*////////////////////////////////////////////////////////////// 42 | STORAGE 43 | //////////////////////////////////////////////////////////////*/ 44 | 45 | /** 46 | * @notice The IdRegistry contract 47 | */ 48 | function idRegistry() external view returns (IIdRegistry); 49 | 50 | /** 51 | * @notice The StorageRegistry contract 52 | */ 53 | function storageRegistry() external view returns (IStorageRegistry); 54 | 55 | /*////////////////////////////////////////////////////////////// 56 | PRICE VIEW 57 | //////////////////////////////////////////////////////////////*/ 58 | 59 | /** 60 | * @notice Calculate the total price to register, equal to 1 storage unit. 61 | * 62 | * @return Total price in wei. 63 | */ 64 | function price() external view returns (uint256); 65 | 66 | /** 67 | * @notice Calculate the total price to register, including additional storage. 68 | * 69 | * @param extraStorage Number of additional storage units to rent. 70 | * 71 | * @return Total price in wei. 72 | */ 73 | function price( 74 | uint256 extraStorage 75 | ) external view returns (uint256); 76 | 77 | /*////////////////////////////////////////////////////////////// 78 | REGISTRATION LOGIC 79 | //////////////////////////////////////////////////////////////*/ 80 | 81 | /** 82 | * @notice Register a new Farcaster ID (fid) to the caller. The caller must not have an fid. 83 | * 84 | * @param recovery Address which can recover the fid. Set to zero to disable recovery. 85 | * 86 | * @return fid registered FID. 87 | */ 88 | function register( 89 | address recovery 90 | ) external payable returns (uint256 fid, uint256 overpayment); 91 | 92 | /** 93 | * @notice Register a new Farcaster ID (fid) to the caller and rent additional storage. 94 | * The caller must not have an fid. 95 | * 96 | * @param recovery Address which can recover the fid. Set to zero to disable recovery. 97 | * @param extraStorage Number of additional storage units to rent. 98 | * 99 | * @return fid registered FID. 100 | */ 101 | function register( 102 | address recovery, 103 | uint256 extraStorage 104 | ) external payable returns (uint256 fid, uint256 overpayment); 105 | 106 | /** 107 | * @notice Register a new Farcaster ID (fid) to any address. A signed message from the address 108 | * must be provided which approves both the to and the recovery. The address must not 109 | * have an fid. 110 | * 111 | * @param to Address which will own the fid. 112 | * @param recovery Address which can recover the fid. Set to zero to disable recovery. 113 | * @param deadline Expiration timestamp of the signature. 114 | * @param sig EIP-712 Register signature signed by the to address. 115 | * 116 | * @return fid registered FID. 117 | */ 118 | function registerFor( 119 | address to, 120 | address recovery, 121 | uint256 deadline, 122 | bytes calldata sig 123 | ) external payable returns (uint256 fid, uint256 overpayment); 124 | 125 | /** 126 | * @notice Register a new Farcaster ID (fid) to any address and rent additional storage. 127 | * A signed message from the address must be provided which approves both the to 128 | * and the recovery. The address must not have an fid. 129 | * 130 | * @param to Address which will own the fid. 131 | * @param recovery Address which can recover the fid. Set to zero to disable recovery. 132 | * @param deadline Expiration timestamp of the signature. 133 | * @param sig EIP-712 Register signature signed by the to address. 134 | * @param extraStorage Number of additional storage units to rent. 135 | * 136 | * @return fid registered FID. 137 | */ 138 | function registerFor( 139 | address to, 140 | address recovery, 141 | uint256 deadline, 142 | bytes calldata sig, 143 | uint256 extraStorage 144 | ) external payable returns (uint256 fid, uint256 overpayment); 145 | 146 | /*////////////////////////////////////////////////////////////// 147 | PERMISSIONED ACTIONS 148 | //////////////////////////////////////////////////////////////*/ 149 | 150 | /** 151 | * @notice Set the StorageRegistry address. Only callable by owner. 152 | * 153 | * @param _storageRegistry The new StorageREgistry address. 154 | */ 155 | function setStorageRegistry( 156 | address _storageRegistry 157 | ) external; 158 | } 159 | -------------------------------------------------------------------------------- /src/interfaces/IKeyGateway.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.21; 3 | 4 | import {IKeyRegistry} from "./IKeyRegistry.sol"; 5 | 6 | interface IKeyGateway { 7 | /*////////////////////////////////////////////////////////////// 8 | CONSTANTS 9 | //////////////////////////////////////////////////////////////*/ 10 | 11 | /** 12 | * @notice Contract version specified in the Farcaster protocol version scheme. 13 | */ 14 | function VERSION() external view returns (string memory); 15 | 16 | /** 17 | * @notice EIP-712 typehash for Add signatures. 18 | */ 19 | function ADD_TYPEHASH() external view returns (bytes32); 20 | 21 | /*////////////////////////////////////////////////////////////// 22 | STORAGE 23 | //////////////////////////////////////////////////////////////*/ 24 | 25 | /** 26 | * @notice The KeyRegistry contract. 27 | */ 28 | function keyRegistry() external view returns (IKeyRegistry); 29 | 30 | /*////////////////////////////////////////////////////////////// 31 | REGISTRATION 32 | //////////////////////////////////////////////////////////////*/ 33 | 34 | /** 35 | * @notice Add a key associated with the caller's fid, setting the key state to ADDED. 36 | * 37 | * @param keyType The key's numeric keyType. 38 | * @param key Bytes of the key to add. 39 | * @param metadataType Metadata type ID. 40 | * @param metadata Metadata about the key, which is not stored and only emitted in an event. 41 | */ 42 | function add(uint32 keyType, bytes calldata key, uint8 metadataType, bytes calldata metadata) external; 43 | 44 | /** 45 | * @notice Add a key on behalf of another fid owner, setting the key state to ADDED. 46 | * caller must supply a valid EIP-712 Add signature from the fid owner. 47 | * 48 | * @param fidOwner The fid owner address. 49 | * @param keyType The key's numeric keyType. 50 | * @param key Bytes of the key to add. 51 | * @param metadataType Metadata type ID. 52 | * @param metadata Metadata about the key, which is not stored and only emitted in an event. 53 | * @param deadline Deadline after which the signature expires. 54 | * @param sig EIP-712 Add signature generated by fid owner. 55 | */ 56 | function addFor( 57 | address fidOwner, 58 | uint32 keyType, 59 | bytes calldata key, 60 | uint8 metadataType, 61 | bytes calldata metadata, 62 | uint256 deadline, 63 | bytes calldata sig 64 | ) external; 65 | } 66 | -------------------------------------------------------------------------------- /src/interfaces/IMetadataValidator.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.21; 3 | 4 | interface IMetadataValidator { 5 | /** 6 | * @notice Validate metadata associated with a key. 7 | * 8 | * @param userFid The fid associated with the key. 9 | * @param key Bytes of the key. 10 | * @param metadata Metadata about the key. 11 | * 12 | * @return bool Whether the provided key and metadata are valid. 13 | */ 14 | function validate(uint256 userFid, bytes memory key, bytes memory metadata) external returns (bool); 15 | } 16 | -------------------------------------------------------------------------------- /src/interfaces/IdRegistryLike.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.21; 3 | 4 | /** 5 | * @dev Minimal interface for IdRegistry, used by the KeyRegistry. 6 | */ 7 | interface IdRegistryLike { 8 | /*////////////////////////////////////////////////////////////// 9 | STORAGE 10 | //////////////////////////////////////////////////////////////*/ 11 | 12 | /** 13 | * @notice Maps each address to an fid, or zero if it does not own an fid. 14 | */ 15 | function idOf( 16 | address fidOwner 17 | ) external view returns (uint256); 18 | 19 | /*////////////////////////////////////////////////////////////// 20 | VIEWS 21 | //////////////////////////////////////////////////////////////*/ 22 | 23 | /** 24 | * @notice Verify that a signature was produced by the custody address that owns an fid. 25 | * 26 | * @param custodyAddress The address to check the signature of. 27 | * @param fid The fid to check the signature of. 28 | * @param digest The digest that was signed. 29 | * @param sig The signature to check. 30 | * 31 | * @return isValid Whether provided signature is valid. 32 | */ 33 | function verifyFidSignature( 34 | address custodyAddress, 35 | uint256 fid, 36 | bytes32 digest, 37 | bytes calldata sig 38 | ) external view returns (bool isValid); 39 | } 40 | -------------------------------------------------------------------------------- /src/interfaces/abstract/IEIP712.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.21; 3 | 4 | interface IEIP712 { 5 | /*////////////////////////////////////////////////////////////// 6 | EIP-712 HELPERS 7 | //////////////////////////////////////////////////////////////*/ 8 | 9 | /** 10 | * @notice Helper view to read EIP-712 domain separator. 11 | * 12 | * @return bytes32 domain separator hash. 13 | */ 14 | function domainSeparatorV4() external view returns (bytes32); 15 | 16 | /** 17 | * @notice Helper view to hash EIP-712 typed data onchain. 18 | * 19 | * @param structHash EIP-712 typed data hash. 20 | * 21 | * @return bytes32 EIP-712 message digest. 22 | */ 23 | function hashTypedDataV4( 24 | bytes32 structHash 25 | ) external view returns (bytes32); 26 | } 27 | -------------------------------------------------------------------------------- /src/interfaces/abstract/IGuardians.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.21; 3 | 4 | interface IGuardians { 5 | /*////////////////////////////////////////////////////////////// 6 | EVENTS 7 | //////////////////////////////////////////////////////////////*/ 8 | 9 | /** 10 | * @dev Emit an event when owner adds a new guardian address. 11 | * 12 | * @param guardian Address of the added guardian. 13 | */ 14 | event Add(address indexed guardian); 15 | 16 | /** 17 | * @dev Emit an event when owner removes a guardian address. 18 | * 19 | * @param guardian Address of the removed guardian. 20 | */ 21 | event Remove(address indexed guardian); 22 | 23 | /*////////////////////////////////////////////////////////////// 24 | ERRORS 25 | //////////////////////////////////////////////////////////////*/ 26 | 27 | /// @dev Revert if an unauthorized caller calls a protected function. 28 | error OnlyGuardian(); 29 | 30 | /*////////////////////////////////////////////////////////////// 31 | PERMISSIONED FUNCTIONS 32 | //////////////////////////////////////////////////////////////*/ 33 | 34 | /** 35 | * @notice Add an address as a guardian. Only callable by owner. 36 | * 37 | * @param guardian Address of the guardian. 38 | */ 39 | function addGuardian( 40 | address guardian 41 | ) external; 42 | 43 | /** 44 | * @notice Remove a guardian. Only callable by owner. 45 | * 46 | * @param guardian Address of the guardian. 47 | */ 48 | function removeGuardian( 49 | address guardian 50 | ) external; 51 | 52 | /** 53 | * @notice Pause the contract. Only callable by owner or a guardian. 54 | */ 55 | function pause() external; 56 | 57 | /** 58 | * @notice Unpause the contract. Only callable by owner. 59 | */ 60 | function unpause() external; 61 | } 62 | -------------------------------------------------------------------------------- /src/interfaces/abstract/IMigration.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.21; 3 | 4 | import {IGuardians} from "./IGuardians.sol"; 5 | 6 | interface IMigration is IGuardians { 7 | /*////////////////////////////////////////////////////////////// 8 | ERRORS 9 | //////////////////////////////////////////////////////////////*/ 10 | 11 | /// @dev Revert if the caller is not the migrator. 12 | error OnlyMigrator(); 13 | 14 | /// @dev Revert if the migrator calls a migration function after the grace period. 15 | error PermissionRevoked(); 16 | 17 | /// @dev Revert if the migrator calls migrate more than once. 18 | error AlreadyMigrated(); 19 | 20 | /*////////////////////////////////////////////////////////////// 21 | EVENTS 22 | //////////////////////////////////////////////////////////////*/ 23 | 24 | /** 25 | * @dev Emit an event when the admin calls migrate(). Used to migrate 26 | * Hubs from reading events from one contract to another. 27 | * 28 | * @param migratedAt The timestamp at which the migration occurred. 29 | */ 30 | event Migrated(uint256 indexed migratedAt); 31 | 32 | /** 33 | * @notice Emit an event when the owner changes the migrator address. 34 | * 35 | * @param oldMigrator The address of the previous migrator. 36 | * @param newMigrator The address of the new migrator. 37 | */ 38 | event SetMigrator(address oldMigrator, address newMigrator); 39 | 40 | /*////////////////////////////////////////////////////////////// 41 | IMMUTABLES 42 | //////////////////////////////////////////////////////////////*/ 43 | 44 | /** 45 | * @notice Period in seconds after migration during which admin can continue to call protected 46 | * migration functions. Admins can make corrections to the migrated data during the 47 | * grace period if necessary, but cannot make changes after it expires. 48 | */ 49 | function gracePeriod() external view returns (uint24); 50 | 51 | /*////////////////////////////////////////////////////////////// 52 | STORAGE 53 | //////////////////////////////////////////////////////////////*/ 54 | 55 | /** 56 | * @notice Migration admin address. 57 | */ 58 | function migrator() external view returns (address); 59 | 60 | /** 61 | * @notice Timestamp at which data is migrated. Hubs will cut over to use this contract as their 62 | * source of truth after this timestamp. 63 | */ 64 | function migratedAt() external view returns (uint40); 65 | 66 | /*////////////////////////////////////////////////////////////// 67 | VIEWS 68 | //////////////////////////////////////////////////////////////*/ 69 | 70 | /** 71 | * @notice Check if the contract has been migrated. 72 | * 73 | * @return true if the contract has been migrated, false otherwise. 74 | */ 75 | function isMigrated() external view returns (bool); 76 | 77 | /*////////////////////////////////////////////////////////////// 78 | PERMISSIONED ACTIONS 79 | //////////////////////////////////////////////////////////////*/ 80 | 81 | /** 82 | * @notice Set the time of the migration and emit an event. Hubs will watch this event and 83 | * cut over to use this contract as their source of truth after this timestamp. 84 | * Only callable by the migrator. 85 | */ 86 | function migrate() external; 87 | 88 | /** 89 | * @notice Set the migrator address. Only callable by owner. 90 | * 91 | * @param _migrator Migrator address. 92 | */ 93 | function setMigrator( 94 | address _migrator 95 | ) external; 96 | } 97 | -------------------------------------------------------------------------------- /src/interfaces/abstract/INonces.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.21; 3 | 4 | interface INonces { 5 | /*////////////////////////////////////////////////////////////// 6 | NONCE MANAGEMENT 7 | //////////////////////////////////////////////////////////////*/ 8 | 9 | /** 10 | * @notice Increase caller's nonce, invalidating previous signatures. 11 | * 12 | * @return uint256 The caller's new nonce. 13 | */ 14 | function useNonce() external returns (uint256); 15 | } 16 | -------------------------------------------------------------------------------- /src/interfaces/abstract/ISignatures.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.21; 3 | 4 | interface ISignatures { 5 | /*////////////////////////////////////////////////////////////// 6 | ERRORS 7 | //////////////////////////////////////////////////////////////*/ 8 | 9 | /// @dev Revert when the signature provided is invalid. 10 | error InvalidSignature(); 11 | 12 | /// @dev Revert when the block.timestamp is ahead of the signature deadline. 13 | error SignatureExpired(); 14 | } 15 | -------------------------------------------------------------------------------- /src/libraries/EnumerableKeySet.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.21; 3 | 4 | struct KeySet { 5 | // Storage of set values 6 | bytes[] _values; 7 | // Position of the value in the `values` array, plus 1 because index 0 8 | // means a value is not in the set. 9 | mapping(bytes => uint256) _indexes; 10 | } 11 | 12 | /** 13 | * @dev Modified from OpenZeppelin v4.9.3 EnumerableSet 14 | */ 15 | library EnumerableKeySet { 16 | /** 17 | * @dev Add a key to the set. O(1). 18 | * 19 | * Returns true if the value was added to the set, that is if it was not 20 | * already present. 21 | */ 22 | function add(KeySet storage set, bytes calldata value) internal returns (bool) { 23 | if (!contains(set, value)) { 24 | set._values.push(value); 25 | // The value is stored at length-1, but we add 1 to all indexes 26 | // and use 0 as a sentinel value 27 | set._indexes[value] = set._values.length; 28 | return true; 29 | } else { 30 | return false; 31 | } 32 | } 33 | 34 | /** 35 | * @dev Removes a key from the set. O(1). 36 | * 37 | * Returns true if the value was removed from the set, that is if it was 38 | * present. 39 | */ 40 | function remove(KeySet storage set, bytes calldata value) internal returns (bool) { 41 | // We read and store the value's index to prevent multiple reads from the same storage slot 42 | uint256 valueIndex = set._indexes[value]; 43 | 44 | if (valueIndex != 0) { 45 | // Equivalent to contains(set, value) 46 | // To delete an element from the _values array in O(1), we swap the element to delete with the last one in 47 | // the array, and then remove the last element (sometimes called as 'swap and pop'). 48 | // This modifies the order of the array. 49 | 50 | uint256 toDeleteIndex = valueIndex - 1; 51 | uint256 lastIndex = set._values.length - 1; 52 | 53 | if (lastIndex != toDeleteIndex) { 54 | bytes memory lastValue = set._values[lastIndex]; 55 | 56 | // Move the last value to the index where the value to delete is 57 | set._values[toDeleteIndex] = lastValue; 58 | // Update the index for the moved value 59 | set._indexes[lastValue] = valueIndex; // Replace lastValue's index to valueIndex 60 | } 61 | 62 | // Delete the slot where the moved value was stored 63 | set._values.pop(); 64 | 65 | // Delete the index for the deleted slot 66 | delete set._indexes[value]; 67 | 68 | return true; 69 | } else { 70 | return false; 71 | } 72 | } 73 | 74 | /** 75 | * @dev Returns true if the value is in the set. O(1). 76 | */ 77 | function contains(KeySet storage set, bytes calldata value) internal view returns (bool) { 78 | return set._indexes[value] != 0; 79 | } 80 | 81 | /** 82 | * @dev Returns the number of values in the set. O(1). 83 | */ 84 | function length( 85 | KeySet storage set 86 | ) internal view returns (uint256) { 87 | return set._values.length; 88 | } 89 | 90 | /** 91 | * @dev Returns the value stored at position `index` in the set. O(1). 92 | * 93 | * Note that there are no guarantees on the ordering of values inside the 94 | * array, and it may change when more values are added or removed. 95 | * 96 | * Requirements: 97 | * 98 | * - `index` must be strictly less than {length}. 99 | */ 100 | function at(KeySet storage set, uint256 index) internal view returns (bytes memory) { 101 | return set._values[index]; 102 | } 103 | 104 | /** 105 | * @dev Return the entire set in an array 106 | * 107 | * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed 108 | * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that 109 | * this function has an unbounded cost, and using it as part of a state-changing function may render the function 110 | * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block. 111 | */ 112 | function values( 113 | KeySet storage set 114 | ) internal view returns (bytes[] memory) { 115 | return set._values; 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/libraries/TransferHelper.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.21; 3 | 4 | library TransferHelper { 5 | /// @dev Revert when a native token transfer fails. 6 | error CallFailed(); 7 | 8 | /** 9 | * @dev Native token transfer helper. 10 | */ 11 | function sendNative(address to, uint256 amount) internal { 12 | bool success; 13 | 14 | // solhint-disable-next-line no-inline-assembly 15 | assembly ("memory-safe") { 16 | // Transfer the native token and store if it succeeded or not. 17 | success := call(gas(), to, amount, 0, 0, 0, 0) 18 | } 19 | 20 | if (!success) revert CallFailed(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/validators/SignedKeyRequestValidator.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.21; 3 | 4 | import {Ownable2Step} from "openzeppelin/contracts/access/Ownable2Step.sol"; 5 | 6 | import {EIP712} from "../abstract/EIP712.sol"; 7 | import {IMetadataValidator} from "../interfaces/IMetadataValidator.sol"; 8 | import {IdRegistryLike} from "../interfaces/IdRegistryLike.sol"; 9 | 10 | /** 11 | * @title Farcaster SignedKeyRequestValidator 12 | * 13 | * @notice See https://github.com/farcasterxyz/contracts/blob/v3.1.0/docs/docs.md for an overview. 14 | * 15 | * @custom:security-contact security@merklemanufactory.com 16 | */ 17 | contract SignedKeyRequestValidator is IMetadataValidator, Ownable2Step, EIP712 { 18 | /*////////////////////////////////////////////////////////////// 19 | STRUCTS 20 | //////////////////////////////////////////////////////////////*/ 21 | 22 | /** 23 | * @notice Signed key request specific metadata. 24 | * 25 | * @param requestFid The fid of the entity requesting to add 26 | * a signer key. 27 | * @param requestSigner Signer address. Must be the owner of 28 | * requestFid. 29 | * @param signature EIP-712 SignedKeyRequest signature. 30 | * @param deadline block.timestamp after which signature expires. 31 | */ 32 | struct SignedKeyRequestMetadata { 33 | uint256 requestFid; 34 | address requestSigner; 35 | bytes signature; 36 | uint256 deadline; 37 | } 38 | 39 | /*////////////////////////////////////////////////////////////// 40 | EVENTS 41 | //////////////////////////////////////////////////////////////*/ 42 | 43 | /** 44 | * @dev Emit an event when the admin sets a new IdRegistry contract address. 45 | * 46 | * @param oldIdRegistry The previous IdRegistry address. 47 | * @param newIdRegistry The new IdRegistry address. 48 | */ 49 | event SetIdRegistry(address oldIdRegistry, address newIdRegistry); 50 | 51 | /*////////////////////////////////////////////////////////////// 52 | CONSTANTS 53 | //////////////////////////////////////////////////////////////*/ 54 | 55 | /** 56 | * @dev Contract version specified using Farcaster protocol version scheme. 57 | */ 58 | string public constant VERSION = "2023.08.23"; 59 | 60 | bytes32 public constant METADATA_TYPEHASH = 61 | keccak256("SignedKeyRequest(uint256 requestFid,bytes key,uint256 deadline)"); 62 | 63 | /*////////////////////////////////////////////////////////////// 64 | STORAGE 65 | //////////////////////////////////////////////////////////////*/ 66 | 67 | /** 68 | * @dev The IdRegistry contract. 69 | */ 70 | IdRegistryLike public idRegistry; 71 | 72 | /*////////////////////////////////////////////////////////////// 73 | CONSTRUCTOR 74 | //////////////////////////////////////////////////////////////*/ 75 | 76 | /** 77 | * @notice Set the IdRegistry and owner. 78 | * 79 | * @param _idRegistry IdRegistry contract address. 80 | * @param _initialOwner Initial contract owner address. 81 | */ 82 | constructor(address _idRegistry, address _initialOwner) EIP712("Farcaster SignedKeyRequestValidator", "1") { 83 | idRegistry = IdRegistryLike(_idRegistry); 84 | _transferOwnership(_initialOwner); 85 | } 86 | 87 | /*////////////////////////////////////////////////////////////// 88 | VALIDATION 89 | //////////////////////////////////////////////////////////////*/ 90 | 91 | /** 92 | * @notice Validate the SignedKeyRequest metadata associated with a signer key. 93 | * (Key type 1, Metadata type 1) 94 | * 95 | * @param key The EdDSA public key of the signer. 96 | * @param signedKeyRequestBytes An abi-encoded SignedKeyRequest struct, provided as the 97 | * metadata argument to KeyRegistry.add. 98 | * 99 | * @return true if signature is valid and signer owns requestFid, false otherwise. 100 | */ 101 | function validate( 102 | uint256, /* userFid */ 103 | bytes memory key, 104 | bytes calldata signedKeyRequestBytes 105 | ) external view returns (bool) { 106 | SignedKeyRequestMetadata memory metadata = abi.decode(signedKeyRequestBytes, (SignedKeyRequestMetadata)); 107 | 108 | if (idRegistry.idOf(metadata.requestSigner) != metadata.requestFid) { 109 | return false; 110 | } 111 | if (block.timestamp > metadata.deadline) return false; 112 | if (key.length != 32) return false; 113 | 114 | return idRegistry.verifyFidSignature( 115 | metadata.requestSigner, 116 | metadata.requestFid, 117 | _hashTypedDataV4( 118 | keccak256(abi.encode(METADATA_TYPEHASH, metadata.requestFid, keccak256(key), metadata.deadline)) 119 | ), 120 | metadata.signature 121 | ); 122 | } 123 | 124 | /*////////////////////////////////////////////////////////////// 125 | HELPERS 126 | //////////////////////////////////////////////////////////////*/ 127 | 128 | /** 129 | * @notice ABI-encode a SignedKeyRequestMetadata struct. 130 | * 131 | * @param metadata The SignedKeyRequestMetadata struct to encode. 132 | * 133 | * @return bytes memory Bytes of ABI-encoded struct. 134 | */ 135 | function encodeMetadata( 136 | SignedKeyRequestMetadata calldata metadata 137 | ) external pure returns (bytes memory) { 138 | return abi.encode(metadata); 139 | } 140 | 141 | /*////////////////////////////////////////////////////////////// 142 | ADMIN 143 | //////////////////////////////////////////////////////////////*/ 144 | 145 | /** 146 | * @notice Set the IdRegistry contract address. Only callable by owner. 147 | * 148 | * @param _idRegistry The new IdRegistry address. 149 | */ 150 | function setIdRegistry( 151 | address _idRegistry 152 | ) external onlyOwner { 153 | emit SetIdRegistry(address(idRegistry), _idRegistry); 154 | idRegistry = IdRegistryLike(_idRegistry); 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /test/Bundler/BundlerTestSuite.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.21; 3 | 4 | import {TestSuiteSetup} from "../TestSuiteSetup.sol"; 5 | import {IdGatewayTestSuite} from "../IdGateway/IdGatewayTestSuite.sol"; 6 | 7 | import {KeyGateway} from "../../src/KeyGateway.sol"; 8 | import {Bundler} from "../../src/Bundler.sol"; 9 | 10 | /* solhint-disable state-visibility */ 11 | 12 | abstract contract BundlerTestSuite is IdGatewayTestSuite { 13 | KeyGateway keyGateway; 14 | Bundler bundler; 15 | 16 | function setUp() public virtual override { 17 | super.setUp(); 18 | 19 | keyGateway = new KeyGateway(address(keyRegistry), owner); 20 | 21 | vm.prank(owner); 22 | keyRegistry.setKeyGateway(address(keyGateway)); 23 | 24 | // Set up the BundleRegistry 25 | bundler = new Bundler(address(idGateway), address(keyGateway)); 26 | 27 | addKnownContract(address(keyGateway)); 28 | addKnownContract(address(bundler)); 29 | } 30 | 31 | // Assert that a given fname was correctly registered with id 1 and recovery 32 | function _assertSuccessfulRegistration(address account, address recovery) internal { 33 | assertEq(idRegistry.idOf(account), 1); 34 | assertEq(idRegistry.recoveryOf(1), recovery); 35 | } 36 | 37 | // Assert that a given fname was not registered and the contracts have no registrations 38 | function _assertUnsuccessfulRegistration( 39 | address account 40 | ) internal { 41 | assertEq(idRegistry.idOf(account), 0); 42 | assertEq(idRegistry.recoveryOf(1), address(0)); 43 | } 44 | 45 | function _signAdd( 46 | uint256 pk, 47 | address owner, 48 | uint32 keyType, 49 | bytes memory key, 50 | uint8 metadataType, 51 | bytes memory metadata, 52 | uint256 deadline 53 | ) internal returns (bytes memory signature) { 54 | return _signAdd(pk, owner, keyType, key, metadataType, metadata, keyGateway.nonces(owner), deadline); 55 | } 56 | 57 | function _signAdd( 58 | uint256 pk, 59 | address owner, 60 | uint32 keyType, 61 | bytes memory key, 62 | uint8 metadataType, 63 | bytes memory metadata, 64 | uint256 nonce, 65 | uint256 deadline 66 | ) internal returns (bytes memory signature) { 67 | bytes32 digest = keyGateway.hashTypedDataV4( 68 | keccak256( 69 | abi.encode( 70 | keyGateway.ADD_TYPEHASH(), 71 | owner, 72 | keyType, 73 | keccak256(key), 74 | metadataType, 75 | keccak256(metadata), 76 | nonce, 77 | deadline 78 | ) 79 | ) 80 | ); 81 | (uint8 v, bytes32 r, bytes32 s) = vm.sign(pk, digest); 82 | signature = abi.encodePacked(r, s, v); 83 | assertEq(signature.length, 65); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /test/BundlerV1/BundlerV1.gas.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.21; 3 | 4 | import "forge-std/Test.sol"; 5 | 6 | import {BundlerV1, IBundlerV1} from "../../src/BundlerV1.sol"; 7 | import {BundlerV1TestSuite} from "./BundlerV1TestSuite.sol"; 8 | 9 | /* solhint-disable state-visibility */ 10 | 11 | contract BundleRegistryGasUsageTest is BundlerV1TestSuite { 12 | function setUp() public override { 13 | super.setUp(); 14 | _registerValidator(1, 1); 15 | } 16 | 17 | function testGasRegisterWithSig() public { 18 | for (uint256 i = 1; i < 10; i++) { 19 | address account = vm.addr(i); 20 | bytes memory sig = _signRegister(i, account, address(0), type(uint40).max); 21 | uint256 price = bundler.price(1); 22 | 23 | IBundlerV1.SignerParams[] memory signers = new IBundlerV1.SignerParams[](0); 24 | 25 | vm.deal(account, 10_000 ether); 26 | vm.prank(account); 27 | bundler.register{value: price}( 28 | IBundlerV1.RegistrationParams({to: account, recovery: address(0), deadline: type(uint40).max, sig: sig}), 29 | signers, 30 | 1 31 | ); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /test/BundlerV1/BundlerV1TestSuite.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.21; 3 | 4 | import {TestSuiteSetup} from "../TestSuiteSetup.sol"; 5 | import {IdGatewayTestSuite} from "../IdGateway/IdGatewayTestSuite.sol"; 6 | 7 | import {KeyGateway} from "../../src/KeyGateway.sol"; 8 | import {BundlerV1} from "../../src/BundlerV1.sol"; 9 | 10 | /* solhint-disable state-visibility */ 11 | 12 | abstract contract BundlerV1TestSuite is IdGatewayTestSuite { 13 | KeyGateway keyGateway; 14 | BundlerV1 bundler; 15 | 16 | function setUp() public virtual override { 17 | super.setUp(); 18 | 19 | keyGateway = new KeyGateway(address(keyRegistry), owner); 20 | 21 | vm.prank(owner); 22 | keyRegistry.setKeyGateway(address(keyGateway)); 23 | 24 | bundler = new BundlerV1(address(idGateway), address(keyGateway)); 25 | 26 | addKnownContract(address(keyGateway)); 27 | addKnownContract(address(bundler)); 28 | } 29 | 30 | // Assert that a given fname was correctly registered with id 1 and recovery 31 | function _assertSuccessfulRegistration(address account, address recovery) internal { 32 | assertEq(idRegistry.idOf(account), 1); 33 | assertEq(idRegistry.recoveryOf(1), recovery); 34 | } 35 | 36 | // Assert that a given fname was not registered and the contracts have no registrations 37 | function _assertUnsuccessfulRegistration( 38 | address account 39 | ) internal { 40 | assertEq(idRegistry.idOf(account), 0); 41 | assertEq(idRegistry.recoveryOf(1), address(0)); 42 | } 43 | 44 | function _signAdd( 45 | uint256 pk, 46 | address owner, 47 | uint32 keyType, 48 | bytes memory key, 49 | uint8 metadataType, 50 | bytes memory metadata, 51 | uint256 deadline 52 | ) internal returns (bytes memory signature) { 53 | return _signAdd(pk, owner, keyType, key, metadataType, metadata, keyGateway.nonces(owner), deadline); 54 | } 55 | 56 | function _signAdd( 57 | uint256 pk, 58 | address owner, 59 | uint32 keyType, 60 | bytes memory key, 61 | uint8 metadataType, 62 | bytes memory metadata, 63 | uint256 nonce, 64 | uint256 deadline 65 | ) internal returns (bytes memory signature) { 66 | bytes32 digest = keyGateway.hashTypedDataV4( 67 | keccak256( 68 | abi.encode( 69 | keyGateway.ADD_TYPEHASH(), 70 | owner, 71 | keyType, 72 | keccak256(key), 73 | metadataType, 74 | keccak256(metadata), 75 | nonce, 76 | deadline 77 | ) 78 | ) 79 | ); 80 | (uint8 v, bytes32 r, bytes32 s) = vm.sign(pk, digest); 81 | signature = abi.encodePacked(r, s, v); 82 | assertEq(signature.length, 65); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /test/Deploy/DeployL1.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.21; 3 | 4 | import {Test} from "forge-std/Test.sol"; 5 | import {DeployL1, FnameResolver} from "../../script/DeployL1.s.sol"; 6 | import {IResolverService} from "../../src/FnameResolver.sol"; 7 | import {FnameResolverTestSuite} from "../../test/FnameResolver/FnameResolverTestSuite.sol"; 8 | import "forge-std/console.sol"; 9 | 10 | /* solhint-disable state-visibility */ 11 | 12 | contract DeployL1Test is DeployL1, FnameResolverTestSuite { 13 | address internal deployer = address(this); 14 | address internal alpha = makeAddr("alpha"); 15 | address internal alice = makeAddr("alice"); 16 | 17 | function setUp() public override { 18 | vm.createSelectFork("eth_mainnet"); 19 | 20 | (signer, signerPk) = makeAddrAndKey("signer"); 21 | 22 | DeployL1.DeploymentParams memory params = DeployL1.DeploymentParams({ 23 | serverURL: "https://fnames.farcaster.xyz/ccip/{sender}/{data}.json", 24 | signer: signer, 25 | owner: alpha, 26 | deployer: deployer 27 | }); 28 | 29 | DeployL1.Contracts memory contracts = runDeploy(params, false); 30 | 31 | resolver = contracts.fnameResolver; 32 | } 33 | 34 | function test_deploymentParams() public { 35 | // Check deployment parameters 36 | assertEq(resolver.url(), "https://fnames.farcaster.xyz/ccip/{sender}/{data}.json"); 37 | assertEq(resolver.owner(), alpha); 38 | assertEq(resolver.signers(signer), true); 39 | } 40 | 41 | function test_e2e() public { 42 | uint256 timestamp = block.timestamp - 60; 43 | bytes memory signature = _signProof(signerPk, "alice.fcast.id", timestamp, alice); 44 | bytes memory extraData = abi.encodeCall(IResolverService.resolve, (DNS_ENCODED_NAME, ADDR_QUERY_CALLDATA)); 45 | bytes memory response = 46 | resolver.resolveWithProof(abi.encode("alice.fcast.id", timestamp, alice, signature), extraData); 47 | assertEq(response, abi.encode(alice)); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /test/Deploy/DeployTierRegistry.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.21; 3 | 4 | import "forge-std/console.sol"; 5 | import "openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; 6 | import {TierRegistry, DeployTierRegistry} from "../../script/DeployTierRegistry.s.sol"; 7 | 8 | /* solhint-disable state-visibility */ 9 | 10 | contract DeployTierRegistryTest is DeployTierRegistry { 11 | address public alice = makeAddr("alice"); 12 | address public bob = makeAddr("bob"); 13 | address public vault = makeAddr("vault"); 14 | address public owner = makeAddr("owner"); 15 | address public migrator = makeAddr("migrator"); 16 | address public deployer = makeAddr("deployer"); 17 | 18 | TierRegistry public tierRegistry; 19 | 20 | event PurchasedTier(uint256 indexed fid, uint256 indexed tier, uint256 forDays, address indexed payer); 21 | 22 | function setUp() public { 23 | vm.createSelectFork("base_mainnet", 31172413); 24 | 25 | DeployTierRegistry.DeploymentParams memory params = DeployTierRegistry.DeploymentParams({ 26 | deployer: deployer, 27 | vault: vault, 28 | owner: owner, 29 | migrator: migrator, 30 | salts: DeployTierRegistry.Salts({tierRegistry: 0}) 31 | }); 32 | 33 | vm.startPrank(deployer); 34 | DeployTierRegistry.Contracts memory contracts = runDeploy(params, false); 35 | runSetup(contracts, params, false); 36 | vm.stopPrank(); 37 | 38 | tierRegistry = contracts.tierRegistry; 39 | } 40 | 41 | function test_deploymentParams() public { 42 | // Ownership parameters 43 | assertEq(tierRegistry.owner(), deployer); 44 | assertEq(tierRegistry.pendingOwner(), owner); 45 | assertEq(tierRegistry.migrator(), migrator); 46 | assertEq(tierRegistry.isMigrated(), false); 47 | assertEq(tierRegistry.paused(), true); 48 | 49 | // Initial tier 50 | TierRegistry.TierInfo memory tier = tierRegistry.tierInfo(1); 51 | assertEq(tier.minDays, 30); 52 | assertEq(tier.maxDays, 365); 53 | assertEq(tier.vault, vault); 54 | assertEq(address(tier.paymentToken), BASE_USDC); 55 | assertEq(tier.tokenPricePerDay, PRICE_PER_DAY); 56 | } 57 | 58 | function test_e2e() public { 59 | // Owner accepts ownership 60 | vm.prank(owner); 61 | tierRegistry.acceptOwnership(); 62 | assertEq(tierRegistry.owner(), owner); 63 | 64 | // Migrator backfills and migrates 65 | vm.startPrank(migrator); 66 | uint256[] memory fids = new uint256[](2); 67 | fids[0] = 1; 68 | fids[1] = 2; 69 | 70 | vm.expectEmit(); 71 | emit PurchasedTier(1, 1, 365, migrator); 72 | emit PurchasedTier(2, 1, 365, migrator); 73 | tierRegistry.batchCreditTier(1, fids, 365); 74 | tierRegistry.migrate(); 75 | vm.stopPrank(); 76 | 77 | // Cannot purchase 78 | vm.expectRevert("Pausable: paused"); 79 | vm.prank(alice); 80 | tierRegistry.purchaseTier(3, 1, 30); 81 | 82 | // Owner unpauses 83 | vm.prank(owner); 84 | tierRegistry.unpause(); 85 | 86 | // Public purchase 87 | deal(BASE_USDC, alice, 100 * 1e6); 88 | vm.startPrank(alice); 89 | uint256 price = tierRegistry.price(1, 30); 90 | IERC20(BASE_USDC).approve(address(tierRegistry), price); 91 | tierRegistry.purchaseTier(3, 1, 30); 92 | vm.stopPrank(); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /test/FnameResolver/FnameResolverTestSuite.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.21; 3 | 4 | import {EIP712} from "openzeppelin/contracts/utils/cryptography/EIP712.sol"; 5 | 6 | import {TestSuiteSetup} from "../TestSuiteSetup.sol"; 7 | import {FnameResolver} from "../../src/FnameResolver.sol"; 8 | 9 | /* solhint-disable state-visibility */ 10 | 11 | abstract contract FnameResolverTestSuite is TestSuiteSetup { 12 | FnameResolver internal resolver; 13 | 14 | string internal constant FNAME_SERVER_URL = "https://fnames.fcast.id/ccip/{sender}/{data}.json"; 15 | 16 | /** 17 | * @dev DNS-encoding of "alice.fcast.id". The DNS-encoded name consists of: 18 | * - 1 byte for the length of the first label (5) 19 | * - 5 bytes for the label ("alice") 20 | * - 1 byte for the length of the second label (5) 21 | * - 5 bytes for the label ("fcast") 22 | * - 1 byte for the length of the third label (2) 23 | * - 2 bytes for the label ("id") 24 | * - A null byte terminating the encoded name. 25 | */ 26 | bytes internal constant DNS_ENCODED_NAME = 27 | (hex"05" hex"616c696365" hex"05" hex"6663617374" hex"02" hex"6964" hex"00"); 28 | 29 | /** 30 | * @dev Encoded calldata for a call to addr(bytes32 node), where node is the ENS 31 | * nameHash encoded value of "alice.fcast.id" 32 | */ 33 | bytes internal constant ADDR_QUERY_CALLDATA = hex"c30dc5a16498c5b6d46f97ca0c74d092ebbee1290b1c88f6e435dd4fb306ca36"; 34 | 35 | address internal signer; 36 | uint256 internal signerPk; 37 | 38 | address internal mallory; 39 | uint256 internal malloryPk; 40 | 41 | function setUp() public virtual override { 42 | (signer, signerPk) = makeAddrAndKey("signer"); 43 | (mallory, malloryPk) = makeAddrAndKey("mallory"); 44 | resolver = new FnameResolver(FNAME_SERVER_URL, signer, owner); 45 | } 46 | 47 | /*////////////////////////////////////////////////////////////// 48 | HELPERS 49 | //////////////////////////////////////////////////////////////*/ 50 | 51 | function _signProof( 52 | string memory name, 53 | uint256 timestamp, 54 | address owner 55 | ) internal returns (bytes memory signature) { 56 | return _signProof(signerPk, name, timestamp, owner); 57 | } 58 | 59 | function _signProof( 60 | uint256 pk, 61 | string memory name, 62 | uint256 timestamp, 63 | address owner 64 | ) internal returns (bytes memory signature) { 65 | bytes32 eip712hash = resolver.hashTypedDataV4( 66 | keccak256(abi.encode(resolver.USERNAME_PROOF_TYPEHASH(), keccak256(bytes(name)), timestamp, owner)) 67 | ); 68 | (uint8 v, bytes32 r, bytes32 s) = vm.sign(pk, eip712hash); 69 | signature = abi.encodePacked(r, s, v); 70 | assertEq(signature.length, 65); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /test/IdGateway/IdGateway.gas.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.21; 3 | 4 | import {IdGatewayTestSuite} from "./IdGatewayTestSuite.sol"; 5 | 6 | /* solhint-disable state-visibility */ 7 | 8 | contract IdGatewayGasUsageTest is IdGatewayTestSuite { 9 | address constant RECOVERY = address(0x6D1217BD164119E2ddE6ce1723879844FD73114e); 10 | 11 | // Perform actions many times to get a good median, since the first run initializes storage 12 | 13 | function testGasRegister() public { 14 | for (uint256 i = 1; i < 15; i++) { 15 | address caller = vm.addr(i); 16 | uint256 fee = idGateway.price(); 17 | vm.deal(caller, fee); 18 | vm.prank(caller); 19 | idGateway.register{value: fee}(RECOVERY); 20 | assertEq(idRegistry.idOf(caller), i); 21 | } 22 | } 23 | 24 | function testGasRegisterForAndRecover() public { 25 | for (uint256 i = 1; i < 15; i++) { 26 | address registrationRecipient = vm.addr(i); 27 | uint40 deadline = type(uint40).max; 28 | 29 | uint256 recoveryRecipientPk = i + 100; 30 | address recoveryRecipient = vm.addr(recoveryRecipientPk); 31 | 32 | uint256 fee = idGateway.price(); 33 | vm.deal(registrationRecipient, fee); 34 | vm.prank(registrationRecipient); 35 | (uint256 fid,) = idGateway.register{value: fee}(RECOVERY); 36 | assertEq(idRegistry.idOf(registrationRecipient), i); 37 | 38 | bytes memory transferSig = _signTransfer(recoveryRecipientPk, fid, recoveryRecipient, deadline); 39 | vm.prank(RECOVERY); 40 | idRegistry.recover(registrationRecipient, recoveryRecipient, deadline, transferSig); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /test/IdGateway/IdGateway.owner.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.19; 3 | 4 | import {IdGateway} from "../../src/IdGateway.sol"; 5 | import {IGuardians} from "../../src/abstract/Guardians.sol"; 6 | import {IdGatewayTestSuite} from "./IdGatewayTestSuite.sol"; 7 | 8 | /* solhint-disable state-visibility */ 9 | 10 | contract IdGatewayOwnerTest is IdGatewayTestSuite { 11 | /*////////////////////////////////////////////////////////////// 12 | EVENTS 13 | //////////////////////////////////////////////////////////////*/ 14 | 15 | event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); 16 | 17 | /*////////////////////////////////////////////////////////////// 18 | TRANSFER OWNERSHIP 19 | //////////////////////////////////////////////////////////////*/ 20 | 21 | function testFuzzTransferOwnership(address newOwner, address newOwner2) public { 22 | vm.assume(newOwner != address(0) && newOwner2 != address(0)); 23 | assertEq(idGateway.owner(), owner); 24 | assertEq(idGateway.pendingOwner(), address(0)); 25 | 26 | vm.prank(owner); 27 | idGateway.transferOwnership(newOwner); 28 | assertEq(idGateway.owner(), owner); 29 | assertEq(idGateway.pendingOwner(), newOwner); 30 | 31 | vm.prank(owner); 32 | idGateway.transferOwnership(newOwner2); 33 | assertEq(idGateway.owner(), owner); 34 | assertEq(idGateway.pendingOwner(), newOwner2); 35 | } 36 | 37 | function testFuzzCannotTransferOwnershipUnlessOwner(address alice, address newOwner) public { 38 | vm.assume(alice != owner && newOwner != address(0)); 39 | assertEq(idGateway.owner(), owner); 40 | assertEq(idGateway.pendingOwner(), address(0)); 41 | 42 | vm.prank(alice); 43 | vm.expectRevert("Ownable: caller is not the owner"); 44 | idGateway.transferOwnership(newOwner); 45 | 46 | assertEq(idGateway.owner(), owner); 47 | assertEq(idGateway.pendingOwner(), address(0)); 48 | } 49 | 50 | /*////////////////////////////////////////////////////////////// 51 | ACCEPT OWNERSHIP 52 | //////////////////////////////////////////////////////////////*/ 53 | 54 | function testFuzzAcceptOwnership( 55 | address newOwner 56 | ) public { 57 | vm.assume(newOwner != owner && newOwner != address(0)); 58 | vm.prank(owner); 59 | idGateway.transferOwnership(newOwner); 60 | 61 | vm.expectEmit(); 62 | emit OwnershipTransferred(owner, newOwner); 63 | vm.prank(newOwner); 64 | idGateway.acceptOwnership(); 65 | 66 | assertEq(idGateway.owner(), newOwner); 67 | assertEq(idGateway.pendingOwner(), address(0)); 68 | } 69 | 70 | function testFuzzCannotAcceptOwnershipUnlessPendingOwner(address alice, address newOwner) public { 71 | vm.assume(alice != owner && alice != address(0)); 72 | vm.assume(newOwner != alice && newOwner != address(0)); 73 | 74 | vm.prank(owner); 75 | idGateway.transferOwnership(newOwner); 76 | 77 | vm.prank(alice); 78 | vm.expectRevert("Ownable2Step: caller is not the new owner"); 79 | idGateway.acceptOwnership(); 80 | 81 | assertEq(idGateway.owner(), owner); 82 | assertEq(idGateway.pendingOwner(), newOwner); 83 | } 84 | 85 | /*////////////////////////////////////////////////////////////// 86 | PAUSE 87 | //////////////////////////////////////////////////////////////*/ 88 | 89 | function testPause() public { 90 | assertEq(idGateway.owner(), owner); 91 | assertEq(idGateway.paused(), false); 92 | 93 | vm.prank(idGateway.owner()); 94 | idGateway.pause(); 95 | assertEq(idGateway.paused(), true); 96 | } 97 | 98 | function testFuzzCannotPauseUnlessGuardian( 99 | address alice 100 | ) public { 101 | vm.assume(alice != owner && alice != address(0)); 102 | assertEq(idGateway.owner(), owner); 103 | assertEq(idGateway.paused(), false); 104 | 105 | vm.prank(alice); 106 | vm.expectRevert(IGuardians.OnlyGuardian.selector); 107 | idGateway.pause(); 108 | 109 | assertEq(idGateway.paused(), false); 110 | } 111 | 112 | function testUnpause() public { 113 | vm.prank(idGateway.owner()); 114 | idGateway.pause(); 115 | assertEq(idGateway.paused(), true); 116 | 117 | vm.prank(owner); 118 | idGateway.unpause(); 119 | 120 | assertEq(idGateway.paused(), false); 121 | } 122 | 123 | function testFuzzCannotUnpauseUnlessOwner( 124 | address alice 125 | ) public { 126 | vm.assume(alice != owner && alice != address(0)); 127 | assertEq(idGateway.owner(), owner); 128 | 129 | vm.prank(idGateway.owner()); 130 | idGateway.pause(); 131 | assertEq(idGateway.paused(), true); 132 | 133 | vm.prank(alice); 134 | vm.expectRevert("Ownable: caller is not the owner"); 135 | idGateway.unpause(); 136 | 137 | assertEq(idGateway.paused(), true); 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /test/IdGateway/IdGatewayTestSuite.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.21; 3 | 4 | import {TestSuiteSetup} from "../TestSuiteSetup.sol"; 5 | import {StorageRegistryTestSuite} from "../StorageRegistry/StorageRegistryTestSuite.sol"; 6 | import {IdRegistryTestSuite} from "../IdRegistry/IdRegistryTestSuite.sol"; 7 | import {KeyRegistryTestSuite} from "../KeyRegistry/KeyRegistryTestSuite.sol"; 8 | 9 | import {IdGateway} from "../../src/IdGateway.sol"; 10 | 11 | /* solhint-disable state-visibility */ 12 | 13 | abstract contract IdGatewayTestSuite is StorageRegistryTestSuite, KeyRegistryTestSuite { 14 | IdGateway idGateway; 15 | 16 | function setUp() public virtual override(StorageRegistryTestSuite, KeyRegistryTestSuite) { 17 | super.setUp(); 18 | 19 | idGateway = new IdGateway(address(idRegistry), address(storageRegistry), owner); 20 | 21 | vm.startPrank(owner); 22 | idRegistry.setIdGateway(address(idGateway)); 23 | vm.stopPrank(); 24 | 25 | addKnownContract(address(idGateway)); 26 | } 27 | 28 | function _registerTo( 29 | address caller 30 | ) internal returns (uint256 fid) { 31 | fid = _registerWithRecovery(caller, address(0)); 32 | } 33 | 34 | function _registerToWithRecovery(address caller, address recovery) internal returns (uint256 fid) { 35 | vm.prank(caller); 36 | (fid,) = idGateway.register(recovery); 37 | } 38 | 39 | function _registerFor(uint256 callerPk, uint40 _deadline) internal { 40 | _registerForWithRecovery(callerPk, address(0), _deadline); 41 | } 42 | 43 | function _registerForWithRecovery(uint256 callerPk, address recovery, uint40 _deadline) internal { 44 | uint256 deadline = _boundDeadline(_deadline); 45 | callerPk = _boundPk(callerPk); 46 | 47 | address caller = vm.addr(callerPk); 48 | bytes memory sig = _signRegister(callerPk, caller, recovery, deadline); 49 | 50 | vm.prank(caller); 51 | idGateway.registerFor(caller, recovery, deadline, sig); 52 | } 53 | 54 | function _signRegister( 55 | uint256 pk, 56 | address to, 57 | address recovery, 58 | uint256 deadline 59 | ) internal returns (bytes memory signature) { 60 | address signer = vm.addr(pk); 61 | bytes32 digest = idGateway.hashTypedDataV4( 62 | keccak256(abi.encode(idGateway.REGISTER_TYPEHASH(), to, recovery, idGateway.nonces(signer), deadline)) 63 | ); 64 | (uint8 v, bytes32 r, bytes32 s) = vm.sign(pk, digest); 65 | signature = abi.encodePacked(r, s, v); 66 | assertEq(signature.length, 65); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /test/IdRegistry/IdRegistry.owner.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.19; 3 | 4 | import {IdRegistry} from "../../src/IdRegistry.sol"; 5 | import {IdRegistryTestSuite} from "./IdRegistryTestSuite.sol"; 6 | import {IGuardians} from "../../src/abstract/Guardians.sol"; 7 | 8 | /* solhint-disable state-visibility */ 9 | 10 | contract IdRegistryOwnerTest is IdRegistryTestSuite { 11 | /*////////////////////////////////////////////////////////////// 12 | EVENTS 13 | //////////////////////////////////////////////////////////////*/ 14 | 15 | event Add(address indexed guardian); 16 | event Remove(address indexed guardian); 17 | event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); 18 | 19 | /*////////////////////////////////////////////////////////////// 20 | TRANSFER OWNERSHIP 21 | //////////////////////////////////////////////////////////////*/ 22 | 23 | function testFuzzTransferOwnership(address newOwner, address newOwner2) public { 24 | vm.assume(newOwner != address(0) && newOwner2 != address(0)); 25 | assertEq(idRegistry.owner(), owner); 26 | assertEq(idRegistry.pendingOwner(), address(0)); 27 | 28 | vm.prank(owner); 29 | idRegistry.transferOwnership(newOwner); 30 | assertEq(idRegistry.owner(), owner); 31 | assertEq(idRegistry.pendingOwner(), newOwner); 32 | 33 | vm.prank(owner); 34 | idRegistry.transferOwnership(newOwner2); 35 | assertEq(idRegistry.owner(), owner); 36 | assertEq(idRegistry.pendingOwner(), newOwner2); 37 | } 38 | 39 | function testFuzzCannotTransferOwnershipUnlessOwner(address alice, address newOwner) public { 40 | vm.assume(alice != owner && newOwner != address(0)); 41 | assertEq(idRegistry.owner(), owner); 42 | assertEq(idRegistry.pendingOwner(), address(0)); 43 | 44 | vm.prank(alice); 45 | vm.expectRevert("Ownable: caller is not the owner"); 46 | idRegistry.transferOwnership(newOwner); 47 | 48 | assertEq(idRegistry.owner(), owner); 49 | assertEq(idRegistry.pendingOwner(), address(0)); 50 | } 51 | 52 | /*////////////////////////////////////////////////////////////// 53 | ACCEPT OWNERSHIP 54 | //////////////////////////////////////////////////////////////*/ 55 | 56 | function testFuzzAcceptOwnership( 57 | address newOwner 58 | ) public { 59 | vm.assume(newOwner != owner && newOwner != address(0)); 60 | vm.prank(owner); 61 | idRegistry.transferOwnership(newOwner); 62 | 63 | vm.expectEmit(); 64 | emit OwnershipTransferred(owner, newOwner); 65 | vm.prank(newOwner); 66 | idRegistry.acceptOwnership(); 67 | 68 | assertEq(idRegistry.owner(), newOwner); 69 | assertEq(idRegistry.pendingOwner(), address(0)); 70 | } 71 | 72 | function testFuzzCannotAcceptOwnershipUnlessPendingOwner(address alice, address newOwner) public { 73 | vm.assume(alice != owner && alice != address(0)); 74 | vm.assume(newOwner != alice && newOwner != address(0)); 75 | 76 | vm.prank(owner); 77 | idRegistry.transferOwnership(newOwner); 78 | 79 | vm.prank(alice); 80 | vm.expectRevert("Ownable2Step: caller is not the new owner"); 81 | idRegistry.acceptOwnership(); 82 | 83 | assertEq(idRegistry.owner(), owner); 84 | assertEq(idRegistry.pendingOwner(), newOwner); 85 | } 86 | 87 | /*////////////////////////////////////////////////////////////// 88 | PAUSE 89 | //////////////////////////////////////////////////////////////*/ 90 | 91 | function testPause() public { 92 | assertEq(idRegistry.owner(), owner); 93 | assertEq(idRegistry.paused(), false); 94 | 95 | _pause(); 96 | } 97 | 98 | function testAddRemoveGuardian( 99 | address guardian 100 | ) public { 101 | assertEq(idRegistry.guardians(guardian), false); 102 | 103 | vm.expectEmit(); 104 | emit Add(guardian); 105 | 106 | vm.prank(owner); 107 | idRegistry.addGuardian(guardian); 108 | 109 | assertEq(idRegistry.guardians(guardian), true); 110 | 111 | vm.expectEmit(); 112 | emit Remove(guardian); 113 | 114 | vm.prank(owner); 115 | idRegistry.removeGuardian(guardian); 116 | 117 | assertEq(idRegistry.guardians(guardian), false); 118 | } 119 | 120 | function testFuzzCannotPauseUnlessGuardian( 121 | address alice 122 | ) public { 123 | vm.assume(alice != owner && alice != address(0)); 124 | assertEq(idRegistry.owner(), owner); 125 | assertEq(idRegistry.paused(), false); 126 | 127 | vm.prank(alice); 128 | vm.expectRevert(IGuardians.OnlyGuardian.selector); 129 | idRegistry.pause(); 130 | 131 | assertEq(idRegistry.paused(), false); 132 | } 133 | 134 | function testUnpause() public { 135 | _pause(); 136 | 137 | vm.prank(owner); 138 | idRegistry.unpause(); 139 | 140 | assertEq(idRegistry.paused(), false); 141 | } 142 | 143 | function testFuzzCannotUnpauseUnlessOwner( 144 | address alice 145 | ) public { 146 | vm.assume(alice != owner && alice != address(0)); 147 | assertEq(idRegistry.owner(), owner); 148 | _pause(); 149 | 150 | vm.prank(alice); 151 | vm.expectRevert("Ownable: caller is not the owner"); 152 | idRegistry.unpause(); 153 | 154 | assertEq(idRegistry.paused(), true); 155 | } 156 | 157 | function testCannotAddGuardianUnlessOwner(address caller, address guardian) public { 158 | vm.assume(caller != owner); 159 | assertEq(idRegistry.guardians(guardian), false); 160 | 161 | vm.prank(caller); 162 | vm.expectRevert("Ownable: caller is not the owner"); 163 | idRegistry.addGuardian(guardian); 164 | 165 | assertEq(idRegistry.guardians(guardian), false); 166 | } 167 | 168 | function testCannotRemoveGuardianUnlessOwner(address caller, address guardian) public { 169 | vm.assume(caller != owner); 170 | assertEq(idRegistry.guardians(guardian), false); 171 | 172 | vm.prank(caller); 173 | vm.expectRevert("Ownable: caller is not the owner"); 174 | idRegistry.addGuardian(guardian); 175 | 176 | assertEq(idRegistry.guardians(guardian), false); 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /test/IdRegistry/IdRegistryTestHelpers.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.21; 3 | 4 | import {IIdRegistry} from "../../src/interfaces/IIdRegistry.sol"; 5 | 6 | library BulkRegisterDataBuilder { 7 | function empty() internal pure returns (IIdRegistry.BulkRegisterData[] memory) { 8 | return new IIdRegistry.BulkRegisterData[](0); 9 | } 10 | 11 | function addFid( 12 | IIdRegistry.BulkRegisterData[] memory addData, 13 | uint24 fid 14 | ) internal pure returns (IIdRegistry.BulkRegisterData[] memory) { 15 | IIdRegistry.BulkRegisterData[] memory newData = new IIdRegistry.BulkRegisterData[](addData.length + 1); 16 | for (uint256 i; i < addData.length; i++) { 17 | newData[i] = addData[i]; 18 | } 19 | newData[addData.length].fid = fid; 20 | newData[addData.length].custody = address(uint160(uint256(keccak256(abi.encodePacked(fid))))); 21 | newData[addData.length].recovery = 22 | address(uint160(uint256(keccak256(abi.encodePacked(keccak256(abi.encodePacked(fid))))))); 23 | return newData; 24 | } 25 | 26 | function custodyOf( 27 | uint24 fid 28 | ) internal pure returns (address) { 29 | return address(uint160(uint256(keccak256(abi.encodePacked(fid))))); 30 | } 31 | 32 | function recoveryOf( 33 | uint24 fid 34 | ) internal pure returns (address) { 35 | return address(uint160(uint256(keccak256(abi.encodePacked(keccak256(abi.encodePacked(fid))))))); 36 | } 37 | } 38 | 39 | library BulkRegisterDefaultRecoveryDataBuilder { 40 | function empty() internal pure returns (IIdRegistry.BulkRegisterDefaultRecoveryData[] memory) { 41 | return new IIdRegistry.BulkRegisterDefaultRecoveryData[](0); 42 | } 43 | 44 | function addFid( 45 | IIdRegistry.BulkRegisterDefaultRecoveryData[] memory addData, 46 | uint24 fid 47 | ) internal pure returns (IIdRegistry.BulkRegisterDefaultRecoveryData[] memory) { 48 | IIdRegistry.BulkRegisterDefaultRecoveryData[] memory newData = 49 | new IIdRegistry.BulkRegisterDefaultRecoveryData[](addData.length + 1); 50 | for (uint256 i; i < addData.length; i++) { 51 | newData[i] = addData[i]; 52 | } 53 | newData[addData.length].fid = fid; 54 | newData[addData.length].custody = address(uint160(uint256(keccak256(abi.encodePacked(fid))))); 55 | return newData; 56 | } 57 | 58 | function custodyOf( 59 | uint24 fid 60 | ) internal pure returns (address) { 61 | return address(uint160(uint256(keccak256(abi.encodePacked(fid))))); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /test/IdRegistry/IdRegistryTestSuite.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.21; 3 | 4 | import {IdRegistry} from "../../src/IdRegistry.sol"; 5 | import {TestSuiteSetup} from "../TestSuiteSetup.sol"; 6 | 7 | /* solhint-disable state-visibility */ 8 | 9 | abstract contract IdRegistryTestSuite is TestSuiteSetup { 10 | IdRegistry idRegistry; 11 | 12 | function setUp() public virtual override { 13 | super.setUp(); 14 | 15 | idRegistry = new IdRegistry(migrator, owner); 16 | 17 | vm.prank(owner); 18 | idRegistry.unpause(); 19 | 20 | addKnownContract(address(idRegistry)); 21 | } 22 | 23 | /*////////////////////////////////////////////////////////////// 24 | TEST HELPERS 25 | //////////////////////////////////////////////////////////////*/ 26 | 27 | function _register( 28 | address caller 29 | ) internal returns (uint256 fid) { 30 | fid = _registerWithRecovery(caller, address(0)); 31 | } 32 | 33 | function _registerWithRecovery(address caller, address recovery) internal returns (uint256 fid) { 34 | vm.prank(idRegistry.idGateway()); 35 | fid = idRegistry.register(caller, recovery); 36 | } 37 | 38 | function _pause() public { 39 | vm.prank(owner); 40 | idRegistry.pause(); 41 | assertEq(idRegistry.paused(), true); 42 | } 43 | 44 | function _signTransfer( 45 | uint256 pk, 46 | uint256 fid, 47 | address to, 48 | uint256 deadline 49 | ) internal returns (bytes memory signature) { 50 | address signer = vm.addr(pk); 51 | bytes32 digest = idRegistry.hashTypedDataV4( 52 | keccak256(abi.encode(idRegistry.TRANSFER_TYPEHASH(), fid, to, idRegistry.nonces(signer), deadline)) 53 | ); 54 | (uint8 v, bytes32 r, bytes32 s) = vm.sign(pk, digest); 55 | signature = abi.encodePacked(r, s, v); 56 | assertEq(signature.length, 65); 57 | } 58 | 59 | function _signTransferAndChangeRecovery( 60 | uint256 pk, 61 | uint256 fid, 62 | address to, 63 | address recovery, 64 | uint256 deadline 65 | ) internal returns (bytes memory signature) { 66 | address signer = vm.addr(pk); 67 | bytes32 digest = idRegistry.hashTypedDataV4( 68 | keccak256( 69 | abi.encode( 70 | idRegistry.TRANSFER_AND_CHANGE_RECOVERY_TYPEHASH(), 71 | fid, 72 | to, 73 | recovery, 74 | idRegistry.nonces(signer), 75 | deadline 76 | ) 77 | ) 78 | ); 79 | (uint8 v, bytes32 r, bytes32 s) = vm.sign(pk, digest); 80 | signature = abi.encodePacked(r, s, v); 81 | assertEq(signature.length, 65); 82 | } 83 | 84 | function _signChangeRecoveryAddress( 85 | uint256 pk, 86 | uint256 fid, 87 | address from, 88 | address to, 89 | uint256 deadline 90 | ) internal returns (bytes memory signature) { 91 | address signer = vm.addr(pk); 92 | bytes32 digest = idRegistry.hashTypedDataV4( 93 | keccak256( 94 | abi.encode( 95 | idRegistry.CHANGE_RECOVERY_ADDRESS_TYPEHASH(), fid, from, to, idRegistry.nonces(signer), deadline 96 | ) 97 | ) 98 | ); 99 | (uint8 v, bytes32 r, bytes32 s) = vm.sign(pk, digest); 100 | signature = abi.encodePacked(r, s, v); 101 | assertEq(signature.length, 65); 102 | } 103 | 104 | function _signDigest(uint256 pk, bytes32 digest) internal returns (bytes memory signature) { 105 | (uint8 v, bytes32 r, bytes32 s) = vm.sign(pk, digest); 106 | signature = abi.encodePacked(r, s, v); 107 | assertEq(signature.length, 65); 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /test/KeyGateway/KeyGatewayTestSuite.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.21; 3 | 4 | import "forge-std/Test.sol"; 5 | 6 | import {KeyRegistryTestSuite} from "../KeyRegistry/KeyRegistryTestSuite.sol"; 7 | import {StorageRegistryTestSuite} from "../StorageRegistry/StorageRegistryTestSuite.sol"; 8 | import {KeyGateway} from "../../src/KeyGateway.sol"; 9 | 10 | /* solhint-disable state-visibility */ 11 | 12 | abstract contract KeyGatewayTestSuite is KeyRegistryTestSuite, StorageRegistryTestSuite { 13 | KeyGateway internal keyGateway; 14 | 15 | function setUp() public virtual override(KeyRegistryTestSuite, StorageRegistryTestSuite) { 16 | super.setUp(); 17 | 18 | keyGateway = new KeyGateway(address(keyRegistry), owner); 19 | 20 | vm.startPrank(owner); 21 | keyRegistry.setKeyGateway(address(keyGateway)); 22 | vm.stopPrank(); 23 | 24 | addKnownContract(address(keyGateway)); 25 | } 26 | 27 | function _signAdd( 28 | uint256 pk, 29 | address owner, 30 | uint32 keyType, 31 | bytes memory key, 32 | uint8 metadataType, 33 | bytes memory metadata, 34 | uint256 deadline 35 | ) internal returns (bytes memory signature) { 36 | return _signAdd(pk, owner, keyType, key, metadataType, metadata, keyGateway.nonces(owner), deadline); 37 | } 38 | 39 | function _signAdd( 40 | uint256 pk, 41 | address owner, 42 | uint32 keyType, 43 | bytes memory key, 44 | uint8 metadataType, 45 | bytes memory metadata, 46 | uint256 nonce, 47 | uint256 deadline 48 | ) internal returns (bytes memory signature) { 49 | bytes32 digest = keyGateway.hashTypedDataV4( 50 | keccak256( 51 | abi.encode( 52 | keyGateway.ADD_TYPEHASH(), 53 | owner, 54 | keyType, 55 | keccak256(key), 56 | metadataType, 57 | keccak256(metadata), 58 | nonce, 59 | deadline 60 | ) 61 | ) 62 | ); 63 | (uint8 v, bytes32 r, bytes32 s) = vm.sign(pk, digest); 64 | signature = abi.encodePacked(r, s, v); 65 | assertEq(signature.length, 65); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /test/KeyRegistry/KeyRegistry.integration.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.19; 3 | 4 | import "forge-std/console.sol"; 5 | 6 | import {KeyRegistry, IKeyRegistry} from "../../src/KeyRegistry.sol"; 7 | import {IMetadataValidator} from "../../src/interfaces/IMetadataValidator.sol"; 8 | import {SignedKeyRequestValidator} from "../../src/validators/SignedKeyRequestValidator.sol"; 9 | 10 | import {SignedKeyRequestValidatorTestSuite} from 11 | "../validators/SignedKeyRequestValidator/SignedKeyRequestValidatorTestSuite.sol"; 12 | import {KeyRegistryTestSuite} from "./KeyRegistryTestSuite.sol"; 13 | 14 | /* solhint-disable state-visibility */ 15 | 16 | contract KeyRegistryIntegrationTest is KeyRegistryTestSuite, SignedKeyRequestValidatorTestSuite { 17 | function setUp() public override(KeyRegistryTestSuite, SignedKeyRequestValidatorTestSuite) { 18 | super.setUp(); 19 | 20 | vm.prank(owner); 21 | keyRegistry.setValidator(1, 1, IMetadataValidator(address(validator))); 22 | } 23 | 24 | event Add( 25 | uint256 indexed fid, 26 | uint32 indexed keyType, 27 | bytes indexed key, 28 | bytes keyBytes, 29 | uint8 metadataType, 30 | bytes metadata 31 | ); 32 | 33 | function testFuzzAdd( 34 | address to, 35 | uint256 signerPk, 36 | address recovery, 37 | bytes calldata _keyBytes, 38 | uint40 _deadline 39 | ) public { 40 | signerPk = _boundPk(signerPk); 41 | uint256 deadline = _boundDeadline(_deadline); 42 | address signer = vm.addr(signerPk); 43 | vm.assume(signer != to); 44 | 45 | uint256 userFid = _registerFid(to, recovery); 46 | uint256 requestFid = _register(signer); 47 | bytes memory key = _validKey(_keyBytes); 48 | 49 | bytes memory sig = _signMetadata(signerPk, requestFid, key, deadline); 50 | 51 | bytes memory metadata = abi.encode( 52 | SignedKeyRequestValidator.SignedKeyRequestMetadata({ 53 | requestFid: requestFid, 54 | requestSigner: signer, 55 | signature: sig, 56 | deadline: deadline 57 | }) 58 | ); 59 | 60 | vm.expectEmit(); 61 | emit Add(userFid, 1, key, key, 1, metadata); 62 | vm.prank(keyRegistry.keyGateway()); 63 | keyRegistry.add(to, 1, key, 1, metadata); 64 | 65 | assertAdded(userFid, key, 1); 66 | } 67 | 68 | function testFuzzAddRevertsShortKey( 69 | address to, 70 | uint256 signerPk, 71 | address recovery, 72 | bytes calldata _keyBytes, 73 | uint40 _deadline, 74 | uint8 _shortenBy 75 | ) public { 76 | _registerFid(to, recovery); 77 | bytes memory key = _shortKey(_keyBytes, _shortenBy); 78 | 79 | signerPk = _boundPk(signerPk); 80 | uint256 deadline = _boundDeadline(_deadline); 81 | address signer = vm.addr(signerPk); 82 | vm.assume(signer != to); 83 | 84 | uint256 requestFid = _register(signer); 85 | 86 | bytes memory sig = _signMetadata(signerPk, requestFid, key, deadline); 87 | 88 | bytes memory metadata = abi.encode( 89 | SignedKeyRequestValidator.SignedKeyRequestMetadata({ 90 | requestFid: requestFid, 91 | requestSigner: signer, 92 | signature: sig, 93 | deadline: deadline 94 | }) 95 | ); 96 | 97 | vm.prank(keyRegistry.keyGateway()); 98 | vm.expectRevert(IKeyRegistry.InvalidMetadata.selector); 99 | keyRegistry.add(to, 1, key, 1, metadata); 100 | } 101 | 102 | function testFuzzAddRevertsLongKey( 103 | address to, 104 | uint256 signerPk, 105 | address recovery, 106 | bytes calldata _keyBytes, 107 | uint40 _deadline, 108 | uint8 _lengthenBy 109 | ) public { 110 | _registerFid(to, recovery); 111 | bytes memory key = _longKey(_keyBytes, _lengthenBy); 112 | 113 | signerPk = _boundPk(signerPk); 114 | uint256 deadline = _boundDeadline(_deadline); 115 | address signer = vm.addr(signerPk); 116 | vm.assume(signer != to); 117 | 118 | uint256 requestFid = _register(signer); 119 | 120 | bytes memory sig = _signMetadata(signerPk, requestFid, key, deadline); 121 | 122 | bytes memory metadata = abi.encode( 123 | SignedKeyRequestValidator.SignedKeyRequestMetadata({ 124 | requestFid: requestFid, 125 | requestSigner: signer, 126 | signature: sig, 127 | deadline: deadline 128 | }) 129 | ); 130 | 131 | vm.prank(keyRegistry.keyGateway()); 132 | vm.expectRevert(IKeyRegistry.InvalidMetadata.selector); 133 | keyRegistry.add(to, 1, key, 1, metadata); 134 | } 135 | 136 | function testFuzzAddRevertsInvalidSig( 137 | address to, 138 | uint256 signerPk, 139 | uint256 otherPk, 140 | address recovery, 141 | bytes calldata key, 142 | uint40 _deadline 143 | ) public { 144 | signerPk = _boundPk(signerPk); 145 | otherPk = _boundPk(otherPk); 146 | uint256 deadline = _boundDeadline(_deadline); 147 | vm.assume(signerPk != otherPk); 148 | address signer = vm.addr(signerPk); 149 | vm.assume(signer != to); 150 | 151 | _registerFid(to, recovery); 152 | uint256 requestFid = _register(signer); 153 | 154 | bytes memory sig = _signMetadata(otherPk, requestFid, key, deadline); 155 | 156 | bytes memory metadata = abi.encode( 157 | SignedKeyRequestValidator.SignedKeyRequestMetadata({ 158 | requestFid: requestFid, 159 | requestSigner: signer, 160 | signature: sig, 161 | deadline: deadline 162 | }) 163 | ); 164 | 165 | vm.prank(keyRegistry.keyGateway()); 166 | vm.expectRevert(IKeyRegistry.InvalidMetadata.selector); 167 | keyRegistry.add(to, 1, key, 1, metadata); 168 | } 169 | 170 | /*////////////////////////////////////////////////////////////// 171 | HELPERS 172 | //////////////////////////////////////////////////////////////*/ 173 | 174 | function _registerFid(address to, address recovery) internal returns (uint256) { 175 | vm.prank(idRegistry.idGateway()); 176 | return idRegistry.register(to, recovery); 177 | } 178 | 179 | function assertEq(IKeyRegistry.KeyState a, IKeyRegistry.KeyState b) internal { 180 | assertEq(uint8(a), uint8(b)); 181 | } 182 | 183 | function assertNull(uint256 fid, bytes memory key) internal { 184 | assertEq(keyRegistry.keyDataOf(fid, key).state, IKeyRegistry.KeyState.NULL); 185 | assertEq(keyRegistry.keyDataOf(fid, key).keyType, 0); 186 | } 187 | 188 | function assertAdded(uint256 fid, bytes memory key, uint32 keyType) internal { 189 | assertEq(keyRegistry.keyDataOf(fid, key).state, IKeyRegistry.KeyState.ADDED); 190 | assertEq(keyRegistry.keyDataOf(fid, key).keyType, keyType); 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /test/KeyRegistry/KeyRegistryTestHelpers.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.21; 3 | 4 | import {KeyRegistry} from "../../src/KeyRegistry.sol"; 5 | 6 | library BulkAddDataBuilder { 7 | function empty() internal pure returns (KeyRegistry.BulkAddData[] memory) { 8 | return new KeyRegistry.BulkAddData[](0); 9 | } 10 | 11 | function addFid( 12 | KeyRegistry.BulkAddData[] memory addData, 13 | uint256 fid 14 | ) internal pure returns (KeyRegistry.BulkAddData[] memory) { 15 | KeyRegistry.BulkAddData[] memory newData = new KeyRegistry.BulkAddData[](addData.length + 1); 16 | for (uint256 i; i < addData.length; i++) { 17 | newData[i] = addData[i]; 18 | } 19 | newData[addData.length].fid = fid; 20 | return newData; 21 | } 22 | 23 | function addFidsWithKeys( 24 | KeyRegistry.BulkAddData[] memory addData, 25 | uint256[] memory fids, 26 | bytes[][] memory keys 27 | ) internal pure returns (KeyRegistry.BulkAddData[] memory) { 28 | for (uint256 i; i < fids.length; ++i) { 29 | addData = addFid(addData, fids[i]); 30 | bytes[] memory fidKeys = keys[i]; 31 | for (uint256 j; j < fidKeys.length; ++j) { 32 | addData = addKey(addData, i, fidKeys[j], bytes.concat("metadata-", fidKeys[j])); 33 | } 34 | } 35 | return addData; 36 | } 37 | 38 | function addKey( 39 | KeyRegistry.BulkAddData[] memory addData, 40 | uint256 index, 41 | bytes memory key, 42 | bytes memory metadata 43 | ) internal pure returns (KeyRegistry.BulkAddData[] memory) { 44 | KeyRegistry.BulkAddKey[] memory keys = addData[index].keys; 45 | KeyRegistry.BulkAddKey[] memory newKeys = new KeyRegistry.BulkAddKey[](keys.length + 1); 46 | 47 | for (uint256 i; i < keys.length; i++) { 48 | newKeys[i] = keys[i]; 49 | } 50 | newKeys[keys.length].key = key; 51 | newKeys[keys.length].metadata = metadata; 52 | addData[index].keys = newKeys; 53 | return addData; 54 | } 55 | } 56 | 57 | library BulkResetDataBuilder { 58 | function empty() internal pure returns (KeyRegistry.BulkResetData[] memory) { 59 | return new KeyRegistry.BulkResetData[](0); 60 | } 61 | 62 | function addFid( 63 | KeyRegistry.BulkResetData[] memory resetData, 64 | uint256 fid 65 | ) internal pure returns (KeyRegistry.BulkResetData[] memory) { 66 | KeyRegistry.BulkResetData[] memory newData = new KeyRegistry.BulkResetData[](resetData.length + 1); 67 | for (uint256 i; i < resetData.length; i++) { 68 | newData[i] = resetData[i]; 69 | } 70 | newData[resetData.length].fid = fid; 71 | return newData; 72 | } 73 | 74 | function addFidsWithKeys( 75 | KeyRegistry.BulkResetData[] memory resetData, 76 | uint256[] memory fids, 77 | bytes[][] memory keys 78 | ) internal pure returns (KeyRegistry.BulkResetData[] memory) { 79 | for (uint256 i; i < fids.length; ++i) { 80 | resetData = addFid(resetData, fids[i]); 81 | bytes[] memory fidKeys = keys[i]; 82 | for (uint256 j; j < fidKeys.length; ++j) { 83 | resetData = addKey(resetData, i, fidKeys[j]); 84 | } 85 | } 86 | return resetData; 87 | } 88 | 89 | function addKey( 90 | KeyRegistry.BulkResetData[] memory resetData, 91 | uint256 index, 92 | bytes memory key 93 | ) internal pure returns (KeyRegistry.BulkResetData[] memory) { 94 | bytes[] memory prevKeys = resetData[index].keys; 95 | bytes[] memory newKeys = new bytes[](prevKeys.length + 1); 96 | 97 | for (uint256 i; i < prevKeys.length; i++) { 98 | newKeys[i] = prevKeys[i]; 99 | } 100 | newKeys[prevKeys.length] = key; 101 | resetData[index].keys = newKeys; 102 | return resetData; 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /test/KeyRegistry/KeyRegistryTestSuite.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.21; 3 | 4 | import "forge-std/Test.sol"; 5 | 6 | import {IdRegistryTestSuite} from "../IdRegistry/IdRegistryTestSuite.sol"; 7 | import {IMetadataValidator} from "../../src/interfaces/IMetadataValidator.sol"; 8 | import {StubValidator} from "../Utils.sol"; 9 | import {KeyRegistry} from "../../src/KeyRegistry.sol"; 10 | 11 | /* solhint-disable state-visibility */ 12 | 13 | abstract contract KeyRegistryTestSuite is IdRegistryTestSuite { 14 | KeyRegistry internal keyRegistry; 15 | StubValidator internal stubValidator; 16 | 17 | function setUp() public virtual override { 18 | super.setUp(); 19 | 20 | keyRegistry = new KeyRegistry(address(idRegistry), migrator, owner, 10); 21 | stubValidator = new StubValidator(); 22 | 23 | vm.prank(owner); 24 | keyRegistry.unpause(); 25 | 26 | addKnownContract(address(keyRegistry)); 27 | addKnownContract(address(stubValidator)); 28 | } 29 | 30 | function _signRemove( 31 | uint256 pk, 32 | address owner, 33 | bytes memory key, 34 | uint256 deadline 35 | ) internal returns (bytes memory signature) { 36 | bytes32 digest = keyRegistry.hashTypedDataV4( 37 | keccak256( 38 | abi.encode(keyRegistry.REMOVE_TYPEHASH(), owner, keccak256(key), keyRegistry.nonces(owner), deadline) 39 | ) 40 | ); 41 | (uint8 v, bytes32 r, bytes32 s) = vm.sign(pk, digest); 42 | signature = abi.encodePacked(r, s, v); 43 | assertEq(signature.length, 65); 44 | } 45 | 46 | function _registerValidator(uint32 keyType, uint8 typeId) internal { 47 | _registerValidator(keyType, typeId, true); 48 | } 49 | 50 | function _registerValidator(uint32 keyType, uint8 typeId, bool isValid) internal { 51 | vm.prank(owner); 52 | keyRegistry.setValidator(keyType, typeId, IMetadataValidator(address(stubValidator))); 53 | stubValidator.setIsValid(isValid); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /test/KeyRegistry/utils/KeyRegistryHarness.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.19; 3 | 4 | import {KeyRegistry} from "../../../src/KeyRegistry.sol"; 5 | 6 | /** 7 | * @dev Adding enumerable sets tracking keys in the KeyRegistry blew up the 8 | * state space and triggered internal Halmos errors related to symbolic 9 | * storage. This harness class overrides and simplifies internal methods 10 | * that add/remove from KeySet as a workaround, enabling us to test 11 | * the core state transition invariants but ignore KeySet internals. 12 | */ 13 | contract KeyRegistryHarness is KeyRegistry { 14 | constructor( 15 | address idRegistry, 16 | address migrator, 17 | address owner, 18 | uint256 maxKeysPerFid 19 | ) KeyRegistry(idRegistry, migrator, owner, maxKeysPerFid) {} 20 | 21 | mapping(uint256 fid => bytes[] activeKeys) activeKeys; 22 | 23 | function totalKeys(uint256 fid, KeyState) public view override returns (uint256) { 24 | return activeKeys[fid].length; 25 | } 26 | 27 | function _addToKeySet(uint256 fid, bytes calldata key) internal override { 28 | activeKeys[fid].push(key); 29 | } 30 | 31 | function _removeFromKeySet(uint256 fid, bytes calldata) internal override { 32 | activeKeys[fid].pop(); 33 | } 34 | 35 | function _resetFromKeySet(uint256 fid, bytes calldata) internal override { 36 | activeKeys[fid].pop(); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /test/RecoveryProxy/RecoveryProxy.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.19; 3 | 4 | import {IIdRegistry} from "../../src/interfaces/IIdRegistry.sol"; 5 | import {RecoveryProxyTestSuite} from "./RecoveryProxyTestSuite.sol"; 6 | 7 | /* solhint-disable state-visibility */ 8 | 9 | contract RecoveryProxyTest is RecoveryProxyTestSuite { 10 | event SetIdRegistry(address oldIdRegistry, address newIdRegistry); 11 | 12 | function testIdRegistry() public { 13 | assertEq(address(recoveryProxy.idRegistry()), address(idRegistry)); 14 | } 15 | 16 | function testInitialOwner() public { 17 | assertEq(recoveryProxy.owner(), owner); 18 | } 19 | 20 | function testFuzzRecoveryByProxy(address from, uint256 toPk, uint40 _deadline) public { 21 | toPk = _boundPk(toPk); 22 | address to = vm.addr(toPk); 23 | vm.assume(from != to); 24 | 25 | uint256 deadline = _boundDeadline(_deadline); 26 | uint256 fid = _registerWithRecovery(from, address(recoveryProxy)); 27 | bytes memory sig = _signTransfer(toPk, fid, to, deadline); 28 | 29 | assertEq(idRegistry.idOf(from), 1); 30 | assertEq(idRegistry.idOf(to), 0); 31 | assertEq(idRegistry.recoveryOf(1), address(recoveryProxy)); 32 | 33 | vm.prank(owner); 34 | recoveryProxy.recover(from, to, deadline, sig); 35 | 36 | assertEq(idRegistry.idOf(from), 0); 37 | assertEq(idRegistry.idOf(to), 1); 38 | assertEq(idRegistry.recoveryOf(1), address(recoveryProxy)); 39 | } 40 | 41 | function testFuzzRecoveryByProxyRevertsUnauthorized( 42 | address from, 43 | uint256 toPk, 44 | uint40 _deadline, 45 | address caller 46 | ) public { 47 | vm.assume(caller != owner); 48 | toPk = _boundPk(toPk); 49 | address to = vm.addr(toPk); 50 | vm.assume(from != to); 51 | 52 | uint256 deadline = _boundDeadline(_deadline); 53 | uint256 fid = _registerWithRecovery(from, address(recoveryProxy)); 54 | bytes memory sig = _signTransfer(toPk, fid, to, deadline); 55 | 56 | assertEq(idRegistry.idOf(from), 1); 57 | assertEq(idRegistry.idOf(to), 0); 58 | assertEq(idRegistry.recoveryOf(1), address(recoveryProxy)); 59 | 60 | vm.prank(caller); 61 | vm.expectRevert("Ownable: caller is not the owner"); 62 | recoveryProxy.recover(from, to, deadline, sig); 63 | 64 | assertEq(idRegistry.idOf(from), 1); 65 | assertEq(idRegistry.idOf(to), 0); 66 | assertEq(idRegistry.recoveryOf(1), address(recoveryProxy)); 67 | } 68 | 69 | function testFuzzChangeOwner(address from, uint256 toPk, uint40 _deadline, address newOwner) public { 70 | vm.assume(newOwner != owner); 71 | toPk = _boundPk(toPk); 72 | address to = vm.addr(toPk); 73 | vm.assume(from != to); 74 | 75 | uint256 deadline = _boundDeadline(_deadline); 76 | uint256 fid = _registerWithRecovery(from, address(recoveryProxy)); 77 | bytes memory sig = _signTransfer(toPk, fid, to, deadline); 78 | 79 | assertEq(idRegistry.idOf(from), 1); 80 | assertEq(idRegistry.idOf(to), 0); 81 | assertEq(idRegistry.recoveryOf(1), address(recoveryProxy)); 82 | 83 | vm.prank(owner); 84 | recoveryProxy.transferOwnership(newOwner); 85 | 86 | vm.prank(newOwner); 87 | recoveryProxy.acceptOwnership(); 88 | 89 | vm.prank(owner); 90 | vm.expectRevert("Ownable: caller is not the owner"); 91 | recoveryProxy.recover(from, to, deadline, sig); 92 | 93 | vm.prank(newOwner); 94 | recoveryProxy.recover(from, to, deadline, sig); 95 | 96 | assertEq(idRegistry.idOf(from), 0); 97 | assertEq(idRegistry.idOf(to), 1); 98 | assertEq(idRegistry.recoveryOf(1), address(recoveryProxy)); 99 | } 100 | 101 | function testFuzzOnlyOwnerCanSetIdRegistry(address caller, IIdRegistry _idRegistry) public { 102 | vm.assume(caller != owner); 103 | 104 | vm.prank(caller); 105 | vm.expectRevert("Ownable: caller is not the owner"); 106 | recoveryProxy.setIdRegistry(_idRegistry); 107 | } 108 | 109 | function testFuzzSetIdRegistry( 110 | IIdRegistry newIdRegistry 111 | ) public { 112 | IIdRegistry currentIdRegistry = recoveryProxy.idRegistry(); 113 | 114 | vm.expectEmit(false, false, false, true); 115 | emit SetIdRegistry(address(currentIdRegistry), address(newIdRegistry)); 116 | 117 | vm.prank(owner); 118 | recoveryProxy.setIdRegistry(newIdRegistry); 119 | 120 | assertEq(address(recoveryProxy.idRegistry()), address(newIdRegistry)); 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /test/RecoveryProxy/RecoveryProxyTestSuite.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.21; 3 | 4 | import {RecoveryProxy} from "../../src/RecoveryProxy.sol"; 5 | import {IdRegistryTestSuite} from "../IdRegistry/IdRegistryTestSuite.sol"; 6 | 7 | /* solhint-disable state-visibility */ 8 | 9 | abstract contract RecoveryProxyTestSuite is IdRegistryTestSuite { 10 | RecoveryProxy recoveryProxy; 11 | 12 | function setUp() public virtual override { 13 | super.setUp(); 14 | 15 | recoveryProxy = new RecoveryProxy(address(idRegistry), owner); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /test/StorageRegistry/StorageRegistry.gas.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.19; 3 | 4 | import {StorageRegistryTestSuite} from "./StorageRegistryTestSuite.sol"; 5 | 6 | /* solhint-disable state-visibility */ 7 | 8 | contract StorageRegistryGasUsageTest is StorageRegistryTestSuite { 9 | function testGasRent() public { 10 | uint256 units = 1; 11 | uint256 price = storageRegistry.price(units); 12 | 13 | for (uint256 i = 0; i < 10; i++) { 14 | storageRegistry.rent{value: price}(i, units); 15 | } 16 | } 17 | 18 | function testGasBatchRent() public { 19 | uint256[] memory units = new uint256[](5); 20 | units[0] = 1; 21 | units[1] = 1; 22 | units[2] = 1; 23 | units[3] = 1; 24 | units[4] = 1; 25 | 26 | uint256[] memory ids = new uint256[](5); 27 | ids[0] = 1; 28 | ids[1] = 2; 29 | ids[2] = 3; 30 | ids[3] = 4; 31 | ids[4] = 5; 32 | 33 | uint256 totalCost = storageRegistry.price(5); 34 | vm.deal(address(this), totalCost * 10); 35 | 36 | for (uint256 i = 0; i < 10; i++) { 37 | storageRegistry.batchRent{value: totalCost}(ids, units); 38 | } 39 | } 40 | 41 | function testGasCredit() public { 42 | uint256 units = 1; 43 | 44 | for (uint256 i = 0; i < 10; i++) { 45 | vm.prank(operator); 46 | storageRegistry.credit(1, units); 47 | } 48 | } 49 | 50 | function testGasBatchCredit() public { 51 | uint256[] memory ids = new uint256[](5); 52 | ids[0] = 1; 53 | ids[1] = 2; 54 | ids[2] = 3; 55 | ids[3] = 4; 56 | ids[4] = 5; 57 | 58 | for (uint256 i = 0; i < 10; i++) { 59 | vm.prank(operator); 60 | storageRegistry.batchCredit(ids, 1); 61 | } 62 | } 63 | 64 | function testGasContinuousCredit() public { 65 | for (uint256 i = 0; i < 10; i++) { 66 | vm.prank(operator); 67 | storageRegistry.continuousCredit(1, 5, 1); 68 | } 69 | } 70 | 71 | // solhint-disable-next-line no-empty-blocks 72 | receive() external payable {} 73 | } 74 | -------------------------------------------------------------------------------- /test/StorageRegistry/StorageRegistryTestSuite.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.21; 3 | 4 | import {StorageRegistryHarness, MockPriceFeed, MockUptimeFeed, MockChainlinkFeed, RevertOnReceive} from "../Utils.sol"; 5 | import {TestSuiteSetup} from "../TestSuiteSetup.sol"; 6 | 7 | /* solhint-disable state-visibility */ 8 | 9 | abstract contract StorageRegistryTestSuite is TestSuiteSetup { 10 | StorageRegistryHarness internal storageRegistry; 11 | MockPriceFeed internal priceFeed; 12 | MockUptimeFeed internal uptimeFeed; 13 | RevertOnReceive internal revertOnReceive; 14 | 15 | /*////////////////////////////////////////////////////////////// 16 | CONSTANTS 17 | //////////////////////////////////////////////////////////////*/ 18 | 19 | address internal deployer = address(this); 20 | address internal mallory = makeAddr("mallory"); 21 | address internal vault = makeAddr("vault"); 22 | address internal roleAdmin = makeAddr("roleAdmin"); 23 | address internal operator = makeAddr("operator"); 24 | address internal treasurer = makeAddr("treasurer"); 25 | 26 | uint256 internal immutable DEPLOYED_AT = block.timestamp + 3600; 27 | 28 | uint256 internal constant INITIAL_RENTAL_PERIOD = 365 days; 29 | uint256 internal constant INITIAL_USD_UNIT_PRICE = 5e8; // $5 USD 30 | uint256 internal constant INITIAL_MAX_UNITS = 2_000_000; 31 | 32 | int256 internal constant SEQUENCER_UP = 0; 33 | int256 internal constant ETH_USD_PRICE = 2000e8; // $2000 USD/ETH 34 | 35 | uint256 internal constant INITIAL_PRICE_FEED_CACHE_DURATION = 1 days; 36 | uint256 internal constant INITIAL_PRICE_FEED_MAX_AGE = 2 hours; 37 | uint256 internal constant INITIAL_UPTIME_FEED_GRACE_PERIOD = 1 hours; 38 | uint256 internal constant INITIAL_PRICE_IN_ETH = 0.0025 ether; 39 | uint256 internal constant INITIAL_PRICE_FEED_MIN_ANSWER = 100e8; // $100 USD 40 | uint256 internal constant INITIAL_PRICE_FEED_MAX_ANSWER = 10_000e8; // $10k USD 41 | 42 | function setUp() public virtual override { 43 | super.setUp(); 44 | 45 | priceFeed = new MockPriceFeed(); 46 | uptimeFeed = new MockUptimeFeed(); 47 | revertOnReceive = new RevertOnReceive(); 48 | 49 | uptimeFeed.setRoundData( 50 | MockChainlinkFeed.RoundData({ 51 | roundId: 1, 52 | answer: SEQUENCER_UP, 53 | startedAt: 0, 54 | timeStamp: block.timestamp, 55 | answeredInRound: 1 56 | }) 57 | ); 58 | 59 | priceFeed.setRoundData( 60 | MockChainlinkFeed.RoundData({ 61 | roundId: 1, 62 | answer: ETH_USD_PRICE, 63 | startedAt: 0, 64 | timeStamp: block.timestamp, 65 | answeredInRound: 1 66 | }) 67 | ); 68 | 69 | vm.warp(DEPLOYED_AT); 70 | 71 | storageRegistry = new StorageRegistryHarness( 72 | priceFeed, 73 | uptimeFeed, 74 | INITIAL_USD_UNIT_PRICE, 75 | INITIAL_MAX_UNITS, 76 | vault, 77 | roleAdmin, 78 | owner, 79 | operator, 80 | treasurer 81 | ); 82 | 83 | addKnownContract(address(priceFeed)); 84 | addKnownContract(address(uptimeFeed)); 85 | addKnownContract(address(revertOnReceive)); 86 | addKnownContract(address(storageRegistry)); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /test/TestSuiteSetup.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.21; 3 | 4 | import {Test} from "forge-std/Test.sol"; 5 | import {ERC1271WalletMock, ERC1271MaliciousMockForceRevert} from "./Utils.sol"; 6 | 7 | abstract contract TestSuiteSetup is Test { 8 | /*////////////////////////////////////////////////////////////// 9 | CONSTANTS 10 | //////////////////////////////////////////////////////////////*/ 11 | 12 | uint256 constant SECP_256K1_ORDER = 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141; 13 | 14 | address constant ADMIN = address(0xa6a4daBC320300cd0D38F77A6688C6b4048f4682); 15 | 16 | // Known contracts that must not be made to call other contracts in tests 17 | address[] internal knownContracts = [ 18 | address(0xCe71065D4017F316EC606Fe4422e11eB2c47c246), // FuzzerDict 19 | address(0x4e59b44847b379578588920cA78FbF26c0B4956C), // CREATE2 Factory 20 | address(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D), // Vm cheatcode address 21 | address(0x000000000000000000636F6e736F6c652e6c6f67), // console.sol 22 | address(0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f), // Default test contract 23 | address(0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496), // address(this) 24 | address(0x185a4dc360CE69bDCceE33b3784B0282f7961aea), // ??? 25 | address(0x2e234DAe75C793f67A35089C9d99245E1C58470b), // ??? 26 | address(0xEFc56627233b02eA95bAE7e19F648d7DcD5Bb132), // ??? 27 | address(0xf5a2fE45F4f1308502b1C136b9EF8af136141382) // ??? 28 | ]; 29 | 30 | address owner = makeAddr("owner"); 31 | address trustedCaller = makeAddr("trustedCaller"); 32 | address migrator = makeAddr("migrator"); 33 | 34 | // Address of known contracts, in a mapping for faster lookup when fuzzing 35 | mapping(address => bool) isKnownContract; 36 | 37 | /*////////////////////////////////////////////////////////////// 38 | CONSTRUCTOR 39 | //////////////////////////////////////////////////////////////*/ 40 | 41 | function setUp() public virtual { 42 | // Set up the known contracts map 43 | for (uint256 i = 0; i < knownContracts.length; i++) { 44 | isKnownContract[knownContracts[i]] = true; 45 | } 46 | } 47 | 48 | /*////////////////////////////////////////////////////////////// 49 | HELPERS 50 | //////////////////////////////////////////////////////////////*/ 51 | 52 | function addKnownContract( 53 | address contractAddress 54 | ) public { 55 | isKnownContract[contractAddress] = true; 56 | } 57 | 58 | // Ensures that a fuzzed address input does not match a known contract address 59 | function _assumeClean( 60 | address a 61 | ) internal { 62 | assumeNotPrecompile(a); 63 | vm.assume(!isKnownContract[a]); 64 | vm.assume(a != ADMIN); 65 | vm.assume(a != address(0)); 66 | } 67 | 68 | function _boundPk( 69 | uint256 pk 70 | ) internal view returns (uint256) { 71 | return bound(pk, 1, SECP_256K1_ORDER - 1); 72 | } 73 | 74 | function _boundDeadline( 75 | uint40 deadline 76 | ) internal view returns (uint256) { 77 | return block.timestamp + uint256(bound(deadline, 0, type(uint40).max)); 78 | } 79 | 80 | function _createMockERC1271( 81 | address ownerAddress 82 | ) internal returns (ERC1271WalletMock mockWallet, address mockWalletAddress) { 83 | mockWallet = new ERC1271WalletMock(ownerAddress); 84 | mockWalletAddress = address(mockWallet); 85 | } 86 | 87 | function _createMaliciousMockERC1271( 88 | address ownerAddress 89 | ) internal returns (ERC1271MaliciousMockForceRevert mockWallet, address mockWalletAddress) { 90 | mockWallet = new ERC1271MaliciousMockForceRevert(ownerAddress); 91 | mockWalletAddress = address(mockWallet); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /test/TierRegistry/TierRegistryTestSuite.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.21; 3 | 4 | import {TestSuiteSetup} from "../TestSuiteSetup.sol"; 5 | import "openzeppelin/contracts/token/ERC20/ERC20.sol"; 6 | import {TierRegistry} from "../../src/TierRegistry.sol"; 7 | 8 | contract TestToken is ERC20 { 9 | constructor( 10 | address source 11 | ) ERC20("TestToken", "TTK") { 12 | _mint(source, 1_000_000_000_000); 13 | } 14 | } 15 | 16 | abstract contract TierRegistryTestSuite is TestSuiteSetup { 17 | TierRegistry tierRegistry; 18 | TestToken public token; 19 | 20 | address internal deployer = address(this); 21 | address internal tokenSource = makeAddr("tokenSource"); 22 | 23 | uint256 internal immutable DEPLOYED_AT = block.timestamp + 3600; 24 | uint256 public immutable DEFAULT_MIN_DAYS = 1; 25 | uint256 public immutable DEFAULT_MAX_DAYS = type(uint64).max; 26 | 27 | function setUp() public virtual override { 28 | super.setUp(); 29 | 30 | token = new TestToken(tokenSource); 31 | 32 | vm.warp(DEPLOYED_AT); 33 | 34 | tierRegistry = new TierRegistry(migrator, owner); 35 | 36 | vm.prank(owner); 37 | tierRegistry.unpause(); 38 | 39 | addKnownContract(address(tierRegistry)); 40 | addKnownContract(address(token)); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /test/Utils.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.21; 3 | 4 | import {AggregatorV3Interface} from "chainlink/v0.8/interfaces/AggregatorV3Interface.sol"; 5 | 6 | import {FnameResolver} from "../src/FnameResolver.sol"; 7 | import {IdRegistry} from "../src/IdRegistry.sol"; 8 | import {KeyRegistry} from "../src/KeyRegistry.sol"; 9 | import {StorageRegistry} from "../src/StorageRegistry.sol"; 10 | import {SignedKeyRequestValidator} from "../src/validators/SignedKeyRequestValidator.sol"; 11 | import {BundlerV1} from "../src/BundlerV1.sol"; 12 | import {Ownable} from "openzeppelin/contracts/access/Ownable.sol"; 13 | import {IERC1271} from "openzeppelin/contracts/interfaces/IERC1271.sol"; 14 | import {SignatureChecker} from "openzeppelin/contracts/utils/cryptography/SignatureChecker.sol"; 15 | 16 | /* solhint-disable no-empty-blocks */ 17 | 18 | contract StorageRegistryHarness is StorageRegistry { 19 | constructor( 20 | AggregatorV3Interface _priceFeed, 21 | AggregatorV3Interface _uptimeFeed, 22 | uint256 _usdUnitPrice, 23 | uint256 _maxUnits, 24 | address _vault, 25 | address _roleAdmin, 26 | address _owner, 27 | address _operator, 28 | address _treasurer 29 | ) 30 | StorageRegistry( 31 | _priceFeed, 32 | _uptimeFeed, 33 | _usdUnitPrice, 34 | _maxUnits, 35 | _vault, 36 | _roleAdmin, 37 | _owner, 38 | _operator, 39 | _treasurer 40 | ) 41 | {} 42 | 43 | function ownerRoleId() external pure returns (bytes32) { 44 | return OWNER_ROLE; 45 | } 46 | 47 | function operatorRoleId() external pure returns (bytes32) { 48 | return OPERATOR_ROLE; 49 | } 50 | 51 | function treasurerRoleId() external pure returns (bytes32) { 52 | return TREASURER_ROLE; 53 | } 54 | } 55 | 56 | contract StubValidator { 57 | bool isValid = true; 58 | 59 | function validate( 60 | uint256, /* userFid */ 61 | bytes memory, /* signerPubKey */ 62 | bytes memory /* appIdBytes */ 63 | ) external view returns (bool) { 64 | return isValid; 65 | } 66 | 67 | function setIsValid( 68 | bool val 69 | ) external { 70 | isValid = val; 71 | } 72 | } 73 | 74 | contract MockChainlinkFeed is AggregatorV3Interface { 75 | struct RoundData { 76 | uint80 roundId; 77 | int256 answer; 78 | uint256 startedAt; 79 | uint256 timeStamp; 80 | uint80 answeredInRound; 81 | } 82 | 83 | RoundData public roundData; 84 | 85 | uint8 public decimals; 86 | string public description; 87 | uint256 public version = 1; 88 | 89 | bool public shouldRevert; 90 | bool public stubTimeStamp; 91 | 92 | constructor(uint8 _decimals, string memory _description) { 93 | decimals = _decimals; 94 | description = _description; 95 | } 96 | 97 | function setShouldRevert( 98 | bool _shouldRevert 99 | ) external { 100 | shouldRevert = _shouldRevert; 101 | } 102 | 103 | function setStubTimeStamp( 104 | bool _stubTimeStamp 105 | ) external { 106 | stubTimeStamp = _stubTimeStamp; 107 | } 108 | 109 | function setAnswer( 110 | int256 value 111 | ) external { 112 | roundData.answer = value; 113 | } 114 | 115 | function setRoundData( 116 | RoundData calldata _roundData 117 | ) external { 118 | roundData = _roundData; 119 | } 120 | 121 | function getRoundData( 122 | uint80 123 | ) external view returns (uint80, int256, uint256, uint256, uint80) { 124 | return latestRoundData(); 125 | } 126 | 127 | function latestRoundData() public view returns (uint80, int256, uint256, uint256, uint80) { 128 | if (shouldRevert) revert("MockChainLinkFeed: Call failed"); 129 | return ( 130 | roundData.roundId, 131 | roundData.answer, 132 | roundData.startedAt, 133 | stubTimeStamp ? roundData.timeStamp : block.timestamp, 134 | roundData.answeredInRound 135 | ); 136 | } 137 | } 138 | 139 | contract MockPriceFeed is MockChainlinkFeed(8, "Mock ETH/USD Price Feed") { 140 | function setPrice( 141 | int256 _price 142 | ) external { 143 | roundData.answer = _price; 144 | } 145 | } 146 | 147 | contract MockUptimeFeed is MockChainlinkFeed(0, "Mock L2 Sequencer Uptime Feed") {} 148 | 149 | contract RevertOnReceive { 150 | receive() external payable { 151 | revert("Cannot receive ETH"); 152 | } 153 | } 154 | 155 | /*////////////////////////////////////////////////////////////// 156 | SMART CONTRACT WALLET MOCKS 157 | //////////////////////////////////////////////////////////////*/ 158 | 159 | contract ERC1271WalletMock is Ownable, IERC1271 { 160 | constructor( 161 | address owner 162 | ) { 163 | super.transferOwnership(owner); 164 | } 165 | 166 | function isValidSignature(bytes32 hash, bytes memory signature) public view returns (bytes4 magicValue) { 167 | return 168 | SignatureChecker.isValidSignatureNow(owner(), hash, signature) ? this.isValidSignature.selector : bytes4(0); 169 | } 170 | } 171 | 172 | contract ERC1271MaliciousMockForceRevert is Ownable, IERC1271 { 173 | bool internal _forceRevert = true; 174 | 175 | constructor( 176 | address owner 177 | ) { 178 | super.transferOwnership(owner); 179 | } 180 | 181 | function setForceRevert( 182 | bool forceRevert 183 | ) external { 184 | _forceRevert = forceRevert; 185 | } 186 | 187 | function isValidSignature(bytes32 hash, bytes memory signature) public view returns (bytes4) { 188 | if (_forceRevert) { 189 | assembly { 190 | mstore(0, 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff) 191 | return(0, 32) 192 | } 193 | } 194 | 195 | return 196 | SignatureChecker.isValidSignatureNow(owner(), hash, signature) ? this.isValidSignature.selector : bytes4(0); 197 | } 198 | } 199 | -------------------------------------------------------------------------------- /test/abstract/EIP712/EIP712.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.19; 3 | 4 | import {EIP712} from "../../../src/abstract/EIP712.sol"; 5 | import {TestSuiteSetup} from "../../TestSuiteSetup.sol"; 6 | 7 | /* solhint-disable state-visibility */ 8 | /* solhint-disable no-empty-blocks */ 9 | 10 | contract EIP712Example is EIP712("EIP712 Example", "1") {} 11 | 12 | contract EIP712Test is TestSuiteSetup { 13 | EIP712Example eip712; 14 | 15 | function setUp() public override { 16 | super.setUp(); 17 | 18 | eip712 = new EIP712Example(); 19 | } 20 | 21 | function testExposesDomainSeparator() public { 22 | assertEq(eip712.domainSeparatorV4(), 0x0617e266f62048821cb1d443cca5b7a0e073cb89f23c9f20046cdf79ecb42429); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /test/abstract/Guardians/Guardians.symbolic.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.19; 3 | 4 | import {SymTest} from "halmos-cheatcodes/SymTest.sol"; 5 | import {Test} from "forge-std/Test.sol"; 6 | 7 | import {Guardians} from "../../../src/abstract/Guardians.sol"; 8 | 9 | contract GuardiansExample is Guardians { 10 | constructor( 11 | address owner 12 | ) Guardians(owner) {} 13 | } 14 | 15 | contract GuardiansSymTest is SymTest, Test { 16 | GuardiansExample guarded; 17 | address owner; 18 | address x; 19 | address y; 20 | 21 | function setUp() public { 22 | owner = address(0x1000); 23 | 24 | // Setup Guardians 25 | guarded = new GuardiansExample(owner); 26 | 27 | // Create symbolic addresses 28 | x = svm.createAddress("x"); 29 | y = svm.createAddress("y"); 30 | } 31 | 32 | function check_Invariants(bytes4 selector, address caller) public { 33 | _initState(); 34 | vm.assume(x != owner); 35 | vm.assume(x != y); 36 | 37 | // Record pre-state 38 | bool oldPaused = guarded.paused(); 39 | bool oldGuardianX = guarded.guardians(x); 40 | bool oldGuardianY = guarded.guardians(y); 41 | 42 | // Execute an arbitrary tx 43 | vm.prank(caller); 44 | (bool success,) = address(guarded).call(_calldataFor(selector)); 45 | vm.assume(success); // ignore reverting cases 46 | 47 | // Record post-state 48 | bool newPaused = guarded.paused(); 49 | bool newGuardianX = guarded.guardians(x); 50 | bool newGuardianY = guarded.guardians(y); 51 | 52 | // If the paused state is changed by any transaction... 53 | if (newPaused != oldPaused) { 54 | // If it wasn't paused before... 55 | if (!oldPaused) { 56 | // It must be paused now. 57 | assert(guarded.paused()); 58 | 59 | // The function called was pause(). 60 | assert(selector == guarded.pause.selector); 61 | 62 | // The caller must be the owner or a guardian. 63 | assert(caller == owner || guarded.guardians(caller)); 64 | } // Otherwise, if it *was* paused before... 65 | else { 66 | // It must be unpaused now. 67 | assert(!guarded.paused()); 68 | 69 | // The function called was unpause(). 70 | assert(selector == guarded.unpause.selector); 71 | 72 | // The caller must be the owner. 73 | assert(caller == owner); 74 | } 75 | } 76 | 77 | // If X's guardian state is changed by any transaction... 78 | if (newGuardianX != oldGuardianX) { 79 | // The caller must be the owner. 80 | assert(caller == owner); 81 | 82 | // Y's guardian state must not be changed. 83 | assert(newGuardianY == oldGuardianY); 84 | } 85 | 86 | // If Y's guardian state is changed by any transaction... 87 | if (newGuardianY != oldGuardianY) { 88 | // The caller must be the owner. 89 | assert(caller == owner); 90 | 91 | // X's guardian state must not be changed. 92 | assert(newGuardianX == oldGuardianX); 93 | } 94 | } 95 | 96 | /*////////////////////////////////////////////////////////////// 97 | HELPERS 98 | //////////////////////////////////////////////////////////////*/ 99 | 100 | /** 101 | * @dev Initialize IdRegistry with symbolic arguments for state. 102 | */ 103 | function _initState() public { 104 | if (svm.createBool("pause?")) { 105 | vm.prank(owner); 106 | guarded.pause(); 107 | } 108 | } 109 | 110 | /** 111 | * @dev Generates valid calldata for a given function selector. 112 | */ 113 | function _calldataFor( 114 | bytes4 selector 115 | ) internal returns (bytes memory) { 116 | return abi.encodePacked(selector, svm.createBytes(1024, "data")); 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /test/abstract/Migration/Migration.symbolic.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.19; 3 | 4 | import {SymTest} from "halmos-cheatcodes/SymTest.sol"; 5 | import {Test} from "forge-std/Test.sol"; 6 | 7 | import {Migration} from "../../../src/abstract/Migration.sol"; 8 | 9 | contract MigrationExample is Migration { 10 | constructor(uint256 gracePeriod, address migrator, address owner) Migration(uint24(gracePeriod), migrator, owner) {} 11 | 12 | function onlyCallableDuringMigration() external onlyMigrator {} 13 | } 14 | 15 | contract MigrationSymTest is SymTest, Test { 16 | MigrationExample migration; 17 | address migrator; 18 | address owner; 19 | uint256 gracePeriod; 20 | 21 | function setUp() public { 22 | owner = address(0x1000); 23 | migrator = address(0x2000); 24 | 25 | // Create symbolic gracePeriod 26 | gracePeriod = svm.createUint256("gracePeriod"); 27 | 28 | // Setup Migration 29 | migration = new MigrationExample(gracePeriod, migrator, owner); 30 | } 31 | 32 | function check_Invariants(bytes4 selector, address caller) public { 33 | _initState(); 34 | 35 | // Record pre-state 36 | uint40 oldMigratedAt = migration.migratedAt(); 37 | address oldMigrator = migration.migrator(); 38 | 39 | // Execute an arbitrary tx 40 | vm.prank(caller); 41 | (bool success,) = address(migration).call(_calldataFor(selector)); 42 | vm.assume(success); // ignore reverting cases 43 | 44 | // Record post-state 45 | uint40 newMigratedAt = migration.migratedAt(); 46 | address newMigrator = migration.migrator(); 47 | 48 | bool isPaused = migration.paused(); 49 | bool isMigrated = migration.isMigrated(); 50 | bool isInGracePeriod = block.timestamp <= migration.migratedAt() + migration.gracePeriod(); 51 | 52 | // If the migratedAt timestamp is changed by any transaction... 53 | if (newMigratedAt != oldMigratedAt) { 54 | // The previous value was zero. 55 | assert(oldMigratedAt == 0); 56 | 57 | // The function called was migrate(). 58 | assert(selector == migration.migrate.selector); 59 | 60 | // The caller must be the migrator. 61 | assert(caller == oldMigrator && oldMigrator == newMigrator); 62 | 63 | // The contract is paused. 64 | assert(isPaused); 65 | } 66 | 67 | // If the migrator address is changed by any transaction... 68 | if (newMigrator != oldMigrator) { 69 | // The function called was setMigrator(). 70 | assert(selector == migration.setMigrator.selector); 71 | 72 | // The caller must be the owner. 73 | assert(caller == owner); 74 | 75 | // The contract is unmigrated. 76 | assert(oldMigratedAt == 0 && oldMigratedAt == newMigratedAt); 77 | 78 | // The contract is paused. 79 | assert(isPaused); 80 | } 81 | 82 | // If the call was protected by a migration modifier... 83 | if (selector == migration.onlyCallableDuringMigration.selector) { 84 | // The state must be unchanged. 85 | assert(newMigratedAt == oldMigratedAt); 86 | 87 | // The caller must be the migrator. 88 | assert(caller == oldMigrator && oldMigrator == newMigrator); 89 | 90 | // The contract is unmigrated or in the grace period. 91 | assert(!isMigrated || isInGracePeriod); 92 | 93 | // The contract is paused. 94 | assert(isPaused); 95 | } 96 | } 97 | 98 | /*////////////////////////////////////////////////////////////// 99 | HELPERS 100 | //////////////////////////////////////////////////////////////*/ 101 | 102 | /** 103 | * @dev Initialize IdRegistry with symbolic arguments for state. 104 | */ 105 | function _initState() public { 106 | if (svm.createBool("isMigrated?")) { 107 | vm.prank(migration.migrator()); 108 | migration.migrate(); 109 | } 110 | } 111 | 112 | /** 113 | * @dev Generates valid calldata for a given function selector. 114 | */ 115 | function _calldataFor( 116 | bytes4 selector 117 | ) internal returns (bytes memory) { 118 | return abi.encodePacked(selector, svm.createBytes(1024, "data")); 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /test/validators/SignedKeyRequestValidator/SignedKeyRequestValidatorTestSuite.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.21; 3 | 4 | import {IdRegistryTestSuite} from "../../IdRegistry/IdRegistryTestSuite.sol"; 5 | 6 | import {SignedKeyRequestValidator} from "../../../src/validators/SignedKeyRequestValidator.sol"; 7 | 8 | /* solhint-disable state-visibility */ 9 | 10 | abstract contract SignedKeyRequestValidatorTestSuite is IdRegistryTestSuite { 11 | SignedKeyRequestValidator internal validator; 12 | 13 | function setUp() public virtual override { 14 | super.setUp(); 15 | 16 | validator = new SignedKeyRequestValidator(address(idRegistry), owner); 17 | } 18 | 19 | function _validKey( 20 | bytes memory keyBytes 21 | ) internal pure returns (bytes memory) { 22 | if (keyBytes.length < 32) { 23 | // pad with zero bytes 24 | bytes memory padding = new bytes(32 - keyBytes.length); 25 | return bytes.concat(keyBytes, padding); 26 | } else if (keyBytes.length > 32) { 27 | // truncate length 28 | assembly { 29 | mstore(keyBytes, 32) 30 | } 31 | return keyBytes; 32 | } else { 33 | return keyBytes; 34 | } 35 | } 36 | 37 | function _shortKey(bytes memory keyBytes, uint8 _amount) internal view returns (bytes memory) { 38 | uint256 amount = bound(_amount, 0, 31); 39 | assembly { 40 | mstore(keyBytes, amount) 41 | } 42 | return keyBytes; 43 | } 44 | 45 | function _longKey(bytes memory keyBytes, uint8 _amount) internal view returns (bytes memory) { 46 | uint256 amount = bound(_amount, 1, type(uint8).max); 47 | bytes memory padding = new bytes(amount); 48 | return bytes.concat(_validKey(keyBytes), padding); 49 | } 50 | 51 | function _signMetadata( 52 | uint256 pk, 53 | uint256 requestingFid, 54 | bytes memory signerPubKey, 55 | uint256 deadline 56 | ) internal returns (bytes memory signature) { 57 | bytes32 digest = validator.hashTypedDataV4( 58 | keccak256(abi.encode(validator.METADATA_TYPEHASH(), requestingFid, keccak256(signerPubKey), deadline)) 59 | ); 60 | (uint8 v, bytes32 r, bytes32 s) = vm.sign(pk, digest); 61 | signature = abi.encodePacked(r, s, v); 62 | assertEq(signature.length, 65); 63 | } 64 | } 65 | --------------------------------------------------------------------------------