diff --git a/.env.example b/.env.example deleted file mode 100644 index 7559d012..00000000 --- a/.env.example +++ /dev/null @@ -1 +0,0 @@ -MAINNET_RPC_URL="" \ No newline at end of file diff --git a/LICENSE b/LICENSE index 358a3057..b6366641 100644 --- a/LICENSE +++ b/LICENSE @@ -2,20 +2,15 @@ MIT License Copyright (c) 2023 Worldcoin -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the Software without restriction, including without limitation the +rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit +persons to whom the Software is furnished to do so, subject to the following conditions: -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the +Software. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md index 226daced..93c10a92 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # world-id-state-bridge -![spec](docs/state-bridge2.svg) +![spec](docs/state-bridge.svg) ## Description @@ -10,14 +10,42 @@ can be found in `docs/spec.md`. ## Deployments The addresses of the contract deployments for production and staging can be found in -[`docs/deployments.md`](./docs/deployments.md). +[`docs/deployments.md`](./docs/deployments.md#production). ## Supported networks -Currently, the supported networks are Polygon PoS (for backwards compatibility) and Optimism. The next iteration of the -bridge will most-likely be based on storage proofs and will support most EVM networks off the get-go, with other +Currently, the supported networks are Polygon PoS (for backwards compatibility), Optimism and Base. The next iteration +of the bridge will most-likely be based on storage proofs and will support most EVM networks off the get-go, with other networks pending storage proof verifier and Semaphore verifier implementations and deployments. +### Future integrations + +If you want to support World ID on your network, please reach out to us by opening a GitHub issue. To support World ID +the current requirements are: + +- have a native L1<>L2 bridge with Ethereum mainnet and Ethereum Goerli/Sepolia (for testnet integration tests) +- your network needs to have an EVM execution environment (like Optimism, Arbitrum, Scroll, Polygon zkEVM, zkSync Era, + etc) + +If your network meets these requirements, please reach out to us and we will work with you to integrate World ID. In the +mid-term future we plan on supporting storage proof solutions like [Axiom](https://axiom.xyz/) and +[Herodotus](https://herodotus.dev/) to bridge World ID state to other networks more seamlessly and with better UX and +for cheaper costs. Currently each bridge incurs the cost of each cross-chain message sent by calling the +`propagateRoot()` function. Subsidizing these costs on your end would make the integration process very simple. + +If your network is not EVM-compatible/equivalent, a new implementation of the World ID contracts will need to be done +for your execution environment. Requirements are: + +- Have a way to implement the `SemaphoreVerifier` and `semaphore-mtb` circuits verifier contracts which verify `Groth16` + proofs over the `BN254` curve. +- Have the capabilities to support all cryptographic primitives that will be implemented in future versions of World ID + as time goes on. +- Support the primitives needed to verify Axiom or Herodotus storage proofs. + +For L1 to L1 bridges, there are solutions like [Succinct](https://succinct.xyz/)'s +[Telepathy](https://www.telepathy.xyz/) which have weaker security guarantees than storage proofs or native L1<>L2 +bridges, but possibly allow for a World ID integration, provided that the reqirements above are met. + ## Documentation Run `make doc` to build and deploy a simple documentation webpage on [localhost:3000](https://localhost:3000). Uses @@ -87,30 +115,31 @@ Run the tests: make test ``` -### Environment - -Clone `.env.example` to `.env`, fill the environment variables and `source .env` before running any scripts. Beware that -there is a different Etherscan API key for every single network that we are deploying a contract onto -([Ethereum](https://etherscan.io/myaccount), [Polygon](https://polygonscan.com/myaccount) and -[Optimism](https://optimistic.etherscan.io/login)- mainnet/testnet). - ### Deploy -Deploy the WorldID state bridge and all its components to Ethereum mainnet using the CLI tool. +Deploy the WorldID state bridge and all its components to Ethereum mainnet using the CLI tool. For a more detailed +description of the deployment process and production and staging (testnet) contract addresses, see +[deployments.md](./docs/deployments.md) . Integration with full system: 1. Deploy [`world-id-contracts`](https://github.com/worldcoin/world-id-contracts) 2. Get deployment address for `WorldIDIdentityManager` -3. Deploy [`world-id-state-bridge`](https://github.com/worldcoin/world-id-state-bridge) by running `make deploy` +3. (optional) you can also use the `WorldIDIdentityManager` address that can be found in + [`docs/deployments.md`](./docs/deployments.md) to bridge existing roots. +4. Deploy [`world-id-state-bridge`](https://github.com/worldcoin/world-id-state-bridge) by running `make deploy-testnet` (requires 2.) -4. Get deployment address for `StateBridge` -5. Call - [`setStateBridge`](https://github.com/worldcoin/world-id-contracts/blob/5f0f56c22b916815eecc82eef877d141acd7e139/src/WorldIDIdentityManagerImplV1.sol#L682-L707) - on `WorldIDIdentityManager` with the output from 4. -6. Start inserting identities and monitor `PolygonWorldID` and `OpWorldID` for root updates (using - [`signup-sequencer`](https://github.com/worldcoin/signup-sequencer)) -7. Try and create a proof and call `verifyProof` on `OpWorldID` or `PolygonWorldID` to check whether everything works. +5. Start inserting identities into the + [`WorldIDIdentityManager`](https://github.com/worldcoin/world-id-contracts/blob/main/src/WorldIDIdentityManagerImplV1.sol) +6. Propagate roots from each StateBridge contract (`OpStateBridge` on Optimism and Base and `PolygonStateBridge`) to + their bridged World ID counterparts (`OpWorldID` on Base and Optimism and `PolygonWorldID`) by individually calling + `propagateRoot()` on each bridge contract using + [`state-bridge-relay`](https://github.com/worldcoin/state-bridge-relay) or any other method of calling the public + `propagateRoot()` functions on the respective state bridges. You can monitor `PolygonWorldID` and `OpWorldID` + (Optimism/Base) for root updates either using a block explorer or some other monitoring service (Tenderly, + OpenZeppelin Sentinel, DataDog, ...). +7. Try and create a proof and call `verifyProof` on `OpWorldID` (Optimism/Base) or `PolygonWorldID` to check whether + everything works. **Note:** Remember to call all functions that change state on these contracts via the owner address, which is the deployer address by default. @@ -123,15 +152,21 @@ Integration with full system: 1. Deploy [`world-id-contracts`](https://github.com/worldcoin/world-id-contracts) 2. Get deployment address for `WorldIDIdentityManager` -3. Deploy [`world-id-state-bridge`](https://github.com/worldcoin/world-id-state-bridge) by running `make deploy-testnet` +3. (optional) you can also use the `WorldIDIdentityManager` address that can be found in + [`docs/deployments.md`](./docs/deployments.md) to bridge existing roots. +4. Deploy [`world-id-state-bridge`](https://github.com/worldcoin/world-id-state-bridge) by running `make deploy-testnet` (requires 2.) -4. Get deployment address for `StateBridge` -5. Call - [`setStateBridge`](https://github.com/worldcoin/world-id-contracts/blob/5f0f56c22b916815eecc82eef877d141acd7e139/src/WorldIDIdentityManagerImplV1.sol#L682-L707) - on `WorldIDIdentityManager` with the output from 4. -6. Start inserting identities and monitor `PolygonWorldID` and `OpWorldID` for root updates (using - [`signup-sequencer`](https://github.com/worldcoin/signup-sequencer)) -7. Try and create a proof and call `verifyProof` on `OpWorldID` or `PolygonWorldID` to check whether everything works. +5. Start inserting identities into the + [`WorldIDIdentityManager`](https://github.com/worldcoin/world-id-contracts/blob/main/src/WorldIDIdentityManagerImplV1.sol) +6. Propagate roots from each StateBridge contract (`OpStateBridge` on Optimism and Base and `PolygonStateBridge`) to + their bridged World ID counterparts (`OpWorldID` on Base and Optimism and `PolygonWorldID`) by individually calling + `propagateRoot()` on each bridge contract using + [`state-bridge-relay`](https://github.com/worldcoin/state-bridge-relay) or any other method of calling the public + `propagateRoot()` functions on the respective state bridges. You can monitor `PolygonWorldID` and `OpWorldID` + (Optimism/Base) for root updates either using a block explorer or some other monitoring service (Tenderly, + OpenZeppelin Sentinel, DataDog, ...). +7. Try and create a proof and call `verifyProof` on `OpWorldID` (Optimism/Base) or `PolygonWorldID` to check whether + everything works. **Note:** Remember to call all functions that change state on these contracts via the owner address, which is the deployer address by default. @@ -140,10 +175,27 @@ deployer address by default. Deploy the WorldID state bridge and a mock WorldID identity manager to the Goerli testnet for integration tests. -```sh +```bash +# to do a mock of WorlIDIdentityManager and test bridge contracts on Goerli make mock ``` +#### Local Mock + +For local mock tests use localhost:8545 as the RPC URL (or just hit enter to use it by default) and use any non-empty +string as the Etherscan API key (or just hit enter to use a placeholder by default). + +Run a local anvil instance to deploy the contracts locally: + +```bash +anvil --mnemonic --network goerli --deploy +``` + +```bash +# meant for local testing, deploys mock contracts to localhost +make local-mock +``` + ## Credits This repo uses Paul Razvan Berg's [foundry template](https://github.com/paulrberg/foundry-template/): A Foundry-based diff --git a/docs/deployments.md b/docs/deployments.md index 9d5383a4..065df7e9 100644 --- a/docs/deployments.md +++ b/docs/deployments.md @@ -1,8 +1,144 @@ -# Addresses of the production and staging deployments +# Deployment Guide -## Production +The deployment logic is implemented in [`src/script/deploy.js`](../src/script/deploy.js). The script is meant to be run +via the `make deploy`, `make deploy-testnet`, `make-mock` and `make-local-mock` commands. Here is a description of what +each of the deployment commands do and which forge scripts are executed: -### Orb (group id: 1) +- `make deploy` (`deploymentMainnet()` in `deploy.js`): Deploys the contracts to the production networks (Ethereum + mainnet, Optimism mainnet, Base mainnet and Polygon PoS mainnet). The script executes the following: + + - generates the `src/script/.deploy-config.json` file with the deployment configuration + - asks the user for a deployer address private key + - asks for Ethereum, Optimism, Base and Polygon PoS RPC URLs + - asks For [Ethereum](https://etherscan.io/login), [Optimism](https://optimistic.etherscan.io/login), + [Base](https://basescan.org/login) and [Polygon PoS](https://polygonscan.com/login) Etherscan API keys for smart + contract verification (uses `forge script` automatic verification on deployment) + - asks for the WorldIDIdentityManager tree depth (currently hardcoded to 30 in `DeployOpWorldID` and + `DeployPolygonWorldID` forge scripts found in `src/script/deploy`) + - saves the above values to `.deploy-config.json` + - deploys `OpWorldID.sol` to Optimism mainnet + ([DeployOpWorldID.s.sol](../src/script/deploy/op-stack/DeployOpWorldID.s.sol)) + - deploys `OpWorldID.sol` to Base mainnet + ([DeployOpWorldID.s.sol](../src/script/deploy/op-stack/DeployOpWorldID.s.sol)) + - deploys `PolygonWorldID.sol` to Polygon PoS mainnet + ([DeployPolygonWorldIDMainnet.s.sol](../src/script/deploy/polygon/DeployPolygonWorldIDMainnet.s.sol)) + - prompts the user for the `WorldIDIdentityManager` address + - asks for the deployment address of the `OpWorldID` contract on Optimism mainnet + - asks for the deployment address of the `OpWorldID` contract on Base mainnet + - asks for the deployment address of the `PolygonWorldID` contract on Polygon PoS mainnet + - saves the above values to `.deploy-config.json` + - deploys `PolygonStateBridge.sol` on Ethereum mainnet + ([DeployPolygonStateBridgeMainnet.s.sol](../src/script/deploy/polygon/DeployPolygonStateBridgeMainnet.s.sol)) + - deploys `OpStateBridge.sol` (Optimism) on Ethereum mainnet + ([DeployOptimismStateBridgeMainnet.s.sol](../src/script/deploy/op-stack/optimism/DeployOptimismStateBridgeMainnet.s.sol)) + - deploys `OpStateBridge.sol` (Base) on Ethereum mainnet + ([DeployBaseStateBridgeMainnet.s.sol](../src/script/deploy/op-stack/base/DeployBaseStateBridgeMainnet.s.sol)) + - asks the user for the `OpStateBridge.sol` (Optimism) contract address + - asks the user for the `OpStateBridge.sol` (Base) contract address + - asks the user for the `PolygonStateBridge.sol` contract address + - saves the above values to `.deploy-config.json` + - initializes `PolygonWorldID.sol` with the `PolygonStateBridge.sol` contract address + ([InitializePolygonWorldID.s.sol](../src/script/initialize/polygon/InitializePolygonWorldID.s.sol)) + - transfers ownership of `OpWorldID.sol` on Optimism mainnet to the `OpStateBridge.sol` (Optimism) contract on + Ethereum mainnet using the `transferOwnership()` method in + [`CrossDomainOwnable3.sol`](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/L2/CrossDomainOwnable3.sol) + ([TransferOpWorldIDOwnership.s.sol](../src/script/initialize/op-stack/optimism/LocalTransferOwnershipofOptimismWorldID.s.sol)) + - transfers ownership of `OpWorldID.sol` on Base mainnet to the `OpStateBridge.sol` (Base) contract on Ethereum + mainnet using the `transferOwnership()` method in + [`CrossDomainOwnable3.sol`](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/L2/CrossDomainOwnable3.sol) + ([TransferOpWorldIDOwnership.s.sol](../src/script/initialize/op-stack/base/LocalTransferOwnershipofBaseWorldID.s.sol)) + +- `make deploy-testnet` (`deploymentTestnet()` in `deploy.js`): Deploys the contracts to the production networks + (Ethereum Goerli, Optimism Goerli, Base Goerli and Polygon PoS Mumbai). The script executes the following: + + - generates the `src/script/.deploy-config.json` file with the deployment configuration + - asks the user for a deployer address private key + - asks for Ethereum Goerli, Optimism Goerli, Base Goerli and Polygon PoS (Mumbai) RPC URLs + - asks For [Ethereum](https://etherscan.io/login), [Optimism](https://optimistic.etherscan.io/login), + [Base](https://basescan.org/login) and [Polygon PoS](https://polygonscan.com/login) Etherscan API keys for smart + contract verification (uses `forge script` automatic verification on deployment) + - asks for the WorldIDIdentityManager tree depth (currently hardcoded to 30 in `DeployOpWorldID` and + `DeployPolygonWorldID` forge scripts found in `src/script/deploy`) + - saves the above values to `.deploy-config.json` + - deploys `OpWorldID.sol` to Optimism Goerli + ([DeployOpWorldID.s.sol](../src/script/deploy/op-stack/DeployOpWorldID.s.sol)) + - deploys `OpWorldID.sol` to Base Goerli + ([DeployOpWorldID.s.sol](../src/script/deploy/op-stack/DeployOpWorldID.s.sol)) + - deploys `PolygonWorldID.sol` to Polygon PoS Goerli + ([DeployPolygonWorldIDMumbai.s.sol](../src/script/deploy/polygon/DeployPolygonWorldIDMumbai.s.sol)) + - prompts the user for the `WorldIDIdentityManager` address + - asks for the deployment address of the `OpWorldID` contract on Optimism Goerli + - asks for the deployment address of the `OpWorldID` contract on Base Goerli + - asks for the deployment address of the `PolygonWorldID` contract on Polygon PoS Mumbai + - saves the above values to `.deploy-config.json` + - deploys `PolygonStateBridge.sol` on Ethereum Goerli + ([DeployPolygonStateBridgeGoerli.s.sol](../src/script/deploy/polygon/DeployPolygonStateBridgeGoerli.s.sol)) + - deploys `OpStateBridge.sol` (Optimism) on Ethereum Goerli + ([DeployOptimismStateBridgeGoerli.s.sol](../src/script/deploy/op-stack/optimism/DeployOptimismStateBridgeGoerli.s.sol)) + - deploys `OpStateBridge.sol` (Base) on Ethereum Goerli + ([DeployBaseStateBridgeGoerli.s.sol](../src/script/deploy/op-stack/base/DeployBaseStateBridgeGoerli.s.sol)) + - asks the user for the `OpStateBridge.sol` (Optimism) contract address + - asks the user for the `OpStateBridge.sol` (Base) contract address + - asks the user for the `PolygonStateBridge.sol` contract address + - saves the above values to `.deploy-config.json` + - initializes `PolygonWorldID.sol` with the `PolygonStateBridge.sol` contract address + ([InitializePolygonWorldID.s.sol](../src/script/initialize/polygon/InitializePolygonWorldID.s.sol)) + - transfers ownership of `OpWorldID.sol` on Optimism Goerli to the `OpStateBridge.sol` (Optimism) contract on Ethereum + Goerli using the `transferOwnership()` method in + [`CrossDomainOwnable3.sol`](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/L2/CrossDomainOwnable3.sol) + ([TransferOpWorldIDOwnership.s.sol](../src/script/initialize/op-stack/optimism/LocalTransferOwnershipofOptimismWorldID.s.sol)) + - transfers ownership of `OpWorldID.sol` on Base Goerli to the `OpStateBridge.sol` (Base) contract on Ethereum Goerli + using the `transferOwnership()` method in + [`CrossDomainOwnable3.sol`](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/L2/CrossDomainOwnable3.sol) + ([TransferOpWorldIDOwnership.s.sol](../src/script/initialize/op-stack/base/LocalTransferOwnershipofBaseWorldID.s.sol)) + +- `make mock` (`mockDeploymenth()` in `deploy.js`): Deploys the contracts to Ethereum Goerli and simulates the + `WorldIdIdentityManager` contract. + + - same as `make deploy-testnet`, but also deploys a mock `WorldIDIdentityManager` contract to the Goerli testnet + ([DeployMockWorldID.s.sol](../src/script/deploy/mock/DeployMockWorldID.s.sol)) + - can be used to test all the bridges and do integration tests as the mock will have a `sampleRoot` to propagate to + all the supported chains using the state bridges by exposing the `latestRoot()` method to query a root the same way + that `WorldIDIdentityManger` does. + +- `make local-mock` (`mockLocalDeployment()` in `deploy.js`): Deploys the contracts to a local anvil instance, mocks the + state bridge and the `WorldIDIdentityManager` contracts and tests the interface for propagating roots to a different + chain by mocking cross-chain messaging and just keeping the right interfaces. Good for local development and testing. + + - when prompted for RPC URLs and Etherscan API keys, leave the field empty and press empty to use the default values + (anvil's localhost:8545 endpoint for the RPC URL and a placeholder string for the Etherscan API key) + - before running this script do `anvil --mnemonic ` and make sure that you are using one of the private keys + provided by `anvil` for the script when being promted on the CLI. + - good to test the generic function interface of the state bridges, the `WorldIDIdentityManager` contract, and the + target chain World ID contract (`PolygonWorldID` / `OpWorldID`). + - deploys a `WorldIDIdentityManagerMock.sol` contract to the local anvil instance + ([DeployMockWorldID.s.sol](../src/script/deploy/mock/DeployMockWorldID.s.sol)) + - inserts a sample root to the contract on deploy time (constructor param in the script above) + - deploys a `MockStateBridge.sol` contract to the local anvil instance + ([DeployMockStateBridge.s.sol](../src/script/deploy/mock/DeployMockStateBridge.s.sol)) + - propagates the sample root using `MockStateBridge.sol` contract to a `MockBridgedWorldID.sol` contract on the local + anvil instance ([PropagateMockRoot.s.sol](../src/script/test/PropagateMockRoot.s.sol)) + +- `make set-op-gas-limit` (`setOpGasLimit` in `deploy.js`): Sets the gas limit for the `OpStateBridge.sol` contract on + Ethereum mainnet/testnet. The gas limit purchased is for OP Stack L2 execution and calldata gas and is currently set + to 100k gas, but can be changed if needed. + + - asks the user for the deployer private key (needs to be the owner of the `OpStateBridge.sol` contracts (Base and + Optimism)) + - asks for the Ethereum RPC URL + - asks for the `OpStateBridge.sol` (Optimism) contract address + - asks for the `OpStateBridge.sol` (Base) contract address + - asks for the new gas limits for different actions + - sets the OP-stack `CrossDomainMessenger` gas limits for the `OpStateBridge.sol` contract on Ethereum mainnet/testnet + using the `setGasLimitSendRoot()`, `setGasLimitSetRootHistoryExpiry()`, and`setGasLimitTransferOwnershipOp()` + methods. + +## Addresses of the production and staging deployments + +### Orb - production on Ethereum mainnet (group id: 1) + +Note: There is an upgrade to the production state bridge architecture currently running on staging which will be +performed once an audit of the changes are complete. - WorldID State Bridge (Ethereum mainnet): [0x86d26ed31556ea7694bd0cc4e674d7526f70511a](https://etherscan.io/address/0x86d26ed31556ea7694bd0cc4e674d7526f70511a#code) @@ -11,14 +147,17 @@ - PolygonWorldID (Polygon PoS mainnet): [0x2Ad412A1dF96434Eed0779D2dB4A8694a06132f8](https://polygonscan.com/address/0x2Ad412A1dF96434Eed0779D2dB4A8694a06132f8#code) -## Staging +### Orb - staging on Ethereum Goerli (group id: 1) -### Orb (group id: 1) - -- WorldID State Bridge (Ethereum Goerli): - [0x316eb4c12edb9bb9e8721663444bb9818e6fab67](https://goerli.etherscan.io/address/0x316eb4c12edb9bb9e8721663444bb9818e6fab67#code) -- OpWorldID (Optimism Goerli): - [0x047ee5313f98e26cc8177fa38877cb36292d2364](https://goerli-optimism.etherscan.io/address/0x047ee5313f98e26cc8177fa38877cb36292d2364#code) +- Polygon State Bridge (Ethereum Goerli): + [0x42Af76FB754ea23769Ad337bBC4456FD0893552f](https://goerli.etherscan.io/address/0x42Af76FB754ea23769Ad337bBC4456FD0893552f#code) +- Optimism State Bridge (Ethereum Goerli): + [0x7acdc12cbcba53e1ea2206844d0a8ccb6f3b08fb](https://goerli.etherscan.io/address/0x7acdc12cbcba53e1ea2206844d0a8ccb6f3b08fb#code) +- Base State Bridge (Ethereum Goerli): + [0x39911b3242e952d86270857bc8efc3fce8d84abe](https://goerli.etherscan.io/address/0x39911b3242e952d86270857bc8efc3fce8d84abe#code) - PolygonWorldID (Polygon Mumbai): - [0x047eE5313F98E26Cc8177fA38877cB36292D2364](https://mumbai.polygonscan.com/address/0x047eE5313F98E26Cc8177fA38877cB36292D2364#code) -- BaseWorldID (Base Goerli): [0x047ee5313f98e26cc8177fa38877cb36292d2364](https://goerli.basescan.org/address/0x047ee5313f98e26cc8177fa38877cb36292d2364#code) + [0xB3E7771a6e2d7DD8C0666042B7a07C39b938eb7d](https://mumbai.polygonscan.com/address/0xB3E7771a6e2d7DD8C0666042B7a07C39b938eb7d#code) +- OpWorldID (Optimism Goerli): + [0x316350d3ec608ffc30b01dcb7475de1c676ce910](https://goerli-optimism.etherscan.io/address/0x316350d3ec608ffc30b01dcb7475de1c676ce910#code) +- OpWorldID (Base Goerli): + [0xa3cd15ebed6075e33a54483c59818bc43d57c556](https://goerli.basescan.org/address/0xa3cd15ebed6075e33a54483c59818bc43d57c556#code) diff --git a/docs/spec.md b/docs/spec.md index 39c4f79b..d617bb46 100644 --- a/docs/spec.md +++ b/docs/spec.md @@ -3,30 +3,26 @@ ![state-bridge.svg](state-bridge.svg) Propagates new World ID merkle tree roots from the `WorldIDIdentityManager` contract -([`world-id-contracts`](https://github.com/worldcoin/world-id-contracts) repo) on Ethereum mainnet to Optimism and -Polygon PoS. - -Currently the state bridge supports Optimism and Polygon PoS. The root and the timestamp get sent from -[`StateBridge.sol`](../src/StateBridge.sol) to the [`OpWorldID.sol`](../src/OpWorldID.sol) contract on the Optimism L2 -which accepts them in order to allow developers on Optimism to call `verifyProof` on [WorldID](https://id.worldcoin.org) -actions. The same applies for the [`PolygonWorldID.sol`](../src/PolygonWorldID.sol) contract. Both of these contracts -have a `require()` statement which checks whether the `StateBridge.sol` contract is the originator of the call to -`receiveRoot()`. This means that only the `StateBridge.sol` contract is able to push new roots to the target WorldID -contracts on Optimism and Polygon PoS. In the future, this mechanism will be replaced by introducing a public method -with a storage proof verifier that will verify the latest `eth_getProof` storage proof on the target network. +([`world-id-contracts`](https://github.com/worldcoin/world-id-contracts) repo) on Ethereum mainnet to Optimism, Base and +Polygon PoS (there is also a staging environment on Goerli testnets, more info in [deployments.md](./deployments.md)). + +Currently there are 3 different state bridges which support Optimism, Base and Polygon PoS respectively. World ID merkle +tree roots are queried from the `WorldIDIdentityManager` contracts using the `latestRoot()` public method and propagated +to their respective chain using the `propagateRoot()` method. ## Polygon PoS state transfer infrastructure -In order to bridge state from Ethereum to Polygon the `world-id-state-bridge` is currently using the -[FxPortal contracts](https://wiki.polygon.technology/docs/develop/l1-l2-communication/fx-portal/). It takes about 20 -minutes to sync the state from the state bridge to the `PolygonWorldID.sol` contract and about 1 hour to checkpoint said -state to the Polygon bridge on L1. +In order to bridge state from Ethereum to Polygon the `PolygonStateBridge.sol` contract is currently using the +[FxPortal contracts](https://wiki.polygon.technology/docs/pos/design/bridge/l1-l2-communication/fx-portal/). It takes +about 20-40 minutes to sync the state from the state bridge to the `PolygonWorldID.sol` contract and about 1 hour to +checkpoint said state to the Polygon bridge on L1. ## Optimism L1<>L2 infrastructure -The `StateBridge.sol` uses Optimism's native bridge contract and `L2Messenger` to relay messages from L1 to L2. A guide -can be found in the [Optimism documentation](https://community.optimism.io/docs/developers/bridge/messaging/) to learn -more about how this mechanism works. +The `OpStateBridge.sol` contract uses the OP Stack chain native bridge contract `L1CrossDomainMessenger` and +`L2CrossDomainMessenger` to relay messages from L1 to L2. A guide can be found in the +[Optimism documentation](https://community.optimism.io/docs/developers/bridge/messaging/) to learn more about how this +mechanism works. Assumption: Optimism Bridge currently relies on OP labs submitting output commitments, however they are working on making it fully permissionless so that anyone can submit their own output commitment. The L2 node is used to fetch the diff --git a/docs/state-bridge.svg b/docs/state-bridge.svg index 552fe7c1..1907e057 100644 --- a/docs/state-bridge.svg +++ b/docs/state-bridge.svg @@ -1,772 +1,17 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + World ID State BridgeWorldIDIdentityManager(0 - phone)(1 - orb)OpStateBridge (Optimism)OpWorldIDPolygonWorldIDreceiveRoot()receiveRoot()OpStateBridge (Base)PolygonStateBridgelatestRoot()latestRoot()latestRoot()OpWorldIDreceiveRoot()propagateRoot()propagateRoot()propagateRoot() \ No newline at end of file diff --git a/docs/state-bridge2.svg b/docs/state-bridge2.svg deleted file mode 100644 index 4600d82c..00000000 --- a/docs/state-bridge2.svg +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - World ID State Bridgeonce registerIdentities() inserts a new batch of identity commitments into the WorldIDIdentityManagerWorldIDIdentityManager(0 - phone)(1 - orb)StateBridgeOpWorldIDPolygonWorldIDsendRootMultichain()_sendRootToOptimism()_sendRootToPolygon()receiveRoot()receiveRoot() \ No newline at end of file diff --git a/src/OpStateBridge.sol b/src/OpStateBridge.sol new file mode 100644 index 00000000..29584577 --- /dev/null +++ b/src/OpStateBridge.sol @@ -0,0 +1,204 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.15; + +// Optimism interface for cross domain messaging +import {ICrossDomainMessenger} from + "@eth-optimism/contracts/libraries/bridge/ICrossDomainMessenger.sol"; +import {IOpWorldID} from "./interfaces/IOpWorldID.sol"; +import {IRootHistory} from "./interfaces/IRootHistory.sol"; +import {IWorldIDIdentityManager} from "./interfaces/IWorldIDIdentityManager.sol"; +import {Ownable2Step} from "openzeppelin-contracts/access/Ownable2Step.sol"; +import {ICrossDomainOwnable3} from "./interfaces/ICrossDomainOwnable3.sol"; + +/// @title World ID State Bridge Optimism +/// @author Worldcoin +/// @notice Distributes new World ID Identity Manager roots to an OP Stack network +/// @dev This contract lives on Ethereum mainnet and works for Optimism and any OP Stack based chain +/// @dev Ownable2Step allows for transferOwnership to the zero address +contract OpStateBridge is Ownable2Step { + /////////////////////////////////////////////////////////////////// + /// STORAGE /// + /////////////////////////////////////////////////////////////////// + + /// @notice The address of the OpWorldID contract on any OP Stack chain + address public immutable opWorldIDAddress; + + /// @notice address for OP Stack chain Ethereum mainnet L1CrossDomainMessenger contract + address internal immutable crossDomainMessengerAddress; + + /// @notice Ethereum mainnet worldID Address + address public immutable worldIDAddress; + + /// @notice Amount of gas purchased on the OP Stack chain for _propagateRootOptimism + uint32 internal _gasLimitSendRoot; + + /// @notice Amount of gas purchased on the OP Stack chain for SetRootHistoryExpirytimism + uint32 internal _gasLimitSetRootHistoryExpiry; + + /// @notice Amount of gas purchased on the OP Stack chain for transferOwnershipOptimism + uint32 internal _gasLimitTransferOwnership; + + /////////////////////////////////////////////////////////////////// + /// EVENTS /// + /////////////////////////////////////////////////////////////////// + + /// @notice Emmitted when the the StateBridge gives ownership of the OPWorldID contract + /// to the WorldID Identity Manager contract away + /// @param previousOwner The previous owner of the OPWorldID contract + /// @param newOwner The new owner of the OPWorldID contract + /// @param isLocal Whether the ownership transfer is local (Optimism/OP Stack chain EOA/contract) + /// or an Ethereum EOA or contract + event OwnershipTransferredOp( + address indexed previousOwner, address indexed newOwner, bool isLocal + ); + + /// @notice Emmitted when the the StateBridge sends a root to the OPWorldID contract + /// @param root The root sent to the OPWorldID contract on the OP Stack chain + event RootPropagated(uint256 root); + + /// @notice Emmitted when the the StateBridge sets the root history expiry for OpWorldID and PolygonWorldID + /// @param rootHistoryExpiry The new root history expiry + event SetRootHistoryExpiry(uint256 rootHistoryExpiry); + + /// @notice Emmitted when the the StateBridge sets the gas limit for sendRootOp + /// @param _opGasLimit The new opGasLimit for sendRootOp + event SetGasLimitSendRoot(uint32 _opGasLimit); + + /// @notice Emmitted when the the StateBridge sets the gas limit for SetRootHistoryExpiryt + /// @param _opGasLimit The new opGasLimit for SetRootHistoryExpirytimism + event SetGasLimitSetRootHistoryExpiry(uint32 _opGasLimit); + + /// @notice Emmitted when the the StateBridge sets the gas limit for transferOwnershipOp + /// @param _opGasLimit The new opGasLimit for transferOwnershipOptimism + event SetGasLimitTransferOwnershipOp(uint32 _opGasLimit); + + /////////////////////////////////////////////////////////////////// + /// ERRORS /// + /////////////////////////////////////////////////////////////////// + + /// @notice Emitted when an attempt is made to renounce ownership. + error CannotRenounceOwnership(); + + /////////////////////////////////////////////////////////////////// + /// CONSTRUCTOR /// + /////////////////////////////////////////////////////////////////// + + /// @notice constructor + /// @param _worldIDIdentityManager Deployment address of the WorldID Identity Manager contract + /// @param _opWorldIDAddress Address of the Optimism contract that will receive the new root and timestamp + /// @param _crossDomainMessenger L1CrossDomainMessenger contract used to communicate with the desired OP + /// Stack network + constructor( + address _worldIDIdentityManager, + address _opWorldIDAddress, + address _crossDomainMessenger + ) { + opWorldIDAddress = _opWorldIDAddress; + worldIDAddress = _worldIDIdentityManager; + crossDomainMessengerAddress = _crossDomainMessenger; + _gasLimitSendRoot = 100000; + _gasLimitSetRootHistoryExpiry = 100000; + _gasLimitTransferOwnership = 100000; + } + + /////////////////////////////////////////////////////////////////// + /// PUBLIC API /// + /////////////////////////////////////////////////////////////////// + + /// @notice Sends the latest WorldID Identity Manager root to the IOpStack. + /// @dev Calls this method on the L1 Proxy contract to relay roots to the destination OP Stack chain + function propagateRoot() external { + uint256 latestRoot = IWorldIDIdentityManager(worldIDAddress).latestRoot(); + + // The `encodeCall` function is strongly typed, so this checks that we are passing the + // correct data to the optimism bridge. + bytes memory message = abi.encodeCall(IOpWorldID.receiveRoot, (latestRoot)); + + ICrossDomainMessenger(crossDomainMessengerAddress).sendMessage( + // Contract address on the OP Stack Chain + opWorldIDAddress, + message, + _gasLimitSendRoot + ); + + emit RootPropagated(latestRoot); + } + + /// @notice Adds functionality to the StateBridge to transfer ownership + /// of OpWorldID to another contract on L1 or to a local OP Stack chain EOA + /// @param _owner new owner (EOA or contract) + /// @param _isLocal true if new owner is on Optimism, false if it is a cross-domain owner + function transferOwnershipOp(address _owner, bool _isLocal) external onlyOwner { + // The `encodeCall` function is strongly typed, so this checks that we are passing the + // correct data to the OP Stack chain bridge. + bytes memory message = + abi.encodeCall(ICrossDomainOwnable3.transferOwnership, (_owner, _isLocal)); + + ICrossDomainMessenger(crossDomainMessengerAddress).sendMessage( + // Contract address on the OP Stack Chain + opWorldIDAddress, + message, + _gasLimitTransferOwnership + ); + + emit OwnershipTransferredOp(owner(), _owner, _isLocal); + } + + /// @notice Adds functionality to the StateBridge to set the root history expiry on OpWorldID + /// @param _rootHistoryExpiry new root history expiry + function setRootHistoryExpiry(uint256 _rootHistoryExpiry) external onlyOwner { + // The `encodeCall` function is strongly typed, so this checks that we are passing the + // correct data to the optimism bridge. + bytes memory message = + abi.encodeCall(IRootHistory.setRootHistoryExpiry, (_rootHistoryExpiry)); + + ICrossDomainMessenger(crossDomainMessengerAddress).sendMessage( + // Contract address on the OP Stack Chain + opWorldIDAddress, + message, + _gasLimitSetRootHistoryExpiry + ); + + emit SetRootHistoryExpiry(_rootHistoryExpiry); + } + + /////////////////////////////////////////////////////////////////// + /// OP GAS LIMIT /// + /////////////////////////////////////////////////////////////////// + + /// @notice Sets the gas limit for the sendRootOp method + /// @param _opGasLimit The new gas limit for the sendRootOp method + function setGasLimitSendRoot(uint32 _opGasLimit) external onlyOwner { + _gasLimitSetRootHistoryExpiry = _opGasLimit; + + emit SetGasLimitSendRoot(_opGasLimit); + } + + /// @notice Sets the gas limit for the SetRootHistoryExpiry method + /// @param _opGasLimit The new gas limit for the SetRootHistoryExpiry method + function setGasLimitSetRootHistoryExpiry(uint32 _opGasLimit) external onlyOwner { + _gasLimitSetRootHistoryExpiry = _opGasLimit; + + emit SetGasLimitSetRootHistoryExpiry(_opGasLimit); + } + + /// @notice Sets the gas limit for the transferOwnershipOp method + /// @param _opGasLimit The new gas limit for the transferOwnershipOp method + function setGasLimitTransferOwnershipOp(uint32 _opGasLimit) external onlyOwner { + _gasLimitTransferOwnership = _opGasLimit; + + emit SetGasLimitTransferOwnershipOp(_opGasLimit); + } + + /////////////////////////////////////////////////////////////////// + /// OWNERSHIP /// + /////////////////////////////////////////////////////////////////// + /// @notice Ensures that ownership of WorldID implementations cannot be renounced. + /// @dev This function is intentionally not `virtual` as we do not want it to be possible to + /// renounce ownership for any WorldID implementation. + /// @dev This function is marked as `onlyOwner` to maintain the access restriction from the base + /// contract. + function renounceOwnership() public view override onlyOwner { + revert CannotRenounceOwnership(); + } +} diff --git a/src/OpWorldID.sol b/src/OpWorldID.sol index 16e1029c..3151853c 100644 --- a/src/OpWorldID.sol +++ b/src/OpWorldID.sol @@ -32,18 +32,16 @@ contract OpWorldID is WorldIDBridge, CrossDomainOwnable3, IOpWorldID { /// @dev This function can revert if Optimism's CrossDomainMessenger stops processing proofs /// or if OPLabs stops submitting them. Next iteration of Optimism's cross-domain messaging, will be /// fully permissionless for message-passing, so this will not be an issue. - /// Sequencer needs to include changes to the CrossDomainMessenger contract on L1, not economically penalized - /// if messages are not included, however the fraud prover (Cannon) can force the sequencer to include it. + /// Sequencer needs to include changes to the CrossDomainMessenger contract on L1, + /// not economically penalized if messages are not included, however the fraud prover (Cannon) + /// can force the sequencer to include it. /// /// @param newRoot The value of the new root. - /// @param supersedeTimestamp The value of the L1 timestamp at the time that `newRoot` became - /// the current root. This timestamp is associated with the latest root at the time of - /// the call being inserted into the root history. /// /// @custom:reverts CannotOverwriteRoot If the root already exists in the root history. /// @custom:reverts string If the caller is not the owner. - function receiveRoot(uint256 newRoot, uint128 supersedeTimestamp) external virtual onlyOwner { - _receiveRoot(newRoot, supersedeTimestamp); + function receiveRoot(uint256 newRoot) external virtual onlyOwner { + _receiveRoot(newRoot); } /////////////////////////////////////////////////////////////////////////////// diff --git a/src/PolygonStateBridge.sol b/src/PolygonStateBridge.sol new file mode 100644 index 00000000..a462225e --- /dev/null +++ b/src/PolygonStateBridge.sol @@ -0,0 +1,119 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.15; + +// Optimism interface for cross domain messaging +import {IPolygonWorldID} from "./interfaces/IPolygonWorldID.sol"; +import {IWorldIDIdentityManager} from "./interfaces/IWorldIDIdentityManager.sol"; +import {IRootHistory} from "./interfaces/IRootHistory.sol"; +import {Ownable2Step} from "openzeppelin-contracts/access/Ownable2Step.sol"; +import {FxBaseRootTunnel} from "fx-portal/contracts/tunnel/FxBaseRootTunnel.sol"; + +/// @title Polygon World ID State Bridge +/// @author Worldcoin +/// @notice Distributes new World ID Identity Manager roots to World ID supported networks +/// @dev This contract lives on Ethereum mainnet and is called by the World ID Identity Manager contract +/// in the registerIdentities method +/// @dev Ownable2Step allows for transferOwnership to the zero address +contract PolygonStateBridge is FxBaseRootTunnel, Ownable2Step { + /////////////////////////////////////////////////////////////////// + /// STORAGE /// + /////////////////////////////////////////////////////////////////// + + /// @notice WorldID Identity Manager contract + IWorldIDIdentityManager public immutable worldID; + + /////////////////////////////////////////////////////////////////// + /// EVENTS /// + /////////////////////////////////////////////////////////////////// + + /// @notice Emmitted when the the StateBridge sets the root history expiry for OpWorldID and PolygonWorldID + /// @param rootHistoryExpiry The new root history expiry + event SetRootHistoryExpiry(uint256 rootHistoryExpiry); + + /// @notice Emmitted when a root is sent to PolygonWorldID + /// @param root The latest WorldID Identity Manager root. + event RootPropagated(uint256 root); + + /////////////////////////////////////////////////////////////////// + /// ERRORS /// + /////////////////////////////////////////////////////////////////// + + /// @notice Emitted when an attempt is made to renounce ownership. + error CannotRenounceOwnership(); + + /////////////////////////////////////////////////////////////////// + /// CONSTRUCTOR /// + /////////////////////////////////////////////////////////////////// + + /// @notice constructor + /// @param _checkpointManager address of the checkpoint manager contract + /// @param _fxRoot address of Polygon's fxRoot contract, part of the FxPortal bridge (Goerli or Mainnet) + /// @param _worldIDIdentityManager Deployment address of the WorldID Identity Manager contract + constructor(address _checkpointManager, address _fxRoot, address _worldIDIdentityManager) + FxBaseRootTunnel(_checkpointManager, _fxRoot) + { + worldID = IWorldIDIdentityManager(_worldIDIdentityManager); + } + + /////////////////////////////////////////////////////////////////// + /// PUBLIC API /// + /////////////////////////////////////////////////////////////////// + + /// @notice Sends the latest WorldIDIdentityManager root + /// to Polygon's StateChild contract (PolygonWorldID) + function propagateRoot() external { + uint256 latestRoot = worldID.latestRoot(); + + bytes memory message = abi.encodeCall(IPolygonWorldID.receiveRoot, (latestRoot)); + + /// @notice FxBaseRootTunnel method to send bytes payload to FxBaseChildTunnel contract + _sendMessageToChild(message); + + emit RootPropagated(latestRoot); + } + + /// @notice Sets the root history expiry for PolygonWorldID + /// @param _rootHistoryExpiry The new root history expiry + function setRootHistoryExpiryPolygon(uint256 _rootHistoryExpiry) external onlyOwner { + bytes memory message = + abi.encodeCall(IRootHistory.setRootHistoryExpiry, (_rootHistoryExpiry)); + + /// @notice FxBaseRootTunnel method to send bytes payload to FxBaseChildTunnel contract + _sendMessageToChild(message); + + emit SetRootHistoryExpiry(_rootHistoryExpiry); + } + + /// @notice boilerplate function to satisfy FxBaseRootTunnel inheritance (not going to be used) + function _processMessageFromChild(bytes memory) internal override { + /// WorldID 🌎🆔 State Bridge + } + + /////////////////////////////////////////////////////////////////////////////// + /// ADDRESS MANAGEMENT /// + /////////////////////////////////////////////////////////////////////////////// + + /// @notice Sets the `fxChildTunnel` address if not already set. + /// @dev This implementation replicates the logic from `FxBaseRootTunnel` due to the inability + /// to call `external` superclass methods when overriding them. + /// + /// @param _fxChildTunnel The address of the child (non-L1) tunnel contract. + /// + /// @custom:reverts string If the root tunnel has already been set. + function setFxChildTunnel(address _fxChildTunnel) public virtual override onlyOwner { + require(fxChildTunnel == address(0x0), "FxBaseRootTunnel: CHILD_TUNNEL_ALREADY_SET"); + fxChildTunnel = _fxChildTunnel; + } + + /////////////////////////////////////////////////////////////////// + /// OWNERSHIP /// + /////////////////////////////////////////////////////////////////// + /// @notice Ensures that ownership of WorldID implementations cannot be renounced. + /// @dev This function is intentionally not `virtual` as we do not want it to be possible to + /// renounce ownership for any WorldID implementation. + /// @dev This function is marked as `onlyOwner` to maintain the access restriction from the base + /// contract. + function renounceOwnership() public view override onlyOwner { + revert CannotRenounceOwnership(); + } +} diff --git a/src/PolygonWorldID.sol b/src/PolygonWorldID.sol index 6e1bd501..635d454f 100644 --- a/src/PolygonWorldID.sol +++ b/src/PolygonWorldID.sol @@ -13,6 +13,7 @@ import {BytesUtils} from "./utils/BytesUtils.sol"; /// @notice A contract that manages the root history of the WorldID merkle root on Polygon PoS. /// @dev This contract is deployed on Polygon PoS and is called by the StateBridge contract for each /// new root insertion. +/// @dev Ownable2Step allows for transferOwnership to the zero address contract PolygonWorldID is WorldIDBridge, FxBaseChildTunnel, Ownable2Step { /////////////////////////////////////////////////////////////////// /// STORAGE /// @@ -21,21 +22,22 @@ contract PolygonWorldID is WorldIDBridge, FxBaseChildTunnel, Ownable2Step { /// @notice The selector of the `receiveRoot` function. /// @dev this selector is precomputed in the constructor to not have to recompute them for every /// call of the _processMesageFromRoot function - bytes4 private receiveRootSelector; + bytes4 private immutable receiveRootSelector = bytes4(keccak256("receiveRoot(uint256)")); /// @notice The selector of the `receiveRootHistoryExpiry` function. /// @dev this selector is precomputed in the constructor to not have to recompute them for every /// call of the _processMesageFromRoot function - bytes4 private receiveRootHistoryExpirySelector; + bytes4 private immutable receiveRootHistoryExpirySelector = + bytes4(keccak256("setRootHistoryExpiry(uint256)")); /////////////////////////////////////////////////////////////////// /// ERRORS /// /////////////////////////////////////////////////////////////////// - /// @notice Thrown when the message selector passed from FxRoot is invalid. + /// @notice Emitted when the message selector passed from FxRoot is invalid. error InvalidMessageSelector(bytes4 selector); - /// @notice Thrown when an attempt is made to renounce ownership. + /// @notice Emitted when an attempt is made to renounce ownership. error CannotRenounceOwnership(); /////////////////////////////////////////////////////////////////////////////// @@ -50,10 +52,7 @@ contract PolygonWorldID is WorldIDBridge, FxBaseChildTunnel, Ownable2Step { constructor(uint8 _treeDepth, address _fxChild) WorldIDBridge(_treeDepth) FxBaseChildTunnel(_fxChild) - { - receiveRootSelector = bytes4(keccak256("receiveRoot(uint256,uint128)")); - receiveRootHistoryExpirySelector = bytes4(keccak256("setRootHistoryExpiry(uint256)")); - } + {} /////////////////////////////////////////////////////////////////////////////// /// ROOT MIRRORING /// @@ -82,8 +81,8 @@ contract PolygonWorldID is WorldIDBridge, FxBaseChildTunnel, Ownable2Step { bytes memory payload = BytesUtils.substring(message, 4, message.length - 4); if (selector == receiveRootSelector) { - (uint256 root, uint128 timestamp) = abi.decode(payload, (uint256, uint128)); - _receiveRoot(root, timestamp); + uint256 root = abi.decode(payload, (uint256)); + _receiveRoot(root); } else if (selector == receiveRootHistoryExpirySelector) { uint256 rootHistoryExpiry = abi.decode(payload, (uint256)); _setRootHistoryExpiry(rootHistoryExpiry); diff --git a/src/StateBridge.sol b/src/StateBridge.sol deleted file mode 100644 index 29f97acb..00000000 --- a/src/StateBridge.sol +++ /dev/null @@ -1,426 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.15; - -// Optimism interface for cross domain messaging -import {ICrossDomainMessenger} from - "@eth-optimism/contracts/libraries/bridge/ICrossDomainMessenger.sol"; -import {IOpWorldID} from "./interfaces/IOpWorldID.sol"; -import {IPolygonWorldID} from "./interfaces/IPolygonWorldID.sol"; -import {IRootHistory} from "./interfaces/IRootHistory.sol"; -import {Ownable2Step} from "openzeppelin-contracts/access/Ownable2Step.sol"; -import {ICrossDomainOwnable3} from "./interfaces/ICrossDomainOwnable3.sol"; -import {FxBaseRootTunnel} from "fx-portal/contracts/tunnel/FxBaseRootTunnel.sol"; - -/// @title World ID State Bridge -/// @author Worldcoin -/// @notice Distributes new World ID Identity Manager roots to World ID supported networks -/// @dev This contract lives on Ethereum mainnet and is called by the World ID Identity Manager contract -/// in the registerIdentities method -contract StateBridge is FxBaseRootTunnel, Ownable2Step { - /////////////////////////////////////////////////////////////////// - /// STORAGE /// - /////////////////////////////////////////////////////////////////// - - /// @notice The address of the OPWorldID contract on Optimism - address public immutable opWorldIDAddress; - - /// @notice address for Optimism's Ethereum mainnet L1CrossDomainMessenger contract - address internal immutable opCrossDomainMessengerAddress; - - /// @notice The address of the BaseWorldID contract on Base - address public immutable baseWorldIDAddress; - - /// @notice address for Base's Ethereum mainnet L1CrossDomainMessenger contract - address internal immutable baseCrossDomainMessengerAddress; - - /// @notice worldID Address - address public immutable worldIDAddress; - - /// @notice Amount of gas purchased on Optimism for _sendRootToOptimism - uint32 internal gasLimitSendRootOptimism; - - /// @notice Amount of gas purchased on Optimism for setRootHistoryExpiryOptimism - uint32 internal gasLimitSetRootHistoryExpiryOptimism; - - /// @notice Amount of gas purchased on Optimism for transferOwnershipOptimism - uint32 internal gasLimitTransferOwnershipOptimism; - - /// @notice Amount of gas purchased on Base for _sendRootToBase - uint32 internal gasLimitSendRootBase; - - /// @notice Amount of gas purchased on Base for setRootHistoryExpiryBase - uint32 internal gasLimitSetRootHistoryExpiryBase; - - /// @notice Amount of gas purchased on Base for transferOwnershipBase - uint32 internal gasLimitTransferOwnershipBase; - - /////////////////////////////////////////////////////////////////// - /// EVENTS /// - /////////////////////////////////////////////////////////////////// - - /// @notice Emmitted when the the StateBridge gives ownership of the OPWorldID contract - /// to the WorldID Identity Manager contract away - /// @param previousOwner The previous owner of the OPWorldID contract - /// @param newOwner The new owner of the OPWorldID contract - /// @param isLocal Whether the ownership transfer is local (Optimism EOA/contract) or an Ethereum EOA or contract - event OwnershipTransferredOptimism( - address indexed previousOwner, address indexed newOwner, bool isLocal - ); - - /// @notice Emmitted when the the StateBridge gives ownership of the OPWorldID contract - /// to the WorldID Identity Manager contract away - /// @param previousOwner The previous owner of the OPWorldID contract - /// @param newOwner The new owner of the OPWorldID contract - /// @param isLocal Whether the ownership transfer is local (Base EOA/contract) or an Ethereum EOA or contract - event OwnershipTransferredBase( - address indexed previousOwner, address indexed newOwner, bool isLocal - ); - - /// @notice Emmitted when the the StateBridge sets the root history expiry for OpWorldID and PolygonWorldID - /// @param rootHistoryExpiry The new root history expiry - event SetRootHistoryExpiry(uint256 rootHistoryExpiry); - - /// @notice Emmitted when a root is sent to OpWorldID and PolygonWorldID - /// @param root The latest WorldID Identity Manager root. - /// @param timestamp The Ethereum block timestamp of the latest WorldID Identity Manager root. - event RootSentMultichain(uint256 root, uint128 timestamp); - - /// @notice Emmitted when the the StateBridge sets the gas limit for sendRootOptimism - /// @param _opGasLimit The new opGasLimit for sendRootOptimism - event SetGasLimitSendRootOptimism(uint32 _opGasLimit); - - /// @notice Emmitted when the the StateBridge sets the gas limit for setRootHistoryExpiryOptimism - /// @param _opGasLimit The new opGasLimit for setRootHistoryExpiryOptimism - event SetGasLimitSetRootHistoryExpiryOptimism(uint32 _opGasLimit); - - /// @notice Emmitted when the the StateBridge sets the gas limit for transferOwnershipOptimism - /// @param _opGasLimit The new opGasLimit for transferOwnershipOptimism - event SetGasLimitTransferOwnershipOptimism(uint32 _opGasLimit); - - /// @notice Emmitted when the the StateBridge sets the gas limit for sendRootBase - /// @param _baseGasLimit The new baseGasLimit for sendRootBase - event SetGasLimitSendRootBase(uint32 _baseGasLimit); - - /// @notice Emmitted when the the StateBridge sets the gas limit for setRootHistoryExpiryBase - /// @param _baseGasLimit The new baseGasLimit for setRootHistoryExpiryBase - event SetGasLimitSetRootHistoryExpiryBase(uint32 _baseGasLimit); - - /// @notice Emmitted when the the StateBridge sets the gas limit for transferOwnershipBase - /// @param _baseGasLimit The new baseGasLimit for transferOwnershipBase - event SetGasLimitTransferOwnershipBase(uint32 _baseGasLimit); - - /////////////////////////////////////////////////////////////////// - /// ERRORS /// - /////////////////////////////////////////////////////////////////// - - /// @notice Thrown when the caller of `sendRootMultichain` is not the WorldID Identity Manager contract. - error NotWorldIDIdentityManager(); - - /// @notice Thrown when an attempt is made to renounce ownership. - error CannotRenounceOwnership(); - - /////////////////////////////////////////////////////////////////// - /// MODIFIERS /// - /////////////////////////////////////////////////////////////////// - modifier onlyWorldIDIdentityManager() { - if (msg.sender != worldIDAddress) { - revert NotWorldIDIdentityManager(); - } - _; - } - - /////////////////////////////////////////////////////////////////// - /// CONSTRUCTOR /// - /////////////////////////////////////////////////////////////////// - - /// @notice constructor - /// @param _checkpointManager address of the checkpoint manager contract - /// @param _fxRoot address of Polygon's fxRoot contract, part of the FxPortal bridge (Goerli or Mainnet) - /// @param _worldIDIdentityManager Deployment address of the WorldID Identity Manager contract - /// @param _opWorldIDAddress Address of the Optimism contract that will receive the new root and timestamp - /// @param _opCrossDomainMessenger L1CrossDomainMessenger contract used to communicate with the Optimism network - /// @param _baseWorldIDAddress Address of the Base contract that will receive the new root and timestamp - /// @param _baseCrossDomainMessenger L1CrossDomainMessenger contract used to communicate with the Base OP-Stack network - constructor( - address _checkpointManager, - address _fxRoot, - address _worldIDIdentityManager, - address _opWorldIDAddress, - address _opCrossDomainMessenger, - address _baseWorldIDAddress, - address _baseCrossDomainMessenger - ) FxBaseRootTunnel(_checkpointManager, _fxRoot) { - opWorldIDAddress = _opWorldIDAddress; - worldIDAddress = _worldIDIdentityManager; - baseWorldIDAddress = _baseWorldIDAddress; - opCrossDomainMessengerAddress = _opCrossDomainMessenger; - baseCrossDomainMessengerAddress = _baseCrossDomainMessenger; - gasLimitSendRootOptimism = 100000; - gasLimitSetRootHistoryExpiryOptimism = 100000; - gasLimitTransferOwnershipOptimism = 100000; - gasLimitSendRootBase = 100000; - gasLimitSetRootHistoryExpiryBase = 100000; - gasLimitTransferOwnershipBase = 100000; - } - - /////////////////////////////////////////////////////////////////// - /// PUBLIC API /// - /////////////////////////////////////////////////////////////////// - - /// @notice Sends the latest WorldID Identity Manager root to all chains. - /// @dev Calls this method on the L1 Proxy contract to relay roots and timestamps to WorldID supported chains. - /// @param root The latest WorldID Identity Manager root. - function sendRootMultichain(uint256 root) external onlyWorldIDIdentityManager { - uint128 timestamp = uint128(block.timestamp); - _sendRootToOptimism(root, timestamp); - _sendRootToPolygon(root, timestamp); - _sendRootToBase(root, timestamp); - // add other chains here - - emit RootSentMultichain(root, timestamp); - } - - /// @notice Sets the root history expiry for OpWorldID (on Optimism) and PolygonWorldID (on Polygon) - /// @param expiryTime The new root history expiry for OpWorldID and PolygonWorldID - function setRootHistoryExpiry(uint256 expiryTime) public onlyWorldIDIdentityManager { - setRootHistoryExpiryOptimism(expiryTime); - setRootHistoryExpiryPolygon(expiryTime); - setRootHistoryExpiryBase(expiryTime); - - emit SetRootHistoryExpiry(expiryTime); - } - - /////////////////////////////////////////////////////////////////// - /// OPTIMISM /// - /////////////////////////////////////////////////////////////////// - - /// @notice Sends the latest WorldID Identity Manager root to all chains. - /// @dev Calls this method on the L1 Proxy contract to relay roots and timestamps to WorldID supported chains. - /// @param root The latest WorldID Identity Manager root. - /// @param timestamp The Ethereum block timestamp of the latest WorldID Identity Manager root. - function _sendRootToOptimism(uint256 root, uint128 timestamp) internal { - // The `encodeCall` function is strongly typed, so this checks that we are passing the - // correct data to the optimism bridge. - bytes memory message = abi.encodeCall(IOpWorldID.receiveRoot, (root, timestamp)); - - ICrossDomainMessenger(opCrossDomainMessengerAddress).sendMessage( - // Contract address on Optimism - opWorldIDAddress, - message, - gasLimitSendRootOptimism - ); - } - - /// @notice Adds functionality to the StateBridge to transfer ownership - /// of OpWorldID to another contract on L1 or to a local Optimism EOA - /// @param _owner new owner (EOA or contract) - /// @param _isLocal true if new owner is on Optimism, false if it is a cross-domain owner - function transferOwnershipOptimism(address _owner, bool _isLocal) public onlyOwner { - bytes memory message; - - // The `encodeCall` function is strongly typed, so this checks that we are passing the - // correct data to the optimism bridge. - message = abi.encodeCall(ICrossDomainOwnable3.transferOwnership, (_owner, _isLocal)); - - ICrossDomainMessenger(opCrossDomainMessengerAddress).sendMessage( - // Contract address on Optimism - opWorldIDAddress, - message, - gasLimitTransferOwnershipOptimism - ); - - emit OwnershipTransferredOptimism(owner(), _owner, _isLocal); - } - - /// @notice Adds functionality to the StateBridge to set the root history expiry on OpWorldID - /// @param _rootHistoryExpiry new root history expiry - function setRootHistoryExpiryOptimism(uint256 _rootHistoryExpiry) internal { - bytes memory message; - - // The `encodeCall` function is strongly typed, so this checks that we are passing the - // correct data to the optimism bridge. - message = abi.encodeCall(IRootHistory.setRootHistoryExpiry, (_rootHistoryExpiry)); - - ICrossDomainMessenger(opCrossDomainMessengerAddress).sendMessage( - // Contract address on Optimism - opWorldIDAddress, - message, - gasLimitSetRootHistoryExpiryOptimism - ); - } - - /////////////////////////////////////////////////////////////////// - /// OP GAS LIMIT /// - /////////////////////////////////////////////////////////////////// - - /// @notice Sets the gas limit for the Optimism sendRootMultichain method - /// @param _opGasLimit The new gas limit for the sendRootMultichain method - function setGasLimitSendRootOptimism(uint32 _opGasLimit) external onlyOwner { - gasLimitSendRootOptimism = _opGasLimit; - - emit SetGasLimitSendRootOptimism(_opGasLimit); - } - - /// @notice Sets the gas limit for the Optimism setRootHistoryExpiry method - /// @param _opGasLimit The new gas limit for the setRootHistoryExpiry method - function setGasLimitSetRootHistoryExpiryOptimism(uint32 _opGasLimit) external onlyOwner { - gasLimitSetRootHistoryExpiryOptimism = _opGasLimit; - - emit SetGasLimitSetRootHistoryExpiryOptimism(_opGasLimit); - } - - /// @notice Sets the gas limit for the transferOwnershipOptimism method - /// @param _opGasLimit The new gas limit for the transferOwnershipOptimism method - function setGasLimitTransferOwnershipOptimism(uint32 _opGasLimit) external onlyOwner { - gasLimitTransferOwnershipOptimism = _opGasLimit; - - emit SetGasLimitTransferOwnershipOptimism(_opGasLimit); - } - - /////////////////////////////////////////////////////////////////// - /// BASE /// - /////////////////////////////////////////////////////////////////// - - /// @notice Sends the latest WorldID Identity Manager root to all chains. - /// @dev Calls this method on the L1 Proxy contract to relay roots and timestamps to WorldID supported chains. - /// @param root The latest WorldID Identity Manager root. - /// @param timestamp The Ethereum block timestamp of the latest WorldID Identity Manager root. - function _sendRootToBase(uint256 root, uint128 timestamp) internal { - // The `encodeCall` function is strongly typed, so this checks that we are passing the - // correct data to the optimism bridge. - bytes memory message = abi.encodeCall(IOpWorldID.receiveRoot, (root, timestamp)); - - ICrossDomainMessenger(baseCrossDomainMessengerAddress).sendMessage( - // Contract address on Base - baseWorldIDAddress, - message, - gasLimitSendRootBase - ); - } - - /// @notice Adds functionality to the StateBridge to transfer ownership - /// of OpWorldID to another contract on L1 or to a local Base EOA - /// @param _owner new owner (EOA or contract) - /// @param _isLocal true if new owner is on Base, false if it is a cross-domain owner - function transferOwnershipBase(address _owner, bool _isLocal) public onlyOwner { - bytes memory message; - - // The `encodeCall` function is strongly typed, so this checks that we are passing the - // correct data to the optimism bridge. - message = abi.encodeCall(ICrossDomainOwnable3.transferOwnership, (_owner, _isLocal)); - - ICrossDomainMessenger(baseCrossDomainMessengerAddress).sendMessage( - // Contract address on Base - baseWorldIDAddress, - message, - gasLimitTransferOwnershipBase - ); - - emit OwnershipTransferredBase(owner(), _owner, _isLocal); - } - - /// @notice Adds functionality to the StateBridge to set the root history expiry on OpWorldID - /// @param _rootHistoryExpiry new root history expiry - function setRootHistoryExpiryBase(uint256 _rootHistoryExpiry) internal { - bytes memory message; - - // The `encodeCall` function is strongly typed, so this checks that we are passing the - // correct data to the optimism bridge. - message = abi.encodeCall(IRootHistory.setRootHistoryExpiry, (_rootHistoryExpiry)); - - ICrossDomainMessenger(baseCrossDomainMessengerAddress).sendMessage( - // Contract address on Base - baseWorldIDAddress, - message, - gasLimitSetRootHistoryExpiryBase - ); - } - - /////////////////////////////////////////////////////////////////// - /// BASE GAS LIMIT /// - /////////////////////////////////////////////////////////////////// - - /// @notice Sets the gas limit for the Base sendRootMultichain method - /// @param _baseGasLimit The new gas limit for the sendRootMultichain method - function setGasLimitSendRootBase(uint32 _baseGasLimit) external onlyOwner { - gasLimitSendRootBase = _baseGasLimit; - - emit SetGasLimitSendRootBase(_baseGasLimit); - } - - /// @notice Sets the gas limit for the Base setRootHistoryExpiry method - /// @param _baseGasLimit The new gas limit for the setRootHistoryExpiry method - function setGasLimitSetRootHistoryExpiryBase(uint32 _baseGasLimit) external onlyOwner { - gasLimitSetRootHistoryExpiryBase = _baseGasLimit; - - emit SetGasLimitSetRootHistoryExpiryBase(_baseGasLimit); - } - - /// @notice Sets the gas limit for the transferOwnershipBase method - /// @param _baseGasLimit The new gas limit for the transferOwnershipBase method - function setGasLimitTransferOwnershipBase(uint32 _baseGasLimit) external onlyOwner { - gasLimitTransferOwnershipBase = _baseGasLimit; - - emit SetGasLimitTransferOwnershipBase(_baseGasLimit); - } - - /////////////////////////////////////////////////////////////////// - /// POLYGON /// - /////////////////////////////////////////////////////////////////// - - /// @notice Sends root and timestamp to Polygon's StateChild contract (PolygonWorldID) - /// @param root The latest WorldID Identity Manager root to be sent to Polygon - /// @param timestamp The Ethereum block timestamp of the latest WorldID Identity Manager root - function _sendRootToPolygon(uint256 root, uint128 timestamp) internal { - bytes memory message; - - message = abi.encodeCall(IPolygonWorldID.receiveRoot, (root, timestamp)); - - /// @notice FxBaseRootTunnel method to send bytes payload to FxBaseChildTunnel contract - _sendMessageToChild(message); - } - - /// @notice Sets the root history expiry for PolygonWorldID - /// @param _rootHistoryExpiry The new root history expiry - function setRootHistoryExpiryPolygon(uint256 _rootHistoryExpiry) internal { - bytes memory message; - - message = abi.encodeCall(IRootHistory.setRootHistoryExpiry, (_rootHistoryExpiry)); - - /// @notice FxBaseRootTunnel method to send bytes payload to FxBaseChildTunnel contract - _sendMessageToChild(message); - } - - /// @notice boilerplate function to satisfy FxBaseRootTunnel inheritance (not going to be used) - function _processMessageFromChild(bytes memory) internal override { - /// WorldID 🌎🆔 State Bridge - } - - /////////////////////////////////////////////////////////////////////////////// - /// ADDRESS MANAGEMENT /// - /////////////////////////////////////////////////////////////////////////////// - - /// @notice Sets the `fxChildTunnel` address if not already set. - /// @dev This implementation replicates the logic from `FxBaseRootTunnel` due to the inability - /// to call `external` superclass methods when overriding them. - /// - /// @param _fxChildTunnel The address of the child (non-L1) tunnel contract. - /// - /// @custom:reverts string If the root tunnel has already been set. - function setFxChildTunnel(address _fxChildTunnel) public virtual override onlyOwner { - require(fxChildTunnel == address(0x0), "FxBaseRootTunnel: CHILD_TUNNEL_ALREADY_SET"); - fxChildTunnel = _fxChildTunnel; - } - - /////////////////////////////////////////////////////////////////// - /// OWNERSHIP /// - /////////////////////////////////////////////////////////////////// - /// @notice Ensures that ownership of WorldID implementations cannot be renounced. - /// @dev This function is intentionally not `virtual` as we do not want it to be possible to - /// renounce ownership for any WorldID implementation. - /// @dev This function is marked as `onlyOwner` to maintain the access restriction from the base - /// contract. - function renounceOwnership() public view override onlyOwner { - revert CannotRenounceOwnership(); - } -} diff --git a/src/abstract/WorldIDBridge.sol b/src/abstract/WorldIDBridge.sol index 51d41c12..4371bcad 100644 --- a/src/abstract/WorldIDBridge.sol +++ b/src/abstract/WorldIDBridge.sol @@ -26,7 +26,7 @@ abstract contract WorldIDBridge is IWorldID { uint8 internal treeDepth; /// @notice The amount of time a root is considered as valid on the bridged chain. - uint256 internal ROOT_HISTORY_EXPIRY = 1 hours; + uint256 internal ROOT_HISTORY_EXPIRY = 1 weeks; /// @notice The value of the latest merkle tree root. uint256 internal _latestRoot; @@ -46,22 +46,22 @@ abstract contract WorldIDBridge is IWorldID { /// ERRORS /// /////////////////////////////////////////////////////////////////////////////// - /// @notice Thrown when the provided semaphore tree depth is unsupported. + /// @notice Emitted when the provided semaphore tree depth is unsupported. /// /// @param depth The tree depth that was passed. error UnsupportedTreeDepth(uint8 depth); - /// @notice Thrown when attempting to validate a root that has expired. + /// @notice Emitted when attempting to validate a root that has expired. error ExpiredRoot(); - /// @notice Thrown when attempting to validate a root that has yet to be added to the root + /// @notice Emitted when attempting to validate a root that has yet to be added to the root /// history. error NonExistentRoot(); - /// @notice Thrown when attempting to update the timestamp for a root that already has one. + /// @notice Emitted when attempting to update the timestamp for a root that already has one. error CannotOverwriteRoot(); - /// @notice Thrown if the latest root is requested but the bridge has not seen any roots yet. + /// @notice Emitted if the latest root is requested but the bridge has not seen any roots yet. error NoRootsSeen(); /////////////////////////////////////////////////////////////////////////////// @@ -71,9 +71,8 @@ abstract contract WorldIDBridge is IWorldID { /// @notice Emitted when a new root is received by the contract. /// /// @param root The value of the root that was added. - /// @param supersedeTimestamp The L1 time at which `root` became the latest root, and the prior - /// latest root became a member of the root history. - event RootAdded(uint256 root, uint128 supersedeTimestamp); + /// @param timestamp The timestamp of insertion for the given root. + event RootAdded(uint256 root, uint128 timestamp); /// @notice Emitted when the expiry time for the root history is updated. /// @@ -105,24 +104,22 @@ abstract contract WorldIDBridge is IWorldID { /// equivalent operation. /// /// @param newRoot The value of the new root. - /// @param supersedeTimestamp The value of the L1 timestamp at the time that `newRoot` became - /// the current root. This timestamp is associated with the latest root at the time of - /// the call being inserted into the root history. /// /// @custom:reverts CannotOverwriteRoot If the root already exists in the root history. /// @custom:reverts string If the caller is not the owner. - function _receiveRoot(uint256 newRoot, uint128 supersedeTimestamp) internal { + function _receiveRoot(uint256 newRoot) internal { uint256 existingTimestamp = rootHistory[newRoot]; if (existingTimestamp != NULL_ROOT_TIME) { revert CannotOverwriteRoot(); } - uint256 rootBeingReplaced = _latestRoot; + uint128 currTimestamp = uint128(block.timestamp); + _latestRoot = newRoot; - rootHistory[rootBeingReplaced] = supersedeTimestamp; + rootHistory[newRoot] = currTimestamp; - emit RootAdded(newRoot, supersedeTimestamp); + emit RootAdded(newRoot, currTimestamp); } /// @notice Reverts if the provided root value is not valid. diff --git a/src/interfaces/IOpStateBridgeTransferOwnership.sol b/src/interfaces/IOpStateBridgeTransferOwnership.sol new file mode 100644 index 00000000..e0c39a23 --- /dev/null +++ b/src/interfaces/IOpStateBridgeTransferOwnership.sol @@ -0,0 +1,13 @@ +pragma solidity ^0.8.15; + +/// @title OpStateBridgeTransferOwnership +/// @notice Interface for the StateBridge to transfer ownership +/// of OpWorldID to another contract on L1 or to a OP Stack chain EOA or contract +/// @dev This is a subset of the OpStateBridge contract +abstract contract IOpStateBridgeTransferOwnership { + /// @notice Adds functionality to the StateBridge to transfer ownership + /// of OpWorldID to another contract on L1 or to a local OP Stack chain EOA + /// @param _owner new owner (EOA or contract) + /// @param _isLocal true if new owner is on Optimism, false if it is a cross-domain owner + function transferOwnershipOp(address _owner, bool _isLocal) external virtual; +} diff --git a/src/interfaces/IOpWorldID.sol b/src/interfaces/IOpWorldID.sol index 417cbef2..208746cf 100644 --- a/src/interfaces/IOpWorldID.sol +++ b/src/interfaces/IOpWorldID.sol @@ -18,11 +18,8 @@ interface IOpWorldID { /// if messages are not included, however the fraud prover (Cannon) can force the sequencer to include it. /// /// @param newRoot The value of the new root. - /// @param supersedeTimestamp The value of the L1 timestamp at the time that `newRoot` became - /// the current root. This timestamp is associated with the latest root at the time of - /// the call being inserted into the root history. /// /// @custom:reverts CannotOverwriteRoot If the root already exists in the root history. /// @custom:reverts string If the caller is not the owner. - function receiveRoot(uint256 newRoot, uint128 supersedeTimestamp) external; + function receiveRoot(uint256 newRoot) external; } diff --git a/src/interfaces/IPolygonWorldID.sol b/src/interfaces/IPolygonWorldID.sol index ab6e42ad..94f929c7 100644 --- a/src/interfaces/IPolygonWorldID.sol +++ b/src/interfaces/IPolygonWorldID.sol @@ -14,11 +14,8 @@ interface IPolygonWorldID { /// the bridged WorldID. /// /// @param newRoot The value of the new root. - /// @param supersedeTimestamp The value of the L1 timestamp at the time that `newRoot` became - /// the current root. This timestamp is associated with the latest root at the time of - /// the call being inserted into the root history. /// /// @custom:reverts CannotOverwriteRoot If the root already exists in the root history. /// @custom:reverts string If the caller is not the owner. - function receiveRoot(uint256 newRoot, uint128 supersedeTimestamp) external; + function receiveRoot(uint256 newRoot) external; } diff --git a/src/interfaces/IWorldIDIdentityManager.sol b/src/interfaces/IWorldIDIdentityManager.sol index d0d5b01d..5213a2b0 100644 --- a/src/interfaces/IWorldIDIdentityManager.sol +++ b/src/interfaces/IWorldIDIdentityManager.sol @@ -1,14 +1,10 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.15; -/// @title ISemaphoreRoot +/// @title IWorldIDIdentityManager /// @author Worldcoin -/// @dev used to check if a root is valid for the StateBridge +/// @dev used to fetch the latest root from the WorldIDIdentityManager interface IWorldIDIdentityManager { - /// @notice Checks if a given root value is valid and has been added to the root history. - /// @dev Reverts with `ExpiredRoot` if the root has expired, and `NonExistentRoot` if the root - /// is not in the root history. - /// - /// @param root The root of a given identity group. - function checkValidRoot(uint256 root) external view returns (bool); + /// @notice returns the latest root + function latestRoot() external view returns (uint256); } diff --git a/src/mock/MockOpPolygonWorldID.sol b/src/mock/MockBridgedWorldID.sol similarity index 80% rename from src/mock/MockOpPolygonWorldID.sol rename to src/mock/MockBridgedWorldID.sol index 55fb515c..74dfa8ef 100644 --- a/src/mock/MockOpPolygonWorldID.sol +++ b/src/mock/MockBridgedWorldID.sol @@ -10,7 +10,7 @@ import {Ownable} from "openzeppelin-contracts/access/Ownable.sol"; /// @author Worldcoin /// @notice Mock of PolygonWorldID and OpWorldID in order to test functionality on a local chain /// @custom:deployment deployed through make local-mock -contract MockOpPolygonWorldID is WorldIDBridge, Ownable { +contract MockBridgedWorldID is WorldIDBridge, Ownable { /////////////////////////////////////////////////////////////////////////////// /// CONSTRUCTION /// /////////////////////////////////////////////////////////////////////////////// @@ -29,18 +29,16 @@ contract MockOpPolygonWorldID is WorldIDBridge, Ownable { /// @dev This function can revert if Optimism's CrossDomainMessenger stops processing proofs /// or if OPLabs stops submitting them. Next iteration of Optimism's cross-domain messaging, will be /// fully permissionless for message-passing, so this will not be an issue. - /// Sequencer needs to include changes to the CrossDomainMessenger contract on L1, not economically penalized - /// if messages are not included, however the fraud prover (Cannon) can force the sequencer to include it. + /// Sequencer needs to include changes to the CrossDomainMessenger contract on L1, + /// not economically penalized if messages are not included, however the fraud prover (Cannon) + /// can force the sequencer to include it. /// /// @param newRoot The value of the new root. - /// @param supersedeTimestamp The value of the L1 timestamp at the time that `newRoot` became - /// the current root. This timestamp is associated with the latest root at the time of - /// the call being inserted into the root history. /// /// @custom:reverts CannotOverwriteRoot If the root already exists in the root history. /// @custom:reverts string If the caller is not the owner. - function receiveRoot(uint256 newRoot, uint128 supersedeTimestamp) public virtual { - _receiveRoot(newRoot, supersedeTimestamp); + function receiveRoot(uint256 newRoot) public virtual onlyOwner { + _receiveRoot(newRoot); } /////////////////////////////////////////////////////////////////////////////// diff --git a/src/mock/MockPolygonBridge.sol b/src/mock/MockPolygonBridge.sol index 6c6a66ae..11f101b5 100644 --- a/src/mock/MockPolygonBridge.sol +++ b/src/mock/MockPolygonBridge.sol @@ -1,6 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.15; +import {WorldIDBridge} from "../abstract/WorldIDBridge.sol"; import {IWorldIDIdentityManager} from "../interfaces/IWorldIDIdentityManager.sol"; import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; import {BytesUtils} from "src/utils/BytesUtils.sol"; @@ -9,36 +10,26 @@ import {BytesUtils} from "src/utils/BytesUtils.sol"; /// @author Worldcoin /// @dev of the Polygon FxPortal Bridge to test low-level assembly functions /// `grabSelector` and `stripSelector` in the PolygonWorldID contract -contract MockPolygonBridge is Ownable { - /// @notice mock rootHistory - mapping(uint256 => uint128) public rootHistory; - - /// @notice mock rootHistoryExpiry - uint256 public rootHistoryExpiry = 1 hours; - +contract MockPolygonBridge is WorldIDBridge, Ownable { /// @notice The selector of the `receiveRoot` function. /// @dev this selector is precomputed in the constructor to not have to recompute them for every /// call of the _processMesageFromRoot function - bytes4 private receiveRootSelector; + bytes4 internal _receiveRootSelector; /// @notice The selector of the `receiveRootHistoryExpiry` function. /// @dev this selector is precomputed in the constructor to not have to recompute them for every /// call of the _processMesageFromRoot function - bytes4 private receiveRootHistoryExpirySelector; - - /// @notice Thrown when root history expiry is set - event RootHistoryExpirySet(uint256 rootHistoryExpiry); + bytes4 internal _receiveRootHistoryExpirySelector; - /// @notice Thrown when new root is inserted - event ReceivedRoot(uint256 root, uint128 supersedeTimestamp); - - /// @notice Thrown when the message selector passed from FxRoot is invalid. + /// @notice Emitted when the message selector passed from FxRoot is invalid. error InvalidMessageSelector(bytes4 selector); - /// @notice constructor - constructor() { - receiveRootSelector = bytes4(keccak256("receiveRoot(uint256,uint128)")); - receiveRootHistoryExpirySelector = bytes4(keccak256("setRootHistoryExpiry(uint256)")); + /// @notice Initializes the contract's storage variables with the correct parameters + /// + /// @param _treeDepth The depth of the WorldID Identity Manager merkle tree. + constructor(uint8 _treeDepth) WorldIDBridge(_treeDepth) { + _receiveRootSelector = bytes4(keccak256("receiveRoot(uint256)")); + _receiveRootHistoryExpirySelector = bytes4(keccak256("setRootHistoryExpiry(uint256)")); } /////////////////////////////////////////////////////////////////////////////// @@ -54,37 +45,24 @@ contract MockPolygonBridge is Ownable { bytes4 selector = bytes4(BytesUtils.substring(message, 0, 4)); bytes memory payload = BytesUtils.substring(message, 4, message.length - 4); - if (selector == receiveRootSelector) { - (uint256 root, uint128 timestamp) = abi.decode(payload, (uint256, uint128)); - receiveRoot(root, timestamp); - } else if (selector == receiveRootHistoryExpirySelector) { + if (selector == _receiveRootSelector) { + uint256 root = abi.decode(payload, (uint256)); + _receiveRoot(root); + } else if (selector == _receiveRootHistoryExpirySelector) { uint256 newRootHistoryExpiry = abi.decode(payload, (uint256)); - setRootHistoryExpiry(newRootHistoryExpiry); + _setRootHistoryExpiry(newRootHistoryExpiry); } else { revert InvalidMessageSelector(selector); } } - /// @notice Updates the WorldID root history with a new root. - /// @param newRoot The new root to add to the root history. - /// @param supersedeTimestamp The timestamp at which the new root supersedes the current root. - /// @dev This function is called by the StateBridge contract. - function receiveRoot(uint256 newRoot, uint128 supersedeTimestamp) internal { - rootHistory[newRoot] = supersedeTimestamp; - - emit ReceivedRoot(newRoot, supersedeTimestamp); - } - /////////////////////////////////////////////////////////////////////////////// /// DATA MANAGEMENT /// /////////////////////////////////////////////////////////////////////////////// - /// @notice Sets the `rootHistoryExpiry` variable to the provided value. - /// @param newRootHistoryExpiry The new value for `rootHistoryExpiry`. - /// @dev This function is called by the StateBridge contract. - function setRootHistoryExpiry(uint256 newRootHistoryExpiry) internal { - rootHistoryExpiry = newRootHistoryExpiry; - - emit RootHistoryExpirySet(newRootHistoryExpiry); + /// @notice Placeholder to satisfy WorldIDBridge inheritance + /// @dev This function is not used on Polygon PoS because of FxPortal message passing architecture + function setRootHistoryExpiry(uint256) public virtual override { + revert("PolygonWorldID: Root history expiry should only be set via the state bridge"); } } diff --git a/src/mock/MockStateBridge.sol b/src/mock/MockStateBridge.sol index c3a75fe7..59988696 100644 --- a/src/mock/MockStateBridge.sol +++ b/src/mock/MockStateBridge.sol @@ -1,8 +1,8 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.15; -// Optimism interface for cross domain messaging -import {MockOpPolygonWorldID} from "./MockOpPolygonWorldID.sol"; +import {MockBridgedWorldID} from "./MockBridgedWorldID.sol"; +import {WorldIDIdentityManagerMock} from "./WorldIDIdentityManagerMock.sol"; import {Ownable} from "openzeppelin-contracts/access/Ownable.sol"; import {IWorldIDIdentityManager} from "../interfaces/IWorldIDIdentityManager.sol"; @@ -11,48 +11,32 @@ import {IWorldIDIdentityManager} from "../interfaces/IWorldIDIdentityManager.sol /// @notice Mock of the StateBridge to test functionality on a local chain /// @custom:deployment deployed through make local-mock contract MockStateBridge is Ownable { - /// @notice The address of the MockOpPolygonWorldID contract - address public mockOpPolygonWorldIDAddress; + /// @notice WorldIDIdentityManagerMock contract which will hold a mock root + WorldIDIdentityManagerMock public worldID; - /// @notice Interface for checkValidRoot within the WorldID Identity Manager contract - address public worldIDAddress; - - IWorldIDIdentityManager internal worldID; + /// @notice MockBridgedWorldID contract which will receive the root + MockBridgedWorldID public mockBridgedWorldID; /// @notice Emmited when the root is not a valid root in the canonical WorldID Identity Manager contract error InvalidRoot(); /// @notice constructor - /// @param _worldIDIdentityManager Deployment address of the WorldID Identity Manager contract - /// @param _mockOpPolygonWorldIDAddress Address of the MockOpPolygonWorldID contract for the new root and timestamp - constructor(address _worldIDIdentityManager, address _mockOpPolygonWorldIDAddress) { - mockOpPolygonWorldIDAddress = _mockOpPolygonWorldIDAddress; - worldIDAddress = _worldIDIdentityManager; - worldID = IWorldIDIdentityManager(_worldIDIdentityManager); + constructor() { + worldID = new WorldIDIdentityManagerMock(uint256(0x111)); + mockBridgedWorldID = new MockBridgedWorldID(uint8(30)); } - /// @notice Sends the latest WorldID Identity Manager root to all chains. - /// @dev Calls this method on the L1 Proxy contract to relay roots and timestamps to WorldID supported chains. - /// @param root The latest WorldID Identity Manager root. - function sendRootMultichain(uint256 root) public { - // If the root is not a valid root in the canonical WorldID Identity Manager contract, revert - // comment out for mock deployments - - if (!worldID.checkValidRoot(root)) revert InvalidRoot(); - - uint128 timestamp = uint128(block.timestamp); - _sendRootToMockOpPolygonWorldID(root, timestamp); + /// @notice Sends the latest WorldID Identity Manager root to the Bridged WorldID contract. + /// @dev Calls this method on the L1 Proxy contract to relay roots to WorldID supported chains. + function propagateRoot() public { + uint256 latestRoot = worldID.latestRoot(); + _sendRootToMockBridgedWorldID(latestRoot); } - /////////////////////////////////////////////////////////////////// - /// OPTIMISM /// - /////////////////////////////////////////////////////////////////// - // @notice Sends the latest WorldID Identity Manager root to all chains. - /// @dev Calls this method on the L1 Proxy contract to relay roots and timestamps to WorldID supported chains. + /// @dev Calls this method on the L1 Proxy contract to relay roots to WorldID supported chains. /// @param root The latest WorldID Identity Manager root. - /// @param timestamp The Ethereum block timestamp of the latest WorldID Identity Manager root. - function _sendRootToMockOpPolygonWorldID(uint256 root, uint128 timestamp) internal { - MockOpPolygonWorldID(mockOpPolygonWorldIDAddress).receiveRoot(root, timestamp); + function _sendRootToMockBridgedWorldID(uint256 root) internal { + mockBridgedWorldID.receiveRoot(root); } } diff --git a/src/mock/WorldIDIdentityManagerMock.sol b/src/mock/WorldIDIdentityManagerMock.sol index c4b814cc..fb9c1747 100644 --- a/src/mock/WorldIDIdentityManagerMock.sol +++ b/src/mock/WorldIDIdentityManagerMock.sol @@ -1,28 +1,20 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.15; -import {Initializable} from "@openzeppelin/contracts/proxy/utils/Initializable.sol"; -import {IBridge} from "src/interfaces/IBridge.sol"; +import {IWorldIDIdentityManager} from "src/interfaces/IWorldIDIdentityManager.sol"; /// @title WorldID Identity Manager Mock /// @author Worldcoin /// @notice Mock of the WorldID Identity Manager contract (world-id-contracts) to test functionality on a local chain /// @dev deployed through make mock and make local-mock -contract WorldIDIdentityManagerMock is Initializable { - address public stateBridge; - mapping(uint256 => bool) public rootHistory; +contract WorldIDIdentityManagerMock is IWorldIDIdentityManager { + uint256 internal _latestRoot; - function initialize(address _stateBridge) public virtual { - stateBridge = _stateBridge; + constructor(uint256 newRoot) { + _latestRoot = newRoot; } - function sendRootToStateBridge(uint256 root) public { - rootHistory[root] = true; - - IBridge(stateBridge).sendRootMultichain(root); - } - - function checkValidRoot(uint256 root) public view returns (bool) { - return rootHistory[root]; + function latestRoot() external view returns (uint256) { + return _latestRoot; } } diff --git a/src/script/deploy.js b/src/script/deploy.js index 5e5beb4c..7c6bf380 100644 --- a/src/script/deploy.js +++ b/src/script/deploy.js @@ -11,6 +11,7 @@ import { ethers } from "ethers"; // === Constants ================================================================================== const DEFAULT_RPC_URL = "http://localhost:8545"; +const PLACEHOLDER_ETHERSCAN_API_KEY = "AAAAAAAAAAAAAAAAAA"; const CONFIG_FILENAME = "src/script/.deploy-config.json"; // === Implementation ============================================================================= @@ -66,6 +67,10 @@ function ask(question, type) { }); } +/////////////////////////////////////////////////////////////////// +/// DEPLOYMENT CONFIG /// +/////////////////////////////////////////////////////////////////// + async function getPrivateKey(config) { if (!config.privateKey) { config.privateKey = process.env.PRIVATE_KEY; @@ -138,9 +143,12 @@ async function getOptimismEtherscanApiKey(config) { } if (!config.optimismEtherscanApiKey) { config.optimismEtherscanApiKey = await ask( - `Enter Optimism Etherscan API KEY: (https://optimistic.etherscan.io/myaccount) `, + `Enter Optimism Etherscan API KEY: (https://optimistic.etherscan.io/myaccount (Leave it empty for mocks) `, ); } + if (!config.optimismEtherscanApiKey) { + config.optimismEtherscanApiKey = PLACEHOLDER_ETHERSCAN_API_KEY; + } } async function getBaseEtherscanApiKey(config) { @@ -148,7 +156,12 @@ async function getBaseEtherscanApiKey(config) { config.baseEtherscanApiKey = process.env.BASE_ETHERSCAN_API_KEY; } if (!config.baseEtherscanApiKey) { - config.baseEtherscanApiKey = await ask(`Enter BaseScan API KEY: (https://basescan.org/register) `); + config.baseEtherscanApiKey = await ask( + `Enter BaseScan API KEY: (https://basescan.org/register) (Leave it empty for mocks) `, + ); + } + if (!config.baseEtherscanApiKey) { + config.baseEtherscanApiKey = PLACEHOLDER_ETHERSCAN_API_KEY; } } @@ -157,7 +170,12 @@ async function getEthereumEtherscanApiKey(config) { config.ethereumEtherscanApiKey = process.env.ETHERSCAN_API_KEY; } if (!config.ethereumEtherscanApiKey) { - config.ethereumEtherscanApiKey = await ask(`Enter Ethereum Etherscan API KEY: (https://etherscan.io/myaccount) `); + config.ethereumEtherscanApiKey = await ask( + `Enter Ethereum Etherscan API KEY: (https://etherscan.io/myaccount) (Leave it empty for mocks) `, + ); + } + if (!config.ethereumEtherscanApiKey) { + config.ethereumEtherscanApiKey = PLACEHOLDER_ETHERSCAN_API_KEY; } } @@ -166,7 +184,12 @@ async function getPolygonscanApiKey(config) { config.polygonscanApiKey = process.env.POLYGONSCAN_API_KEY; } if (!config.polygonscanApiKey) { - config.polygonscanApiKey = await ask(`Enter Polygonscan API KEY: (https://polygonscan.com/myaccount) `); + config.polygonscanApiKey = await ask( + `Enter Polygonscan API KEY: (https://polygonscan.com/myaccount) (Leave it empty for mocks) `, + ); + } + if (!config.polygonscanApiKey) { + config.polygonscanApiKey = PLACEHOLDER_ETHERSCAN_API_KEY; } } @@ -176,12 +199,39 @@ async function getTreeDepth(config) { } } -async function getStateBridgeAddress(config) { - if (!config.stateBridgeAddress) { - config.stateBridgeAddress = process.env.STATE_BRIDGE_ADDRESS; +async function getOptimismStateBridgeAddress(config) { + if (!config.optimismStateBridgeAddress) { + config.optimismStateBridgeAddress = process.env.OPTIMISM_STATE_BRIDGE_ADDRESS; + } + if (!config.optimismStateBridgeAddress) { + config.optimismStateBridgeAddress = await ask("Enter Optimism State Bridge Address: "); + } +} + +async function getBaseStateBridgeAddress(config) { + if (!config.baseStateBridgeAddress) { + config.baseStateBridgeAddress = process.env.BASE_STATE_BRIDGE_ADDRESS; + } + if (!config.baseStateBridgeAddress) { + config.baseStateBridgeAddress = await ask("Enter Base State Bridge Address: "); + } +} + +async function getPolygonStateBridgeAddress(config) { + if (!config.polygonStateBridgeAddress) { + config.polygonStateBridgeAddress = process.env.POLYGON_STATE_BRIDGE_ADDRESS; + } + if (!config.polygonStateBridgeAddress) { + config.polygonStateBridgeAddress = await ask("Enter Polygon State Bridge Address: "); + } +} + +async function getMockStateBridgeAddress(config) { + if (!config.mockStateBridgeAddress) { + config.mockStateBridgeAddress = process.env.MOCK_STATE_BRIDGE_ADDRESS; } - if (!config.stateBridgeAddress) { - config.stateBridgeAddress = await ask("Enter State Bridge Address: "); + if (!config.mockStateBridgeAddress) { + config.mockStateBridgeAddress = await ask("Enter Mock State Bridge Address: "); } } @@ -223,12 +273,12 @@ async function getWorldIDIdentityManagerAddress(config) { } } -async function getNewRoot(config) { - if (!config.newRoot) { - config.newRoot = process.env.NEW_ROOT; +async function getSampleRoot(config) { + if (!config.sampleRoot) { + config.sampleRoot = process.env.SAMPLE_ROOT; } - if (!config.newRoot) { - config.newRoot = await ask("Enter WorldID root to be inserted into MockWorldID: "); + if (!config.sampleRoot) { + config.sampleRoot = await ask("Enter root to be inserted into WorldIDIdentityManagerMock: "); } } @@ -241,6 +291,34 @@ async function getDeployerAddress(config) { } } +/////////////////////////////////////////////////////////////////// +/// OWNERSHIP /// +/////////////////////////////////////////////////////////////////// + +async function getNewOptimismWorldIDOwner(config) { + if (!config.newOptimismWorldIDOwner) { + config.newOptimismWorldIDOwner = process.env.NEW_OPTIMISM_WORLD_ID_OWNER; + } + + if (!config.newOptimismWorldIDOwner) { + config.newOptimismWorldIDOwner = await ask("Enter new Optimism WorldID owner: "); + } +} + +async function getNewBaseWorldIDOwner(config) { + if (!config.newBaseWorldIDOwner) { + config.newBaseWorldIDOwner = process.env.NEW_BASE_WORLD_ID_OWNER; + } + + if (!config.newBaseWorldIDOwner) { + config.newBaseWorldIDOwner = await ask("Enter new Base WorldID owner: "); + } +} + +/////////////////////////////////////////////////////////////////// +/// GAS LIMIT /// +/////////////////////////////////////////////////////////////////// + async function getGasLimitSendRootOptimism(config) { if (!config.gasLimitSendRootOptimism) { config.gasLimitSendRootOptimism = process.env.GAS_LIMIT_SEND_ROOT_OPTIMISM; @@ -299,6 +377,10 @@ async function getGasLimitTransferOwnershipBase(config) { } } +/////////////////////////////////////////////////////////////////// +/// UTILS /// +/////////////////////////////////////////////////////////////////// + async function loadConfiguration(useConfig) { if (!useConfig) { return {}; @@ -347,27 +429,91 @@ async function saveConfiguration(config) { fs.writeFileSync(CONFIG_FILENAME, data); } -async function deployStateBridgeGoerli(config) { - const spinner = ora("Deploying State Bridge...").start(); +/////////////////////////////////////////////////////////////////// +/// DEPLOYMENT /// +/////////////////////////////////////////////////////////////////// + +async function deployOptimismOpStateBridgeGoerli(config) { + const spinner = ora("Deploying Optimism State Bridge...").start(); + + try { + const data = + execSync(`forge script src/script/deploy/op-stack/optimism/DeployOptimismStateBridgeGoerli.s.sol:DeployOpStateBridgeGoerli --fork-url ${config.ethereumRpcUrl} \ + --etherscan-api-key ${config.ethereumEtherscanApiKey} --broadcast --verify -vvvv`); + console.log(data.toString()); + } catch (err) { + console.error(err); + } + + spinner.succeed("DeployStateBridgeMainnet.s.sol ran successfully!"); +} + +async function deployOptimismOpStateBridgeMainnet(config) { + const spinner = ora("Deploying Optimism State Bridge...").start(); + + try { + const data = + execSync(`forge script src/script/deploy/op-stack/optimism/DeployOptimismStateBridgeMainnet.s.sol:DeployOpStateBridgeMainnet --fork-url ${config.ethereumRpcUrl} \ + --etherscan-api-key ${config.ethereumEtherscanApiKey} --broadcast --verify -vvvv`); + console.log(data.toString()); + } catch (err) { + console.error(err); + } + + spinner.succeed("DeployStateBridgeMainnet.s.sol ran successfully!"); +} + +async function deployBaseOpStateBridgeGoerli(config) { + const spinner = ora("Deploying Base State Bridge...").start(); + + try { + const data = + execSync(`forge script src/script/deploy/op-stack/base/DeployBaseStateBridgeGoerli.s.sol:DeployBaseStateBridgeGoerli --fork-url ${config.ethereumRpcUrl} \ + --etherscan-api-key ${config.ethereumEtherscanApiKey} --broadcast --verify -vvvv`); + console.log(data.toString()); + } catch (err) { + console.error(err); + } + + spinner.succeed("DeployBaseStateBridgeMainnet.s.sol ran successfully!"); +} + +async function deployBaseOpStateBridgeMainnet(config) { + const spinner = ora("Deploying Base State Bridge...").start(); + + try { + const data = + execSync(`forge script src/script/deploy/op-stack/base/DeployBaseStateBridgeMainnet.s.sol:DeployBaseStateBridgeMainnet --fork-url ${config.ethereumRpcUrl} \ + --etherscan-api-key ${config.ethereumEtherscanApiKey} --broadcast --verify -vvvv`); + console.log(data.toString()); + } catch (err) { + console.error(err); + } + + spinner.succeed("DeployBaseStateBridgeMainnet.s.sol ran successfully!"); +} + +async function deployPolygonStateBridgeGoerli(config) { + const spinner = ora("Deploying Polygon State Bridge...").start(); try { const data = - execSync(`forge script src/script/deploy/DeployStateBridgeGoerli.s.sol:DeployStateBridge --fork-url ${config.ethereumRpcUrl} \ + execSync(`forge script src/script/deploy/polygon/DeployPolygonStateBridgeGoerli.s.sol:DeployPolygonStateBridgeGoerli --fork-url ${config.ethereumRpcUrl} \ --etherscan-api-key ${config.ethereumEtherscanApiKey} --broadcast --verify -vvvv`); console.log(data.toString()); } catch (err) { console.error(err); } - spinner.succeed("DeployStateBridgeGoerli.s.sol ran successfully!"); + spinner.succeed("DeployStateBridgeMainnet.s.sol ran successfully!"); } -async function deployStateBridgeMainnet(config) { - const spinner = ora("Deploying State Bridge...").start(); +async function deployPolygonStateBridgeMainnet(config) { + const spinner = ora("Deploying Polygon State Bridge...").start(); try { const data = - execSync(`forge script src/script/deploy/DeployStateBridgeMainnet.s.sol:DeployStateBridge --fork-url ${config.ethereumRpcUrl} \ + execSync(`forge script src/script/deploy/polygon/DeployPolygonStateBridgeMainnet.s.sol:DeployPolygonStateBridgeMainnet --fork-url ${config.ethereumRpcUrl} \ --etherscan-api-key ${config.ethereumEtherscanApiKey} --broadcast --verify -vvvv`); console.log(data.toString()); } catch (err) { @@ -382,7 +528,7 @@ async function deployPolygonWorldIDMumbai(config) { try { const data = - execSync(`forge script src/script/deploy/DeployPolygonWorldIDMumbai.s.sol:DeployPolygonWorldIDMumbai --fork-url ${config.polygonRpcUrl} \ + execSync(`forge script src/script/deploy/polygon/DeployPolygonWorldIDMumbai.s.sol:DeployPolygonWorldIDMumbai --fork-url ${config.polygonRpcUrl} \ --etherscan-api-key ${config.polygonscanApiKey} --legacy --broadcast --verify -vvvv`); console.log(data.toString()); } catch (err) { @@ -397,7 +543,7 @@ async function deployPolygonWorldIDMainnet(config) { try { const data = - execSync(`forge script src/script/deploy/DeployPolygonWorldIDMainnet.s.sol:DeployPolygonWorldID --fork-url ${config.polygonRpcUrl} \ + execSync(`forge script src/script/deploy/polygon/DeployPolygonWorldIDMainnet.s.sol:DeployPolygonWorldID --fork-url ${config.polygonRpcUrl} \ --etherscan-api-key ${config.polygonscanApiKey} --legacy --broadcast --verify -vvvv`); console.log(data.toString()); } catch (err) { @@ -407,29 +553,29 @@ async function deployPolygonWorldIDMainnet(config) { spinner.succeed("DeployPolygonWorldID.s.sol ran successfully!"); } -async function deployMockWorldID(config) { - const spinner = ora("Deploying Mock WorldID...").start(); +async function deployOptimismWorldID(config) { + const spinner = ora("Deploying OpWorldID on Optimism...").start(); try { const data = execSync( - `forge script src/script/deploy/DeployMockWorldID.s.sol:DeployMockWorldID --fork-url ${config.ethereumRpcUrl} \ - --etherscan-api-key ${config.ethereumEtherscanApiKey} --broadcast --verify -vvvv`, + `forge script src/script/deploy/op-stack/DeployOpWorldID.s.sol:DeployOpWorldID --fork-url ${config.optimismRpcUrl} \ + --etherscan-api-key ${config.optimismEtherscanApiKey} --broadcast --verify -vvvv`, ); console.log(data.toString()); } catch (err) { console.error(err); } - spinner.succeed("DeployMockWorldID.s.sol ran successfully!"); + spinner.succeed("DeployOpWorldID.s.sol ran successfully!"); } -async function deployOptimismWorldID(config) { - const spinner = ora("Deploying OpWorldID...").start(); +async function deployBaseWorldID(config) { + const spinner = ora("Deploying OpWorldID on Base...").start(); try { const data = execSync( - `forge script src/script/deploy/DeployOpWorldID.s.sol:DeployOpWorldID --fork-url ${config.optimismRpcUrl} \ - --etherscan-api-key ${config.optimismEtherscanApiKey} --broadcast --verify -vvvv`, + `forge script src/script/deploy/op-stack/DeployOpWorldID.s.sol:DeployOpWorldID --fork-url ${config.baseRpcUrl} \ + --etherscan-api-key ${config.baseEtherscanApiKey} --broadcast --verify -vvvv`, ); console.log(data.toString()); } catch (err) { @@ -439,28 +585,32 @@ async function deployOptimismWorldID(config) { spinner.succeed("DeployOpWorldID.s.sol ran successfully!"); } -async function deployBaseWorldID(config) { - const spinner = ora("Deploying BaseWorldID...").start(); +/////////////////////////////////////////////////////////////////// +/// MOCKS /// +/////////////////////////////////////////////////////////////////// + +async function deployMockWorldID(config) { + const spinner = ora("Deploying Mock WorldID...").start(); try { const data = execSync( - `forge script src/script/deploy/DeployOpWorldID.s.sol:DeployOpWorldID --fork-url ${config.baseRpcUrl} \ - --etherscan-api-key ${config.baseEtherscanApiKey} --broadcast --verify -vvvv`, + `forge script src/script/deploy/mock/DeployMockWorldID.s.sol:DeployMockWorldID --fork-url ${config.ethereumRpcUrl} \ + --etherscan-api-key ${config.ethereumEtherscanApiKey} --broadcast --verify -vvvv`, ); console.log(data.toString()); } catch (err) { console.error(err); } - spinner.succeed("DeployOpWorldID.s.sol ran successfully!"); + spinner.succeed("DeployMockWorldID.s.sol ran successfully!"); } -async function deployMockOpPolygonWorldID(config) { - const spinner = ora("Deploying MockOpPolygonWorldID...").start(); +async function DeployMockBridgedWorldID(config) { + const spinner = ora("Deploying DeployMockBridgedWorldID...").start(); try { const data = execSync( - `forge script src/script/deploy/DeployMockOpPolygonWorldID.s.sol:DeployMockOpPolygonWorldID --fork-url ${config.ethereumRpcUrl} \ + `forge script src/script/deploy/mock/DeployMockBridgedWorldID.s.sol:DeployMockBridgedWorldID --fork-url ${config.ethereumRpcUrl} \ --etherscan-api-key ${config.ethereumEtherscanApiKey} --broadcast --verify -vvvv`, ); console.log(data.toString()); @@ -468,7 +618,7 @@ async function deployMockOpPolygonWorldID(config) { console.error(err); } - spinner.succeed("DeployMockOpPolygonWorldID.s.sol ran successfully!"); + spinner.succeed("DeployMockBridgedWorldID.s.sol ran successfully!"); } async function deployMockStateBridge(config) { @@ -476,7 +626,7 @@ async function deployMockStateBridge(config) { try { const data = execSync( - `forge script src/script/deploy/DeployMockStateBridge.s.sol:DeployMockStateBridge --fork-url ${config.ethereumRpcUrl} \ + `forge script src/script/deploy/mock/DeployMockStateBridge.s.sol:DeployMockStateBridge --fork-url ${config.ethereumRpcUrl} \ --etherscan-api-key ${config.ethereumEtherscanApiKey} --broadcast --verify -vvvv`, ); console.log(data.toString()); @@ -487,42 +637,67 @@ async function deployMockStateBridge(config) { spinner.succeed("DeployMockStateBridge.s.sol ran successfully!"); } -async function initializeMockWorldID(config) { - const spinner = ora("Initializing MockWorldID...").start(); +/////////////////////////////////////////////////////////////////// +/// INITIALIZE /// +/////////////////////////////////////////////////////////////////// + +async function initializePolygonWorldID(config) { + const spinner = ora("Initializing PolygonWorldID...").start(); try { const data = execSync( - `forge script src/script/initialize/InitializeMockWorldID.s.sol:InitializeMockWorldID --fork-url ${config.ethereumRpcUrl} --broadcast --verify -vvvv`, + `forge script src/script/initialize/polygon/InitializePolygonWorldID.s.sol:InitializePolygonWorldID --fork-url ${config.polygonRpcUrl} --broadcast -vvvv --legacy`, ); console.log(data.toString()); } catch (err) { console.error(err); } - spinner.succeed("InitializeMockWorldID.s.sol ran successfully!"); + spinner.succeed("InitializePolygonWorldID.s.sol ran successfully!"); } -async function initializePolygonWorldID(config) { - const spinner = ora("Initializing PolygonWorldID...").start(); +/////////////////////////////////////////////////////////////////// +/// OWNERSHIP /// +/////////////////////////////////////////////////////////////////// + +async function localTransferOwnershipOfOpWorldIDToStateBridge(config) { + const spinner = ora("Transfering ownership of OpWorldID...").start(); try { const data = execSync( - `forge script src/script/initialize/InitializePolygonWorldID.s.sol:InitializePolygonWorldID --fork-url ${config.polygonRpcUrl} --broadcast -vvvv --legacy`, + `forge script src/script/initialize/op-stack/optimism/LocalTransferOwnershipOfOptimismWorldID.s.sol:LocalTransferOwnershipOfOptimismWorldID --fork-url ${config.optimismRpcUrl} \ + --broadcast -vvvv`, ); console.log(data.toString()); } catch (err) { console.error(err); } - spinner.succeed("InitializePolygonWorldID.s.sol ran successfully!"); + spinner.succeed("LocalTransferOwnershipOfOptimismWorldID.s.sol ran successfully!"); +} + +async function crossTransferOwnershipOfOptimismWorldIDToStateBridge(config) { + const spinner = ora("Transfering ownership of OpWorldID...").start(); + + try { + const data = execSync( + `forge script src/script/initialize/op-stack/optimism/CrossTransferOwnershipOfOptimismWorldID.s.sol:CrossTransferOwnershipOfOptimismWorldID --fork-url ${config.ethereumRpcUrl} \ + --broadcast -vvvv`, + ); + console.log(data.toString()); + } catch (err) { + console.error(err); + } + + spinner.succeed("CrossTransferOwnershipOfOptimismWorldID.s.sol ran successfully!"); } -async function transferOwnershipOfOpWorldIDGoerli(config) { - const spinner = ora("Transfering ownership of OpWorldID to StateBridge...").start(); +async function localTransferOwnershipOfBaseWorldIDToStateBridge(config) { + const spinner = ora("Transfering ownership of OpWorldID on Base...").start(); try { const data = execSync( - `forge script src/script/initialize/TransferOwnershipOfOpWorldIDGoerli.s.sol:TransferOwnershipOfOpWorldIDGoerli --fork-url ${config.optimismRpcUrl} \ + `forge script src/script/initialize/op-stack/base/LocalTransferOwnershipOfBaseWorldID.s.sol:LocalTransferOwnershipOfBaseWorldID --fork-url ${config.baseRpcUrl} \ --broadcast -vvvv`, ); console.log(data.toString()); @@ -530,15 +705,15 @@ async function transferOwnershipOfOpWorldIDGoerli(config) { console.error(err); } - spinner.succeed("TransferOwnershipOfOpWorldIDGoerli.s.sol ran successfully!"); + spinner.succeed("LocalTransferOwnershipOfBaseWorldID.s.sol ran successfully!"); } -async function transferOwnershipOfOpWorldIDMainnet(config) { - const spinner = ora("Transfering ownership of OpWorldID to StateBridge...").start(); +async function crossTransferOwnershipOfBaseWorldIDToStateBridge(config) { + const spinner = ora("Transfering ownership of OpWorldID on Base...").start(); try { const data = execSync( - `forge script src/script/initialize/TransferOwnershipOfOpWorldIDMainnet.s.sol:TransferOwnershipOfOpWorldIDMainnet --fork-url ${config.optimismRpcUrl} \ + `forge script src/script/initialize/op-stack/base/CrossTransferOwnershipOfBaseWorldID.s.sol:CrossTransferOwnershipOfBaseWorldID --fork-url ${config.ethereumRpcUrl} \ --broadcast -vvvv`, ); console.log(data.toString()); @@ -546,17 +721,19 @@ async function transferOwnershipOfOpWorldIDMainnet(config) { console.error(err); } - spinner.succeed("TransferOwnershipOfOpWorldIDMainnet.s.sol ran successfully!"); + spinner.succeed("CrossTransferOwnershipOfBaseWorldID.s.sol ran successfully!"); } -// Simple integration test for Mock WorldID +/////////////////////////////////////////////////////////////////// +/// MOCK /// +/////////////////////////////////////////////////////////////////// -async function sendStateRootToStateBridge(config) { - const spinner = ora("Sending test WorldID merkle tree root from MockWorldID to StateBridge...").start(); +async function propagateMockRoot(config) { + const spinner = ora("Propagating Mock Root...").start(); try { const data = execSync( - `forge script src/script/test/SendStateRootToStateBridge.s.sol:SendStateRootToStateBridge --fork-url ${config.ethereumRpcUrl} \ + `forge script src/script/test/PropagateMockRoot.s.sol:PropagateMockRoot --fork-url ${config.ethereumRpcUrl} \ --broadcast -vvvv`, ); console.log(data.toString()); @@ -564,8 +741,45 @@ async function sendStateRootToStateBridge(config) { console.error(err); } - spinner.succeed("SendStateRootToStateBridge.s.sol ran successfully!"); + spinner.succeed("PropagateMockRoot.s.sol ran successfully!"); +} + +/////////////////////////////////////////////////////////////////// +/// GAS LIMIT /// +/////////////////////////////////////////////////////////////////// + +async function setGasLimitOptimismStateBridge(config) { + const spinner = ora("Setting Optimism gas limits for the Optimism StateBridge...").start(); + + try { + const data = + execSync(`forge script src/script/initialize/op-stack/optimism/SetGasLimitOptimism.s.sol:SetOpGasLimitOptimism --fork-url ${config.ethereumRpcUrl} \ + --broadcast -vvvv`); + console.log(data.toString()); + } catch (err) { + console.error(err); + } + + spinner.succeed("SetGasLimitOptimism.s.sol ran successfully!"); +} + +async function setGasLimitBaseStateBridge(config) { + spinner = ora("Setting Base gas limits for the Base StateBridge...").start(); + + try { + const data = + execSync(`forge script src/script/initialize/op-stack/base/SetGasLimitBase.s.sol:SetOpGasLimitBase --fork-url ${config.ethereumRpcUrl} \ + --broadcast -vvvv`); + console.log(data.toString()); + } catch (err) { + console.error(err); + } + + spinner.succeed("SetGasLimitBase.s.sol ran successfully!"); } +/////////////////////////////////////////////////////////////////// +/// SCRIPT ORCHESTRATION /// +/////////////////////////////////////////////////////////////////// async function deploymentMainnet(config) { dotenv.config(); @@ -589,11 +803,16 @@ async function deploymentMainnet(config) { await getBaseWorldIDAddress(config); await getPolygonWorldIDAddress(config); await saveConfiguration(config); - await deployStateBridgeMainnet(config); - await getStateBridgeAddress(config); + await deployPolygonStateBridgeMainnet(config); + await deployOptimismOpStateBridgeMainnet(config); + await deployBaseOpStateBridgeMainnet(config); + await getOptimismStateBridgeAddress(config); + await getBaseStateBridgeAddress(config); + await getPolygonStateBridgeAddress(config); await saveConfiguration(config); await initializePolygonWorldID(config); - // await transferOwnershipOfOpWorldIDMainnet(config); + await localTransferOwnershipOfOpWorldIDToStateBridge(config); + await localTransferOwnershipOfBaseWorldIDToStateBridge(config); } async function deploymentTestnet(config) { @@ -618,11 +837,16 @@ async function deploymentTestnet(config) { await getBaseWorldIDAddress(config); await getPolygonWorldIDAddress(config); await saveConfiguration(config); - await deployStateBridgeGoerli(config); - await getStateBridgeAddress(config); + await deployPolygonStateBridgeGoerli(config); + await deployOptimismOpStateBridgeGoerli(config); + await deployBaseOpStateBridgeGoerli(config); + await getOptimismStateBridgeAddress(config); + await getBaseStateBridgeAddress(config); + await getPolygonStateBridgeAddress(config); await saveConfiguration(config); await initializePolygonWorldID(config); - // await transferOwnershipOfOpWorldIDGoerli(config); + await localTransferOwnershipOfOpWorldIDToStateBridge(config); + await localTransferOwnershipOfBaseWorldIDToStateBridge(config); } async function mockDeployment(config) { @@ -638,24 +862,28 @@ async function mockDeployment(config) { await getBaseEtherscanApiKey(config); await getPolygonscanApiKey(config); await getTreeDepth(config); + await getSampleRoot(config); await saveConfiguration(config); await deployMockWorldID(config); await deployOptimismWorldID(config); + await deployBaseWorldID(config); await deployPolygonWorldIDMumbai(config); await getWorldIDIdentityManagerAddress(config); await getOptimismWorldIDAddress(config); await getBaseWorldIDAddress(config); await getPolygonWorldIDAddress(config); await saveConfiguration(config); - await deployStateBridgeGoerli(config); - await getStateBridgeAddress(config); + await deployPolygonStateBridgeGoerli(config); + await deployOptimismOpStateBridgeGoerli(config); + await deployBaseOpStateBridgeGoerli(config); + await getOptimismStateBridgeAddress(config); + await getBaseStateBridgeAddress(config); + await getPolygonStateBridgeAddress(config); await saveConfiguration(config); await initializeMockWorldID(config); await initializePolygonWorldID(config); await transferOwnershipOfOpWorldIDGoerli(config); - await getNewRoot(config); await saveConfiguration(config); - await sendStateRootToStateBridge(config); } async function mockLocalDeployment(config) { @@ -670,58 +898,35 @@ async function mockLocalDeployment(config) { await getOptimismEtherscanApiKey(config); await getBaseEtherscanApiKey(config); await getPolygonscanApiKey(config); - await getTreeDepth(config); - await saveConfiguration(config); - await deployMockWorldID(config); - await deployMockOpPolygonWorldID(config); - await getWorldIDIdentityManagerAddress(config); - await getOptimismWorldIDAddress(config); - await getBaseWorldIDAddress(config); - await getPolygonWorldIDAddress(config); await saveConfiguration(config); await deployMockStateBridge(config); - await getStateBridgeAddress(config); - await saveConfiguration(config); - await initializeMockWorldID(config); - await getNewRoot(config); + await getMockStateBridgeAddress(config); await saveConfiguration(config); - await sendStateRootToStateBridge(config); + await propagateMockRoot(config); } async function setOpGasLimit(config) { dotenv.config(); + await getPrivateKey(config); await getEthereumRpcUrl(config); - await getOptimismWorldIDAddress(config); - await getOptimismRpcUrl(config); - await getBaseWorldIDAddress(config); - await getBaseRpcUrl(config); - await getDeployerAddress(config); - await saveConfiguration(config); + await getOptimismStateBridgeAddress(config); + await getBaseStateBridgeAddress(config); await getGasLimitSendRootOptimism(config); await getGasLimitSetRootHistoryExpiryOptimism(config); await getGasLimitTransferOwnershipOptimism(config); await getGasLimitSendRootBase(config); await getGasLimitSetRootHistoryExpiryBase(config); await getGasLimitTransferOwnershipBase(config); - - // await getOpGasLimitEstimates(config); await saveConfiguration(config); - - const spinner = ora("Setting Optimism gas limits for the StateBridge...").start(); - - try { - const data = - execSync(`forge script src/script/initialize/SetOpGasLimit.s.sol:SetOpGasLimit --fork-url ${config.ethereumRpcUrl} \ - --broadcast -vvvv`); - console.log(data.toString()); - } catch (err) { - console.error(err); - } - - spinner.succeed("SetOpGasLimit.s.sol ran successfully!"); + await setGasLimitOptimismStateBridge(config); + await setGasLimitBaseStateBridge(config); } +/////////////////////////////////////////////////////////////////// +/// CLI /// +/////////////////////////////////////////////////////////////////// + async function main() { const program = new Command(); diff --git a/src/script/deploy/DeployMockOpPolygonWorldID.s.sol b/src/script/deploy/mock/DeployMockBridgedWorldID.s.sol similarity index 71% rename from src/script/deploy/DeployMockOpPolygonWorldID.s.sol rename to src/script/deploy/mock/DeployMockBridgedWorldID.s.sol index bbfe286d..50474115 100644 --- a/src/script/deploy/DeployMockOpPolygonWorldID.s.sol +++ b/src/script/deploy/mock/DeployMockBridgedWorldID.s.sol @@ -2,14 +2,14 @@ pragma solidity ^0.8.15; import {Script} from "forge-std/Script.sol"; -import {MockOpPolygonWorldID} from "src/mock/MockOpPolygonWorldID.sol"; +import {MockBridgedWorldID} from "src/mock/MockBridgedWorldID.sol"; -/// @title Mock OpPolygonWorldID deployment script -/// @notice forge script to deploy MockOpPolygonWorldID.sol +/// @title MockBridgedWorldID deployment script +/// @notice forge script to deploy MockBridgedWorldID.sol /// @author Worldcoin /// @dev Can be executed by running `make local-mock`. -contract DeployMockOpPolygonWorldID is Script { - MockOpPolygonWorldID public opPolygonWorldID; +contract DeployMockBridgedWorldID is Script { + MockBridgedWorldID public mockBridgedWorldID; /////////////////////////////////////////////////////////////////// /// CONFIG /// @@ -24,7 +24,7 @@ contract DeployMockOpPolygonWorldID is Script { function run() external { vm.startBroadcast(privateKey); - opPolygonWorldID = new MockOpPolygonWorldID(treeDepth); + mockBridgedWorldID = new MockBridgedWorldID(treeDepth); vm.stopBroadcast(); } diff --git a/src/script/deploy/DeployMockStateBridge.s.sol b/src/script/deploy/mock/DeployMockStateBridge.s.sol similarity index 64% rename from src/script/deploy/DeployMockStateBridge.s.sol rename to src/script/deploy/mock/DeployMockStateBridge.s.sol index eaa86f84..b3ead019 100644 --- a/src/script/deploy/DeployMockStateBridge.s.sol +++ b/src/script/deploy/mock/DeployMockStateBridge.s.sol @@ -11,7 +11,6 @@ import {MockStateBridge} from "src/mock/MockStateBridge.sol"; contract DeployMockStateBridge is Script { MockStateBridge public bridge; - address public opWorldIDAddress; address public worldIDIdentityManagerAddress; address public stateBridgeAddress; @@ -24,19 +23,14 @@ contract DeployMockStateBridge is Script { uint256 public privateKey = abi.decode(vm.parseJson(json, ".privateKey"), (uint256)); - function setUp() public { - /////////////////////////////////////////////////////////////////// - /// WORLD ID /// - /////////////////////////////////////////////////////////////////// - worldIDIdentityManagerAddress = - abi.decode(vm.parseJson(json, ".worldIDIdentityManagerAddress"), (address)); - opWorldIDAddress = abi.decode(vm.parseJson(json, ".optimismWorldIDAddress"), (address)); - } + function setUp() public {} function run() public { vm.startBroadcast(privateKey); - bridge = new MockStateBridge(worldIDIdentityManagerAddress, opWorldIDAddress); + bridge = new MockStateBridge(); + + bridge.propagateRoot(); vm.stopBroadcast(); } diff --git a/src/script/deploy/DeployMockWorldID.s.sol b/src/script/deploy/mock/DeployMockWorldID.s.sol similarity index 89% rename from src/script/deploy/DeployMockWorldID.s.sol rename to src/script/deploy/mock/DeployMockWorldID.s.sol index b82d49ad..f64bad63 100644 --- a/src/script/deploy/DeployMockWorldID.s.sol +++ b/src/script/deploy/mock/DeployMockWorldID.s.sol @@ -22,11 +22,12 @@ contract DeployMockWorldID is Script { string public json = vm.readFile(path); uint256 public privateKey = abi.decode(vm.parseJson(json, ".privateKey"), (uint256)); + uint256 public sampleRoot = abi.decode(vm.parseJson(json, ".sampleRoot"), (uint256)); function run() external { vm.startBroadcast(privateKey); - worldID = new WorldIDIdentityManagerMock(); + worldID = new WorldIDIdentityManagerMock(sampleRoot); vm.stopBroadcast(); } diff --git a/src/script/deploy/DeployOpWorldID.s.sol b/src/script/deploy/op-stack/DeployOpWorldID.s.sol similarity index 79% rename from src/script/deploy/DeployOpWorldID.s.sol rename to src/script/deploy/op-stack/DeployOpWorldID.s.sol index d90f86d2..abbbbcd2 100644 --- a/src/script/deploy/DeployOpWorldID.s.sol +++ b/src/script/deploy/op-stack/DeployOpWorldID.s.sol @@ -1,14 +1,11 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.15; -/// @dev Demo deployments -/// @custom:deployment Optimism Goerli (420) 0x0ed95bda37cc9c14596adba8bf37fc60e2fd9080 -/// @custom:link https://goerli-optimism.etherscan.io/address/0x0ed95bda37cc9c14596adba8bf37fc60e2fd9080 import {Script} from "forge-std/Script.sol"; import {OpWorldID} from "src/OpWorldID.sol"; /// @title OpWorldID deployment script -/// @notice forge script to deploy OpWorldID.sol +/// @notice forge script to deploy OpWorldID.sol to an OP Stack chain /// @author Worldcoin /// @dev Can be executed by running `make mock`, `make deploy` or `make deploy-testnet`. contract DeployOpWorldID is Script { diff --git a/src/script/deploy/op-stack/base/DeployBaseStateBridgeGoerli.s.sol b/src/script/deploy/op-stack/base/DeployBaseStateBridgeGoerli.s.sol new file mode 100644 index 00000000..d25cf263 --- /dev/null +++ b/src/script/deploy/op-stack/base/DeployBaseStateBridgeGoerli.s.sol @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.15; + +import {Script} from "forge-std/Script.sol"; +import {OpStateBridge} from "src/OpStateBridge.sol"; + +/// @title Base State Bridge deployment script +/// @notice forge script to deploy OpStateBridge.sol on Base +/// @author Worldcoin +/// @dev Can be executed by running `make mock`, `make local-mock`, `make deploy` or `make deploy-testnet`. +contract DeployBaseStateBridgeGoerli is Script { + OpStateBridge public bridge; + + address public baseWorldIDAddress; + address public worldIDIdentityManagerAddress; + address public baseCrossDomainMessengerAddress; + + /////////////////////////////////////////////////////////////////// + /// CONFIG /// + /////////////////////////////////////////////////////////////////// + string public root = vm.projectRoot(); + string public path = string.concat(root, "/src/script/.deploy-config.json"); + string public json = vm.readFile(path); + + uint256 public privateKey = abi.decode(vm.parseJson(json, ".privateKey"), (uint256)); + + function setUp() public { + /////////////////////////////////////////////////////////////////// + /// BASE /// + /////////////////////////////////////////////////////////////////// + // Taken from https://docs.base.org/base-contracts + baseCrossDomainMessengerAddress = address(0x8e5693140eA606bcEB98761d9beB1BC87383706D); + + /////////////////////////////////////////////////////////////////// + /// WORLD ID /// + /////////////////////////////////////////////////////////////////// + worldIDIdentityManagerAddress = + abi.decode(vm.parseJson(json, ".worldIDIdentityManagerAddress"), (address)); + baseWorldIDAddress = abi.decode(vm.parseJson(json, ".baseWorldIDAddress"), (address)); + } + + function run() public { + vm.startBroadcast(privateKey); + + bridge = new OpStateBridge( + worldIDIdentityManagerAddress, + baseWorldIDAddress, + baseCrossDomainMessengerAddress + ); + + vm.stopBroadcast(); + } +} diff --git a/src/script/deploy/op-stack/base/DeployBaseStateBridgeMainnet.s.sol b/src/script/deploy/op-stack/base/DeployBaseStateBridgeMainnet.s.sol new file mode 100644 index 00000000..f3a92723 --- /dev/null +++ b/src/script/deploy/op-stack/base/DeployBaseStateBridgeMainnet.s.sol @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.15; + +import {Script} from "forge-std/Script.sol"; +import {OpStateBridge} from "src/OpStateBridge.sol"; + +/// @title Deploy Base State Bridge +/// @notice forge script to deploy OpStateBridge contract for Base +/// @author Worldcoin +/// @dev Can be executed by running `make mock`, `make deploy` or `make deploy-testnet`. +contract DeployBaseStateBridgeMainnet is Script { + OpStateBridge public bridge; + + address public baseWorldIDAddress; + address public worldIDIdentityManagerAddress; + address public baseCrossDomainMessengerAddress; + + /////////////////////////////////////////////////////////////////// + /// CONFIG /// + /////////////////////////////////////////////////////////////////// + string public root = vm.projectRoot(); + string public path = string.concat(root, "/src/script/.deploy-config.json"); + string public json = vm.readFile(path); + + uint256 public privateKey = abi.decode(vm.parseJson(json, ".privateKey"), (uint256)); + + function setUp() public { + /////////////////////////////////////////////////////////////////// + /// BASE /// + /////////////////////////////////////////////////////////////////// + // Taken from https://docs.base.org/base-contracts + baseCrossDomainMessengerAddress = address(0x866E82a600A1414e583f7F13623F1aC5d58b0Afa); + + /////////////////////////////////////////////////////////////////// + /// WORLD ID /// + /////////////////////////////////////////////////////////////////// + worldIDIdentityManagerAddress = + abi.decode(vm.parseJson(json, ".worldIDIdentityManagerAddress"), (address)); + baseWorldIDAddress = abi.decode(vm.parseJson(json, ".baseWorldIDAddress"), (address)); + } + + function run() public { + vm.startBroadcast(privateKey); + + bridge = new OpStateBridge( + worldIDIdentityManagerAddress, + baseWorldIDAddress, + baseCrossDomainMessengerAddress + ); + + vm.stopBroadcast(); + } +} diff --git a/src/script/deploy/op-stack/optimism/DeployOptimismStateBridgeGoerli.s.sol b/src/script/deploy/op-stack/optimism/DeployOptimismStateBridgeGoerli.s.sol new file mode 100644 index 00000000..f2c99960 --- /dev/null +++ b/src/script/deploy/op-stack/optimism/DeployOptimismStateBridgeGoerli.s.sol @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.15; + +/// @dev Demo deployments +import {Script} from "forge-std/Script.sol"; +import {OpStateBridge} from "src/OpStateBridge.sol"; + +/// @title Optimism State Bridge deployment script +/// @notice forge script to deploy StateBridge.sol on Optimism +/// @author Worldcoin +/// @dev Can be executed by running `make mock`, `make local-mock`, `make deploy` or `make deploy-testnet`. +contract DeployOpStateBridgeGoerli is Script { + OpStateBridge public bridge; + + address public opWorldIDAddress; + address public worldIDIdentityManagerAddress; + address public opCrossDomainMessengerAddress; + + /////////////////////////////////////////////////////////////////// + /// CONFIG /// + /////////////////////////////////////////////////////////////////// + string public root = vm.projectRoot(); + string public path = string.concat(root, "/src/script/.deploy-config.json"); + string public json = vm.readFile(path); + + uint256 public privateKey = abi.decode(vm.parseJson(json, ".privateKey"), (uint256)); + + function setUp() public { + /////////////////////////////////////////////////////////////////// + /// OPTIMISM /// + /////////////////////////////////////////////////////////////////// + opCrossDomainMessengerAddress = address(0x5086d1eEF304eb5284A0f6720f79403b4e9bE294); + + /////////////////////////////////////////////////////////////////// + /// WORLD ID /// + /////////////////////////////////////////////////////////////////// + worldIDIdentityManagerAddress = + abi.decode(vm.parseJson(json, ".worldIDIdentityManagerAddress"), (address)); + opWorldIDAddress = abi.decode(vm.parseJson(json, ".optimismWorldIDAddress"), (address)); + } + + function run() public { + vm.startBroadcast(privateKey); + + bridge = new OpStateBridge ( + worldIDIdentityManagerAddress, + opWorldIDAddress, + opCrossDomainMessengerAddress + ); + + vm.stopBroadcast(); + } +} diff --git a/src/script/deploy/op-stack/optimism/DeployOptimismStateBridgeMainnet.s.sol b/src/script/deploy/op-stack/optimism/DeployOptimismStateBridgeMainnet.s.sol new file mode 100644 index 00000000..95e8fc12 --- /dev/null +++ b/src/script/deploy/op-stack/optimism/DeployOptimismStateBridgeMainnet.s.sol @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.15; + +import {Script} from "forge-std/Script.sol"; +import {OpStateBridge} from "src/OpStateBridge.sol"; + +/// @title Deploy State Bridge Optimism +/// @notice forge script to deploy OpStateBridge.sol on Ethereum mainnet +/// @author Worldcoin +/// @dev Can be executed by running `make mock`, `make deploy` or `make deploy-testnet`. +contract DeployOpStateBridgeMainnet is Script { + OpStateBridge public bridge; + + address public opWorldIDAddress; + address public worldIDIdentityManagerAddress; + address public opCrossDomainMessengerAddress; + + /////////////////////////////////////////////////////////////////// + /// CONFIG /// + /////////////////////////////////////////////////////////////////// + string public root = vm.projectRoot(); + string public path = string.concat(root, "/src/script/.deploy-config.json"); + string public json = vm.readFile(path); + + uint256 public privateKey = abi.decode(vm.parseJson(json, ".privateKey"), (uint256)); + + function setUp() public { + /////////////////////////////////////////////////////////////////// + /// OPTIMISM /// + /////////////////////////////////////////////////////////////////// + opCrossDomainMessengerAddress = address(0x25ace71c97B33Cc4729CF772ae268934F7ab5fA1); + + /////////////////////////////////////////////////////////////////// + /// WORLD ID /// + /////////////////////////////////////////////////////////////////// + worldIDIdentityManagerAddress = + abi.decode(vm.parseJson(json, ".worldIDIdentityManagerAddress"), (address)); + opWorldIDAddress = abi.decode(vm.parseJson(json, ".optimismWorldIDAddress"), (address)); + } + + function run() public { + vm.startBroadcast(privateKey); + + bridge = new OpStateBridge ( + worldIDIdentityManagerAddress, + opWorldIDAddress, + opCrossDomainMessengerAddress + ); + + vm.stopBroadcast(); + } +} diff --git a/src/script/deploy/DeployStateBridgeGoerli.s.sol b/src/script/deploy/polygon/DeployPolygonStateBridgeGoerli.s.sol similarity index 57% rename from src/script/deploy/DeployStateBridgeGoerli.s.sol rename to src/script/deploy/polygon/DeployPolygonStateBridgeGoerli.s.sol index 5b811b38..e8453a84 100644 --- a/src/script/deploy/DeployStateBridgeGoerli.s.sol +++ b/src/script/deploy/polygon/DeployPolygonStateBridgeGoerli.s.sol @@ -1,26 +1,19 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.15; -/// @dev Demo deployments -/// @custom:deployment Goerli 0x1ca56798e14fc4cb75de85cc1d465231eaf242e3 -/// @custom:link https://goerli.etherscan.io/address/0x1ca56798e14fc4cb75de85cc1d465231eaf242e3 import {Script} from "forge-std/Script.sol"; -import {StateBridge} from "src/StateBridge.sol"; +import {PolygonStateBridge} from "src/PolygonStateBridge.sol"; -/// @title State Bridge deployment script +/// @title PolygonState Bridge deployment script /// @notice forge script to deploy StateBridge.sol /// @author Worldcoin /// @dev Can be executed by running `make mock`, `make local-mock`, `make deploy` or `make deploy-testnet`. -contract DeployStateBridge is Script { - StateBridge public bridge; +contract DeployPolygonStateBridgeGoerli is Script { + PolygonStateBridge public bridge; address public opWorldIDAddress; address public polygonWorldIDAddress; - address public baseWorldIDAddress; address public worldIDIdentityManagerAddress; - address public opCrossDomainMessengerAddress; - address public baseCrossDomainMessengerAddress; - address public stateBridgeAddress; address public checkpointManagerAddress; address public fxRootAddress; @@ -46,38 +39,21 @@ contract DeployStateBridge is Script { // FxRoot fxRootAddress = address(0x3d1d3E34f7fB6D26245E6640E1c50710eFFf15bA); - /////////////////////////////////////////////////////////////////// - /// OPTIMISM /// - /////////////////////////////////////////////////////////////////// - opCrossDomainMessengerAddress = address(0x5086d1eEF304eb5284A0f6720f79403b4e9bE294); - - /////////////////////////////////////////////////////////////////// - /// BASE /// - /////////////////////////////////////////////////////////////////// - // Taken from https://docs.base.org/base-contracts - baseCrossDomainMessengerAddress = address(0x8e5693140eA606bcEB98761d9beB1BC87383706D); - /////////////////////////////////////////////////////////////////// /// WORLD ID /// /////////////////////////////////////////////////////////////////// worldIDIdentityManagerAddress = abi.decode(vm.parseJson(json, ".worldIDIdentityManagerAddress"), (address)); - opWorldIDAddress = abi.decode(vm.parseJson(json, ".optimismWorldIDAddress"), (address)); - baseWorldIDAddress = abi.decode(vm.parseJson(json, ".baseWorldIDAddress"), (address)); polygonWorldIDAddress = abi.decode(vm.parseJson(json, ".polygonWorldIDAddress"), (address)); } function run() public { vm.startBroadcast(privateKey); - bridge = new StateBridge( + bridge = new PolygonStateBridge ( checkpointManagerAddress, fxRootAddress, - worldIDIdentityManagerAddress, - opWorldIDAddress, - opCrossDomainMessengerAddress, - baseWorldIDAddress, - baseCrossDomainMessengerAddress + worldIDIdentityManagerAddress ); bridge.setFxChildTunnel(polygonWorldIDAddress); diff --git a/src/script/deploy/DeployStateBridgeMainnet.s.sol b/src/script/deploy/polygon/DeployPolygonStateBridgeMainnet.s.sol similarity index 57% rename from src/script/deploy/DeployStateBridgeMainnet.s.sol rename to src/script/deploy/polygon/DeployPolygonStateBridgeMainnet.s.sol index 3bb830fc..c8335094 100644 --- a/src/script/deploy/DeployStateBridgeMainnet.s.sol +++ b/src/script/deploy/polygon/DeployPolygonStateBridgeMainnet.s.sol @@ -2,23 +2,17 @@ pragma solidity ^0.8.15; import {Script} from "forge-std/Script.sol"; -import {StateBridge} from "src/StateBridge.sol"; +import {PolygonStateBridge} from "src/PolygonStateBridge.sol"; -/// @title PolygonWorldID deployment script on Polygon Mumbai -/// @notice forge script to deploy PolygonWorldID.sol +/// @title Deploy PolygonStateBridge on Mainnet +/// @notice forge script to deploy PolygonStateBridge.sol /// @author Worldcoin /// @dev Can be executed by running `make mock`, `make deploy` or `make deploy-testnet`. -contract DeployStateBridge is Script { - StateBridge public bridge; +contract DeployPolygonStateBridgeMainnet is Script { + PolygonStateBridge public bridge; - address public opWorldIDAddress; - address public polygonWorldIDAddress; - address public baseWorldIDAddress; address public worldIDIdentityManagerAddress; - address public opCrossDomainMessengerAddress; - address public baseCrossDomainMessengerAddress; - address public stateBridgeAddress; - + address public polygonWorldIDAddress; address public checkpointManagerAddress; address public fxRootAddress; @@ -42,38 +36,21 @@ contract DeployStateBridge is Script { // FxRoot fxRootAddress = address(0xfe5e5D361b2ad62c541bAb87C45a0B9B018389a2); - /////////////////////////////////////////////////////////////////// - /// OPTIMISM /// - /////////////////////////////////////////////////////////////////// - opCrossDomainMessengerAddress = address(0x25ace71c97B33Cc4729CF772ae268934F7ab5fA1); - - /////////////////////////////////////////////////////////////////// - /// BASE /// - /////////////////////////////////////////////////////////////////// - // Taken from https://docs.base.org/base-contracts - baseCrossDomainMessengerAddress = address(0x866E82a600A1414e583f7F13623F1aC5d58b0Afa); - /////////////////////////////////////////////////////////////////// /// WORLD ID /// /////////////////////////////////////////////////////////////////// worldIDIdentityManagerAddress = abi.decode(vm.parseJson(json, ".worldIDIdentityManagerAddress"), (address)); - opWorldIDAddress = abi.decode(vm.parseJson(json, ".optimismWorldIDAddress"), (address)); polygonWorldIDAddress = abi.decode(vm.parseJson(json, ".polygonWorldIDAddress"), (address)); - baseWorldIDAddress = abi.decode(vm.parseJson(json, ".baseWorldIDAddress"), (address)); } function run() public { vm.startBroadcast(privateKey); - bridge = new StateBridge( + bridge = new PolygonStateBridge( checkpointManagerAddress, fxRootAddress, - worldIDIdentityManagerAddress, - opWorldIDAddress, - opCrossDomainMessengerAddress, - baseWorldIDAddress, - baseCrossDomainMessengerAddress + worldIDIdentityManagerAddress ); bridge.setFxChildTunnel(polygonWorldIDAddress); diff --git a/src/script/deploy/DeployPolygonWorldIDMainnet.s.sol b/src/script/deploy/polygon/DeployPolygonWorldIDMainnet.s.sol similarity index 93% rename from src/script/deploy/DeployPolygonWorldIDMainnet.s.sol rename to src/script/deploy/polygon/DeployPolygonWorldIDMainnet.s.sol index 6a23ac7d..0d68e70e 100644 --- a/src/script/deploy/DeployPolygonWorldIDMainnet.s.sol +++ b/src/script/deploy/polygon/DeployPolygonWorldIDMainnet.s.sol @@ -12,7 +12,7 @@ contract DeployPolygonWorldID is Script { address public stateBridgeAddress; // Polygon PoS Mainnet Child Tunnel - address fxChildAddress = address(0x8397259c983751DAf40400790063935a11afa28a); + address public fxChildAddress = address(0x8397259c983751DAf40400790063935a11afa28a); PolygonWorldID public polygonWorldId; uint256 public privateKey; diff --git a/src/script/deploy/DeployPolygonWorldIDMumbai.s.sol b/src/script/deploy/polygon/DeployPolygonWorldIDMumbai.s.sol similarity index 87% rename from src/script/deploy/DeployPolygonWorldIDMumbai.s.sol rename to src/script/deploy/polygon/DeployPolygonWorldIDMumbai.s.sol index b68b01e6..1fd79e74 100644 --- a/src/script/deploy/DeployPolygonWorldIDMumbai.s.sol +++ b/src/script/deploy/polygon/DeployPolygonWorldIDMumbai.s.sol @@ -1,9 +1,6 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.15; -/// @dev Demo deployments -/// @custom:deployment Polygon Mumbai 0x771ef55049f02f08101f68c6e71653ab920a98e9 -/// @custom:link https://mumbai.polygonscan.com/address/0x771ef55049f02f08101f68c6e71653ab920a98e9#code import {Script} from "forge-std/Script.sol"; import {PolygonWorldID} from "src/PolygonWorldID.sol"; diff --git a/src/script/initialize/TransferOwnershipOfOpWorldIDMainnet.s.sol b/src/script/initialize/TransferOwnershipOfOpWorldIDMainnet.s.sol deleted file mode 100644 index 8dcca90a..00000000 --- a/src/script/initialize/TransferOwnershipOfOpWorldIDMainnet.s.sol +++ /dev/null @@ -1,73 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.15; - -import {Script} from "forge-std/Script.sol"; -import {OpWorldID} from "src/OpWorldID.sol"; -import {ICrossDomainMessenger} from - "@eth-optimism/contracts/libraries/bridge/ICrossDomainMessenger.sol"; -import {ICrossDomainOwnable3} from "src/interfaces/ICrossDomainOwnable3.sol"; - -/// @title Ownership Transfer of OpWorldID script for Mainnet -/// @notice forge script for transferring ownership of OpWorldID to an local (Optimism) -/// or cross-chain (Ethereum) EOA or contract -/// @author Worldcoin -/// @dev Can be executed by running `make mock`, `make local-mock`, `make deploy` or `make deploy-testnet`. -contract TransferOwnershipOfOpWorldIDMainnet is Script { - address public stateBridgeAddress; - address public opWorldIDAddress; - address public immutable crossDomainMessengerAddress; - uint256 public privateKey; - - OpWorldID public opWorldID; - - /// @notice in CrossDomainOwnable3.sol, isLocal is used to set ownership to a new address with a toggle - /// for local or cross domain (using the CrossDomainMessenger to pass messages) - bool public isLocal; - - function setUp() public { - /////////////////////////////////////////////////////////////////// - /// CONFIG /// - /////////////////////////////////////////////////////////////////// - string memory root = vm.projectRoot(); - string memory path = string.concat(root, "/src/script/.deploy-config.json"); - string memory json = vm.readFile(path); - - privateKey = abi.decode(vm.parseJson(json, ".privateKey"), (uint256)); - opWorldIDAddress = abi.decode(vm.parseJson(json, ".optimismWorldIDAddress"), (address)); - stateBridgeAddress = abi.decode(vm.parseJson(json, ".stateBridgeAddress"), (address)); - } - - constructor() { - /////////////////////////////////////////////////////////////////// - /// MAINNET /// - /////////////////////////////////////////////////////////////////// - crossDomainMessengerAddress = address(0x25ace71c97B33Cc4729CF772ae268934F7ab5fA1); - } - - function run() public { - /// @notice cross domain ownership flag - /// false = cross domain (address on Ethereum) - /// true = local (address on Optimism) - isLocal = false; - - vm.startBroadcast(privateKey); - - crossDomainTransferOwnership(stateBridgeAddress, isLocal); - - vm.stopBroadcast(); - } - - function crossDomainTransferOwnership(address newOwner, bool _isLocal) internal { - bytes memory message; - - message = abi.encodeCall(ICrossDomainOwnable3.transferOwnership, (newOwner, _isLocal)); - - // ICrossDomainMessenger is an interface for the L1 Messenger contract deployed on Goerli address - ICrossDomainMessenger(crossDomainMessengerAddress).sendMessage( - // Contract address on Optimism - opWorldIDAddress, - message, - 1000000 // within the free gas limit - ); - } -} diff --git a/src/script/initialize/op-stack/base/CrossTransferOwnershipOfBaseWorldID.s.sol b/src/script/initialize/op-stack/base/CrossTransferOwnershipOfBaseWorldID.s.sol new file mode 100644 index 00000000..a0f4d27a --- /dev/null +++ b/src/script/initialize/op-stack/base/CrossTransferOwnershipOfBaseWorldID.s.sol @@ -0,0 +1,59 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.15; + +import {Script} from "forge-std/Script.sol"; +import {OpWorldID} from "src/OpWorldID.sol"; +import {ICrossDomainOwnable3} from "src/interfaces/ICrossDomainOwnable3.sol"; +import {IOpStateBridgeTransferOwnership} from "src/interfaces/IOpStateBridgeTransferOwnership.sol"; +import {OpStateBridge} from "src/OpStateBridge.sol"; + +/// @title Ownership Transfer of OpWorldID on Base script +/// @notice forge script for transferring ownership of OpWorldID to a local (Base) +/// or cross-chain (Ethereum) EOA or contract +/// @author Worldcoin +/// @dev Can be executed by running `make mock`, `make local-mock`, `make deploy` or `make deploy-testnet`. +contract CrossTransferOwnershipOfBaseWorldID is Script { + uint256 public privateKey; + + address public baseStateBridgeAddress; + + address public newOwner; + + /// @notice in CrossDomainOwnable3.sol, isLocal is used to set ownership to a new address with a toggle + /// for local or cross domain (using the CrossDomainMessenger to pass messages) + bool public isLocal; + + function setUp() public { + /////////////////////////////////////////////////////////////////// + /// CONFIG /// + /////////////////////////////////////////////////////////////////// + string memory root = vm.projectRoot(); + string memory path = string.concat(root, "/src/script/.deploy-config.json"); + string memory json = vm.readFile(path); + + privateKey = abi.decode(vm.parseJson(json, ".privateKey"), (uint256)); + baseStateBridgeAddress = + abi.decode(vm.parseJson(json, ".baseStateBridgeAddress"), (address)); + newOwner = abi.decode(vm.parseJson(json, ".baseStateBridgeAddress"), (address)); + } + + constructor() {} + + function run() public { + /// @notice cross domain ownership flag + /// false = cross domain (address on Ethereum) + /// true = local (address on Optimism) + isLocal = false; + + vm.startBroadcast(privateKey); + + bytes memory call = + abi.encodeCall(IOpStateBridgeTransferOwnership.transferOwnershipOp, (newOwner, isLocal)); + + (bool ok,) = baseStateBridgeAddress.call(call); + + require(ok, "call failed"); + + vm.stopBroadcast(); + } +} diff --git a/src/script/initialize/op-stack/base/LocalTransferOwnershipOfBaseWorldID.s.sol b/src/script/initialize/op-stack/base/LocalTransferOwnershipOfBaseWorldID.s.sol new file mode 100644 index 00000000..91efe5cf --- /dev/null +++ b/src/script/initialize/op-stack/base/LocalTransferOwnershipOfBaseWorldID.s.sol @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.15; + +import {Script} from "forge-std/Script.sol"; +import {ICrossDomainOwnable3} from "src/interfaces/ICrossDomainOwnable3.sol"; +import {OpStateBridge} from "src/OpStateBridge.sol"; + +/// @title Ownership Transfer of OpWorldID on Base +/// @notice forge script for transferring ownership of OpWorldID to a local (Base / Base Goerli) +/// or cross-chain (Ethereum / Ethereum goerli) EOA or contract +/// @author Worldcoin +/// @dev Can be executed by running `make mock`, `make local-mock`, `make deploy` or `make deploy-testnet`. +contract LocalTransferOwnershipOfBaseWorldID is Script { + uint256 public privateKey; + + address public baseWorldIDAddress; + + address public newOwner; + + /// @notice in CrossDomainOwnable3.sol, isLocal is used to set ownership to a new address with a toggle + /// for local or cross domain (using the CrossDomainMessenger to pass messages) + bool public isLocal; + + uint32 public opGasLimit; + + function setUp() public { + /////////////////////////////////////////////////////////////////// + /// CONFIG /// + /////////////////////////////////////////////////////////////////// + string memory root = vm.projectRoot(); + string memory path = string.concat(root, "/src/script/.deploy-config.json"); + string memory json = vm.readFile(path); + + privateKey = abi.decode(vm.parseJson(json, ".privateKey"), (uint256)); + baseWorldIDAddress = abi.decode(vm.parseJson(json, ".baseWorldIDAddress"), (address)); + newOwner = abi.decode(vm.parseJson(json, ".baseStateBridgeAddress"), (address)); + } + + constructor() {} + + function run() public { + /// @notice cross domain ownership flag + /// false = cross domain (address on Ethereum) + /// true = local (address on Optimism) + isLocal = false; + + vm.startBroadcast(privateKey); + + bytes memory call = + abi.encodeCall(ICrossDomainOwnable3.transferOwnership, (newOwner, isLocal)); + + (bool ok,) = baseWorldIDAddress.call(call); + + require(ok, "call failed"); + + vm.stopBroadcast(); + } +} diff --git a/src/script/initialize/op-stack/base/SetGasLimitBase.s.sol b/src/script/initialize/op-stack/base/SetGasLimitBase.s.sol new file mode 100644 index 00000000..72689103 --- /dev/null +++ b/src/script/initialize/op-stack/base/SetGasLimitBase.s.sol @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.15; + +import {Script} from "forge-std/Script.sol"; +import {OpStateBridge} from "src/OpStateBridge.sol"; + +/// @title Base State Bridge Gas Limit setter +/// @notice forge script to set the correct gas limits for crossDomainMessenger calls to Base for the StateBridge +/// @author Worldcoin +/// @dev Can be executed by running `make set-base-gas-limit` +contract SetOpGasLimitBase is Script { + address public baseStateBridgeAddress; + + OpStateBridge public baseStateBridge; + + /////////////////////////////////////////////////////////////////// + /// CONFIG /// + /////////////////////////////////////////////////////////////////// + string public root = vm.projectRoot(); + string public path = string.concat(root, "/src/script/.deploy-config.json"); + string public json = vm.readFile(path); + + uint256 public privateKey = abi.decode(vm.parseJson(json, ".privateKey"), (uint256)); + + /////////////////////////////////////////////////////////////////// + /// OP GAS LIMITS /// + /////////////////////////////////////////////////////////////////// + uint32 public gasLimitSendRootBase = + abi.decode(vm.parseJson(json, ".gasLimitSendRootBase"), (uint32)); + uint32 public gasLimitSetRootHistoryExpiryBase = + abi.decode(vm.parseJson(json, ".gasLimitSetRootHistoryExpiryBase"), (uint32)); + uint32 public gasLimitTransferOwnershipBase = + abi.decode(vm.parseJson(json, ".gasLimitTransferOwnershipBase"), (uint32)); + + function setUp() public { + baseStateBridgeAddress = + abi.decode(vm.parseJson(json, ".baseStateBridgeAddress"), (address)); + + baseStateBridge = OpStateBridge(baseStateBridgeAddress); + } + + function run() public { + vm.startBroadcast(privateKey); + + baseStateBridge.setGasLimitSendRoot(gasLimitSendRootBase); + baseStateBridge.setGasLimitSetRootHistoryExpiry(gasLimitSetRootHistoryExpiryBase); + baseStateBridge.setGasLimitTransferOwnershipOp(gasLimitTransferOwnershipBase); + + vm.stopBroadcast(); + } +} diff --git a/src/script/initialize/op-stack/optimism/CrossTransferOwnershipOfOptimismWorldID.s.sol b/src/script/initialize/op-stack/optimism/CrossTransferOwnershipOfOptimismWorldID.s.sol new file mode 100644 index 00000000..a0f61684 --- /dev/null +++ b/src/script/initialize/op-stack/optimism/CrossTransferOwnershipOfOptimismWorldID.s.sol @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.15; + +import {Script} from "forge-std/Script.sol"; +import {OpWorldID} from "src/OpWorldID.sol"; +import {ICrossDomainOwnable3} from "src/interfaces/ICrossDomainOwnable3.sol"; +import {IOpStateBridgeTransferOwnership} from "src/interfaces/IOpStateBridgeTransferOwnership.sol"; +import {OpStateBridge} from "src/OpStateBridge.sol"; + +/// @title Ownership Transfer of OpWorldID script on Optimism +/// @notice forge script for transferring ownership of OpWorldID to a local (Optimism) +/// or cross-chain (Ethereum) EOA or contract +/// @author Worldcoin +/// @dev Can be executed by running `make mock`, `make local-mock`, `make deploy` or `make deploy-testnet`. +contract CrossTransferOwnershipOfOptimismWorldID is Script { + uint256 public privateKey; + + address public optimismStateBridgeAddress; + + address public newOwner; + + /// @notice in CrossDomainOwnable3.sol, isLocal is used to set ownership to a new address with a toggle + /// for local or cross domain (using the CrossDomainMessenger to pass messages) + bool public isLocal; + + uint32 public opGasLimit; + + function setUp() public { + /////////////////////////////////////////////////////////////////// + /// CONFIG /// + /////////////////////////////////////////////////////////////////// + string memory root = vm.projectRoot(); + string memory path = string.concat(root, "/src/script/.deploy-config.json"); + string memory json = vm.readFile(path); + + privateKey = abi.decode(vm.parseJson(json, ".privateKey"), (uint256)); + optimismStateBridgeAddress = + abi.decode(vm.parseJson(json, ".optimismStateBridgeAddress"), (address)); + newOwner = abi.decode(vm.parseJson(json, ".optimismStateBridgeAddress"), (address)); + } + + constructor() {} + + function run() public { + /// @notice cross domain ownership flag + /// false = cross domain (address on Ethereum) + /// true = local (address on Optimism) + isLocal = false; + + vm.startBroadcast(privateKey); + + bytes memory call = + abi.encodeCall(IOpStateBridgeTransferOwnership.transferOwnershipOp, (newOwner, isLocal)); + + (bool ok,) = optimismStateBridgeAddress.call(call); + + require(ok, "call failed"); + + vm.stopBroadcast(); + } +} diff --git a/src/script/initialize/TransferOwnershipOfOpWorldIDGoerli.s.sol b/src/script/initialize/op-stack/optimism/LocalTransferOwnershipOfOptimismWorldID.s.sol similarity index 55% rename from src/script/initialize/TransferOwnershipOfOpWorldIDGoerli.s.sol rename to src/script/initialize/op-stack/optimism/LocalTransferOwnershipOfOptimismWorldID.s.sol index b390d11f..37430877 100644 --- a/src/script/initialize/TransferOwnershipOfOpWorldIDGoerli.s.sol +++ b/src/script/initialize/op-stack/optimism/LocalTransferOwnershipOfOptimismWorldID.s.sol @@ -2,26 +2,20 @@ pragma solidity ^0.8.15; import {Script} from "forge-std/Script.sol"; -import {OpWorldID} from "src/OpWorldID.sol"; -import {ICrossDomainMessenger} from - "@eth-optimism/contracts/libraries/bridge/ICrossDomainMessenger.sol"; import {ICrossDomainOwnable3} from "src/interfaces/ICrossDomainOwnable3.sol"; -import {StateBridge} from "src/StateBridge.sol"; +import {OpStateBridge} from "src/OpStateBridge.sol"; -/// @title Ownership Transfer of OpWorldID script for testnet -/// @notice forge script for transferring ownership of OpWorldID to a local (Optimism Goerli) +/// @title Ownership Transfer of OpWorldID script for Optimism +/// @notice forge script for transferring ownership of OpWorldID to a local (Optimism) /// or cross-chain (Ethereum Goerli) EOA or contract /// @author Worldcoin /// @dev Can be executed by running `make mock`, `make local-mock`, `make deploy` or `make deploy-testnet`. -contract TransferOwnershipOfOpWorldIDGoerli is Script { - address public stateBridgeAddress; - address public opWorldIDAddress; - address public immutable crossDomainMessengerAddress; +contract LocalTransferOwnershipOfOptimismWorldID is Script { uint256 public privateKey; - OpWorldID public opWorldID; + address public optimismWorldIDAddress; - StateBridge stateBridge; + address public newOwner; /// @notice in CrossDomainOwnable3.sol, isLocal is used to set ownership to a new address with a toggle /// for local or cross domain (using the CrossDomainMessenger to pass messages) @@ -38,17 +32,12 @@ contract TransferOwnershipOfOpWorldIDGoerli is Script { string memory json = vm.readFile(path); privateKey = abi.decode(vm.parseJson(json, ".privateKey"), (uint256)); - opWorldIDAddress = abi.decode(vm.parseJson(json, ".optimismWorldIDAddress"), (address)); - stateBridgeAddress = abi.decode(vm.parseJson(json, ".stateBridgeAddress"), (address)); + optimismWorldIDAddress = + abi.decode(vm.parseJson(json, ".optimismWorldIDAddress"), (address)); + newOwner = abi.decode(vm.parseJson(json, ".optimismStateBridgeAddress"), (address)); } - constructor() { - /////////////////////////////////////////////////////////////////// - /// GOERLI /// - /////////////////////////////////////////////////////////////////// - crossDomainMessengerAddress = address(0x5086d1eEF304eb5284A0f6720f79403b4e9bE294); - stateBridge = StateBridge(stateBridgeAddress); - } + constructor() {} function run() public { /// @notice cross domain ownership flag @@ -58,7 +47,12 @@ contract TransferOwnershipOfOpWorldIDGoerli is Script { vm.startBroadcast(privateKey); - stateBridge.transferOwnershipOptimism(stateBridgeAddress, isLocal); + bytes memory call = + abi.encodeCall(ICrossDomainOwnable3.transferOwnership, (newOwner, isLocal)); + + (bool ok,) = optimismWorldIDAddress.call(call); + + require(ok, "call failed"); vm.stopBroadcast(); } diff --git a/src/script/initialize/SetOpGasLimit.s.sol b/src/script/initialize/op-stack/optimism/SetGasLimitOptimism.s.sol similarity index 56% rename from src/script/initialize/SetOpGasLimit.s.sol rename to src/script/initialize/op-stack/optimism/SetGasLimitOptimism.s.sol index f084e835..c26a9684 100644 --- a/src/script/initialize/SetOpGasLimit.s.sol +++ b/src/script/initialize/op-stack/optimism/SetGasLimitOptimism.s.sol @@ -2,18 +2,16 @@ pragma solidity ^0.8.15; import {Script} from "forge-std/Script.sol"; -import {StateBridge} from "src/StateBridge.sol"; +import {OpStateBridge} from "src/OpStateBridge.sol"; -/// @title State Bridge Optimism Gas Limit setter +/// @title Optimism State Bridge Gas Limit setter /// @notice forge script to set the correct gas limits for crossDomainMessenger calls to Optimism for the StateBridge /// @author Worldcoin /// @dev Can be executed by running `make set-op-gas-limit` -contract SetOpGasLimit is Script { - StateBridge public bridge; +contract SetOpGasLimitOptimism is Script { + address public optimismStateBridgeAddress; - address public stateBridgeAddress; - - StateBridge stateBridge; + OpStateBridge public optimismStateBridge; /////////////////////////////////////////////////////////////////// /// CONFIG /// @@ -33,29 +31,21 @@ contract SetOpGasLimit is Script { abi.decode(vm.parseJson(json, ".gasLimitSetRootHistoryExpiryOptimism"), (uint32)); uint32 public gasLimitTransferOwnershipOptimism = abi.decode(vm.parseJson(json, ".gasLimitTransferOwnershipOptimism"), (uint32)); - uint32 public gasLimitSendRootBase = - abi.decode(vm.parseJson(json, ".gasLimitSendRootBase"), (uint32)); - uint32 public gasLimitSetRootHistoryExpiryBase = - abi.decode(vm.parseJson(json, ".gasLimitSetRootHistoryExpiryBase"), (uint32)); - uint32 public gasLimitTransferOwnershipBase = - abi.decode(vm.parseJson(json, ".gasLimitTransferOwnershipBase"), (uint32)); function setUp() public { - stateBridgeAddress = abi.decode(vm.parseJson(json, ".stateBridgeAddress"), (address)); + optimismStateBridgeAddress = + abi.decode(vm.parseJson(json, ".optimismStateBridgeAddress"), (address)); - stateBridge = StateBridge(stateBridgeAddress); + optimismStateBridge = OpStateBridge(optimismStateBridgeAddress); } function run() public { vm.startBroadcast(privateKey); - stateBridge.setGasLimitSendRootOptimism(gasLimitSendRootOptimism); - stateBridge.setGasLimitSetRootHistoryExpiryOptimism(gasLimitSetRootHistoryExpiryOptimism); - stateBridge.setGasLimitTransferOwnershipOptimism(gasLimitTransferOwnershipOptimism); + optimismStateBridge.setGasLimitSendRoot(gasLimitSendRootOptimism); + optimismStateBridge.setGasLimitSetRootHistoryExpiry(gasLimitSetRootHistoryExpiryOptimism); + optimismStateBridge.setGasLimitTransferOwnershipOp(gasLimitTransferOwnershipOptimism); - stateBridge.setGasLimitSendRootBase(gasLimitSendRootBase); - stateBridge.setGasLimitSetRootHistoryExpiryBase(gasLimitSetRootHistoryExpiryBase); - stateBridge.setGasLimitTransferOwnershipBase(gasLimitTransferOwnershipBase); vm.stopBroadcast(); } } diff --git a/src/script/initialize/InitializePolygonWorldID.s.sol b/src/script/initialize/polygon/InitializePolygonWorldID.s.sol similarity index 85% rename from src/script/initialize/InitializePolygonWorldID.s.sol rename to src/script/initialize/polygon/InitializePolygonWorldID.s.sol index 35ffb76f..70b8ff32 100644 --- a/src/script/initialize/InitializePolygonWorldID.s.sol +++ b/src/script/initialize/polygon/InitializePolygonWorldID.s.sol @@ -2,8 +2,6 @@ pragma solidity ^0.8.15; // Demo deployments -// Goerli 0x09A02586dAf43Ca837b45F34dC2661d642b8Da15 -// https://goerli-optimism.etherscan.io/address/0x09a02586daf43ca837b45f34dc2661d642b8da15#code import {Script} from "forge-std/Script.sol"; import {PolygonWorldID} from "src/PolygonWorldID.sol"; @@ -28,7 +26,7 @@ contract InitializePolygonWorldID is Script { function setUp() public { privateKey = abi.decode(vm.parseJson(json, ".privateKey"), (uint256)); - stateBridgeAddress = abi.decode(vm.parseJson(json, ".stateBridgeAddress"), (address)); + stateBridgeAddress = abi.decode(vm.parseJson(json, ".polygonStateBridgeAddress"), (address)); polygonWorldIDAddress = abi.decode(vm.parseJson(json, ".polygonWorldIDAddress"), (address)); } diff --git a/src/script/ownership/base/CrossTransferOwnershipOfBaseWorldID.s.sol b/src/script/ownership/base/CrossTransferOwnershipOfBaseWorldID.s.sol new file mode 100644 index 00000000..3496cda5 --- /dev/null +++ b/src/script/ownership/base/CrossTransferOwnershipOfBaseWorldID.s.sol @@ -0,0 +1,59 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.15; + +import {Script} from "forge-std/Script.sol"; +import {OpWorldID} from "src/OpWorldID.sol"; +import {ICrossDomainOwnable3} from "src/interfaces/ICrossDomainOwnable3.sol"; +import {IOpStateBridgeTransferOwnership} from "src/interfaces/IOpStateBridgeTransferOwnership.sol"; +import {OpStateBridge} from "src/OpStateBridge.sol"; + +/// @title Ownership Transfer of OpWorldID on Base script +/// @notice forge script for transferring ownership of OpWorldID to a local (Base) +/// or cross-chain (Ethereum) EOA or contract +/// @author Worldcoin +/// @dev Can be executed by running `make mock`, `make local-mock`, `make deploy` or `make deploy-testnet`. +contract CrossTransferOwnershipOfBaseWorldID is Script { + uint256 public privateKey; + + address public baseStateBridgeAddress; + + address public newOwner; + + /// @notice in CrossDomainOwnable3.sol, isLocal is used to set ownership to a new address with a toggle + /// for local or cross domain (using the CrossDomainMessenger to pass messages) + bool public isLocal; + + function setUp() public { + /////////////////////////////////////////////////////////////////// + /// CONFIG /// + /////////////////////////////////////////////////////////////////// + string memory root = vm.projectRoot(); + string memory path = string.concat(root, "/src/script/.deploy-config.json"); + string memory json = vm.readFile(path); + + privateKey = abi.decode(vm.parseJson(json, ".privateKey"), (uint256)); + baseStateBridgeAddress = + abi.decode(vm.parseJson(json, ".baseStateBridgeAddress"), (address)); + newOwner = abi.decode(vm.parseJson(json, ".newBaseWorldIDOwner"), (address)); + } + + constructor() {} + + function run() public { + /// @notice cross domain ownership flag + /// false = cross domain (address on Ethereum) + /// true = local (address on Optimism) + isLocal = false; + + vm.startBroadcast(privateKey); + + bytes memory call = + abi.encodeCall(IOpStateBridgeTransferOwnership.transferOwnershipOp, (newOwner, isLocal)); + + (bool ok,) = baseStateBridgeAddress.call(call); + + require(ok, "call failed"); + + vm.stopBroadcast(); + } +} diff --git a/src/script/ownership/base/LocalTransferOwnershipOfBaseWorldID.s.sol b/src/script/ownership/base/LocalTransferOwnershipOfBaseWorldID.s.sol new file mode 100644 index 00000000..4addaddc --- /dev/null +++ b/src/script/ownership/base/LocalTransferOwnershipOfBaseWorldID.s.sol @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.15; + +import {Script} from "forge-std/Script.sol"; +import {ICrossDomainOwnable3} from "src/interfaces/ICrossDomainOwnable3.sol"; +import {OpStateBridge} from "src/OpStateBridge.sol"; + +/// @title Ownership Transfer of OpWorldID on Base +/// @notice forge script for transferring ownership of OpWorldID to a local (Base / Base Goerli) +/// or cross-chain (Ethereum / Ethereum goerli) EOA or contract +/// @author Worldcoin +/// @dev Can be executed by running `make mock`, `make local-mock`, `make deploy` or `make deploy-testnet`. +contract LocalTransferOwnershipOfBaseWorldID is Script { + uint256 public privateKey; + + address public baseWorldIDAddress; + + address public newOwner; + + /// @notice in CrossDomainOwnable3.sol, isLocal is used to set ownership to a new address with a toggle + /// for local or cross domain (using the CrossDomainMessenger to pass messages) + bool public isLocal; + + uint32 public opGasLimit; + + function setUp() public { + /////////////////////////////////////////////////////////////////// + /// CONFIG /// + /////////////////////////////////////////////////////////////////// + string memory root = vm.projectRoot(); + string memory path = string.concat(root, "/src/script/.deploy-config.json"); + string memory json = vm.readFile(path); + + privateKey = abi.decode(vm.parseJson(json, ".privateKey"), (uint256)); + baseWorldIDAddress = abi.decode(vm.parseJson(json, ".baseWorldIDAddress"), (address)); + newOwner = abi.decode(vm.parseJson(json, ".newBaseWorldIDOwner"), (address)); + } + + constructor() {} + + function run() public { + /// @notice cross domain ownership flag + /// false = cross domain (address on Ethereum) + /// true = local (address on Optimism) + isLocal = false; + + vm.startBroadcast(privateKey); + + bytes memory call = + abi.encodeCall(ICrossDomainOwnable3.transferOwnership, (newOwner, isLocal)); + + (bool ok,) = baseWorldIDAddress.call(call); + + require(ok, "call failed"); + + vm.stopBroadcast(); + } +} diff --git a/src/script/ownership/base/SetGasLimitBase.s.sol b/src/script/ownership/base/SetGasLimitBase.s.sol new file mode 100644 index 00000000..72689103 --- /dev/null +++ b/src/script/ownership/base/SetGasLimitBase.s.sol @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.15; + +import {Script} from "forge-std/Script.sol"; +import {OpStateBridge} from "src/OpStateBridge.sol"; + +/// @title Base State Bridge Gas Limit setter +/// @notice forge script to set the correct gas limits for crossDomainMessenger calls to Base for the StateBridge +/// @author Worldcoin +/// @dev Can be executed by running `make set-base-gas-limit` +contract SetOpGasLimitBase is Script { + address public baseStateBridgeAddress; + + OpStateBridge public baseStateBridge; + + /////////////////////////////////////////////////////////////////// + /// CONFIG /// + /////////////////////////////////////////////////////////////////// + string public root = vm.projectRoot(); + string public path = string.concat(root, "/src/script/.deploy-config.json"); + string public json = vm.readFile(path); + + uint256 public privateKey = abi.decode(vm.parseJson(json, ".privateKey"), (uint256)); + + /////////////////////////////////////////////////////////////////// + /// OP GAS LIMITS /// + /////////////////////////////////////////////////////////////////// + uint32 public gasLimitSendRootBase = + abi.decode(vm.parseJson(json, ".gasLimitSendRootBase"), (uint32)); + uint32 public gasLimitSetRootHistoryExpiryBase = + abi.decode(vm.parseJson(json, ".gasLimitSetRootHistoryExpiryBase"), (uint32)); + uint32 public gasLimitTransferOwnershipBase = + abi.decode(vm.parseJson(json, ".gasLimitTransferOwnershipBase"), (uint32)); + + function setUp() public { + baseStateBridgeAddress = + abi.decode(vm.parseJson(json, ".baseStateBridgeAddress"), (address)); + + baseStateBridge = OpStateBridge(baseStateBridgeAddress); + } + + function run() public { + vm.startBroadcast(privateKey); + + baseStateBridge.setGasLimitSendRoot(gasLimitSendRootBase); + baseStateBridge.setGasLimitSetRootHistoryExpiry(gasLimitSetRootHistoryExpiryBase); + baseStateBridge.setGasLimitTransferOwnershipOp(gasLimitTransferOwnershipBase); + + vm.stopBroadcast(); + } +} diff --git a/src/script/ownership/optimism/CrossTransferOwnershipOfOptimismWorldID.s.sol b/src/script/ownership/optimism/CrossTransferOwnershipOfOptimismWorldID.s.sol new file mode 100644 index 00000000..7bf78e03 --- /dev/null +++ b/src/script/ownership/optimism/CrossTransferOwnershipOfOptimismWorldID.s.sol @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.15; + +import {Script} from "forge-std/Script.sol"; +import {OpWorldID} from "src/OpWorldID.sol"; +import {ICrossDomainOwnable3} from "src/interfaces/ICrossDomainOwnable3.sol"; +import {IOpStateBridgeTransferOwnership} from "src/interfaces/IOpStateBridgeTransferOwnership.sol"; +import {OpStateBridge} from "src/OpStateBridge.sol"; + +/// @title Ownership Transfer of OpWorldID script on Optimism +/// @notice forge script for transferring ownership of OpWorldID to a local (Optimism) +/// or cross-chain (Ethereum) EOA or contract +/// @author Worldcoin +/// @dev Can be executed by running `make mock`, `make local-mock`, `make deploy` or `make deploy-testnet`. +contract CrossTransferOwnershipOfOptimismWorldID is Script { + uint256 public privateKey; + + address public optimismStateBridgeAddress; + + address public newOwner; + + /// @notice in CrossDomainOwnable3.sol, isLocal is used to set ownership to a new address with a toggle + /// for local or cross domain (using the CrossDomainMessenger to pass messages) + bool public isLocal; + + uint32 public opGasLimit; + + function setUp() public { + /////////////////////////////////////////////////////////////////// + /// CONFIG /// + /////////////////////////////////////////////////////////////////// + string memory root = vm.projectRoot(); + string memory path = string.concat(root, "/src/script/.deploy-config.json"); + string memory json = vm.readFile(path); + + privateKey = abi.decode(vm.parseJson(json, ".privateKey"), (uint256)); + optimismStateBridgeAddress = + abi.decode(vm.parseJson(json, ".optimismStateBridgeAddress"), (address)); + newOwner = abi.decode(vm.parseJson(json, ".newOptimismWorldIDOwner"), (address)); + } + + constructor() {} + + function run() public { + /// @notice cross domain ownership flag + /// false = cross domain (address on Ethereum) + /// true = local (address on Optimism) + isLocal = false; + + vm.startBroadcast(privateKey); + + bytes memory call = + abi.encodeCall(IOpStateBridgeTransferOwnership.transferOwnershipOp, (newOwner, isLocal)); + + (bool ok,) = optimismStateBridgeAddress.call(call); + + require(ok, "call failed"); + + vm.stopBroadcast(); + } +} diff --git a/src/script/ownership/optimism/LocalTransferOwnershipOfOptimismWorldID.s.sol b/src/script/ownership/optimism/LocalTransferOwnershipOfOptimismWorldID.s.sol new file mode 100644 index 00000000..60608b2e --- /dev/null +++ b/src/script/ownership/optimism/LocalTransferOwnershipOfOptimismWorldID.s.sol @@ -0,0 +1,59 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.15; + +import {Script} from "forge-std/Script.sol"; +import {ICrossDomainOwnable3} from "src/interfaces/ICrossDomainOwnable3.sol"; +import {OpStateBridge} from "src/OpStateBridge.sol"; + +/// @title Ownership Transfer of OpWorldID script for Optimism +/// @notice forge script for transferring ownership of OpWorldID to a local (Optimism) +/// or cross-chain (Ethereum Goerli) EOA or contract +/// @author Worldcoin +/// @dev Can be executed by running `make mock`, `make local-mock`, `make deploy` or `make deploy-testnet`. +contract LocalTransferOwnershipOfOptimismWorldID is Script { + uint256 public privateKey; + + address public optimismWorldIDAddress; + + address public newOwner; + + /// @notice in CrossDomainOwnable3.sol, isLocal is used to set ownership to a new address with a toggle + /// for local or cross domain (using the CrossDomainMessenger to pass messages) + bool public isLocal; + + uint32 public opGasLimit; + + function setUp() public { + /////////////////////////////////////////////////////////////////// + /// CONFIG /// + /////////////////////////////////////////////////////////////////// + string memory root = vm.projectRoot(); + string memory path = string.concat(root, "/src/script/.deploy-config.json"); + string memory json = vm.readFile(path); + + privateKey = abi.decode(vm.parseJson(json, ".privateKey"), (uint256)); + optimismWorldIDAddress = + abi.decode(vm.parseJson(json, ".optimismWorldIDAddress"), (address)); + newOwner = abi.decode(vm.parseJson(json, ".newOptimismWorldIDOwner"), (address)); + } + + constructor() {} + + function run() public { + /// @notice cross domain ownership flag + /// false = cross domain (address on Ethereum) + /// true = local (address on Optimism) + isLocal = false; + + vm.startBroadcast(privateKey); + + bytes memory call = + abi.encodeCall(ICrossDomainOwnable3.transferOwnership, (newOwner, isLocal)); + + (bool ok,) = optimismWorldIDAddress.call(call); + + require(ok, "call failed"); + + vm.stopBroadcast(); + } +} diff --git a/src/script/ownership/optimism/SetGasLimitOptimism.s.sol b/src/script/ownership/optimism/SetGasLimitOptimism.s.sol new file mode 100644 index 00000000..c26a9684 --- /dev/null +++ b/src/script/ownership/optimism/SetGasLimitOptimism.s.sol @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.15; + +import {Script} from "forge-std/Script.sol"; +import {OpStateBridge} from "src/OpStateBridge.sol"; + +/// @title Optimism State Bridge Gas Limit setter +/// @notice forge script to set the correct gas limits for crossDomainMessenger calls to Optimism for the StateBridge +/// @author Worldcoin +/// @dev Can be executed by running `make set-op-gas-limit` +contract SetOpGasLimitOptimism is Script { + address public optimismStateBridgeAddress; + + OpStateBridge public optimismStateBridge; + + /////////////////////////////////////////////////////////////////// + /// CONFIG /// + /////////////////////////////////////////////////////////////////// + string public root = vm.projectRoot(); + string public path = string.concat(root, "/src/script/.deploy-config.json"); + string public json = vm.readFile(path); + + uint256 public privateKey = abi.decode(vm.parseJson(json, ".privateKey"), (uint256)); + + /////////////////////////////////////////////////////////////////// + /// OP GAS LIMITS /// + /////////////////////////////////////////////////////////////////// + uint32 public gasLimitSendRootOptimism = + abi.decode(vm.parseJson(json, ".gasLimitSendRootOptimism"), (uint32)); + uint32 public gasLimitSetRootHistoryExpiryOptimism = + abi.decode(vm.parseJson(json, ".gasLimitSetRootHistoryExpiryOptimism"), (uint32)); + uint32 public gasLimitTransferOwnershipOptimism = + abi.decode(vm.parseJson(json, ".gasLimitTransferOwnershipOptimism"), (uint32)); + + function setUp() public { + optimismStateBridgeAddress = + abi.decode(vm.parseJson(json, ".optimismStateBridgeAddress"), (address)); + + optimismStateBridge = OpStateBridge(optimismStateBridgeAddress); + } + + function run() public { + vm.startBroadcast(privateKey); + + optimismStateBridge.setGasLimitSendRoot(gasLimitSendRootOptimism); + optimismStateBridge.setGasLimitSetRootHistoryExpiry(gasLimitSetRootHistoryExpiryOptimism); + optimismStateBridge.setGasLimitTransferOwnershipOp(gasLimitTransferOwnershipOptimism); + + vm.stopBroadcast(); + } +} diff --git a/src/script/ownership/polygon/InitializePolygonWorldID.s.sol b/src/script/ownership/polygon/InitializePolygonWorldID.s.sol new file mode 100644 index 00000000..468787ef --- /dev/null +++ b/src/script/ownership/polygon/InitializePolygonWorldID.s.sol @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.15; + +// Demo deployments + +import {Script} from "forge-std/Script.sol"; +import {PolygonWorldID} from "src/PolygonWorldID.sol"; + +contract InitializePolygonWorldID is Script { + address public polygonStateBridgeAddress; + address public polygonWorldIDAddress; + + // Polygon PoS Mumbai Testnet Child Tunnel + address public fxChildAddress = address(0xCf73231F28B7331BBe3124B907840A94851f9f11); + + PolygonWorldID public polygonWorldID; + uint256 public privateKey; + + /////////////////////////////////////////////////////////////////// + /// CONFIG /// + /////////////////////////////////////////////////////////////////// + string public root = vm.projectRoot(); + string public path = string.concat(root, "/src/script/.deploy-config.json"); + string public json = vm.readFile(path); + + function setUp() public { + privateKey = abi.decode(vm.parseJson(json, ".privateKey"), (uint256)); + + polygonStateBridgeAddress = + abi.decode(vm.parseJson(json, ".polygonStateBridgeAddress"), (address)); + polygonWorldIDAddress = abi.decode(vm.parseJson(json, ".polygonWorldIDAddress"), (address)); + } + + // Polygon PoS Mainnet Child Tunnel + // address fxChildAddress = address(0x8397259c983751DAf40400790063935a11afa28a); + + function run() external { + vm.startBroadcast(privateKey); + + polygonWorldID = PolygonWorldID(polygonWorldIDAddress); + + polygonWorldID.setFxRootTunnel(polygonStateBridgeAddress); + + vm.stopBroadcast(); + } +} diff --git a/src/script/initialize/InitializeMockWorldID.s.sol b/src/script/test/PropagateMockRoot.s.sol similarity index 53% rename from src/script/initialize/InitializeMockWorldID.s.sol rename to src/script/test/PropagateMockRoot.s.sol index afb89f44..330451ea 100644 --- a/src/script/initialize/InitializeMockWorldID.s.sol +++ b/src/script/test/PropagateMockRoot.s.sol @@ -2,11 +2,15 @@ pragma solidity ^0.8.15; import {Script} from "forge-std/Script.sol"; -import {WorldIDIdentityManagerMock} from "src/mock/WorldIDIdentityManagerMock.sol"; +import {MockStateBridge} from "src/mock/MockStateBridge.sol"; -/// @notice Initializes the StateBridge contract -contract InitializeMockWorldID is Script { - WorldIDIdentityManagerMock public worldID; +/// @title Propagate Mock Root test script +/// @author Worldcoin +/// @dev Can be executed by running `make local-mock`. +contract PropagateMockRoot is Script { + MockStateBridge public bridge; + + address public mockStateBridgeAddress; /////////////////////////////////////////////////////////////////// /// CONFIG /// @@ -17,17 +21,17 @@ contract InitializeMockWorldID is Script { uint256 public privateKey = abi.decode(vm.parseJson(json, ".privateKey"), (uint256)); - address public worldIDIdentityManagerAddress = - abi.decode(vm.parseJson(json, ".worldIDIdentityManagerAddress"), (address)); - address public stateBridgeAddress = - abi.decode(vm.parseJson(json, ".stateBridgeAddress"), (address)); + function setUp() public { + mockStateBridgeAddress = + abi.decode(vm.parseJson(json, ".mockStateBridgeAddress"), (address)); + } function run() public { vm.startBroadcast(privateKey); - worldID = WorldIDIdentityManagerMock(worldIDIdentityManagerAddress); + bridge = MockStateBridge(mockStateBridgeAddress); - worldID.initialize(stateBridgeAddress); + bridge.propagateRoot(); vm.stopBroadcast(); } diff --git a/src/script/test/SendStateRootToStateBridge.s.sol b/src/script/test/SendStateRootToStateBridge.s.sol deleted file mode 100644 index 67b6bf6a..00000000 --- a/src/script/test/SendStateRootToStateBridge.s.sol +++ /dev/null @@ -1,43 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.15; - -// demo deployments - -import {Script} from "forge-std/Script.sol"; -import {IWorldID} from "src/interfaces/IWorldID.sol"; -import {ISendBridge} from "src/interfaces/ISendBridge.sol"; - -/// @notice Sends the a WorldID state root to the state bridge -contract SendStateRootToStateBridge is Script { - address public worldIDAddress; - uint256 public newRoot; - - ISendBridge public worldID; - - uint256 public privateKey; - - function setUp() public { - /////////////////////////////////////////////////////////////////// - /// CONFIG /// - /////////////////////////////////////////////////////////////////// - string memory root = vm.projectRoot(); - string memory path = string.concat(root, "/src/script/.deploy-config.json"); - string memory json = vm.readFile(path); - - privateKey = abi.decode(vm.parseJson(json, ".privateKey"), (uint256)); - worldIDAddress = abi.decode(vm.parseJson(json, ".worldIDIdentityManagerAddress"), (address)); - newRoot = abi.decode(vm.parseJson(json, ".newRoot"), (uint256)); - - vm.label(worldIDAddress, "WorldIDIdentityManagerMock"); - } - - function run() public { - vm.startBroadcast(privateKey); - - worldID = ISendBridge(worldIDAddress); - - worldID.sendRootToStateBridge(newRoot); - - vm.stopBroadcast(); - } -} diff --git a/src/test/BytesUtils.t.sol b/src/test/BytesUtils.t.sol index d45526a0..fa2d1626 100644 --- a/src/test/BytesUtils.t.sol +++ b/src/test/BytesUtils.t.sol @@ -8,7 +8,7 @@ import {StdCheats} from "forge-std/StdCheats.sol"; /// @author Worldcoin /// @notice Tests the low-level assembly functions `grabSelector` and `stripSelector` in the BytesUtils library contract BytesUtilsTest is PRBTest, StdCheats { - /// @notice Thrown when the payload is too short to contain a selector (at least 4 bytes). + /// @notice Emitted when the payload is too short to contain a selector (at least 4 bytes). error PayloadTooShort(); /////////////////////////////////////////////////////////////////// diff --git a/src/test/MockPolygonBridge.t.sol b/src/test/MockPolygonBridge.t.sol index 7af61b07..ea18c14c 100644 --- a/src/test/MockPolygonBridge.t.sol +++ b/src/test/MockPolygonBridge.t.sol @@ -1,6 +1,7 @@ pragma solidity ^0.8.15; import {MockPolygonBridge} from "src/mock/MockPolygonBridge.sol"; +import {WorldIDBridge} from "src/abstract/WorldIDBridge.sol"; import {PRBTest} from "@prb/test/PRBTest.sol"; import {StdCheats} from "forge-std/StdCheats.sol"; @@ -13,14 +14,18 @@ contract MockPolygonBridgeTest is PRBTest, StdCheats { address owner; - /// @notice Thrown when root history expiry is set - event RootHistoryExpirySet(uint256 rootHistoryExpiry); + /// @notice The time in the `rootHistory` mapping associated with a root that has never been + /// seen before. + uint128 internal constant NULL_ROOT_TIME = 0; - /// @notice Thrown when new root is inserted - event ReceivedRoot(uint256 root, uint128 supersedeTimestamp); + /// @notice Emitted when root history expiry is set + event RootHistoryExpirySet(uint256 rootHistoryExpiry); - /// @notice Thrown when the message selector passed from FxRoot is invalid. - error InvalidMessageSelector(bytes4 selector); + /// @notice Emitted when a new root is received by the contract. + /// + /// @param root The value of the root that was added. + /// @param timestamp The timestamp of insertion for the given root. + event RootAdded(uint256 root, uint128 timestamp); function setUp() public { owner = address(0x1234); @@ -28,24 +33,27 @@ contract MockPolygonBridgeTest is PRBTest, StdCheats { vm.label(owner, "owner"); vm.prank(owner); - polygonWorldID = new MockPolygonBridge(); + polygonWorldID = new MockPolygonBridge(uint8(30)); } + /////////////////////////////////////////////////////////////////// + /// SUCCEEDS /// + /////////////////////////////////////////////////////////////////// + /// @notice tests that receiveRoot succeeds if encoded properly - function testReceiveRootSucceeds(uint256 newRoot, uint128 supersedeTimestamp) public { - bytes memory message = - abi.encodeWithSignature("receiveRoot(uint256,uint128)", newRoot, supersedeTimestamp); + function test_ReceiveRoot_succeeds(uint256 newRoot) public { + bytes memory message = abi.encodeWithSignature("receiveRoot(uint256)", newRoot); vm.expectEmit(true, true, true, true); - emit ReceivedRoot(newRoot, supersedeTimestamp); + emit RootAdded(newRoot, uint128(block.timestamp)); vm.prank(owner); polygonWorldID.processMessageFromRoot(message); } /// @notice tests that setRootHistoryExpiry succeeds if encoded properly - function testSetRootHistoryExpirySucceeds(uint256 rootHistoryExpiry) public { + function test_setRootHistoryExpiry_succeeds(uint256 rootHistoryExpiry) public { bytes memory message = abi.encodeWithSignature("setRootHistoryExpiry(uint256)", rootHistoryExpiry); @@ -57,16 +65,43 @@ contract MockPolygonBridgeTest is PRBTest, StdCheats { polygonWorldID.processMessageFromRoot(message); } + /////////////////////////////////////////////////////////////////// + /// REVERTS /// + /////////////////////////////////////////////////////////////////// + /// @notice tests that an invalid function signature reverts - function testProcessMessageFromRootReverts(bytes4 invalidSelector, bytes32 param) public { + function test_processMessageFromRoot_reverts_InvalidMessageSelector( + bytes4 invalidSelector, + bytes32 param + ) public { vm.assume( - invalidSelector != bytes4(keccak256("receiveRoot(uint256,uint128)")) + invalidSelector != bytes4(keccak256("receiveRoot(uint256)")) && invalidSelector != bytes4(keccak256("setRootHistoryExpiry(uint256)")) ); bytes memory message = abi.encode(invalidSelector, param); - vm.expectRevert(abi.encodeWithSelector(InvalidMessageSelector.selector, invalidSelector)); + vm.expectRevert( + abi.encodeWithSelector( + MockPolygonBridge.InvalidMessageSelector.selector, invalidSelector + ) + ); + vm.prank(owner); + polygonWorldID.processMessageFromRoot(message); + } + + function test_processMessageFromRoot_reverts_CannotOverwriteRoot(uint256 newRoot) public { + vm.assume(newRoot != 0); + + bytes memory message = abi.encodeWithSignature("receiveRoot(uint256)", newRoot); + + vm.expectEmit(true, true, true, true); + emit RootAdded(newRoot, uint128(block.timestamp)); + + vm.prank(owner); + polygonWorldID.processMessageFromRoot(message); + + vm.expectRevert(abi.encodeWithSelector(WorldIDBridge.CannotOverwriteRoot.selector)); vm.prank(owner); polygonWorldID.processMessageFromRoot(message); diff --git a/src/test/OpStateBridge.t.sol b/src/test/OpStateBridge.t.sol new file mode 100644 index 00000000..8556cbcd --- /dev/null +++ b/src/test/OpStateBridge.t.sol @@ -0,0 +1,292 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.15; + +import {OpStateBridge} from "src/OpStateBridge.sol"; +import {WorldIDIdentityManagerMock} from "src/mock/WorldIDIdentityManagerMock.sol"; +import {MockBridgedWorldID} from "src/mock/MockBridgedWorldID.sol"; + +import {PRBTest} from "@prb/test/PRBTest.sol"; +import {StdCheats} from "forge-std/StdCheats.sol"; + +/// @title State Bridge Test +/// @author Worldcoin +/// @notice A test contract for StateBridge.sol +contract OpStateBridgeTest is PRBTest, StdCheats { + /////////////////////////////////////////////////////////////////// + /// STORAGE CONFIG /// + /////////////////////////////////////////////////////////////////// + uint256 public mainnetFork; + string private MAINNET_RPC_URL = vm.envString("MAINNET_RPC_URL"); + + /// @notice emitted if there is no CrossDomainMessenger contract deployed on the fork + error invalidCrossDomainMessengerFork(); + + OpStateBridge public opStateBridge; + WorldIDIdentityManagerMock public mockWorldID; + + uint32 public opGasLimit; + + address public mockWorldIDAddress; + + address public owner; + + /// @notice The address of the OpWorldID contract on any OP Stack chain + address public opWorldIDAddress; + + /// @notice address for OP Stack chain Ethereum mainnet L1CrossDomainMessenger contract + address public opCrossDomainMessengerAddress; + + uint256 public sampleRoot; + + /////////////////////////////////////////////////////////////////// + /// EVENTS /// + /////////////////////////////////////////////////////////////////// + + /// @notice Emmitted when the ownership transfer of OpStateBridge is started (OZ Ownable2Step) + event OwnershipTransferStarted(address indexed previousOwner, address indexed newOwner); + + /// @notice Emmitted when the ownership transfer of OpStateBridge is accepted (OZ Ownable2Step) + event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); + + // @notice Emmitted when the the StateBridge sends a root to the OPWorldID contract + /// @param root The root sent to the OPWorldID contract on the OP Stack chain + event RootPropagated(uint256 root); + + /// @notice Emmitted when the the StateBridge gives ownership of the OPWorldID contract + /// to the WorldID Identity Manager contract away + /// @param previousOwner The previous owner of the OPWorldID contract + /// @param newOwner The new owner of the OPWorldID contract + /// @param isLocal Whether the ownership transfer is local (Optimism/OP Stack chain EOA/contract) or an Ethereum EOA or contract + event OwnershipTransferredOp( + address indexed previousOwner, address indexed newOwner, bool isLocal + ); + + /// @notice Emmitted when the the StateBridge sets the root history expiry for OpWorldID and PolygonWorldID + /// @param rootHistoryExpiry The new root history expiry + event SetRootHistoryExpiry(uint256 rootHistoryExpiry); + + /// @notice Emmitted when the the StateBridge sets the gas limit for sendRootOp + /// @param _opGasLimit The new opGasLimit for sendRootOp + event SetGasLimitSendRoot(uint32 _opGasLimit); + + /// @notice Emmitted when the the StateBridge sets the gas limit for SetRootHistoryExpiryt + /// @param _opGasLimit The new opGasLimit for SetRootHistoryExpirytimism + event SetGasLimitSetRootHistoryExpiry(uint32 _opGasLimit); + + /// @notice Emmitted when the the StateBridge sets the gas limit for transferOwnershipOp + /// @param _opGasLimit The new opGasLimit for transferOwnershipOptimism + event SetGasLimitTransferOwnershipOp(uint32 _opGasLimit); + + /////////////////////////////////////////////////////////////////// + /// ERRORS /// + /////////////////////////////////////////////////////////////////// + + /// @notice Emitted when an attempt is made to renounce ownership. + error CannotRenounceOwnership(); + + function setUp() public { + /// @notice Create a fork of the Ethereum mainnet + mainnetFork = vm.createFork(MAINNET_RPC_URL); + + vm.selectFork(mainnetFork); + /// @notice Roll the fork to a block where both Optimim's and Base's crossDomainMessenger contract is deployed + /// @notice and the Base crossDomainMessenger ResolvedDelegateProxy target address is initialized + vm.rollFork(17711915); + + if (block.chainid == 1) { + opCrossDomainMessengerAddress = address(0x25ace71c97B33Cc4729CF772ae268934F7ab5fA1); + } else { + revert invalidCrossDomainMessengerFork(); + } + + // inserting mock root + sampleRoot = uint256(0x111); + mockWorldID = new WorldIDIdentityManagerMock(sampleRoot); + mockWorldIDAddress = address(mockWorldID); + + opWorldIDAddress = address(0x1); + + opStateBridge = new OpStateBridge ( + mockWorldIDAddress, + opWorldIDAddress, + opCrossDomainMessengerAddress + ); + + owner = opStateBridge.owner(); + } + + /////////////////////////////////////////////////////////////////// + /// SUCCEEDS /// + /////////////////////////////////////////////////////////////////// + + /// @notice select a specific fork + function test_canSelectFork_succeeds() public { + // select the fork + vm.selectFork(mainnetFork); + assertEq(vm.activeFork(), mainnetFork); + } + + function test_propagateRoot_suceeds() public { + vm.expectEmit(true, true, true, true); + emit RootPropagated(sampleRoot); + + opStateBridge.propagateRoot(); + + // Bridging is not emulated + } + + /// @notice Tests that the owner of the StateBridge contract can transfer ownership + /// using Ownable2Step transferOwnership + /// @param newOwner the new owner of the contract + function test_owner_transferOwnership_succeeds(address newOwner) public { + vm.expectEmit(true, true, true, true); + + // OpenZeppelin Ownable2Step transferOwnershipStarted event + emit OwnershipTransferStarted(owner, newOwner); + + vm.prank(owner); + opStateBridge.transferOwnership(newOwner); + + vm.expectEmit(true, true, true, true); + + // OpenZeppelin Ownable2Step transferOwnership event + emit OwnershipTransferred(owner, newOwner); + + vm.prank(newOwner); + opStateBridge.acceptOwnership(); + + assertEq(opStateBridge.owner(), newOwner); + } + + /// @notice tests whether the StateBridge contract can transfer ownership of the OPWorldID contract + /// @param newOwner The new owner of the OPWorldID contract (foundry fuzz) + /// @param isLocal Whether the ownership transfer is local (Optimism EOA/contract) or an Ethereum EOA or contract + function test_owner_transferOwnershipOp_succeeds(address newOwner, bool isLocal) public { + vm.expectEmit(true, true, true, true); + + // CrossDomainOwnable3.sol transferOwnership event + emit OwnershipTransferredOp(owner, newOwner, isLocal); + + vm.prank(owner); + opStateBridge.transferOwnershipOp(newOwner, isLocal); + } + + /// @notice tests whether the StateBridge contract can set root history expiry on Optimism and Polygon + /// @param _rootHistoryExpiry The new root history expiry for OpWorldID and PolygonWorldID + function test_owner_setRootHistoryExpiry_succeeds(uint256 _rootHistoryExpiry) public { + vm.expectEmit(true, true, true, true); + emit SetRootHistoryExpiry(_rootHistoryExpiry); + + vm.prank(owner); + opStateBridge.setRootHistoryExpiry(_rootHistoryExpiry); + } + + /// @notice tests whether the StateBridge contract can set the opGasLimit for sendRootOptimism + /// @param _opGasLimit The new opGasLimit for sendRootOptimism + function test_owner_setGasLimitSendRoot_succeeds(uint32 _opGasLimit) public { + vm.assume(_opGasLimit != 0); + + vm.expectEmit(true, true, true, true); + + emit SetGasLimitSendRoot(_opGasLimit); + + vm.prank(owner); + opStateBridge.setGasLimitSendRoot(_opGasLimit); + } + + /// @notice tests whether the StateBridge contract can set the opGasLimit for SetRootHistoryExpirytimism + /// @param _opGasLimit The new opGasLimit for SetRootHistoryExpirytimism + function test_owner_setGasLimitSetRootHistoryExpiry_succeeds(uint32 _opGasLimit) public { + vm.assume(_opGasLimit != 0); + + vm.expectEmit(true, true, true, true); + + emit SetGasLimitSetRootHistoryExpiry(_opGasLimit); + + vm.prank(owner); + opStateBridge.setGasLimitSetRootHistoryExpiry(_opGasLimit); + } + + /// @notice tests whether the StateBridge contract can set the opGasLimit for transferOwnershipOptimism + /// @param _opGasLimit The new opGasLimit for transferOwnershipOptimism + function test_owner_setGasLimitTransferOwnershipOp_succeeds(uint32 _opGasLimit) public { + vm.assume(_opGasLimit != 0); + + vm.expectEmit(true, true, true, true); + + emit SetGasLimitTransferOwnershipOp(_opGasLimit); + + vm.prank(owner); + opStateBridge.setGasLimitTransferOwnershipOp(_opGasLimit); + } + + /////////////////////////////////////////////////////////////////// + /// REVERTS /// + /////////////////////////////////////////////////////////////////// + + /// @notice tests that the StateBridge contract's ownership can't be changed by a non-owner + /// @param newOwner The new owner of the StateBridge contract (foundry fuzz) + function test_notOwner_transferOwnership_reverts(address nonOwner, address newOwner) public { + vm.assume(nonOwner != owner && nonOwner != address(0) && newOwner != address(0)); + + vm.expectRevert("Ownable: caller is not the owner"); + + vm.prank(nonOwner); + opStateBridge.transferOwnership(newOwner); + } + + /// @notice tests that the StateBridge contract's ownership can't be changed by a non-owner + /// @param newOwner The new owner of the StateBridge contract (foundry fuzz) + function test_notOwner_transferOwnershipOp_reverts( + address nonOwner, + address newOwner, + bool isLocal + ) public { + vm.assume(nonOwner != owner && newOwner != address(0x0)); + + vm.expectRevert("Ownable: caller is not the owner"); + + vm.prank(nonOwner); + opStateBridge.transferOwnershipOp(newOwner, isLocal); + } + + /// @notice tests whether the StateBridge contract can set root history expiry on Optimism and Polygon + /// @param _rootHistoryExpiry The new root history expiry for OpWorldID and PolygonWorldID + function test_notOwner_SetRootHistoryExpiry_reverts( + address nonOwner, + uint256 _rootHistoryExpiry + ) public { + vm.assume(nonOwner != owner && nonOwner != address(0) && _rootHistoryExpiry != 0); + + vm.expectRevert("Ownable: caller is not the owner"); + + vm.prank(nonOwner); + opStateBridge.setRootHistoryExpiry(_rootHistoryExpiry); + } + + /// @notice Tests that a nonPendingOwner can't accept ownership of StateBridge + /// @param newOwner the new owner of the contract + function test_notOwner_acceptOwnership_reverts(address newOwner, address randomAddress) + public + { + vm.assume( + newOwner != address(0) && randomAddress != address(0) && randomAddress != newOwner + ); + + vm.prank(owner); + opStateBridge.transferOwnership(newOwner); + + vm.expectRevert("Ownable2Step: caller is not the new owner"); + + vm.prank(randomAddress); + opStateBridge.acceptOwnership(); + } + + /// @notice Tests that ownership can't be renounced + function test_owner_renounceOwnership_reverts() public { + vm.expectRevert(OpStateBridge.CannotRenounceOwnership.selector); + + vm.prank(owner); + opStateBridge.renounceOwnership(); + } +} diff --git a/src/test/OpWorldID.t.sol b/src/test/OpWorldID.t.sol index 44761178..4f2be6e6 100644 --- a/src/test/OpWorldID.t.sol +++ b/src/test/OpWorldID.t.sol @@ -17,6 +17,7 @@ import { import {AddressAliasHelper} from "@eth-optimism/contracts-bedrock/contracts/vendor/AddressAliasHelper.sol"; import {Encoding} from "@eth-optimism/contracts-bedrock/contracts/libraries/Encoding.sol"; +import {Hashing} from "@eth-optimism/contracts-bedrock/contracts/libraries/Hashing.sol"; import {Bytes32AddressLib} from "solmate/src/utils/Bytes32AddressLib.sol"; /// @title OpWorldIDTest @@ -89,7 +90,7 @@ contract OpWorldIDTest is Messenger_Initializer { _switchToCrossDomainOwnership(id); address owner = id.owner(); - uint128 newRootTimestamp = uint128(block.timestamp + 100); + vm.warp(block.timestamp + 200); // set the xDomainMsgSender storage slot to the L1Messenger @@ -100,7 +101,7 @@ contract OpWorldIDTest is Messenger_Initializer { address(id), 0, 0, - abi.encodeWithSelector(id.receiveRoot.selector, newRoot, newRootTimestamp) + abi.encodeWithSelector(id.receiveRoot.selector, newRoot) ); assert(id.latestRoot() == newRoot); @@ -126,12 +127,10 @@ contract OpWorldIDTest is Messenger_Initializer { function test_onlyOwner_notMessenger_reverts(uint256 newRoot) external { _switchToCrossDomainOwnership(id); - uint128 newRootTimestamp = uint128(block.timestamp + 100); - // calling locally (not as the messenger) vm.prank(bob); vm.expectRevert("CrossDomainOwnable3: caller is not the messenger"); - id.receiveRoot(newRoot, newRootTimestamp); + id.receiveRoot(newRoot); } /// @notice Test that a non-owner can't insert a new root @@ -144,11 +143,9 @@ contract OpWorldIDTest is Messenger_Initializer { bytes32 value = Bytes32AddressLib.fillLast12Bytes(address(bob)); vm.store(address(L2Messenger), key, value); - uint128 newRootTimestamp = uint128(block.timestamp + 100); - vm.prank(address(L2Messenger)); vm.expectRevert("CrossDomainOwnable3: caller is not the owner"); - id.receiveRoot(newRoot, newRootTimestamp); + id.receiveRoot(newRoot); } /// @notice Test that a root that hasn't been inserted is invalid @@ -160,7 +157,6 @@ contract OpWorldIDTest is Messenger_Initializer { address owner = id.owner(); - uint128 newRootTimestamp = uint128(block.timestamp + 100); vm.warp(block.timestamp + 200); uint256 randomRoot = 0x712cab3414951eba341ca234aef42142567c6eea50371dd528d57eb2b856d238; @@ -172,7 +168,7 @@ contract OpWorldIDTest is Messenger_Initializer { address(id), 0, 0, - abi.encodeWithSelector(id.receiveRoot.selector, newRoot, newRootTimestamp) + abi.encodeWithSelector(id.receiveRoot.selector, newRoot) ); vm.expectRevert(WorldIDBridge.NonExistentRoot.selector); @@ -191,9 +187,6 @@ contract OpWorldIDTest is Messenger_Initializer { address owner = id.owner(); - uint128 newRootTimestamp = uint128(block.timestamp + 100); - uint128 secondRootTimestamp = uint128(newRootTimestamp + 1); - // set the xDomainMsgSender storage slot to the L1Messenger vm.prank(AddressAliasHelper.applyL1ToL2Alias(address(L1Messenger))); L2Messenger.relayMessage( @@ -202,7 +195,7 @@ contract OpWorldIDTest is Messenger_Initializer { address(id), 0, 0, - abi.encodeWithSelector(id.receiveRoot.selector, newRoot, newRootTimestamp) + abi.encodeWithSelector(id.receiveRoot.selector, newRoot) ); vm.roll(block.number + 100); @@ -214,11 +207,63 @@ contract OpWorldIDTest is Messenger_Initializer { address(id), 0, 0, - abi.encodeWithSelector(id.receiveRoot.selector, secondRoot, secondRootTimestamp) + abi.encodeWithSelector(id.receiveRoot.selector, secondRoot) ); vm.expectRevert(WorldIDBridge.ExpiredRoot.selector); vm.warp(block.timestamp + 8 days); id.verifyProof(newRoot, 0, 0, 0, proof); } + + function test_receiveRoot_reverts_CannotOverwriteRoot(uint256 newRoot) public { + vm.assume(newRoot != 0); + + _switchToCrossDomainOwnership(id); + + address owner = id.owner(); + + // set the xDomainMsgSender storage slot to the L1Messenger + vm.prank(AddressAliasHelper.applyL1ToL2Alias(address(L1Messenger))); + L2Messenger.relayMessage( + Encoding.encodeVersionedNonce(0, 1), + owner, + address(id), + 0, + 0, + abi.encodeWithSelector(id.receiveRoot.selector, newRoot) + ); + + assert(id.latestRoot() == newRoot); + + bytes32 versionedHash = Hashing.hashCrossDomainMessageV1( + Encoding.encodeVersionedNonce(1, 1), + owner, + address(id), + 0, + 0, + abi.encodeWithSelector(id.receiveRoot.selector, newRoot) + ); + + // It reverts with CannotOverwriteRoot however because of the bridge simulation + // the L2 cross-domain call doesn't revert, however it does emit a FailedRelayedMessage + // issue reported to foundry team: expectRevert doesn't search for errors in nested subcalls + // vm.expectRevert(abi.encodeWithSelector(WorldIDBridge.CannotOverwriteRoot.selector)); + // CannotOverwriteRoot can be seen in the execution trace of the call using the -vvvvv flag + + vm.expectEmit(true, true, true, true); + emit FailedRelayedMessage(versionedHash); + + vm.roll(block.number + 100); + vm.warp(block.timestamp + 200); + // set the xDomainMsgSender storage slot to the L1Messenger + vm.prank(AddressAliasHelper.applyL1ToL2Alias(address(L1Messenger))); + L2Messenger.relayMessage( + Encoding.encodeVersionedNonce(1, 1), + owner, + address(id), + 0, + 0, + abi.encodeWithSelector(id.receiveRoot.selector, newRoot) + ); + } } diff --git a/src/test/PolygonStateBridge.t.sol b/src/test/PolygonStateBridge.t.sol new file mode 100644 index 00000000..801530ba --- /dev/null +++ b/src/test/PolygonStateBridge.t.sol @@ -0,0 +1,194 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.15; + +import {PolygonStateBridge} from "src/PolygonStateBridge.sol"; +import {WorldIDIdentityManagerMock} from "src/mock/WorldIDIdentityManagerMock.sol"; + +import {PRBTest} from "@prb/test/PRBTest.sol"; +import {StdCheats} from "forge-std/StdCheats.sol"; + +/// @title State Bridge Test +/// @author Worldcoin +/// @notice A test contract for StateBridge.sol +contract PolygonStateBridgeTest is PRBTest, StdCheats { + /////////////////////////////////////////////////////////////////// + /// STORAGE CONFIG /// + /////////////////////////////////////////////////////////////////// + uint256 public mainnetFork; + string private MAINNET_RPC_URL = vm.envString("MAINNET_RPC_URL"); + + /// @notice emitted if there is no CrossDomainMessenger contract deployed on the fork + error invalidCrossDomainMessengerFork(); + + PolygonStateBridge polygonStateBridge; + WorldIDIdentityManagerMock public mockWorldID; + + uint32 public opGasLimit; + + address public mockWorldIDAddress; + + address public fxRoot; + address public checkpointManager; + address public owner; + uint256 public sampleRoot; + + /////////////////////////////////////////////////////////////////// + /// EVENTS /// + /////////////////////////////////////////////////////////////////// + + /// @notice OpenZeppelin Ownable.sol transferOwnership event + /// @param previousOwner The previous owner of the StateBridge contract + /// @param newOwner The new owner of the StateBridge contract + event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); + + /// @notice OpenZeppelin Ownable2Step transferOwnership event + /// @param previousOwner The previous owner of the StateBridge contract + /// @param newOwner The new owner of the StateBridge contract + event OwnershipTransferStarted(address indexed previousOwner, address indexed newOwner); + + /// @notice Emmitted when the the StateBridge sets the root history expiry for OpWorldID and PolygonWorldID + /// @param rootHistoryExpiry The new root history expiry + event SetRootHistoryExpiry(uint256 rootHistoryExpiry); + + /// @notice Emmitted when a root is sent to PolygonWorldID + /// @param root The latest WorldID Identity Manager root. + event RootPropagated(uint256 root); + + function setUp() public { + /// @notice Create a fork of the Ethereum mainnet + mainnetFork = vm.createFork(MAINNET_RPC_URL); + + vm.selectFork(mainnetFork); + /// @notice Roll the fork to a block where FXPortal already has been deployed + /// @notice and the Base crossDomainMessenger ResolvedDelegateProxy target address is initialized + vm.rollFork(17711915); + + sampleRoot = uint256(0x123); + mockWorldID = new WorldIDIdentityManagerMock(sampleRoot); + mockWorldIDAddress = address(mockWorldID); + + checkpointManager = address(0x86E4Dc95c7FBdBf52e33D563BbDB00823894C287); + fxRoot = address(0xfe5e5D361b2ad62c541bAb87C45a0B9B018389a2); + + polygonStateBridge = new PolygonStateBridge ( + checkpointManager, + fxRoot, + mockWorldIDAddress + ); + + owner = polygonStateBridge.owner(); + } + + /////////////////////////////////////////////////////////////////// + /// SUCCEEDS /// + /////////////////////////////////////////////////////////////////// + + /// @notice select a specific fork + function test_canSelectFork_succeeds() public { + // select the fork + vm.selectFork(mainnetFork); + assertEq(vm.activeFork(), mainnetFork); + } + + /// @notice tests that a root can be sent successfully to Polygon + function test_propagateRoot_succeeds(address randomAddress) public { + vm.assume(randomAddress != address(0) && randomAddress != owner); + + vm.expectEmit(true, true, true, true); + + emit RootPropagated(sampleRoot); + + vm.prank(randomAddress); + polygonStateBridge.propagateRoot(); + } + + /// @notice Tests that the owner of the StateBridge contract can transfer ownership + /// using Ownable2Step transferOwnership + /// @param newOwner the new owner of the contract + function test_owner_transferOwnership_succeeds(address newOwner) public { + vm.expectEmit(true, true, true, true); + + // OpenZeppelin Ownable2Step transferOwnershipStarted event + emit OwnershipTransferStarted(owner, newOwner); + + vm.prank(owner); + polygonStateBridge.transferOwnership(newOwner); + + vm.expectEmit(true, true, true, true); + + // OpenZeppelin Ownable2Step transferOwnership event + emit OwnershipTransferred(owner, newOwner); + + vm.prank(newOwner); + polygonStateBridge.acceptOwnership(); + + assertEq(polygonStateBridge.owner(), newOwner); + } + + /// @notice tests whether the StateBridge contract can set root history expiry on Optimism and Polygon + /// @param _rootHistoryExpiry The new root history expiry for OpWorldID and PolygonWorldID + function test_owner_setRootHistoryExpiryPolygon_succeeds(uint256 _rootHistoryExpiry) public { + vm.assume(owner != address(0)); + + vm.expectEmit(true, true, true, true); + emit SetRootHistoryExpiry(_rootHistoryExpiry); + + vm.prank(owner); + polygonStateBridge.setRootHistoryExpiryPolygon(_rootHistoryExpiry); + } + + /////////////////////////////////////////////////////////////////// + /// REVERTS /// + /////////////////////////////////////////////////////////////////// + + /// @notice tests that the StateBridge contract's ownership can't be changed by a non-owner + /// @param newOwner The new owner of the StateBridge contract (foundry fuzz) + function test_notOwner_transferOwnership_reverts(address nonOwner, address newOwner) public { + vm.assume(nonOwner != owner && nonOwner != address(0) && newOwner != address(0)); + + vm.expectRevert("Ownable: caller is not the owner"); + + vm.prank(nonOwner); + polygonStateBridge.transferOwnership(newOwner); + } + + /// @notice tests whether the StateBridge contract can set root history expiry on Optimism and Polygon + /// @param _rootHistoryExpiry The new root history expiry for OpWorldID and PolygonWorldID + function test_notOwner_setRootHistoryExpiryPolygon_reverts( + address nonOwner, + uint256 _rootHistoryExpiry + ) public { + vm.assume(nonOwner != owner && _rootHistoryExpiry != 0); + + vm.expectRevert("Ownable: caller is not the owner"); + + vm.prank(nonOwner); + polygonStateBridge.setRootHistoryExpiryPolygon(_rootHistoryExpiry); + } + + /// @notice Tests that a nonPendingOwner can't accept ownership of StateBridge + /// @param newOwner the new owner of the contract + function test_notOwner_acceptOwnership_reverts(address newOwner, address randomAddress) + public + { + vm.assume( + newOwner != address(0) && randomAddress != address(0) && randomAddress != newOwner + ); + + vm.prank(owner); + polygonStateBridge.transferOwnership(newOwner); + + vm.expectRevert("Ownable2Step: caller is not the new owner"); + + vm.prank(randomAddress); + polygonStateBridge.acceptOwnership(); + } + + /// @notice Tests that ownership can't be renounced + function test_owner_renounceOwnership_reverts() public { + vm.expectRevert(PolygonStateBridge.CannotRenounceOwnership.selector); + + vm.prank(owner); + polygonStateBridge.renounceOwnership(); + } +} diff --git a/src/test/PolygonWorldID.t.sol b/src/test/PolygonWorldID.t.sol index cb3fba1b..7745b6dc 100644 --- a/src/test/PolygonWorldID.t.sol +++ b/src/test/PolygonWorldID.t.sol @@ -45,8 +45,6 @@ contract PolygonWorldIDTest is PRBTest, StdCheats { /// using Ownable2Step transferOwnership /// @param newOwner the new owner of the contract function test_owner_transferOwnership_succeeds(address newOwner) public { - vm.assume(newOwner != address(0)); - vm.prank(owner); id.transferOwnership(newOwner); diff --git a/src/test/StateBridge.t.sol b/src/test/StateBridge.t.sol deleted file mode 100644 index d9931f9e..00000000 --- a/src/test/StateBridge.t.sol +++ /dev/null @@ -1,387 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.15; - -import {StateBridge} from "src/StateBridge.sol"; -import {WorldIDIdentityManagerMock} from "src/mock/WorldIDIdentityManagerMock.sol"; - -import {PRBTest} from "@prb/test/PRBTest.sol"; -import {StdCheats} from "forge-std/StdCheats.sol"; - -/// @title State Bridge Test -/// @author Worldcoin -/// @notice A test contract for StateBridge.sol -contract StateBridgeTest is PRBTest, StdCheats { - /////////////////////////////////////////////////////////////////// - /// STORAGE CONFIG /// - /////////////////////////////////////////////////////////////////// - uint256 public mainnetFork; - string private MAINNET_RPC_URL = vm.envString("MAINNET_RPC_URL"); - - /// @notice emitted if there is no CrossDomainMessenger contract deployed on the fork - error invalidCrossDomainMessengerFork(); - - StateBridge public stateBridge; - WorldIDIdentityManagerMock public mockWorldID; - - uint32 public opGasLimit; - - address public mockWorldIDAddress; - address public crossDomainMessengerAddress; - address public opCrossDomainMessengerAddress; - address public baseCrossDomainMessengerAddress; - - address public fxRoot; - address public checkpointManager; - address public owner; - - /////////////////////////////////////////////////////////////////// - /// EVENTS /// - /////////////////////////////////////////////////////////////////// - - /// @notice OpenZeppelin Ownable.sol transferOwnership event - /// @param previousOwner The previous owner of the StateBridge contract - /// @param newOwner The new owner of the StateBridge contract - event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); - - /// @notice OpenZeppelin Ownable2Step transferOwnership event - /// @param previousOwner The previous owner of the StateBridge contract - /// @param newOwner The new owner of the StateBridge contract - event OwnershipTransferStarted(address indexed previousOwner, address indexed newOwner); - - /// @notice Emmitted when the the StateBridge gives ownership of the OPWorldID contract - /// to the WorldID Identity Manager contract away - /// @param previousOwner The previous owner of the OPWorldID contract - /// @param newOwner The new owner of the OPWorldID contract - /// @param isLocal Whether the ownership transfer is local (Optimism EOA/contract) or an Ethereum EOA or contract - event OwnershipTransferredOptimism( - address indexed previousOwner, address indexed newOwner, bool isLocal - ); - - /// @notice Emmitted when the the StateBridge sets the root history expiry for OpWorldID and PolygonWorldID - /// @param rootHistoryExpiry The new root history expiry - event SetRootHistoryExpiry(uint256 rootHistoryExpiry); - - /// @notice Emmitted when a root is sent to PolygonWorldID and OpworldID - /// @param root The latest WorldID Identity Manager root. - /// @param timestamp The Ethereum block timestamp of the latest WorldID Identity Manager root. - event RootSentMultichain(uint256 root, uint128 timestamp); - - /// @notice Emmitted when the the StateBridge sets the opGasLimit for sendRootOptimism - /// @param _opGasLimit The new opGasLimit for sendRootOptimism - event SetGasLimitSendRootOptimism(uint32 _opGasLimit); - - /// @notice Emmitted when the the StateBridge sets the opGasLimit for setRootHistoryExpiryOptimism - /// @param _opGasLimit The new opGasLimit for setRootHistoryExpiryOptimism - event SetGasLimitSetRootHistoryExpiryOptimism(uint32 _opGasLimit); - - /// @notice Emmitted when the the StateBridge sets the opGasLimit for transferOwnershipOptimism - /// @param _opGasLimit The new opGasLimit for transferOwnershipOptimism - event SetGasLimitTransferOwnershipOptimism(uint32 _opGasLimit); - - /// @notice Emmitted when the the StateBridge sets the opGasLimit for sendRootOptimism - /// @param _opGasLimit The new opGasLimit for sendRootOptimism - event SetGasLimitSendRootBase(uint32 _opGasLimit); - - /// @notice Emmitted when the the StateBridge sets the opGasLimit for setRootHistoryExpiryOptimism - /// @param _opGasLimit The new opGasLimit for setRootHistoryExpiryOptimism - event SetGasLimitSetRootHistoryExpiryBase(uint32 _opGasLimit); - - /// @notice Emmitted when the the StateBridge sets the opGasLimit for transferOwnershipOptimism - /// @param _opGasLimit The new opGasLimit for transferOwnershipOptimism - event SetGasLimitTransferOwnershipBase(uint32 _opGasLimit); - - /////////////////////////////////////////////////////////////////// - /// ERRORS /// - /////////////////////////////////////////////////////////////////// - - /// @notice - error NotWorldIDIdentityManager(); - - function setUp() public { - /// @notice Create a fork of the Ethereum mainnet - mainnetFork = vm.createFork(MAINNET_RPC_URL); - - vm.selectFork(mainnetFork); - /// @notice Roll the fork to a block where both Optimim's and Base's crossDomainMessenger contract is deployed - /// @notice and the Base crossDomainMessenger ResolvedDelegateProxy target address is initialized - vm.rollFork(17711915); - - if (block.chainid == 1) { - opCrossDomainMessengerAddress = address(0x25ace71c97B33Cc4729CF772ae268934F7ab5fA1); - baseCrossDomainMessengerAddress = address(0x866E82a600A1414e583f7F13623F1aC5d58b0Afa); - } else { - revert invalidCrossDomainMessengerFork(); - } - - mockWorldID = new WorldIDIdentityManagerMock(); - mockWorldIDAddress = address(mockWorldID); - - checkpointManager = address(0x86E4Dc95c7FBdBf52e33D563BbDB00823894C287); - fxRoot = address(0xfe5e5D361b2ad62c541bAb87C45a0B9B018389a2); - - address opWorldIDAddress = address(0x1); - address baseWorldIDAddress = address(0x2); - - stateBridge = new StateBridge( - checkpointManager, - fxRoot, - mockWorldIDAddress, - opWorldIDAddress, - opCrossDomainMessengerAddress, - baseWorldIDAddress, - baseCrossDomainMessengerAddress - ); - - owner = stateBridge.owner(); - mockWorldID.initialize(address(stateBridge)); - } - - /////////////////////////////////////////////////////////////////// - /// SUCCEEDS /// - /////////////////////////////////////////////////////////////////// - - /// @notice select a specific fork - function test_canSelectFork_succeeds() public { - // select the fork - vm.selectFork(mainnetFork); - assertEq(vm.activeFork(), mainnetFork); - } - - /// @notice tests that a root can be sent successfully to other networks - function test_sendRootMultichain_succeeds(uint256 newRoot) public { - uint128 timestamp = uint128(block.timestamp); - - vm.expectEmit(true, true, true, true); - - emit RootSentMultichain(newRoot, timestamp); - - mockWorldID.sendRootToStateBridge(newRoot); - - assertEq(mockWorldID.checkValidRoot(newRoot), true); - } - - /// @notice Tests that the owner of the StateBridge contract can transfer ownership - /// using Ownable2Step transferOwnership - /// @param newOwner the new owner of the contract - function test_owner_transferOwnership_succeeds(address newOwner) public { - vm.assume(newOwner != address(0)); - - vm.expectEmit(true, true, true, true); - - // OpenZeppelin Ownable2Step transferOwnershipStarted event - emit OwnershipTransferStarted(owner, newOwner); - - vm.prank(owner); - stateBridge.transferOwnership(newOwner); - - vm.expectEmit(true, true, true, true); - - // OpenZeppelin Ownable2Step transferOwnership event - emit OwnershipTransferred(owner, newOwner); - - vm.prank(newOwner); - stateBridge.acceptOwnership(); - - assertEq(stateBridge.owner(), newOwner); - } - - /// @notice tests whether the StateBridge contract can transfer ownership of the OPWorldID contract - /// @param newOwner The new owner of the OPWorldID contract (foundry fuzz) - /// @param isLocal Whether the ownership transfer is local (Optimism EOA/contract) or an Ethereum EOA or contract - function test_owner_transferOwnershipOptimism_succeeds(address newOwner, bool isLocal) public { - vm.assume(newOwner != address(0)); - - vm.expectEmit(true, true, true, true); - - // CrossDomainOwnable3.sol transferOwnership event - emit OwnershipTransferredOptimism(owner, newOwner, isLocal); - - vm.prank(owner); - stateBridge.transferOwnershipOptimism(newOwner, isLocal); - } - - /// @notice tests whether the StateBridge contract can set root history expiry on Optimism and Polygon - /// @param _rootHistoryExpiry The new root history expiry for OpWorldID and PolygonWorldID - function test_owner_setRootHistoryExpiry_succeeds(uint256 _rootHistoryExpiry) public { - vm.assume(_rootHistoryExpiry != 0); - - vm.expectEmit(true, true, true, true); - emit SetRootHistoryExpiry(_rootHistoryExpiry); - - vm.prank(mockWorldIDAddress); - stateBridge.setRootHistoryExpiry(_rootHistoryExpiry); - } - - /// @notice tests whether the StateBridge contract can set the opGasLimit for sendRootOptimism - /// @param _opGasLimit The new opGasLimit for sendRootOptimism - function test_owner_setGasLimitSendRootOptimism_succeeds(uint32 _opGasLimit) public { - vm.assume(_opGasLimit != 0); - - vm.expectEmit(true, true, true, true); - - emit SetGasLimitSendRootOptimism(_opGasLimit); - - vm.prank(owner); - stateBridge.setGasLimitSendRootOptimism(_opGasLimit); - } - - /// @notice tests whether the StateBridge contract can set the opGasLimit for setRootHistoryExpiryOptimism - /// @param _opGasLimit The new opGasLimit for setRootHistoryExpiryOptimism - function test_owner_setGasLimitSetRootHistoryExpiryOptimism_succeeds(uint32 _opGasLimit) - public - { - vm.assume(_opGasLimit != 0); - - vm.expectEmit(true, true, true, true); - - emit SetGasLimitSetRootHistoryExpiryOptimism(_opGasLimit); - - vm.prank(owner); - stateBridge.setGasLimitSetRootHistoryExpiryOptimism(_opGasLimit); - } - - /// @notice tests whether the StateBridge contract can set the opGasLimit for transferOwnershipOptimism - /// @param _opGasLimit The new opGasLimit for transferOwnershipOptimism - function test_owner_setGasLimitTransferOwnershipOptimism_succeeds(uint32 _opGasLimit) public { - vm.assume(_opGasLimit != 0); - - vm.expectEmit(true, true, true, true); - - emit SetGasLimitTransferOwnershipOptimism(_opGasLimit); - - vm.prank(owner); - stateBridge.setGasLimitTransferOwnershipOptimism(_opGasLimit); - } - - /// @notice tests whether the StateBridge contract can set the opGasLimit for sendRootBase - /// @param _baseGasLimit The new opGasLimit for sendRootBase - function test_owner_setGasLimitSendRootBase_succeeds(uint32 _baseGasLimit) public { - vm.assume(_baseGasLimit != 0); - - vm.expectEmit(true, true, true, true); - - emit SetGasLimitSendRootBase(_baseGasLimit); - - vm.prank(owner); - stateBridge.setGasLimitSendRootBase(_baseGasLimit); - } - - /// @notice tests whether the StateBridge contract can set the opGasLimit for setRootHistoryExpiryBase - /// @param _baseGasLimit The new opGasLimit for setRootHistoryExpiryBase - function test_owner_setGasLimitSetRootHistoryExpiryBase_succeeds(uint32 _baseGasLimit) public { - vm.assume(_baseGasLimit != 0); - - vm.expectEmit(true, true, true, true); - - emit SetGasLimitSetRootHistoryExpiryBase(_baseGasLimit); - - vm.prank(owner); - stateBridge.setGasLimitSetRootHistoryExpiryBase(_baseGasLimit); - } - - /// @notice tests whether the StateBridge contract can set the opGasLimit for transferOwnershipBase - /// @param _baseGasLimit The new opGasLimit for transferOwnershipBase - function test_owner_setGasLimitTransferOwnershipBase_succeeds(uint32 _baseGasLimit) public { - vm.assume(_baseGasLimit != 0); - - vm.expectEmit(true, true, true, true); - - emit SetGasLimitTransferOwnershipBase(_baseGasLimit); - - vm.prank(owner); - stateBridge.setGasLimitTransferOwnershipBase(_baseGasLimit); - } - - /////////////////////////////////////////////////////////////////// - /// REVERTS /// - /////////////////////////////////////////////////////////////////// - - /// @notice tests that a root that is not is not a valid root in WorldID Identity Manager contract - /// can't be sent to the StateBridge - function test_sendRootMultichain_reverts(uint256 newRoot, address notWorldID) public { - vm.assume(notWorldID != mockWorldIDAddress); - - vm.expectRevert(StateBridge.NotWorldIDIdentityManager.selector); - vm.prank(notWorldID); - stateBridge.sendRootMultichain(newRoot); - } - - /// @notice tests that the StateBridge contract's ownership can't be changed by a non-owner - /// @param newOwner The new owner of the StateBridge contract (foundry fuzz) - function test_notOwner_transferOwnership_reverts(address nonOwner, address newOwner) public { - vm.assume(nonOwner != owner && nonOwner != address(0) && newOwner != address(0)); - - vm.expectRevert("Ownable: caller is not the owner"); - - vm.prank(nonOwner); - stateBridge.transferOwnership(newOwner); - } - - /// @notice tests that the StateBridge contract's ownership can't be changed by a non-owner - /// @param newOwner The new owner of the StateBridge contract (foundry fuzz) - function test_notOwner_transferOwnershipOptimism_reverts( - address nonOwner, - address newOwner, - bool isLocal - ) public { - vm.assume(nonOwner != owner && newOwner != address(0x0)); - - vm.expectRevert("Ownable: caller is not the owner"); - - vm.prank(nonOwner); - stateBridge.transferOwnershipOptimism(newOwner, isLocal); - } - - /// @notice tests that the StateBridge contract's ownership can't be changed by a non-owner - /// @param newOwner The new owner of the StateBridge contract (foundry fuzz) - function test_notOwner_transferOwnershipBase_reverts( - address nonOwner, - address newOwner, - bool isLocal - ) public { - vm.assume(nonOwner != owner && newOwner != address(0x0)); - - vm.expectRevert("Ownable: caller is not the owner"); - - vm.prank(nonOwner); - stateBridge.transferOwnershipBase(newOwner, isLocal); - } - - /// @notice tests whether the StateBridge contract can set root history expiry on Optimism and Polygon - /// @param _rootHistoryExpiry The new root history expiry for OpWorldID and PolygonWorldID - function test_notOwner_setRootHistoryExpiry_reverts( - address nonWorldID, - uint256 _rootHistoryExpiry - ) public { - vm.assume(nonWorldID != mockWorldIDAddress && _rootHistoryExpiry != 0); - - vm.expectRevert(NotWorldIDIdentityManager.selector); - - vm.prank(nonWorldID); - stateBridge.setRootHistoryExpiry(_rootHistoryExpiry); - } - - /// @notice Tests that a nonPendingOwner can't accept ownership of StateBridge - /// @param newOwner the new owner of the contract - function test_notOwner_acceptOwnership_reverts(address newOwner, address randomAddress) - public - { - vm.assume(newOwner != address(0) && randomAddress != address(0)); - - vm.prank(owner); - stateBridge.transferOwnership(newOwner); - - vm.expectRevert("Ownable2Step: caller is not the new owner"); - - vm.prank(randomAddress); - stateBridge.acceptOwnership(); - } - - /// @notice Tests that ownership can't be renounced - function test_owner_renounceOwnership_reverts() public { - vm.expectRevert(StateBridge.CannotRenounceOwnership.selector); - - vm.prank(owner); - stateBridge.renounceOwnership(); - } -} diff --git a/src/utils/BytesUtils.sol b/src/utils/BytesUtils.sol index d422fed0..2a6d4a5c 100644 --- a/src/utils/BytesUtils.sol +++ b/src/utils/BytesUtils.sol @@ -1,7 +1,7 @@ pragma solidity ^0.8.15; library BytesUtils { - /// @notice Thrown when the payload is too short to contain a selector (at least 4 bytes). + /// @notice Emitted when the payload is too short to contain a selector (at least 4 bytes). error PayloadTooShort(); /// @notice grabSelector, takes a byte array _payload as input and returns the first 4 bytes diff --git a/yarn.lock b/yarn.lock index ec443e66..9b9d0953 100644 --- a/yarn.lock +++ b/yarn.lock @@ -39,15 +39,16 @@ ethers "^5.7.0" hardhat "^2.9.6" -"@eth-optimism/contracts-bedrock@^0.16.0": - version "0.16.0" - resolved "https://registry.yarnpkg.com/@eth-optimism/contracts-bedrock/-/contracts-bedrock-0.16.0.tgz#531cb81529ad3f895be538c1d8762eace6241a95" - integrity sha512-MfHJdeQ/BzHgkoHnA+NGb1hU8CH0OFsp4ylmFi0uwYh3xPJxcHt9qhy1g4MGGMUGAPIUmlBPaqhwbBlQkaeFrA== +"@eth-optimism/contracts-bedrock@^0.13.2": + version "0.13.2" + resolved "https://registry.yarnpkg.com/@eth-optimism/contracts-bedrock/-/contracts-bedrock-0.13.2.tgz#4cdd4bfe7d905fa237719efdadab6bc228f49b0f" + integrity sha512-LMJzK0psPYEGDvvb+Qix8e1cK4+9J8tD9yXFqCt+On87warrTj8DZmLNh3G56DTLBrpz5/U6g62peEEsYha/4w== dependencies: + "@eth-optimism/core-utils" "^0.12.0" "@openzeppelin/contracts" "4.7.3" "@openzeppelin/contracts-upgradeable" "4.7.3" - "@rari-capital/solmate" "github:transmissions11/solmate#8f9b23f8838670afda0fd8983f2c41e8037ae6bc" - clones-with-immutable-args "github:Saw-mon-and-Natalie/clones-with-immutable-args#105efee1b9127ed7f6fedf139e1fc796ce8791f2" + ethers "^5.7.0" + hardhat "^2.9.6" "@eth-optimism/contracts@0.5.40", "@eth-optimism/contracts@^0.5.39": version "0.5.40" @@ -668,10 +669,6 @@ resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-4.7.3.tgz#939534757a81f8d69cc854c7692805684ff3111e" integrity sha512-dGRS0agJzu8ybo44pCIf3xBaPQN/65AIXNgK8+4gzKd5kbvlqyxryUYVLJv7fK98Seyd2hDZzVEHSWAh0Bt1Yw== -"@rari-capital/solmate@github:transmissions11/solmate#8f9b23f8838670afda0fd8983f2c41e8037ae6bc": - version "7.0.0-alpha.3" - resolved "https://codeload.github.com/transmissions11/solmate/tar.gz/8f9b23f8838670afda0fd8983f2c41e8037ae6bc" - "@scure/base@~1.1.0": version "1.1.1" resolved "https://registry.yarnpkg.com/@scure/base/-/base-1.1.1.tgz#ebb651ee52ff84f420097055f4bf46cfba403938" @@ -1330,10 +1327,6 @@ clone@^1.0.2: resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e" integrity sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg== -"clones-with-immutable-args@github:Saw-mon-and-Natalie/clones-with-immutable-args#105efee1b9127ed7f6fedf139e1fc796ce8791f2": - version "2.0.0" - resolved "https://codeload.github.com/Saw-mon-and-Natalie/clones-with-immutable-args/tar.gz/105efee1b9127ed7f6fedf139e1fc796ce8791f2" - color-convert@^1.9.0: version "1.9.3" resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8"