diff --git a/.circleci/config.yml b/.circleci/config.yml index 030c15ae6367d..1bb3cd906419c 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -152,8 +152,8 @@ commands: jobs: cannon-go-lint-and-test: - machine: true - resource_class: ethereum-optimism/latitude-1 + docker: + - image: <> parameters: skip_slow_tests: type: boolean @@ -240,8 +240,8 @@ jobs: working_directory: cannon/mipsevm/tests/open_mips_tests contracts-bedrock-build: - machine: true - resource_class: ethereum-optimism/latitude-1 + docker: + - image: <> parameters: skip_pattern: description: Glob pattern of tests to skip @@ -751,8 +751,8 @@ jobs: description: should load in foundry artifacts type: boolean default: false - machine: true - resource_class: ethereum-optimism/latitude-1 + docker: + - image: <> steps: - checkout - check-changed: @@ -760,14 +760,20 @@ jobs: - attach_workspace: at: "." if: ${{ uses_artifacts }} + - run: + name: Install dependencies + command: | + apt update + apt install -y parallel + working_directory: . - run: name: Fuzz command: make fuzz working_directory: "<>" go-lint: - machine: true - resource_class: ethereum-optimism/latitude-1 + docker: + - image: <> steps: - checkout - run: @@ -834,8 +840,8 @@ jobs: when: always go-test: - machine: true - resource_class: ethereum-optimism/latitude-1 + docker: + - image: <> steps: - checkout - attach_workspace: @@ -893,16 +899,12 @@ jobs: description: Slack user or group to mention when notifying of failures type: string default: "" - resource_class: - description: Machine resource class - type: string - default: ethereum-optimism/latitude-1 skip_slow_tests: description: Indicates that slow tests should be skipped type: boolean default: false - machine: true - resource_class: <> + docker: + - image: <> steps: - checkout - attach_workspace: @@ -949,8 +951,8 @@ jobs: mentions: "<>" cannon-prestate: - machine: true - resource_class: ethereum-optimism/latitude-1 + docker: + - image: <> steps: - checkout - run: diff --git a/Makefile b/Makefile index 3f6f759c59fcd..766d6782573fd 100644 --- a/Makefile +++ b/Makefile @@ -1,3 +1,5 @@ +include celo.mk + COMPOSEFLAGS=-d ITESTS_L2_HOST=http://localhost:9545 BEDROCK_TAGS_REMOTE?=origin diff --git a/README_CELO.md b/README_CELO.md new file mode 100644 index 0000000000000..e7ee7ba3f5c21 --- /dev/null +++ b/README_CELO.md @@ -0,0 +1,73 @@ +# Celo devnet + mainnet deployment scripts + +## Generate Devnet L1 and L2 Allocations + +1. Run + ```bash + make devnet-clean && DEVNET_CELO=true SAFE_AS_OWNER=true make devnet-up + ``` + This will generate and run full devnet in local docker +2. Copy `.devnet/genesis-L1.json`, `.devnet/genesis-L2.json`, and `.devnet/rollup.json` to the `.devnet` folder in other branch +3. Run on other branch + ```bash + DEVNET_CELO=true SAFE_AS_OWNER=true make devnet-up + ``` +4. To double check that everything is working correctly, search for + + ``` L1 genesis already generated.``` + + ``` L2 genesis and rollup configs already generated.``` + + in logs + + +## Generate L2 Allocations for real network (Alfajores, Baklava, Mainnet) + +### Environment Variables + +- `CELO_MONOREPO_DIR` + Root directory of the monorepo. Defaults to current directory. + +- **Directory Paths** + - `CELO_OP_NODE_DIR`: `$(CELO_MONOREPO_DIR)/op-node` + - `CELO_DEVNET_CONFIG_PATH`: `packages/contracts-bedrock/deploy-config/devnetL1.json` + - `CELO_L2_ALLOCS_PATH`: `packages/contracts-bedrock/.devnet/allocs-l2.json` + - `CELO_ADDRESSES_JSON_PATH`: `packages/contracts-bedrock/.devnet/addresses.json` + - `CELO_GENESIS_L2_PATH`: `packages/contracts-bedrock/.devnet/genesis-l2.json` + - `CELO_ROLLUP_CONFIG_PATH`: `packages/contracts-bedrock/.devnet/rollup.json` + - `CELO_CONTRACTS_BEDROCK_DIR`: `packages/contracts-bedrock` + +- **RPC** + - `CELO_L1_RPC_URL`: `http://localhost:8545` + +- **Deployment** + - `CELO_DEPLOYMENT_CONTEXT` (default: `devnetL1`) + - `CELO_L1_CHAIN_ID` (default: `1`) + +- **Derived Paths** + - `CELO_DEPLOY_CONFIG_PATH`: `deploy-config/$(CELO_DEPLOYMENT_CONTEXT).json` + - `CELO_STATE_DUMP_PATH`: `deployments/l2-allocs.json` + - `CELO_CONTRACT_ADDRESSES_PATH`: `deployments/$(CELO_L1_CHAIN_ID)-deploy.json` + +## Makefile Targets + +- **generate_l2_allocs** + - Runs Forge script `L2Genesis` with necessary paths + - Generates and saves l2 allocations to `$(CELO_STATE_DUMP_PATH)` + +## Usage + +1. **Create L1 deploy script** + It is necessary to add L1 deployment file into `deployments` directory with the name convetion `$(CELO_L1_CHAIN_ID)-deploy.json` eg `1-deploy.json` (example of such file https://storage.googleapis.com/cel2-rollup-files/baklava/deployment-l1.json) +1. **Set Environment Variables** + + ```bash + export CELO_MONOREPO_DIR=/path/to/monorepo + export CELO_DEPLOYMENT_CONTEXT=devnetL1 + export CELO_L1_CHAIN_ID=1 + ``` +1. **Generate L2 Allocations** + + ```bash + make generate_l2_allocs + ``` diff --git a/bedrock-devnet/devnet/__init__.py b/bedrock-devnet/devnet/__init__.py index 70243dc790c8c..6a972feba5674 100644 --- a/bedrock-devnet/devnet/__init__.py +++ b/bedrock-devnet/devnet/__init__.py @@ -31,7 +31,7 @@ DEVNET_L2OO = os.getenv('DEVNET_L2OO') == "true" DEVNET_ALTDA = os.getenv('DEVNET_ALTDA') == "true" GENERIC_ALTDA = os.getenv('GENERIC_ALTDA') == "true" - +DEVNET_CELO = os.getenv('DEVNET_CELO') == "true" class Bunch: def __init__(self, **kwds): self.__dict__.update(kwds) @@ -129,6 +129,15 @@ def init_devnet_l1_deploy_config(paths, update_timestamp=False): deploy_config['useAltDA'] = True if GENERIC_ALTDA: deploy_config['daCommitmentType'] = "GenericCommitment" + if DEVNET_CELO: + deploy_config['useFaultProofs'] = True + deploy_config['useCustomGasToken'] = True + deploy_config['gasPriceOracleBlobBaseFeeScalar'] = 0 + deploy_config['gasPriceOracleBaseFeeScalar'] = 0 + deploy_config['deployCeloContracts'] = True + # Usage of the zero address in combination of the useCustomGasToken == True + # will deploy a new contract + deploy_config['customGasTokenAddress'] = "0x0000000000000000000000000000000000000000" write_json(paths.devnet_config_path, deploy_config) def devnet_l1_allocs(paths): diff --git a/celo.mk b/celo.mk new file mode 100644 index 0000000000000..a6ae230061d29 --- /dev/null +++ b/celo.mk @@ -0,0 +1,62 @@ +# ================================ +# Variables Configuration +# ================================ + +# Default to current directory if CELO_MONOREPO_DIR is not set +CELO_MONOREPO_DIR ?= $(shell pwd) + +# Directory Paths +CELO_OP_NODE_DIR := $(CELO_MONOREPO_DIR)/op-node +CELO_DEVNET_CONFIG_PATH := $(CELO_MONOREPO_DIR)/packages/contracts-bedrock/deploy-config/devnetL1.json +CELO_L2_ALLOCS_PATH := $(CELO_MONOREPO_DIR)/packages/contracts-bedrock/.devnet/allocs-l2.json +CELO_ADDRESSES_JSON_PATH := $(CELO_MONOREPO_DIR)/packages/contracts-bedrock/.devnet/addresses.json +CELO_GENESIS_L2_PATH := $(CELO_MONOREPO_DIR)/packages/contracts-bedrock/.devnet/genesis-l2.json +CELO_ROLLUP_CONFIG_PATH := $(CELO_MONOREPO_DIR)/packages/contracts-bedrock/.devnet/rollup.json +CELO_CONTRACTS_BEDROCK_DIR := $(CELO_MONOREPO_DIR)/packages/contracts-bedrock + +# RPC Configuration +CELO_L1_RPC_URL := http://localhost:8545 + +# Deployment Configuration +CELO_DEPLOYMENT_CONTEXT ?= devnetL1 +CELO_L1_CHAIN_ID ?= 1 + +# Derived Paths based on Deployment Context and Chain ID +CELO_DEPLOY_CONFIG_PATH := $(CELO_CONTRACTS_BEDROCK_DIR)/deploy-config/$(CELO_DEPLOYMENT_CONTEXT).json +CELO_STATE_DUMP_PATH := $(CELO_CONTRACTS_BEDROCK_DIR)/deployments/l2-allocs.json +CELO_CONTRACT_ADDRESSES_PATH := $(CELO_CONTRACTS_BEDROCK_DIR)/deployments/$(CELO_L1_CHAIN_ID)-deploy.json + +# ================================ +# Phony Targets +# ================================ +.PHONY: run_Genesis generate_l2_allocs deploy clean help + +# ================================ +# generate_l2_allocs Target +# ================================ +generate_l2_allocs: + @# Validate Environment Variables + @if [ -z "$(CELO_DEPLOYMENT_CONTEXT)" ]; then \ + echo "Error: CELO_DEPLOYMENT_CONTEXT is not set."; \ + echo "Set it using 'make deploy CELO_DEPLOYMENT_CONTEXT=' or export it as an environment variable."; \ + exit 1; \ + fi + @if [ -z "$(CELO_L1_CHAIN_ID)" ]; then \ + echo "Error: CELO_L1_CHAIN_ID is not set."; \ + echo "Set it using 'make deploy CELO_L1_CHAIN_ID=' or export it as an environment variable."; \ + exit 1; \ + fi + + @echo "Running Forge Deployment Script..." + + # Ensure deployment directory exists + @mkdir -p $(CELO_CONTRACTS_BEDROCK_DIR)/deployments + + # Run Forge script with absolute paths + @cd $(CELO_CONTRACTS_BEDROCK_DIR) && \ + DEPLOY_CONFIG_PATH=$(CELO_DEPLOY_CONFIG_PATH) \ + STATE_DUMP_PATH=$(CELO_STATE_DUMP_PATH) \ + CONTRACT_ADDRESSES_PATH=$(CELO_CONTRACT_ADDRESSES_PATH) \ + forge script scripts/L2Genesis.s.sol:L2Genesis --sig 'runWithStateDump()' + + @echo "Saved L2 allocations to $(CELO_STATE_DUMP_PATH)" diff --git a/op-chain-ops/genesis/config.go b/op-chain-ops/genesis/config.go index 7ed0a6e2f682a..9da2206e0f0a9 100644 --- a/op-chain-ops/genesis/config.go +++ b/op-chain-ops/genesis/config.go @@ -271,9 +271,11 @@ var _ ConfigChecker = (*GasTokenDeployConfig)(nil) func (d *GasTokenDeployConfig) Check(log log.Logger) error { if d.UseCustomGasToken { - if d.CustomGasTokenAddress == (common.Address{}) { - return fmt.Errorf("%w: CustomGasTokenAddress cannot be address(0)", ErrInvalidDeployConfig) - } + // NOTE: we are using the address(0) as an instruction to deploy the L1 token, + // so this deploy-config validation has to be disabled + // if d.CustomGasTokenAddress == (common.Address{}) { + // return fmt.Errorf("%w: CustomGasTokenAddress cannot be address(0)", ErrInvalidDeployConfig) + // } log.Info("Using custom gas token", "address", d.CustomGasTokenAddress) } return nil @@ -905,6 +907,15 @@ type DeployConfig struct { // Legacy, ignored, here for strict-JSON decoding to be accepted. LegacyDeployConfig `evm:"-"` + + // DeployCeloContracts indicates whether to deploy Celo contracts. + DeployCeloContracts bool `json:"deployCeloContracts"` + + // Address of the external SuperchainConfig that CeloSuperchainConfig should point to. + ExternalSuperchainConfig common.Address `json:"externalSuperchainConfig"` + + // Used to validate the ProxyAdmin owner address. + ProxyAdminOwnerIsMultisig bool `json:"proxyAdminOwnerIsMultisig"` } // Copy will deeply copy the DeployConfig. This does a JSON roundtrip to copy diff --git a/ops/docker/proofs-tools/Dockerfile b/ops/docker/proofs-tools/Dockerfile index ad462156a8ef3..8560394aa4df0 100644 --- a/ops/docker/proofs-tools/Dockerfile +++ b/ops/docker/proofs-tools/Dockerfile @@ -5,7 +5,7 @@ ARG CHALLENGER_VERSION ARG KONA_VERSION FROM --platform=$BUILDPLATFORM us-docker.pkg.dev/oplabs-tools-artifacts/images/op-challenger:$CHALLENGER_VERSION AS challenger -FROM --platform=$BUILDPLATFORM ghcr.io/anton-rs/kona/kona-fpp-asterisc:$KONA_VERSION AS kona +FROM --platform=$BUILDPLATFORM ghcr.io/op-rs/kona/kona-fpp-asterisc:$KONA_VERSION AS kona FROM --platform=$BUILDPLATFORM ubuntu:22.04 AS proofs-tools RUN apt-get update && apt-get install -y --no-install-recommends musl openssl ca-certificates diff --git a/packages/contracts-bedrock/.gas-snapshot b/packages/contracts-bedrock/.gas-snapshot index 1b1a01905ba5f..830bf12fc4d74 100644 --- a/packages/contracts-bedrock/.gas-snapshot +++ b/packages/contracts-bedrock/.gas-snapshot @@ -6,12 +6,12 @@ GasBenchMark_L1Block_SetValuesEcotone:test_setL1BlockValuesEcotone_benchmark() ( GasBenchMark_L1Block_SetValuesEcotone_Warm:test_setL1BlockValuesEcotone_benchmark() (gas: 7597) GasBenchMark_L1CrossDomainMessenger:test_sendMessage_benchmark_0() (gas: 369280) GasBenchMark_L1CrossDomainMessenger:test_sendMessage_benchmark_1() (gas: 2967420) -GasBenchMark_L1StandardBridge_Deposit:test_depositERC20_benchmark_0() (gas: 564398) -GasBenchMark_L1StandardBridge_Deposit:test_depositERC20_benchmark_1() (gas: 4076613) +GasBenchMark_L1StandardBridge_Deposit:test_depositERC20_benchmark_0() (gas: 564413) +GasBenchMark_L1StandardBridge_Deposit:test_depositERC20_benchmark_1() (gas: 4076628) GasBenchMark_L1StandardBridge_Deposit:test_depositETH_benchmark_0() (gas: 467098) GasBenchMark_L1StandardBridge_Deposit:test_depositETH_benchmark_1() (gas: 3512802) -GasBenchMark_L1StandardBridge_Finalize:test_finalizeETHWithdrawal_benchmark() (gas: 72664) -GasBenchMark_L2OutputOracle:test_proposeL2Output_benchmark() (gas: 92973) +GasBenchMark_L1StandardBridge_Finalize:test_finalizeETHWithdrawal_benchmark() (gas: 85481) +GasBenchMark_L2OutputOracle:test_proposeL2Output_benchmark() (gas: 92997) GasBenchMark_OptimismPortal:test_depositTransaction_benchmark() (gas: 68422) GasBenchMark_OptimismPortal:test_depositTransaction_benchmark_1() (gas: 68986) -GasBenchMark_OptimismPortal:test_proveWithdrawalTransaction_benchmark() (gas: 155610) \ No newline at end of file +GasBenchMark_OptimismPortal:test_proveWithdrawalTransaction_benchmark() (gas: 169989) diff --git a/packages/contracts-bedrock/Celo.README.md b/packages/contracts-bedrock/Celo.README.md new file mode 100644 index 0000000000000..185d765b9252b --- /dev/null +++ b/packages/contracts-bedrock/Celo.README.md @@ -0,0 +1,25 @@ +# Celo Diff + +- Since we are using the CustomGasFeature (which is currently being deprecated from OP), we cannot use the OP Deployer to deploy contracts. Instead, we need to deploy contracts using the `Deploy.s.sol` script. +- The `Deploy.s.sol` script is not from the original 1.8.0 smart contract branch, as it was likely never used for testnet deployment (due to the use of Anvil cheat codes). We ported the `Deploy.s.sol` script from our Celo client branch, specifically based on version 1.9.3. + +## L1 Deploy Script Notes (`Deploy.s.sol`) +- `CeloTokenL1` is deployed with a balance of 1 billion CELO and is used as the Custom Gas token. +- Due to FaultProofs, we are using `OptimismPortal2` rather than `OptimismPortal`. +- Since we are using the Custom Gas Token feature with a preexisting balance, we use a storage setter design pattern (deploying a different implementation to the OptimismPortal proxy during the transition period), which allows us to set a specific storage slot to the desired value. + +## L2 Deploy Script Notes (`L2Genesis.s.sol`) +- The required Celo core contracts were added to `L2Genesis.s.sol` for devnet deployment. For mainnet, this step is skipped, and we instead rely on the blockchain migration tool for the entire blockchain state. + +## CeloSuperchainConfig +- The Superchain has the power to pause/unpause the Celo network. To enable us to unpause the network when necessary, we introduced `CeloSuperchainConfig`, which adds a Guardian account with the same permissions as the Superchain. This allows us to pause/unpause the network in case of an emergency. + +## AbstractFeeCurrency +- We added `AbstractFeeCurrency` to the bridged ERC20 token (specifically, `OptimismMintableERC20.sol`), which implements credit and debit functions. This enhancement allows us to whitelist and use any natively bridged token as the native fee currency. + +# Release Mainnet Process + +1. L1 smart contracts are deployed using the `Deploy.s.sol` script. +2. The L2 Genesis is created either by running `make generate_l2_allocs` or by manually using the `L2Genesis.s.sol` script (which creates the L2 allocs file). +3. The L2 allocs file is fed into the blockchain migration tool. +4. The sequencer and nodes are started. diff --git a/packages/contracts-bedrock/deploy-config/devnetL1-template.json b/packages/contracts-bedrock/deploy-config/devnetL1-template.json index 2f275f46c4f69..c965c56384e0f 100644 --- a/packages/contracts-bedrock/deploy-config/devnetL1-template.json +++ b/packages/contracts-bedrock/deploy-config/devnetL1-template.json @@ -72,5 +72,7 @@ "daChallengeWindow": 16, "daResolveWindow": 16, "daBondSize": 1000000, - "daResolverRefundPercentage": 0 + "daResolverRefundPercentage": 0, + "proxyAdminOwnerIsMultisig": false, + "externalSuperchainConfig": "0x0000000000000000000000000000000000000000" } diff --git a/packages/contracts-bedrock/deploy-config/hardhat.json b/packages/contracts-bedrock/deploy-config/hardhat.json index 965d403d4d823..423ffb0fc42ec 100644 --- a/packages/contracts-bedrock/deploy-config/hardhat.json +++ b/packages/contracts-bedrock/deploy-config/hardhat.json @@ -62,5 +62,6 @@ "daChallengeWindow": 100, "daResolveWindow": 100, "daBondSize": 1000, - "daResolverRefundPercentage": 50 + "daResolverRefundPercentage": 50, + "proxyAdminOwnerIsMultisig": false } diff --git a/packages/contracts-bedrock/scripts/L2Genesis.s.sol b/packages/contracts-bedrock/scripts/L2Genesis.s.sol index 207c59732146e..93bb50dbe4460 100644 --- a/packages/contracts-bedrock/scripts/L2Genesis.s.sol +++ b/packages/contracts-bedrock/scripts/L2Genesis.s.sol @@ -32,6 +32,8 @@ import { IL2CrossDomainMessenger } from "src/L2/interfaces/IL2CrossDomainMesseng import { IGasPriceOracle } from "src/L2/interfaces/IGasPriceOracle.sol"; import { IL1Block } from "src/L2/interfaces/IL1Block.sol"; +import { L2GenesisCelo } from "./L2GenesisCelo.sol"; + struct L1Dependencies { address payable l1CrossDomainMessengerProxy; address payable l1StandardBridgeProxy; @@ -45,7 +47,7 @@ struct L1Dependencies { /// effects in the constructor and no immutables in the bytecode. /// 2. A contract must be deployed using the `new` syntax if there are immutables in the code. /// Any other side effects from the init code besides setting the immutables must be cleaned up afterwards. -contract L2Genesis is Deployer { +contract L2Genesis is L2GenesisCelo { using ForkUtils for Fork; using OutputModeUtils for OutputMode; @@ -95,6 +97,7 @@ contract L2Genesis is Deployer { /// @notice Sets up the script and ensures the deployer account is used to make calls. function setUp() public override { deployer = makeAddr("deployer"); + _celoL2Outfile = celoL2Outfile(); super.setUp(); } @@ -150,10 +153,16 @@ contract L2Genesis is Deployer { vm.startPrank(deployer); vm.chainId(cfg.l2ChainID()); - dealEthToPrecompiles(); + if (cfg.deployCeloContracts()) { + dealEthToPrecompiles(); + } + setPredeployProxies(); setPredeployImplementations(_l1Dependencies); setPreinstalls(); + if (cfg.deployCeloContracts()) { + setCeloPredeploys(); + } if (cfg.fundDevAccounts()) { fundDevAccounts(); } @@ -233,7 +242,9 @@ contract L2Genesis is Deployer { if (Predeploys.isSupportedPredeploy(addr, cfg.useInterop())) { address implementation = Predeploys.predeployToCodeNamespace(addr); console.log("Setting proxy %s implementation: %s", addr, implementation); + string memory name = Predeploys.getName(addr); EIP1967Helper.setImplementation(addr, implementation); + celoSave(name, implementation, addr); } } } @@ -424,6 +435,7 @@ contract L2Genesis is Deployer { /// in the constructor is set manually. function setWETH() public { console.log("Setting %s implementation at: %s", "WETH", Predeploys.WETH); + celoSave("WETH", Predeploys.WETH, address(0)); vm.etch(Predeploys.WETH, vm.getDeployedCode("WETH.sol:WETH")); } @@ -505,6 +517,7 @@ contract L2Genesis is Deployer { ); console.log("Setting %s implementation at: %s", "GovernanceToken", Predeploys.GOVERNANCE_TOKEN); vm.etch(Predeploys.GOVERNANCE_TOKEN, address(token).code); + celoSave("GovernanceToken", Predeploys.GOVERNANCE_TOKEN, address(0)); bytes32 _nameSlot = hex"0000000000000000000000000000000000000000000000000000000000000003"; bytes32 _symbolSlot = hex"0000000000000000000000000000000000000000000000000000000000000004"; diff --git a/packages/contracts-bedrock/scripts/L2GenesisCelo.sol b/packages/contracts-bedrock/scripts/L2GenesisCelo.sol new file mode 100644 index 0000000000000..65a43b96f6daf --- /dev/null +++ b/packages/contracts-bedrock/scripts/L2GenesisCelo.sol @@ -0,0 +1,265 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.15; + +import { stdJson } from "forge-std/StdJson.sol"; +import { console2 as console } from "forge-std/console2.sol"; +import { Deployer } from "scripts/deploy/Deployer.sol"; + +import { GoldToken } from "src/celo/GoldToken.sol"; +import { CeloPredeploys } from "src/celo/CeloPredeploys.sol"; +import { CeloRegistry } from "src/celo/CeloRegistry.sol"; +import { FeeHandler } from "src/celo/FeeHandler.sol"; +import { MentoFeeHandlerSeller } from "src/celo/MentoFeeHandlerSeller.sol"; +import { UniswapFeeHandlerSeller } from "src/celo/UniswapFeeHandlerSeller.sol"; +import { SortedOracles } from "src/celo/stability/SortedOracles.sol"; +import { FeeCurrencyDirectory } from "src/celo/FeeCurrencyDirectory.sol"; +import { FeeCurrency } from "src/celo/testing/FeeCurrency.sol"; +import { StableTokenV2 } from "src/celo/StableTokenV2.sol"; +import { Predeploys } from "src/libraries/Predeploys.sol"; +import { EIP1967Helper } from "test/mocks/EIP1967Helper.sol"; + +contract L2GenesisCelo is Deployer { + mapping(string => address) public deployedContractNamesToAddresses; + string internal _celoL2Outfile; + + address internal deployerCelo = makeAddr("deployer"); + + address[30] internal devAccountsCelo = [ + 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266, // 0 + 0x70997970C51812dc3A010C7d01b50e0d17dc79C8, // 1 + 0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC, // 2 + 0x90F79bf6EB2c4f870365E785982E1f101E93b906, // 3 + 0x15d34AAf54267DB7D7c367839AAf71A00a2C6A65, // 4 + 0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc, // 5 + 0x976EA74026E726554dB657fA54763abd0C3a0aa9, // 6 + 0x14dC79964da2C08b23698B3D3cc7Ca32193d9955, // 7 + 0x23618e81E3f5cdF7f54C3d65f7FBc0aBf5B21E8f, // 8 + 0xa0Ee7A142d267C1f36714E4a8F75612F20a79720, // 9 + 0xBcd4042DE499D14e55001CcbB24a551F3b954096, // 10 + 0x71bE63f3384f5fb98995898A86B02Fb2426c5788, // 11 + 0xFABB0ac9d68B0B445fB7357272Ff202C5651694a, // 12 + 0x1CBd3b2770909D4e10f157cABC84C7264073C9Ec, // 13 + 0xdF3e18d64BC6A983f673Ab319CCaE4f1a57C7097, // 14 + 0xcd3B766CCDd6AE721141F452C550Ca635964ce71, // 15 + 0x2546BcD3c84621e976D8185a91A922aE77ECEc30, // 16 + 0xbDA5747bFD65F08deb54cb465eB87D40e51B197E, // 17 + 0xdD2FD4581271e230360230F9337D5c0430Bf44C0, // 18 + 0x8626f6940E2eb28930eFb4CeF49B2d1F2C9C1199, // 19 + 0x09DB0a93B389bEF724429898f539AEB7ac2Dd55f, // 20 + 0x02484cb50AAC86Eae85610D6f4Bf026f30f6627D, // 21 + 0x08135Da0A343E492FA2d4282F2AE34c6c5CC1BbE, // 22 + 0x5E661B79FE2D3F6cE70F5AAC07d8Cd9abb2743F1, // 23 + 0x61097BA76cD906d2ba4FD106E757f7Eb455fc295, // 24 + 0xDf37F81dAAD2b0327A0A50003740e1C935C70913, // 25 + 0x553BC17A05702530097c3677091C5BB47a3a7931, // 26 + 0x87BdCE72c06C21cd96219BD8521bDF1F42C78b5e, // 27 + 0x40Fc963A729c542424cD800349a7E4Ecc4896624, // 28 + 0x9DCCe783B6464611f38631e6C851bf441907c710 // 29 + ]; + + function celoL2Outfile() internal view returns (string memory _env) { + _env = vm.envOr( + "L2_OUTFILE", + string.concat(vm.projectRoot(), "/deployments/", vm.toString(block.chainid), "-l2-deploy.json") + ); + } + + function celoSave(string memory _name, address _impl, address _proxy) public { + if (deployedContractNamesToAddresses[_name] == address(0)) { + deployedContractNamesToAddresses[_name] = _impl; + + _celoWrite(_name, _impl); + } + + if (_proxy != address(0)) { + string memory _proxyName = string.concat(_name, "Proxy"); + deployedContractNamesToAddresses[_proxyName] = _proxy; + + _celoWrite(_proxyName, _proxy); + } + } + + function _celoWrite(string memory _name, address _deployed) internal { + console.log("Writing l2 deploy %s: %s", _name, _deployed); + + vm.writeJson({ json: stdJson.serialize("celo_l2_deploys", _name, _deployed), path: _celoL2Outfile }); + } + + ///@notice Sets all proxies and implementations for Celo contracts + function setCeloPredeploys() internal { + console.log("Deploying Celo contracts"); + + setCeloRegistry(); + setCeloGoldToken(); + setCeloFeeHandler(); + setCeloMentoFeeHandlerSeller(); + setCeloUniswapFeeHandlerSeller(); + // setCeloSortedOracles(); + // setCeloAddressSortedLinkedListWithMedian(); + setCeloFeeCurrency(); + setFeeCurrencyDirectory(); + + address[] memory initialBalanceAddresses = new address[](1); + initialBalanceAddresses[0] = devAccountsCelo[0]; + + uint256[] memory initialBalances = new uint256[](1); + initialBalances[0] = 100_000 ether; + //deploycUSD(initialBalanceAddresses, initialBalances, 2); + } + + /// @notice Sets up a proxy for the given impl address + function _setupProxy(address addr, address impl) internal returns (address) { + bytes memory code = vm.getDeployedCode("Proxy.sol:Proxy"); + vm.etch(addr, code); + EIP1967Helper.setAdmin(addr, Predeploys.PROXY_ADMIN); + + console.log("Setting proxy %s with implementation: %s", addr, impl); + EIP1967Helper.setImplementation(addr, impl); + + return addr; + } + + function setCeloRegistry() internal { + CeloRegistry kontract = new CeloRegistry({ test: false }); + + address precompile = CeloPredeploys.CELO_REGISTRY; + string memory cname = CeloPredeploys.getName(precompile); + console.log("Deploying %s implementation at: %s", cname, address(kontract)); + + vm.resetNonce(address(kontract)); + _setupProxy(precompile, address(kontract)); + } + + function setCeloGoldToken() internal { + GoldToken kontract = new GoldToken({ test: false }); + + address precompile = CeloPredeploys.GOLD_TOKEN; + string memory cname = CeloPredeploys.getName(precompile); + console.log("Deploying %s implementation at: %s", cname, address(kontract)); + + vm.resetNonce(address(kontract)); + _setupProxy(precompile, address(kontract)); + } + + function setCeloFeeHandler() internal { + FeeHandler kontract = new FeeHandler({ test: false }); + + address precompile = CeloPredeploys.FEE_HANDLER; + string memory cname = CeloPredeploys.getName(precompile); + console.log("Deploying %s implementation at: %s", cname, address(kontract)); + + vm.resetNonce(address(kontract)); + _setupProxy(precompile, address(kontract)); + } + + function setCeloMentoFeeHandlerSeller() internal { + MentoFeeHandlerSeller kontract = new MentoFeeHandlerSeller({ test: false }); + + address precompile = CeloPredeploys.MENTO_FEE_HANDLER_SELLER; + string memory cname = CeloPredeploys.getName(precompile); + console.log("Deploying %s implementation at: %s", cname, address(kontract)); + + vm.resetNonce(address(kontract)); + _setupProxy(precompile, address(kontract)); + } + + function setCeloUniswapFeeHandlerSeller() internal { + UniswapFeeHandlerSeller kontract = new UniswapFeeHandlerSeller({ test: false }); + + address precompile = CeloPredeploys.UNISWAP_FEE_HANDLER_SELLER; + string memory cname = CeloPredeploys.getName(precompile); + console.log("Deploying %s implementation at: %s", cname, address(kontract)); + + vm.resetNonce(address(kontract)); + _setupProxy(precompile, address(kontract)); + } + + function setCeloSortedOracles() internal { + SortedOracles kontract = new SortedOracles({ test: false }); + + address precompile = CeloPredeploys.SORTED_ORACLES; + string memory cname = CeloPredeploys.getName(precompile); + console.log("Deploying %s implementation at: %s", cname, address(kontract)); + + vm.resetNonce(address(kontract)); + _setupProxy(precompile, address(kontract)); + } + + function setFeeCurrencyDirectory() internal { + FeeCurrencyDirectory feeCurrencyDirectory = new FeeCurrencyDirectory({ test: false }); + + address precompile = CeloPredeploys.FEE_CURRENCY_DIRECTORY; + string memory cname = CeloPredeploys.getName(precompile); + console.log("Deploying %s implementation at: %s", cname, address(feeCurrencyDirectory)); + + vm.resetNonce(address(feeCurrencyDirectory)); + _setupProxy(precompile, address(feeCurrencyDirectory)); + + vm.startPrank(devAccountsCelo[0]); + FeeCurrencyDirectory(precompile).initialize(); + vm.stopPrank(); + } + + // function setCeloAddressSortedLinkedListWithMedian() internal { + // AddressSortedLinkedListWithMedian kontract = new AddressSortedLinkedListWithMedian({ + // }); + // address precompile = CeloPredeploys.ADDRESS_SORTED_LINKED_LIST_WITH_MEDIAN; + // string memory cname = CeloPredeploys.getName(precompile); + // console.log("Deploying %s implementation at: %s", cname, address(kontract )); + // vm.resetNonce(address(kontract )); + // _setupProxy(precompile, address(kontract)); + // } + + function setCeloFeeCurrency() internal { + FeeCurrency kontract = new FeeCurrency({ name_: "Test", symbol_: "TST" }); + address precompile = CeloPredeploys.FEE_CURRENCY; + string memory cname = CeloPredeploys.getName(precompile); + console.log("Deploying %s implementation at: %s", cname, address(kontract)); + vm.resetNonce(address(kontract)); + _setupProxy(precompile, address(kontract)); + } + + function deploycUSD( + address[] memory initialBalanceAddresses, + uint256[] memory initialBalanceValues, + uint256 celoPrice + ) + public + { + StableTokenV2 kontract = new StableTokenV2({ disable: false }); + address cusdProxyAddress = CeloPredeploys.cUSD; + string memory cname = CeloPredeploys.getName(cusdProxyAddress); + console.log("Deploying %s implementation at: %s", cname, address(kontract)); + vm.resetNonce(address(kontract)); + + _setupProxy(cusdProxyAddress, address(kontract)); + + kontract.initialize("Celo Dollar", "cUSD", initialBalanceAddresses, initialBalanceValues); + + SortedOracles sortedOracles = SortedOracles(CeloPredeploys.SORTED_ORACLES); + + console.log("beofre add oracle"); + + vm.startPrank(sortedOracles.owner()); + sortedOracles.addOracle(cusdProxyAddress, deployerCelo); + vm.stopPrank(); + vm.startPrank(deployerCelo); + + if (celoPrice != 0) { + sortedOracles.report(cusdProxyAddress, celoPrice * 1e24, address(0), address(0)); // TODO use fixidity + } + + /* + Arbitrary intrinsic gas number take from existing `FeeCurrencyDirectory.t.sol` tests + Source: + https://github.com/celo-org/celo-monorepo/blob/2cec07d43328cf4216c62491a35eacc4960fffb6/packages/protocol/test-sol/common/FeeCurrencyDirectory.t.sol#L27 + */ + uint256 mockIntrinsicGas = 21000; + + FeeCurrencyDirectory feeCurrencyDirectory = FeeCurrencyDirectory(CeloPredeploys.FEE_CURRENCY_DIRECTORY); + vm.startPrank(feeCurrencyDirectory.owner()); + feeCurrencyDirectory.setCurrencyConfig(cusdProxyAddress, address(sortedOracles), mockIntrinsicGas); + vm.stopPrank(); + vm.startPrank(deployerCelo); + } +} diff --git a/packages/contracts-bedrock/scripts/checks/check-semver-diff.sh b/packages/contracts-bedrock/scripts/checks/check-semver-diff.sh index 2aeffc7d35144..f31b9d6077732 100755 --- a/packages/contracts-bedrock/scripts/checks/check-semver-diff.sh +++ b/packages/contracts-bedrock/scripts/checks/check-semver-diff.sh @@ -15,17 +15,24 @@ SEMVER_LOCK="semver-lock.json" temp_dir=$(mktemp -d) trap 'rm -rf "$temp_dir"' EXIT +echo "here" + # Exit early if semver-lock.json has not changed. -if ! { git diff origin/develop...HEAD --name-only; git diff --name-only; git diff --cached --name-only; } | grep -q "$SEMVER_LOCK"; then +if ! { git diff origin/celo-contracts/v1.8.0-1...HEAD --name-only; git diff --name-only; git diff --cached --name-only; } | grep -q "$SEMVER_LOCK"; then echo "No changes detected in semver-lock.json" exit 0 fi +echo "2" + # Get the upstream semver-lock.json. -git show origin/develop:packages/contracts-bedrock/snapshots/semver-lock.json > "$temp_dir/upstream_semver_lock.json" +git show origin/celo-contracts/v1.8.0-1:packages/contracts-bedrock/snapshots/semver-lock.json > "$temp_dir/upstream_semver_lock.json" +echo "3" +echo "3" # Copy the local semver-lock.json. cp "$SEMVER_LOCK" "$temp_dir/local_semver_lock.json" +echo "4" # Get the changed contracts. changed_contracts=$(jq -r ' @@ -44,6 +51,8 @@ changed_contracts=$(jq -r ' # Flag to track if any errors are detected. has_errors=false +echo "there" + # Check each changed contract for a semver version change. for contract in $changed_contracts; do # Check if the contract file exists. @@ -56,7 +65,7 @@ for contract in $changed_contracts; do # Extract the old and new source files. old_source_file="$temp_dir/old_${contract##*/}" new_source_file="$temp_dir/new_${contract##*/}" - git show origin/develop:packages/contracts-bedrock/"$contract" > "$old_source_file" 2>/dev/null || true + git show origin/celo-contracts/v1.8.0-1:packages/contracts-bedrock/"$contract" > "$old_source_file" 2>/dev/null || true cp "$contract" "$new_source_file" # Extract the old and new versions. diff --git a/packages/contracts-bedrock/scripts/checks/interfaces/main.go b/packages/contracts-bedrock/scripts/checks/interfaces/main.go index 8bdbf79514e25..dd1dc0f732ac2 100644 --- a/packages/contracts-bedrock/scripts/checks/interfaces/main.go +++ b/packages/contracts-bedrock/scripts/checks/interfaces/main.go @@ -22,6 +22,8 @@ var excludeContracts = []string{ "IERC165", "IERC165Upgradeable", "ERC721TokenReceiver", "ERC1155TokenReceiver", "ERC777TokensRecipient", "Guard", "IProxy", "Vm", "VmSafe", "IMulticall3", "IERC721TokenReceiver", "IProxyCreationCallback", "IBeacon", + "IERC20Errors", "IERC20Permit", "IERC1155Errors", "IERC5267", "IERC721Errors", + "IEIP712", "IERC20Metadata", // EAS "IEAS", "ISchemaResolver", "ISchemaRegistry", @@ -29,6 +31,7 @@ var excludeContracts = []string{ // TODO: Interfaces that need to be fixed "IInitializable", "IOptimismMintableERC20", "ILegacyMintableERC20", "KontrolCheatsBase", "ISystemConfigInterop", "IResolvedDelegateProxy", + "ICeloSuperchainConfig", } type ContractDefinition struct { diff --git a/packages/contracts-bedrock/scripts/deploy/ChainAssertions.sol b/packages/contracts-bedrock/scripts/deploy/ChainAssertions.sol index 25d67be3828c8..1044302700957 100644 --- a/packages/contracts-bedrock/scripts/deploy/ChainAssertions.sol +++ b/packages/contracts-bedrock/scripts/deploy/ChainAssertions.sol @@ -7,7 +7,6 @@ import { console2 as console } from "forge-std/console2.sol"; // Scripts import { DeployConfig } from "scripts/deploy/DeployConfig.s.sol"; -import { ISystemConfigInterop } from "src/L1/interfaces/ISystemConfigInterop.sol"; // Libraries import { Constants } from "src/libraries/Constants.sol"; @@ -19,6 +18,7 @@ import { IResourceMetering } from "src/L1/interfaces/IResourceMetering.sol"; import { ISystemConfig } from "src/L1/interfaces/ISystemConfig.sol"; import { IL2OutputOracle } from "src/L1/interfaces/IL2OutputOracle.sol"; import { ISuperchainConfig } from "src/L1/interfaces/ISuperchainConfig.sol"; +import { ICeloSuperchainConfig } from "src/L1/interfaces/ICeloSuperchainConfig.sol"; import { IL1CrossDomainMessenger } from "src/L1/interfaces/IL1CrossDomainMessenger.sol"; import { IOptimismPortal } from "src/L1/interfaces/IOptimismPortal.sol"; import { IOptimismPortal2 } from "src/L1/interfaces/IOptimismPortal2.sol"; @@ -28,9 +28,13 @@ import { ProtocolVersion, IProtocolVersions } from "src/L1/interfaces/IProtocolV import { IDisputeGameFactory } from "src/dispute/interfaces/IDisputeGameFactory.sol"; import { IDelayedWETH } from "src/dispute/interfaces/IDelayedWETH.sol"; import { IOptimismMintableERC20Factory } from "src/universal/interfaces/IOptimismMintableERC20Factory.sol"; -import { IPreimageOracle } from "src/cannon/interfaces/IPreimageOracle.sol"; -import { IMIPS } from "src/cannon/interfaces/IMIPS.sol"; -import { OPContractsManager } from "src/L1/OPContractsManager.sol"; + +import { CeloTokenL1 } from "src/celo/CeloTokenL1.sol"; +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + +interface IOptimismPortalBalance { + function balance() external view returns (uint256); +} library ChainAssertions { Vm internal constant vm = Vm(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D); @@ -69,195 +73,125 @@ library ChainAssertions { /// @notice Asserts that the SystemConfig is setup correctly function checkSystemConfig(Types.ContractSet memory _contracts, DeployConfig _cfg, bool _isProxy) internal view { + console.log("Running chain assertions on the SystemConfig"); ISystemConfig config = ISystemConfig(_contracts.SystemConfig); - console.log( - "Running chain assertions on the SystemConfig %s at %s", - _isProxy ? "proxy" : "implementation", - address(config) - ); // Check that the contract is initialized - assertInitializedSlotIsSet({ _contractAddress: address(config), _slot: 0, _offset: 0 }); + assertSlotValueIsOne({ _contractAddress: address(config), _slot: 0, _offset: 0 }); IResourceMetering.ResourceConfig memory resourceConfig = config.resourceConfig(); if (_isProxy) { - require(config.owner() == _cfg.finalSystemOwner(), "CHECK-SCFG-10"); - require(config.basefeeScalar() == _cfg.basefeeScalar(), "CHECK-SCFG-20"); - require(config.blobbasefeeScalar() == _cfg.blobbasefeeScalar(), "CHECK-SCFG-30"); - require(config.batcherHash() == bytes32(uint256(uint160(_cfg.batchSenderAddress()))), "CHECK-SCFG-40"); - require(config.gasLimit() == uint64(_cfg.l2GenesisBlockGasLimit()), "CHECK-SCFG-50"); - require(config.unsafeBlockSigner() == _cfg.p2pSequencerAddress(), "CHECK-SCFG-60"); - require(config.scalar() >> 248 == 1, "CHECK-SCFG-70"); + require(config.owner() == _cfg.finalSystemOwner()); + require(config.basefeeScalar() == _cfg.basefeeScalar()); + require(config.blobbasefeeScalar() == _cfg.blobbasefeeScalar()); + require(config.batcherHash() == bytes32(uint256(uint160(_cfg.batchSenderAddress())))); + require(config.gasLimit() == uint64(_cfg.l2GenesisBlockGasLimit())); + require(config.unsafeBlockSigner() == _cfg.p2pSequencerAddress()); + require(config.scalar() >> 248 == 1); // Check _config IResourceMetering.ResourceConfig memory rconfig = Constants.DEFAULT_RESOURCE_CONFIG(); - require(resourceConfig.maxResourceLimit == rconfig.maxResourceLimit, "CHECK-SCFG-80"); - require(resourceConfig.elasticityMultiplier == rconfig.elasticityMultiplier, "CHECK-SCFG-90"); - require(resourceConfig.baseFeeMaxChangeDenominator == rconfig.baseFeeMaxChangeDenominator, "CHECK-SCFG-100"); - require(resourceConfig.systemTxMaxGas == rconfig.systemTxMaxGas, "CHECK-SCFG-110"); - require(resourceConfig.minimumBaseFee == rconfig.minimumBaseFee, "CHECK-SCFG-120"); - require(resourceConfig.maximumBaseFee == rconfig.maximumBaseFee, "CHECK-SCFG-130"); + require(resourceConfig.maxResourceLimit == rconfig.maxResourceLimit); + require(resourceConfig.elasticityMultiplier == rconfig.elasticityMultiplier); + require(resourceConfig.baseFeeMaxChangeDenominator == rconfig.baseFeeMaxChangeDenominator); + require(resourceConfig.systemTxMaxGas == rconfig.systemTxMaxGas); + require(resourceConfig.minimumBaseFee == rconfig.minimumBaseFee); + require(resourceConfig.maximumBaseFee == rconfig.maximumBaseFee); // Depends on start block being set to 0 in `initialize` uint256 cfgStartBlock = _cfg.systemConfigStartBlock(); - require(config.startBlock() == (cfgStartBlock == 0 ? block.number : cfgStartBlock), "CHECK-SCFG-140"); - require(config.batchInbox() == _cfg.batchInboxAddress(), "CHECK-SCFG-150"); + require(config.startBlock() == (cfgStartBlock == 0 ? block.number : cfgStartBlock)); + require(config.batchInbox() == _cfg.batchInboxAddress()); // Check _addresses - require(config.l1CrossDomainMessenger() == _contracts.L1CrossDomainMessenger, "CHECK-SCFG-160"); - require(config.l1ERC721Bridge() == _contracts.L1ERC721Bridge, "CHECK-SCFG-170"); - require(config.l1StandardBridge() == _contracts.L1StandardBridge, "CHECK-SCFG-180"); - require(config.disputeGameFactory() == _contracts.DisputeGameFactory, "CHECK-SCFG-190"); - require(config.optimismPortal() == _contracts.OptimismPortal, "CHECK-SCFG-200"); - require(config.optimismMintableERC20Factory() == _contracts.OptimismMintableERC20Factory, "CHECK-SCFG-210"); + require(config.l1CrossDomainMessenger() == _contracts.L1CrossDomainMessenger); + require(config.l1ERC721Bridge() == _contracts.L1ERC721Bridge); + require(config.l1StandardBridge() == _contracts.L1StandardBridge); + require(config.disputeGameFactory() == _contracts.DisputeGameFactory); + require(config.optimismPortal() == _contracts.OptimismPortal); + require(config.optimismMintableERC20Factory() == _contracts.OptimismMintableERC20Factory); } else { - require(config.owner() == address(0xdead), "CHECK-SCFG-220"); - require(config.overhead() == 0, "CHECK-SCFG-230"); - require(config.scalar() == uint256(0x01) << 248, "CHECK-SCFG-240"); // version 1 - require(config.basefeeScalar() == 0, "CHECK-SCFG-250"); - require(config.blobbasefeeScalar() == 0, "CHECK-SCFG-260"); - require(config.batcherHash() == bytes32(0), "CHECK-SCFG-270"); - require(config.gasLimit() == 1, "CHECK-SCFG-280"); - require(config.unsafeBlockSigner() == address(0), "CHECK-SCFG-290"); + require(config.owner() == address(0xdead)); + require(config.overhead() == 0); + require(config.scalar() == uint256(0x01) << 248); // version 1 + require(config.basefeeScalar() == 0); + require(config.blobbasefeeScalar() == 0); + require(config.batcherHash() == bytes32(0)); + require(config.gasLimit() == 1); + require(config.unsafeBlockSigner() == address(0)); // Check _config - require(resourceConfig.maxResourceLimit == 1, "CHECK-SCFG-300"); - require(resourceConfig.elasticityMultiplier == 1, "CHECK-SCFG-310"); - require(resourceConfig.baseFeeMaxChangeDenominator == 2, "CHECK-SCFG-320"); - require(resourceConfig.systemTxMaxGas == 0, "CHECK-SCFG-330"); - require(resourceConfig.minimumBaseFee == 0, "CHECK-SCFG-340"); - require(resourceConfig.maximumBaseFee == 0, "CHECK-SCFG-350"); + require(resourceConfig.maxResourceLimit == 1); + require(resourceConfig.elasticityMultiplier == 1); + require(resourceConfig.baseFeeMaxChangeDenominator == 2); + require(resourceConfig.systemTxMaxGas == 0); + require(resourceConfig.minimumBaseFee == 0); + require(resourceConfig.maximumBaseFee == 0); // Check _addresses - require(config.startBlock() == type(uint256).max, "CHECK-SCFG-360"); - require(config.batchInbox() == address(0), "CHECK-SCFG-370"); - require(config.l1CrossDomainMessenger() == address(0), "CHECK-SCFG-380"); - require(config.l1ERC721Bridge() == address(0), "CHECK-SCFG-390"); - require(config.l1StandardBridge() == address(0), "CHECK-SCFG-400"); - require(config.disputeGameFactory() == address(0), "CHECK-SCFG-410"); - require(config.optimismPortal() == address(0), "CHECK-SCFG-420"); - require(config.optimismMintableERC20Factory() == address(0), "CHECK-SCFG-430"); - } - } - - /// @notice Asserts that the SystemConfigInterop is setup correctly - function checkSystemConfigInterop( - Types.ContractSet memory _contracts, - DeployConfig _cfg, - bool _isProxy - ) - internal - view - { - ISystemConfigInterop config = ISystemConfigInterop(_contracts.SystemConfig); - console.log( - "Running chain assertions on the SystemConfigInterop %s at %s", - _isProxy ? "proxy" : "implementation", - address(config) - ); - - checkSystemConfig(_contracts, _cfg, _isProxy); - if (_isProxy) { - // TODO: this is not being set in the deployment, nor is a config value. - // Update this when it has an entry in hardhat.json - require(config.dependencyManager() == address(0), "CHECK-SCFGI-10"); - } else { - require(config.dependencyManager() == address(0), "CHECK-SCFGI-20"); + require(config.startBlock() == type(uint256).max); + require(config.batchInbox() == address(0)); + require(config.l1CrossDomainMessenger() == address(0)); + require(config.l1ERC721Bridge() == address(0)); + require(config.l1StandardBridge() == address(0)); + require(config.disputeGameFactory() == address(0)); + require(config.optimismPortal() == address(0)); + require(config.optimismMintableERC20Factory() == address(0)); } } /// @notice Asserts that the L1CrossDomainMessenger is setup correctly function checkL1CrossDomainMessenger(Types.ContractSet memory _contracts, Vm _vm, bool _isProxy) internal view { + console.log("Running chain assertions on the L1CrossDomainMessenger"); IL1CrossDomainMessenger messenger = IL1CrossDomainMessenger(_contracts.L1CrossDomainMessenger); - console.log( - "Running chain assertions on the L1CrossDomainMessenger %s at %s", - _isProxy ? "proxy" : "implementation", - address(messenger) - ); - require(address(messenger) != address(0), "CHECK-L1XDM-10"); // Check that the contract is initialized - assertInitializedSlotIsSet({ _contractAddress: address(messenger), _slot: 0, _offset: 20 }); + assertSlotValueIsOne({ _contractAddress: address(messenger), _slot: 0, _offset: 20 }); - require(address(messenger.OTHER_MESSENGER()) == Predeploys.L2_CROSS_DOMAIN_MESSENGER, "CHECK-L1XDM-20"); - require(address(messenger.otherMessenger()) == Predeploys.L2_CROSS_DOMAIN_MESSENGER, "CHECK-L1XDM-30"); + require(address(messenger.OTHER_MESSENGER()) == Predeploys.L2_CROSS_DOMAIN_MESSENGER); + require(address(messenger.otherMessenger()) == Predeploys.L2_CROSS_DOMAIN_MESSENGER); if (_isProxy) { - require(address(messenger.PORTAL()) == _contracts.OptimismPortal, "CHECK-L1XDM-40"); - require(address(messenger.portal()) == _contracts.OptimismPortal, "CHECK-L1XDM-50"); - require(address(messenger.superchainConfig()) == _contracts.SuperchainConfig, "CHECK-L1XDM-60"); + require(address(messenger.PORTAL()) == _contracts.OptimismPortal); + require(address(messenger.portal()) == _contracts.OptimismPortal); + require(address(messenger.superchainConfig()) == _contracts.CeloSuperchainConfig); bytes32 xdmSenderSlot = _vm.load(address(messenger), bytes32(uint256(204))); - require(address(uint160(uint256(xdmSenderSlot))) == Constants.DEFAULT_L2_SENDER, "CHECK-L1XDM-70"); + require(address(uint160(uint256(xdmSenderSlot))) == Constants.DEFAULT_L2_SENDER); } else { - require(address(messenger.PORTAL()) == address(0), "CHECK-L1XDM-80"); - require(address(messenger.portal()) == address(0), "CHECK-L1XDM-90"); - require(address(messenger.superchainConfig()) == address(0), "CHECK-L1XDM-100"); + require(address(messenger.PORTAL()) == address(0)); + require(address(messenger.portal()) == address(0)); + require(address(messenger.superchainConfig()) == address(0)); } } /// @notice Asserts that the L1StandardBridge is setup correctly function checkL1StandardBridge(Types.ContractSet memory _contracts, bool _isProxy) internal view { + console.log("Running chain assertions on the L1StandardBridge"); IL1StandardBridge bridge = IL1StandardBridge(payable(_contracts.L1StandardBridge)); - console.log( - "Running chain assertions on the L1StandardBridge %s at %s", - _isProxy ? "proxy" : "implementation", - address(bridge) - ); - require(address(bridge) != address(0), "CHECK-L1SB-10"); // Check that the contract is initialized - assertInitializedSlotIsSet({ _contractAddress: address(bridge), _slot: 0, _offset: 0 }); + assertSlotValueIsOne({ _contractAddress: address(bridge), _slot: 0, _offset: 0 }); if (_isProxy) { - require(address(bridge.MESSENGER()) == _contracts.L1CrossDomainMessenger, "CHECK-L1SB-20"); - require(address(bridge.messenger()) == _contracts.L1CrossDomainMessenger, "CHECK-L1SB-30"); - require(address(bridge.OTHER_BRIDGE()) == Predeploys.L2_STANDARD_BRIDGE, "CHECK-L1SB-40"); - require(address(bridge.otherBridge()) == Predeploys.L2_STANDARD_BRIDGE, "CHECK-L1SB-50"); - require(address(bridge.superchainConfig()) == _contracts.SuperchainConfig, "CHECK-L1SB-60"); + require(address(bridge.MESSENGER()) == _contracts.L1CrossDomainMessenger); + require(address(bridge.messenger()) == _contracts.L1CrossDomainMessenger); + require(address(bridge.OTHER_BRIDGE()) == Predeploys.L2_STANDARD_BRIDGE); + require(address(bridge.otherBridge()) == Predeploys.L2_STANDARD_BRIDGE); + require(address(bridge.superchainConfig()) == _contracts.CeloSuperchainConfig); } else { - require(address(bridge.MESSENGER()) == address(0), "CHECK-L1SB-70"); - require(address(bridge.messenger()) == address(0), "CHECK-L1SB-80"); - require(address(bridge.OTHER_BRIDGE()) == Predeploys.L2_STANDARD_BRIDGE, "CHECK-L1SB-90"); - require(address(bridge.otherBridge()) == Predeploys.L2_STANDARD_BRIDGE, "CHECK-L1SB-100"); - require(address(bridge.superchainConfig()) == address(0), "CHECK-L1SB-110"); + require(address(bridge.MESSENGER()) == address(0)); + require(address(bridge.messenger()) == address(0)); + require(address(bridge.OTHER_BRIDGE()) == Predeploys.L2_STANDARD_BRIDGE); + require(address(bridge.otherBridge()) == Predeploys.L2_STANDARD_BRIDGE); + require(address(bridge.superchainConfig()) == address(0)); } } /// @notice Asserts that the DisputeGameFactory is setup correctly - function checkDisputeGameFactory( - Types.ContractSet memory _contracts, - address _expectedOwner, - bool _isProxy - ) - internal - view - { + function checkDisputeGameFactory(Types.ContractSet memory _contracts, address _expectedOwner) internal view { + console.log("Running chain assertions on the DisputeGameFactory"); IDisputeGameFactory factory = IDisputeGameFactory(_contracts.DisputeGameFactory); - console.log( - "Running chain assertions on the DisputeGameFactory %s at %s", - _isProxy ? "proxy" : "implementation", - address(factory) - ); - require(address(factory) != address(0), "CHECK-DG-10"); // Check that the contract is initialized - assertInitializedSlotIsSet({ _contractAddress: address(factory), _slot: 0, _offset: 0 }); - - // The same check is made for both proxy and implementation - require(factory.owner() == _expectedOwner, "CHECK-DG-20"); - } - - /// @notice Asserts that the PreimageOracle is setup correctly - function checkPreimageOracle(IPreimageOracle _oracle, DeployConfig _cfg) internal view { - console.log("Running chain assertions on the PreimageOracle %s at %s", address(_oracle)); - require(address(_oracle) != address(0), "CHECK-PIO-10"); - - require(_oracle.minProposalSize() == _cfg.preimageOracleMinProposalSize(), "CHECK-PIO-30"); - require(_oracle.challengePeriod() == _cfg.preimageOracleChallengePeriod(), "CHECK-PIO-40"); - } - - /// @notice Asserts that the MIPs contract is setup correctly - function checkMIPS(IMIPS _mips, IPreimageOracle _oracle) internal view { - console.log("Running chain assertions on the MIPS %s at %s", address(_mips)); - require(address(_mips) != address(0), "CHECK-MIPS-10"); + assertSlotValueIsOne({ _contractAddress: address(factory), _slot: 0, _offset: 0 }); - require(_mips.oracle() == _oracle, "CHECK-MIPS-20"); + require(factory.owner() == _expectedOwner); } /// @notice Asserts that the DelayedWETH is setup correctly @@ -270,22 +204,19 @@ library ChainAssertions { internal view { + console.log("Running chain assertions on the DelayedWETH"); IDelayedWETH weth = IDelayedWETH(payable(_contracts.DelayedWETH)); - console.log( - "Running chain assertions on the DelayedWETH %s at %s", _isProxy ? "proxy" : "implementation", address(weth) - ); - require(address(weth) != address(0), "CHECK-DWETH-10"); // Check that the contract is initialized - assertInitializedSlotIsSet({ _contractAddress: address(weth), _slot: 0, _offset: 0 }); + assertSlotValueIsOne({ _contractAddress: address(weth), _slot: 0, _offset: 0 }); if (_isProxy) { - require(weth.owner() == _expectedOwner, "CHECK-DWETH-20"); - require(weth.delay() == _cfg.faultGameWithdrawalDelay(), "CHECK-DWETH-30"); - require(weth.config() == ISuperchainConfig(_contracts.SuperchainConfig), "CHECK-DWETH-40"); + require(weth.owner() == _expectedOwner); + require(weth.delay() == _cfg.faultGameWithdrawalDelay()); + require(address(weth.config()) == address(ISuperchainConfig(_contracts.CeloSuperchainConfig))); } else { - require(weth.owner() == _expectedOwner, "CHECK-DWETH-50"); - require(weth.delay() == _cfg.faultGameWithdrawalDelay(), "CHECK-DWETH-60"); + require(weth.owner() == _expectedOwner); + require(weth.delay() == _cfg.faultGameWithdrawalDelay()); } } @@ -299,24 +230,19 @@ library ChainAssertions { internal view { + console.log("Running chain assertions on the permissioned DelayedWETH"); IDelayedWETH weth = IDelayedWETH(payable(_contracts.PermissionedDelayedWETH)); - console.log( - "Running chain assertions on the PermissionedDelayedWETH %s at %s", - _isProxy ? "proxy" : "implementation", - address(weth) - ); - require(address(weth) != address(0), "CHECK-PDWETH-10"); // Check that the contract is initialized - assertInitializedSlotIsSet({ _contractAddress: address(weth), _slot: 0, _offset: 0 }); + assertSlotValueIsOne({ _contractAddress: address(weth), _slot: 0, _offset: 0 }); if (_isProxy) { - require(weth.owner() == _expectedOwner, "CHECK-PDWETH-20"); - require(weth.delay() == _cfg.faultGameWithdrawalDelay(), "CHECK-PDWETH-30"); - require(weth.config() == ISuperchainConfig(_contracts.SuperchainConfig), "CHECK-PDWETH-40"); + require(weth.owner() == _expectedOwner); + require(weth.delay() == _cfg.faultGameWithdrawalDelay()); + require(address(weth.config()) == address(ISuperchainConfig(_contracts.CeloSuperchainConfig))); } else { - require(weth.owner() == _expectedOwner, "CHECK-PDWETH-50"); - require(weth.delay() == _cfg.faultGameWithdrawalDelay(), "CHECK-PDWETH-60"); + require(weth.owner() == _expectedOwner); + require(weth.delay() == _cfg.faultGameWithdrawalDelay()); } } @@ -330,65 +256,55 @@ library ChainAssertions { internal view { + console.log("Running chain assertions on the L2OutputOracle"); IL2OutputOracle oracle = IL2OutputOracle(_contracts.L2OutputOracle); - console.log( - "Running chain assertions on the L2OutputOracle %s at %s", - _isProxy ? "proxy" : "implementation", - address(oracle) - ); - require(address(oracle) != address(0), "CHECK-L2OO-10"); // Check that the contract is initialized - assertInitializedSlotIsSet({ _contractAddress: address(oracle), _slot: 0, _offset: 0 }); + assertSlotValueIsOne({ _contractAddress: address(oracle), _slot: 0, _offset: 0 }); if (_isProxy) { - require(oracle.SUBMISSION_INTERVAL() == _cfg.l2OutputOracleSubmissionInterval(), "CHECK-L2OO-20"); - require(oracle.submissionInterval() == _cfg.l2OutputOracleSubmissionInterval(), "CHECK-L2OO-30"); - require(oracle.L2_BLOCK_TIME() == _cfg.l2BlockTime(), "CHECK-L2OO-40"); - require(oracle.l2BlockTime() == _cfg.l2BlockTime(), "CHECK-L2OO-50"); - require(oracle.PROPOSER() == _cfg.l2OutputOracleProposer(), "CHECK-L2OO-60"); - require(oracle.proposer() == _cfg.l2OutputOracleProposer(), "CHECK-L2OO-70"); - require(oracle.CHALLENGER() == _cfg.l2OutputOracleChallenger(), "CHECK-L2OO-80"); - require(oracle.challenger() == _cfg.l2OutputOracleChallenger(), "CHECK-L2OO-90"); - require(oracle.FINALIZATION_PERIOD_SECONDS() == _cfg.finalizationPeriodSeconds(), "CHECK-L2OO-100"); - require(oracle.finalizationPeriodSeconds() == _cfg.finalizationPeriodSeconds(), "CHECK-L2OO-110"); - require(oracle.startingBlockNumber() == _cfg.l2OutputOracleStartingBlockNumber(), "CHECK-L2OO-120"); - require(oracle.startingTimestamp() == _l2OutputOracleStartingTimestamp, "CHECK-L2OO-130"); + require(oracle.SUBMISSION_INTERVAL() == _cfg.l2OutputOracleSubmissionInterval()); + require(oracle.submissionInterval() == _cfg.l2OutputOracleSubmissionInterval()); + require(oracle.L2_BLOCK_TIME() == _cfg.l2BlockTime()); + require(oracle.l2BlockTime() == _cfg.l2BlockTime()); + require(oracle.PROPOSER() == _cfg.l2OutputOracleProposer()); + require(oracle.proposer() == _cfg.l2OutputOracleProposer()); + require(oracle.CHALLENGER() == _cfg.l2OutputOracleChallenger()); + require(oracle.challenger() == _cfg.l2OutputOracleChallenger()); + require(oracle.FINALIZATION_PERIOD_SECONDS() == _cfg.finalizationPeriodSeconds()); + require(oracle.finalizationPeriodSeconds() == _cfg.finalizationPeriodSeconds()); + require(oracle.startingBlockNumber() == _cfg.l2OutputOracleStartingBlockNumber()); + require(oracle.startingTimestamp() == _l2OutputOracleStartingTimestamp); } else { - require(oracle.SUBMISSION_INTERVAL() == 1, "CHECK-L2OO-140"); - require(oracle.submissionInterval() == 1, "CHECK-L2OO-150"); - require(oracle.L2_BLOCK_TIME() == 1, "CHECK-L2OO-160"); - require(oracle.l2BlockTime() == 1, "CHECK-L2OO-170"); - require(oracle.PROPOSER() == address(0), "CHECK-L2OO-180"); - require(oracle.proposer() == address(0), "CHECK-L2OO-190"); - require(oracle.CHALLENGER() == address(0), "CHECK-L2OO-200"); - require(oracle.challenger() == address(0), "CHECK-L2OO-210"); - require(oracle.FINALIZATION_PERIOD_SECONDS() == 0, "CHECK-L2OO-220"); - require(oracle.finalizationPeriodSeconds() == 0, "CHECK-L2OO-230"); - require(oracle.startingBlockNumber() == 0, "CHECK-L2OO-240"); - require(oracle.startingTimestamp() == 0, "CHECK-L2OO-250"); + require(oracle.SUBMISSION_INTERVAL() == 1); + require(oracle.submissionInterval() == 1); + require(oracle.L2_BLOCK_TIME() == 1); + require(oracle.l2BlockTime() == 1); + require(oracle.PROPOSER() == address(0)); + require(oracle.proposer() == address(0)); + require(oracle.CHALLENGER() == address(0)); + require(oracle.challenger() == address(0)); + require(oracle.FINALIZATION_PERIOD_SECONDS() == 0); + require(oracle.finalizationPeriodSeconds() == 0); + require(oracle.startingBlockNumber() == 0); + require(oracle.startingTimestamp() == 0); } } /// @notice Asserts that the OptimismMintableERC20Factory is setup correctly function checkOptimismMintableERC20Factory(Types.ContractSet memory _contracts, bool _isProxy) internal view { + console.log("Running chain assertions on the OptimismMintableERC20Factory"); IOptimismMintableERC20Factory factory = IOptimismMintableERC20Factory(_contracts.OptimismMintableERC20Factory); - console.log( - "Running chain assertions on the OptimismMintableERC20Factory %s at %s", - _isProxy ? "proxy" : "implementation", - address(factory) - ); - require(address(factory) != address(0), "CHECK-MERC20F-10"); // Check that the contract is initialized - assertInitializedSlotIsSet({ _contractAddress: address(factory), _slot: 0, _offset: 0 }); + assertSlotValueIsOne({ _contractAddress: address(factory), _slot: 0, _offset: 0 }); if (_isProxy) { - require(factory.BRIDGE() == _contracts.L1StandardBridge, "CHECK-MERC20F-10"); - require(factory.bridge() == _contracts.L1StandardBridge, "CHECK-MERC20F-20"); + require(factory.BRIDGE() == _contracts.L1StandardBridge); + require(factory.bridge() == _contracts.L1StandardBridge); } else { - require(factory.BRIDGE() == address(0), "CHECK-MERC20F-30"); - require(factory.bridge() == address(0), "CHECK-MERC20F-40"); + require(factory.BRIDGE() == address(0)); + require(factory.bridge() == address(0)); } } @@ -396,42 +312,32 @@ library ChainAssertions { function checkL1ERC721Bridge(Types.ContractSet memory _contracts, bool _isProxy) internal view { console.log("Running chain assertions on the L1ERC721Bridge"); IL1ERC721Bridge bridge = IL1ERC721Bridge(_contracts.L1ERC721Bridge); - console.log( - "Running chain assertions on the L1ERC721Bridge %s at %s", - _isProxy ? "proxy" : "implementation", - address(bridge) - ); - require(address(bridge) != address(0), "CHECK-L1ERC721B-10"); // Check that the contract is initialized - assertInitializedSlotIsSet({ _contractAddress: address(bridge), _slot: 0, _offset: 0 }); + assertSlotValueIsOne({ _contractAddress: address(bridge), _slot: 0, _offset: 0 }); - require(address(bridge.OTHER_BRIDGE()) == Predeploys.L2_ERC721_BRIDGE, "CHECK-L1ERC721B-10"); - require(address(bridge.otherBridge()) == Predeploys.L2_ERC721_BRIDGE, "CHECK-L1ERC721B-20"); + require(address(bridge.OTHER_BRIDGE()) == Predeploys.L2_ERC721_BRIDGE); + require(address(bridge.otherBridge()) == Predeploys.L2_ERC721_BRIDGE); if (_isProxy) { - require(address(bridge.MESSENGER()) == _contracts.L1CrossDomainMessenger, "CHECK-L1ERC721B-30"); - require(address(bridge.messenger()) == _contracts.L1CrossDomainMessenger, "CHECK-L1ERC721B-40"); - require(address(bridge.superchainConfig()) == _contracts.SuperchainConfig, "CHECK-L1ERC721B-50"); + require(address(bridge.MESSENGER()) == _contracts.L1CrossDomainMessenger); + require(address(bridge.messenger()) == _contracts.L1CrossDomainMessenger); + require(address(bridge.superchainConfig()) == _contracts.CeloSuperchainConfig); } else { - require(address(bridge.MESSENGER()) == address(0), "CHECK-L1ERC721B-60"); - require(address(bridge.messenger()) == address(0), "CHECK-L1ERC721B-70"); - require(address(bridge.superchainConfig()) == address(0), "CHECK-L1ERC721B-80"); + require(address(bridge.MESSENGER()) == address(0)); + require(address(bridge.messenger()) == address(0)); + require(address(bridge.superchainConfig()) == address(0)); } } /// @notice Asserts the OptimismPortal is setup correctly function checkOptimismPortal(Types.ContractSet memory _contracts, DeployConfig _cfg, bool _isProxy) internal view { + console.log("Running chain assertions on the OptimismPortal"); + IOptimismPortal portal = IOptimismPortal(payable(_contracts.OptimismPortal)); - console.log( - "Running chain assertions on the OptimismPortal %s at %s", - _isProxy ? "proxy" : "implementation", - address(portal) - ); - require(address(portal) != address(0), "CHECK-OP-10"); // Check that the contract is initialized - assertInitializedSlotIsSet({ _contractAddress: address(portal), _slot: 0, _offset: 0 }); + assertSlotValueIsOne({ _contractAddress: address(portal), _slot: 0, _offset: 0 }); address guardian = _cfg.superchainConfigGuardian(); if (guardian.code.length == 0) { @@ -439,18 +345,51 @@ library ChainAssertions { } if (_isProxy) { - require(address(portal.l2Oracle()) == _contracts.L2OutputOracle, "CHECK-OP-20"); - require(address(portal.systemConfig()) == _contracts.SystemConfig, "CHECK-OP-30"); - require(portal.guardian() == guardian, "CHECK-OP-40"); - require(address(portal.superchainConfig()) == address(_contracts.SuperchainConfig), "CHECK-OP-50"); - require(portal.paused() == ISuperchainConfig(_contracts.SuperchainConfig).paused(), "CHECK-OP-60"); - require(portal.l2Sender() == Constants.DEFAULT_L2_SENDER, "CHECK-OP-70"); + require(address(portal.l2Oracle()) == _contracts.L2OutputOracle); + require(address(portal.systemConfig()) == _contracts.SystemConfig); + require(portal.guardian() == guardian); + require(address(portal.superchainConfig()) == address(_contracts.CeloSuperchainConfig)); + require(portal.paused() == ISuperchainConfig(_contracts.SuperchainConfig).paused()); + require(portal.l2Sender() == Constants.DEFAULT_L2_SENDER); + } else { + require(address(portal.l2Oracle()) == address(0)); + require(address(portal.systemConfig()) == address(0)); + require(address(portal.superchainConfig()) == address(0)); + require(portal.l2Sender() == Constants.DEFAULT_L2_SENDER); + } + } + + /// @notice Asserts the OptimismPortal custom gas token is setup correctly + function checkCustomGasTokenOptimismPortal( + Types.ContractSet memory _contracts, + DeployConfig _cfg, + bool _isProxy + ) + internal + view + { + address payable portalAddress; + if (_cfg.useFaultProofs()) { + portalAddress = payable(_contracts.OptimismPortal2); + } else { + portalAddress = payable(_contracts.OptimismPortal); + } + IOptimismPortalBalance portal = IOptimismPortalBalance(portalAddress); + + uint256 expectedInitialBalance = 0; + if (_isProxy && _cfg.useCustomGasToken()) { + address customGasTokenAddress = _cfg.customGasTokenAddress(); + IERC20 token = IERC20(customGasTokenAddress); + expectedInitialBalance = token.balanceOf(address(portal)); + console.log("custom gas token expectedInitialBalance", expectedInitialBalance); + } + + if (_isProxy) { + require(portal.balance() == expectedInitialBalance); } else { - require(address(portal.l2Oracle()) == address(0), "CHECK-OP-80"); - require(address(portal.systemConfig()) == address(0), "CHECK-OP-90"); - require(address(portal.superchainConfig()) == address(0), "CHECK-OP-100"); - require(portal.l2Sender() == Constants.DEFAULT_L2_SENDER, "CHECK-OP-110"); + require(portal.balance() == 0); } + require(vm.load(address(portal), bytes32(uint256(61))) == bytes32(portal.balance())); } /// @notice Asserts the OptimismPortal2 is setup correctly @@ -462,16 +401,12 @@ library ChainAssertions { internal view { + console.log("Running chain assertions on the OptimismPortal2"); + IOptimismPortal2 portal = IOptimismPortal2(payable(_contracts.OptimismPortal2)); - console.log( - "Running chain assertions on the OptimismPortal2 %s at %s", - _isProxy ? "proxy" : "implementation", - address(portal) - ); - require(address(portal) != address(0), "CHECK-OP2-10"); // Check that the contract is initialized - assertInitializedSlotIsSet({ _contractAddress: address(portal), _slot: 0, _offset: 0 }); + assertSlotValueIsOne({ _contractAddress: address(portal), _slot: 0, _offset: 0 }); address guardian = _cfg.superchainConfigGuardian(); if (guardian.code.length == 0) { @@ -479,21 +414,27 @@ library ChainAssertions { } if (_isProxy) { - require(address(portal.disputeGameFactory()) == _contracts.DisputeGameFactory, "CHECK-OP2-20"); - require(address(portal.systemConfig()) == _contracts.SystemConfig, "CHECK-OP2-30"); - require(portal.guardian() == guardian, "CHECK-OP2-40"); - require(address(portal.superchainConfig()) == address(_contracts.SuperchainConfig), "CHECK-OP2-50"); - require(portal.paused() == ISuperchainConfig(_contracts.SuperchainConfig).paused(), "CHECK-OP2-60"); - require(portal.l2Sender() == Constants.DEFAULT_L2_SENDER, "CHECK-OP2-70"); + require(address(portal.disputeGameFactory()) == _contracts.DisputeGameFactory); + require(address(portal.systemConfig()) == _contracts.SystemConfig); + require(portal.guardian() == guardian); + require(address(portal.superchainConfig()) == address(_contracts.CeloSuperchainConfig)); + require(portal.paused() == ISuperchainConfig(_contracts.SuperchainConfig).paused()); + require(portal.l2Sender() == Constants.DEFAULT_L2_SENDER); } else { - require(address(portal.disputeGameFactory()) == address(0), "CHECK-OP2-80"); - require(address(portal.systemConfig()) == address(0), "CHECK-OP2-90"); - require(address(portal.superchainConfig()) == address(0), "CHECK-OP2-100"); - require(portal.l2Sender() == Constants.DEFAULT_L2_SENDER, "CHECK-OP2-110"); + require(address(portal.disputeGameFactory()) == address(0)); + require(address(portal.systemConfig()) == address(0)); + require(address(portal.superchainConfig()) == address(0)); + require(portal.l2Sender() == Constants.DEFAULT_L2_SENDER); } // This slot is the custom gas token _balance and this check ensures // that it stays unset for forwards compatibility with custom gas token. - require(vm.load(address(portal), bytes32(uint256(61))) == bytes32(0), "CHECK-OP2-120"); + // if we use the pre-locked storage modification, the comparison + // against 0 doesn't hold anymore. + // We do a check of the balance field downstream anyways, that's why we + // can disable this check + if (!_cfg.useCustomGasToken()) { + require(vm.load(address(portal), bytes32(uint256(61))) == bytes32(0)); + } } /// @notice Asserts that the ProtocolVersions is setup correctly @@ -505,25 +446,20 @@ library ChainAssertions { internal view { + console.log("Running chain assertions on the ProtocolVersions"); IProtocolVersions versions = IProtocolVersions(_contracts.ProtocolVersions); - console.log( - "Running chain assertions on the ProtocolVersions %s at %s", - _isProxy ? "proxy" : "implementation", - address(versions) - ); - require(address(versions) != address(0), "CHECK-PV-10"); // Check that the contract is initialized - assertInitializedSlotIsSet({ _contractAddress: address(versions), _slot: 0, _offset: 0 }); + assertSlotValueIsOne({ _contractAddress: address(versions), _slot: 0, _offset: 0 }); if (_isProxy) { - require(versions.owner() == _cfg.finalSystemOwner(), "CHECK-PV-20"); - require(ProtocolVersion.unwrap(versions.required()) == _cfg.requiredProtocolVersion(), "CHECK-PV-30"); - require(ProtocolVersion.unwrap(versions.recommended()) == _cfg.recommendedProtocolVersion(), "CHECK-PV-40"); + require(versions.owner() == _cfg.finalSystemOwner()); + require(ProtocolVersion.unwrap(versions.required()) == _cfg.requiredProtocolVersion()); + require(ProtocolVersion.unwrap(versions.recommended()) == _cfg.recommendedProtocolVersion()); } else { - require(versions.owner() == address(0xdead), "CHECK-PV-50"); - require(ProtocolVersion.unwrap(versions.required()) == 0, "CHECK-PV-60"); - require(ProtocolVersion.unwrap(versions.recommended()) == 0, "CHECK-PV-70"); + require(versions.owner() == address(0xdead)); + require(ProtocolVersion.unwrap(versions.required()) == 0); + require(ProtocolVersion.unwrap(versions.recommended()) == 0); } } @@ -531,60 +467,63 @@ library ChainAssertions { function checkSuperchainConfig( Types.ContractSet memory _contracts, DeployConfig _cfg, - bool _isPaused, - bool _isProxy + bool _isPaused ) internal view { + console.log("Running chain assertions on the SuperchainConfig"); ISuperchainConfig superchainConfig = ISuperchainConfig(_contracts.SuperchainConfig); - console.log( - "Running chain assertions on the SuperchainConfig %s at %s", - _isProxy ? "proxy" : "implementation", - address(superchainConfig) - ); - require(address(superchainConfig) != address(0), "CHECK-SC-10"); // Check that the contract is initialized - assertInitializedSlotIsSet({ _contractAddress: address(superchainConfig), _slot: 0, _offset: 0 }); + assertSlotValueIsOne({ _contractAddress: address(superchainConfig), _slot: 0, _offset: 0 }); - if (_isProxy) { - require(superchainConfig.guardian() == _cfg.superchainConfigGuardian(), "CHECK-SC-20"); - require(superchainConfig.paused() == _isPaused, "CHECK-SC-30"); - } else { - require(superchainConfig.guardian() == address(0), "CHECK-SC-40"); - require(superchainConfig.paused() == false, "CHECK-SC-50"); - } + require(superchainConfig.guardian() == _cfg.superchainConfigGuardian()); + require(superchainConfig.paused() == _isPaused); } - /// @notice Asserts that the SuperchainConfig is setup correctly - function checkOPContractsManager(Types.ContractSet memory _contracts, bool _isProxy) internal view { - OPContractsManager opcm = OPContractsManager(_contracts.OPContractsManager); - console.log( - "Running chain assertions on the OPContractsManager %s at %s", - _isProxy ? "proxy" : "implementation", - address(opcm) - ); - require(address(opcm) != address(0), "CHECK-OPCM-10"); + /// @notice Asserts that the CeloSuperchainConfig is setup correctly + function checkCeloSuperchainConfig( + Types.ContractSet memory _contracts, + DeployConfig _cfg, + bool _isPaused + ) + internal + view + { + console.log("Running chain assertions on the CeloSuperchainConfig"); + ICeloSuperchainConfig celoSuperchainConfig = ICeloSuperchainConfig(_contracts.CeloSuperchainConfig); + address superchainConfig = _contracts.SuperchainConfig; // Check that the contract is initialized - assertInitializedSlotIsSet({ _contractAddress: address(opcm), _slot: 0, _offset: 0 }); - - // These values are immutable so are shared by the proxy and implementation - require(address(opcm.superchainConfig()) == address(_contracts.SuperchainConfig), "CHECK-OPCM-30"); - require(address(opcm.protocolVersions()) == address(_contracts.ProtocolVersions), "CHECK-OPCM-40"); + assertSlotValueIsOne({ _contractAddress: address(celoSuperchainConfig), _slot: 0, _offset: 0 }); - // TODO: Add assertions for blueprints and setters? + require(celoSuperchainConfig.guardian() == _cfg.superchainConfigGuardian()); + require(celoSuperchainConfig.paused() == _isPaused); + require(celoSuperchainConfig.superchainConfig() == superchainConfig); } - /// @dev Asserts that for a given contract the value of a storage slot at an offset is 1 or 0xff. - /// A call to `initialize` will set it to 1 and a call to _disableInitializers will set it to 0xff. - function assertInitializedSlotIsSet(address _contractAddress, uint256 _slot, uint256 _offset) internal view { + /// @dev Asserts that for a given contract the value of a storage slot at an offset is 1. + function assertSlotValueIsOne(address _contractAddress, uint256 _slot, uint256 _offset) internal view { bytes32 slotVal = vm.load(_contractAddress, bytes32(_slot)); - uint8 val = uint8((uint256(slotVal) >> (_offset * 8)) & 0xFF); require( - val == uint8(1) || val == uint8(0xff), - "ChainAssertions: storage value is not 1 or 0xff at the given slot and offset" + uint8((uint256(slotVal) >> (_offset * 8)) & 0xFF) == uint8(1), + "Storage value is not 1 at the given slot and offset" ); } + + /// @notice Asserts the CeloTokenL1 is setup correctly + function checkCeloTokenL1(Types.ContractSet memory _contracts, bool _isProxy) internal view { + console.log("Running chain assertions on the CeloTokenL1"); + + CeloTokenL1 celoToken = CeloTokenL1(payable(_contracts.CustomGasToken)); + + // Check that the contract is initialized + assertSlotValueIsOne({ _contractAddress: address(celoToken), _slot: 0, _offset: 0 }); + + if (_isProxy) { + require(celoToken.totalSupply() == 1000000000e18); // 1 billion CELO + require(celoToken.balanceOf(_contracts.OptimismPortal) == 1000000000e18); + } + } } diff --git a/packages/contracts-bedrock/scripts/deploy/Deploy.s.sol b/packages/contracts-bedrock/scripts/deploy/Deploy.s.sol index d40c03987d534..289c4f80fa647 100644 --- a/packages/contracts-bedrock/scripts/deploy/Deploy.s.sol +++ b/packages/contracts-bedrock/scripts/deploy/Deploy.s.sol @@ -8,57 +8,63 @@ import { stdJson } from "forge-std/StdJson.sol"; import { AlphabetVM } from "test/mocks/AlphabetVM.sol"; import { EIP1967Helper } from "test/mocks/EIP1967Helper.sol"; +// Safe +import { GnosisSafe as Safe } from "safe-contracts/GnosisSafe.sol"; +import { OwnerManager } from "safe-contracts/base/OwnerManager.sol"; +import { GnosisSafeProxyFactory as SafeProxyFactory } from "safe-contracts/proxies/GnosisSafeProxyFactory.sol"; +import { Enum as SafeOps } from "safe-contracts/common/Enum.sol"; + // Scripts import { Deployer } from "scripts/deploy/Deployer.sol"; import { Chains } from "scripts/libraries/Chains.sol"; import { Config } from "scripts/libraries/Config.sol"; -import { StateDiff } from "scripts/libraries/StateDiff.sol"; +import { LibStateDiff } from "scripts/libraries/LibStateDiff.sol"; import { Process } from "scripts/libraries/Process.sol"; import { ChainAssertions } from "scripts/deploy/ChainAssertions.sol"; -import { DeployUtils } from "scripts/libraries/DeployUtils.sol"; -import { DeploySuperchainInput, DeploySuperchain, DeploySuperchainOutput } from "scripts/deploy/DeploySuperchain.s.sol"; -import { - DeployImplementationsInput, - DeployImplementations, - DeployImplementationsInterop, - DeployImplementationsOutput -} from "scripts/deploy/DeployImplementations.s.sol"; // Contracts +import { ProxyAdmin } from "src/universal/ProxyAdmin.sol"; +import { AddressManager } from "src/legacy/AddressManager.sol"; +import { Proxy } from "src/universal/Proxy.sol"; +import { StandardBridge } from "src/universal/StandardBridge.sol"; +import { L1ChugSplashProxy } from "src/legacy/L1ChugSplashProxy.sol"; +import { ResolvedDelegateProxy } from "src/legacy/ResolvedDelegateProxy.sol"; +import { PreimageOracle } from "src/cannon/PreimageOracle.sol"; +import { MIPS } from "src/cannon/MIPS.sol"; +import { MIPS2 } from "src/cannon/MIPS2.sol"; import { StorageSetter } from "src/universal/StorageSetter.sol"; -import { OPContractsManager } from "src/L1/OPContractsManager.sol"; // Libraries import { Constants } from "src/libraries/Constants.sol"; import { Types } from "scripts/libraries/Types.sol"; -import { Duration } from "src/dispute/lib/LibUDT.sol"; -import { StorageSlot, ForgeArtifacts } from "scripts/libraries/ForgeArtifacts.sol"; import "src/dispute/lib/Types.sol"; // Interfaces -import { IProxy } from "src/universal/interfaces/IProxy.sol"; -import { IProxyAdmin } from "src/universal/interfaces/IProxyAdmin.sol"; import { IOptimismPortal } from "src/L1/interfaces/IOptimismPortal.sol"; import { IOptimismPortal2 } from "src/L1/interfaces/IOptimismPortal2.sol"; +import { ICrossDomainMessenger } from "src/universal/interfaces/ICrossDomainMessenger.sol"; +import { IL1CrossDomainMessenger } from "src/L1/interfaces/IL1CrossDomainMessenger.sol"; import { IL2OutputOracle } from "src/L1/interfaces/IL2OutputOracle.sol"; import { ISuperchainConfig } from "src/L1/interfaces/ISuperchainConfig.sol"; +import { ICeloSuperchainConfig } from "src/L1/interfaces/ICeloSuperchainConfig.sol"; import { ISystemConfig } from "src/L1/interfaces/ISystemConfig.sol"; import { IDataAvailabilityChallenge } from "src/L1/interfaces/IDataAvailabilityChallenge.sol"; import { IL1ERC721Bridge } from "src/L1/interfaces/IL1ERC721Bridge.sol"; import { IL1StandardBridge } from "src/L1/interfaces/IL1StandardBridge.sol"; -import { ProtocolVersion } from "src/L1/interfaces/IProtocolVersions.sol"; +import { IProtocolVersions, ProtocolVersion } from "src/L1/interfaces/IProtocolVersions.sol"; import { IBigStepper } from "src/dispute/interfaces/IBigStepper.sol"; import { IDisputeGameFactory } from "src/dispute/interfaces/IDisputeGameFactory.sol"; import { IDisputeGame } from "src/dispute/interfaces/IDisputeGame.sol"; -import { IFaultDisputeGame } from "src/dispute/interfaces/IFaultDisputeGame.sol"; import { IDelayedWETH } from "src/dispute/interfaces/IDelayedWETH.sol"; import { IAnchorStateRegistry } from "src/dispute/interfaces/IAnchorStateRegistry.sol"; -import { IMIPS } from "src/cannon/interfaces/IMIPS.sol"; -import { IMIPS2 } from "src/cannon/interfaces/IMIPS2.sol"; import { IPreimageOracle } from "src/cannon/interfaces/IPreimageOracle.sol"; +import { IOptimismMintableERC20Factory } from "src/universal/interfaces/IOptimismMintableERC20Factory.sol"; + +import { CeloTokenL1 } from "src/celo/CeloTokenL1.sol"; +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { IAddressManager } from "src/legacy/interfaces/IAddressManager.sol"; -import { IL1ChugSplashProxy } from "src/legacy/interfaces/IL1ChugSplashProxy.sol"; -import { IResolvedDelegateProxy } from "src/legacy/interfaces/IResolvedDelegateProxy.sol"; + +import { ForgeArtifacts } from "scripts/libraries/ForgeArtifacts.sol"; /// @title Deploy /// @notice Script used to deploy a bedrock system. The entire system is deployed within the `run` function. @@ -128,7 +134,7 @@ contract Deploy is Deployer { accesses.length, vm.toString(block.chainid) ); - string memory json = StateDiff.encodeAccountAccesses(accesses); + string memory json = LibStateDiff.encodeAccountAccesses(accesses); string memory statediffPath = string.concat(vm.projectRoot(), "/snapshots/state-diff/", vm.toString(block.chainid), ".json"); vm.writeJson({ json: json, path: statediffPath }); @@ -145,8 +151,33 @@ contract Deploy is Deployer { return keccak256(bytes(Config.implSalt())); } - /// @notice Returns the proxy addresses, not reverting if any are unset. + /// @notice Returns the proxy addresses. If a proxy is not found, it will have address(0). function _proxies() internal view returns (Types.ContractSet memory proxies_) { + proxies_ = Types.ContractSet({ + L1CrossDomainMessenger: mustGetAddress("L1CrossDomainMessengerProxy"), + L1StandardBridge: mustGetAddress("L1StandardBridgeProxy"), + L2OutputOracle: mustGetAddress("L2OutputOracleProxy"), + DisputeGameFactory: mustGetAddress("DisputeGameFactoryProxy"), + DelayedWETH: mustGetAddress("DelayedWETHProxy"), + PermissionedDelayedWETH: mustGetAddress("PermissionedDelayedWETHProxy"), + AnchorStateRegistry: mustGetAddress("AnchorStateRegistryProxy"), + OptimismMintableERC20Factory: mustGetAddress("OptimismMintableERC20FactoryProxy"), + OptimismPortal: mustGetAddress("OptimismPortalProxy"), + OptimismPortal2: mustGetAddress("OptimismPortalProxy"), + SystemConfig: mustGetAddress("SystemConfigProxy"), + L1ERC721Bridge: mustGetAddress("L1ERC721BridgeProxy"), + ProtocolVersions: mustGetAddress("ProtocolVersionsProxy"), + SuperchainConfig: mustGetAddress("SuperchainConfigProxy"), + CeloSuperchainConfig: mustGetAddress("CeloSuperchainConfigProxy"), + OPContractsManager: getAddress("OPContractsManagerProxy"), + // allow for address(0) since it is not strictly required for all + // combinations of chain configs + CustomGasToken: getAddress("CustomGasTokenProxy") + }); + } + + /// @notice Returns the proxy addresses, not reverting if any are unset. + function _proxiesUnstrict() internal view returns (Types.ContractSet memory proxies_) { proxies_ = Types.ContractSet({ L1CrossDomainMessenger: getAddress("L1CrossDomainMessengerProxy"), L1StandardBridge: getAddress("L1StandardBridgeProxy"), @@ -162,28 +193,9 @@ contract Deploy is Deployer { L1ERC721Bridge: getAddress("L1ERC721BridgeProxy"), ProtocolVersions: getAddress("ProtocolVersionsProxy"), SuperchainConfig: getAddress("SuperchainConfigProxy"), - OPContractsManager: getAddress("OPContractsManagerProxy") - }); - } - - /// @notice Returns the impl addresses, not reverting if any are unset. - function _impls() internal view returns (Types.ContractSet memory proxies_) { - proxies_ = Types.ContractSet({ - L1CrossDomainMessenger: getAddress("L1CrossDomainMessenger"), - L1StandardBridge: getAddress("L1StandardBridge"), - L2OutputOracle: getAddress("L2OutputOracle"), - DisputeGameFactory: getAddress("DisputeGameFactory"), - DelayedWETH: getAddress("DelayedWETH"), - PermissionedDelayedWETH: getAddress("PermissionedDelayedWETH"), - AnchorStateRegistry: getAddress("AnchorStateRegistry"), - OptimismMintableERC20Factory: getAddress("OptimismMintableERC20Factory"), - OptimismPortal: getAddress("OptimismPortal"), - OptimismPortal2: getAddress("OptimismPortal2"), - SystemConfig: getAddress("SystemConfig"), - L1ERC721Bridge: getAddress("L1ERC721Bridge"), - ProtocolVersions: getAddress("ProtocolVersions"), - SuperchainConfig: getAddress("SuperchainConfig"), - OPContractsManager: getAddress("OPContractsManager") + CeloSuperchainConfig: getAddress("CeloSuperchainConfigProxy"), + OPContractsManager: getAddress("OPContractsManagerProxy"), + CustomGasToken: getAddress("CustomGasTokenProxy") }); } @@ -191,22 +203,80 @@ contract Deploy is Deployer { // State Changing Helper Functions // //////////////////////////////////////////////////////////////// + /// @notice Gets the address of the SafeProxyFactory and Safe singleton for use in deploying a new GnosisSafe. + function _getSafeFactory() internal returns (SafeProxyFactory safeProxyFactory_, Safe safeSingleton_) { + if (getAddress("SafeProxyFactory") != address(0)) { + // The SafeProxyFactory is already saved, we can just use it. + safeProxyFactory_ = SafeProxyFactory(getAddress("SafeProxyFactory")); + safeSingleton_ = Safe(getAddress("SafeSingleton")); + return (safeProxyFactory_, safeSingleton_); + } + + // These are the standard create2 deployed contracts. First we'll check if they are deployed, + // if not we'll deploy new ones, though not at these addresses. + address safeProxyFactory = 0xa6B71E26C5e0845f74c812102Ca7114b6a896AB2; + address safeSingleton = 0xd9Db270c1B5E3Bd161E8c8503c55cEABeE709552; + + safeProxyFactory.code.length == 0 + ? safeProxyFactory_ = new SafeProxyFactory() + : safeProxyFactory_ = SafeProxyFactory(safeProxyFactory); + + safeSingleton.code.length == 0 ? safeSingleton_ = new Safe() : safeSingleton_ = Safe(payable(safeSingleton)); + + save("SafeProxyFactory", address(safeProxyFactory_)); + save("SafeSingleton", address(safeSingleton_)); + } + + /// @notice Make a call from the Safe contract to an arbitrary address with arbitrary data + function _callViaSafe(Safe _safe, address _target, bytes memory _data) internal { + // This is the signature format used when the caller is also the signer. + bytes memory signature = abi.encodePacked(uint256(uint160(msg.sender)), bytes32(0), uint8(1)); + + _safe.execTransaction({ + to: _target, + value: 0, + data: _data, + operation: SafeOps.Operation.Call, + safeTxGas: 0, + baseGas: 0, + gasPrice: 0, + gasToken: address(0), + refundReceiver: payable(address(0)), + signatures: signature + }); + } + + /// @notice Call from the Safe contract to the Proxy Admin's upgrade and call method + function _upgradeAndCallViaSafe(address _proxy, address _implementation, bytes memory _innerCallData) internal { + address proxyAdmin = mustGetAddress("ProxyAdmin"); + + bytes memory data = + abi.encodeCall(ProxyAdmin.upgradeAndCall, (payable(_proxy), _implementation, _innerCallData)); + + Safe safe = Safe(mustGetAddress("SystemOwnerSafe")); + _callViaSafe({ _safe: safe, _target: proxyAdmin, _data: data }); + } + /// @notice Transfer ownership of the ProxyAdmin contract to the final system owner function transferProxyAdminOwnership() public broadcast { - // Get the ProxyAdmin contract. - IProxyAdmin proxyAdmin = IProxyAdmin(mustGetAddress("ProxyAdmin")); - - // Transfer ownership to the final system owner if necessary. + ProxyAdmin proxyAdmin = ProxyAdmin(mustGetAddress("ProxyAdmin")); address owner = proxyAdmin.owner(); - address finalSystemOwner = cfg.finalSystemOwner(); - if (owner != finalSystemOwner) { - proxyAdmin.transferOwnership(finalSystemOwner); - console.log("ProxyAdmin ownership transferred to final system owner at: %s", finalSystemOwner); + address safe = mustGetAddress("SystemOwnerSafe"); + if (owner != safe) { + proxyAdmin.transferOwnership(safe); + console.log("ProxyAdmin ownership transferred to Safe at: %s", safe); } + } - // Make sure the ProxyAdmin owner is set to the final system owner. - owner = proxyAdmin.owner(); - require(owner == finalSystemOwner, "Deploy: ProxyAdmin ownership not transferred to final system owner"); + /// @notice Transfer ownership of a Proxy to the ProxyAdmin contract + /// This is expected to be used in conjusting with deployERC1967ProxyWithOwner after setup actions + /// have been performed on the proxy. + /// @param _name The name of the proxy to transfer ownership of. + function transferProxyToProxyAdmin(string memory _name) public broadcast { + Proxy proxy = Proxy(mustGetAddress(_name)); + address proxyAdmin = mustGetAddress("ProxyAdmin"); + proxy.changeAdmin(proxyAdmin); + console.log("Proxy %s ownership transferred to ProxyAdmin at: %s", _name, proxyAdmin); } //////////////////////////////////////////////////////////////// @@ -219,35 +289,55 @@ contract Deploy is Deployer { _run(); } + /// @notice Deploy all of the L1 contracts necessary for a full Superchain with a single Op Chain. + /// The two parameters correspond to: + /// 1. Whether or not the Superchain contracts need to be deployed. We do not deploy a + /// SuperchainConfig, instead the address of the external one should be provided in + /// the deployment config. + /// 2. Whether or not the fault games need to be initialized. Set to false, and execute latter + /// `setupFaultGames()` to initialize + /// when the config `faultGameGenesisOutputRoot` is known + function runCelo() public { + console.log( + "Deploying a fresh OP Stack, without SuperchainConfig (address should be provided in `externalSuperchainConfig` parameter of the deploy JSON)" + ); + _run(false, false); + } + + /// @notice For testing purposes. + function run(bool _needsSuperchain) public { + _run(_needsSuperchain); + } + /// @notice Deploy a new OP Chain using an existing SuperchainConfig and ProtocolVersions /// @param _superchainConfigProxy Address of the existing SuperchainConfig proxy /// @param _protocolVersionsProxy Address of the existing ProtocolVersions proxy - /// @param _includeDump Whether to include a state dump after deployment + /// @param includeDump Whether to include a state dump after deployment function runWithSuperchain( address payable _superchainConfigProxy, address payable _protocolVersionsProxy, - bool _includeDump + bool includeDump ) public { - require(_superchainConfigProxy != address(0), "Deploy: must specify address for superchain config proxy"); - require(_protocolVersionsProxy != address(0), "Deploy: must specify address for protocol versions proxy"); + require(_superchainConfigProxy != address(0), "must specify address for superchain config proxy"); + require(_protocolVersionsProxy != address(0), "must specify address for protocol versions proxy"); vm.chainId(cfg.l1ChainID()); console.log("Deploying a fresh OP Stack with existing SuperchainConfig and ProtocolVersions"); - IProxy scProxy = IProxy(_superchainConfigProxy); + Proxy scProxy = Proxy(_superchainConfigProxy); save("SuperchainConfig", scProxy.implementation()); save("SuperchainConfigProxy", _superchainConfigProxy); - IProxy pvProxy = IProxy(_protocolVersionsProxy); + Proxy pvProxy = Proxy(_protocolVersionsProxy); save("ProtocolVersions", pvProxy.implementation()); save("ProtocolVersionsProxy", _protocolVersionsProxy); _run(false); - if (_includeDump) { + if (includeDump) { vm.dumpState(Config.stateDumpPath("")); } } @@ -265,47 +355,46 @@ contract Deploy is Deployer { /// @notice Compatibility function for tests that override _run(). function _run() internal virtual { - _run(true); + _run(true, true); + } + + /// @notice (Celo) Compatibility function for OP stack + function _run(bool _needsSuperchain) internal virtual { + _run(_needsSuperchain, true); } /// @notice Internal function containing the deploy logic. - function _run(bool _needsSuperchain) internal { + /// @param _needsSuperchain Whether or not the + /// Superchain contracts need to be deployed. + /// @param _initializeFaultGames Whether or not the fault games need to be initialized. + function _run(bool _needsSuperchain, bool _initializeFaultGames) internal { console.log("start of L1 Deploy!"); + deploySafe("SystemOwnerSafe"); + console.log("deployed Safe!"); - // Set up the Superchain if needed. - if (_needsSuperchain) { - deploySuperchain(); - } - - deployImplementations({ _isInterop: cfg.useInterop() }); + // Deploy a new ProxyAdmin and AddressManager + // This proxy will be used on the SuperchainConfig and ProtocolVersions contracts, as well as the contracts + // in the OP Chain system. + setupAdmin(); - // Deploy Current OPChain Contracts - deployOpChain(); - - // Apply modifications for non-standard configurations not supported by the OPCM deployment - if (cfg.useFaultProofs()) { - vm.startPrank(ISuperchainConfig(mustGetAddress("SuperchainConfigProxy")).guardian()); - IOptimismPortal2(mustGetAddress("OptimismPortalProxy")).setRespectedGameType( - GameType.wrap(uint32(cfg.respectedGameType())) - ); - vm.stopPrank(); + if (_needsSuperchain) { + setupSuperchain(); + console.log("set up superchain!"); } else { - // The L2OutputOracle is not deployed by the OPCM, we deploy the proxy and initialize it here. - deployERC1967Proxy("L2OutputOracleProxy"); - initializeL2OutputOracle(); - - // The OptimismPortalProxy contract is used both with and without Fault Proofs enabled, and is deployed by - // deployOPChain. If Fault Proofs are disabled, then we need to reinitialize the OptimismPortalProxy - // as the legacy OptimismPortal. - resetInitializedProxy("OptimismPortal"); - initializeOptimismPortal(); + require( + cfg.externalSuperchainConfig() != address(0), "Need to provide the external SuperchainConfig address!" + ); + // Inject the external SuperchainConfig address into Artifacts, so it can be retrieved + // by contracts that depend on it (e.g. CeloSuperchainConfig). + save("SuperchainConfigProxy", cfg.externalSuperchainConfig()); + + // Still need a ProtocolVersions deployment. + deployERC1967Proxy("ProtocolVersionsProxy"); + deployProtocolVersions(); + initializeProtocolVersions(); } - if (cfg.useCustomGasToken()) { - // Reset the systemconfig then reinitialize it with the custom gas token - resetInitializedProxy("SystemConfig"); - initializeSystemConfig(); - } + setupCeloSuperchainConfig(); if (cfg.useAltDA()) { bytes32 typeHash = keccak256(bytes(cfg.daCommitmentType())); @@ -314,8 +403,7 @@ contract Deploy is Deployer { setupOpAltDA(); } } - - transferProxyAdminOwnership(); + setupOpChain(_initializeFaultGames); console.log("set up op chain!"); } @@ -323,176 +411,257 @@ contract Deploy is Deployer { // High Level Deployment Functions // //////////////////////////////////////////////////////////////// + /// @notice Deploy the address manager and proxy admin contracts. + function setupAdmin() public { + deployAddressManager(); + deployProxyAdmin(); + transferProxyAdminOwnership(); + } + /// @notice Deploy a full system with a new SuperchainConfig /// The Superchain system has 2 singleton contracts which lie outside of an OP Chain: /// 1. The SuperchainConfig contract /// 2. The ProtocolVersions contract - function deploySuperchain() public { + function setupSuperchain() public { console.log("Setting up Superchain"); - DeploySuperchain ds = new DeploySuperchain(); - (DeploySuperchainInput dsi, DeploySuperchainOutput dso) = ds.etchIOContracts(); - - // Set the input values on the input contract. - // TODO: when DeployAuthSystem is done, finalSystemOwner should be replaced with the Foundation Upgrades Safe - dsi.set(dsi.protocolVersionsOwner.selector, cfg.finalSystemOwner()); - dsi.set(dsi.superchainProxyAdminOwner.selector, cfg.finalSystemOwner()); - dsi.set(dsi.guardian.selector, cfg.superchainConfigGuardian()); - dsi.set(dsi.paused.selector, false); - dsi.set(dsi.requiredProtocolVersion.selector, ProtocolVersion.wrap(cfg.requiredProtocolVersion())); - dsi.set(dsi.recommendedProtocolVersion.selector, ProtocolVersion.wrap(cfg.recommendedProtocolVersion())); - - // Run the deployment script. - ds.run(dsi, dso); - save("SuperchainProxyAdmin", address(dso.superchainProxyAdmin())); - save("SuperchainConfigProxy", address(dso.superchainConfigProxy())); - save("SuperchainConfig", address(dso.superchainConfigImpl())); - save("ProtocolVersionsProxy", address(dso.protocolVersionsProxy())); - save("ProtocolVersions", address(dso.protocolVersionsImpl())); - - // First run assertions for the ProtocolVersions and SuperchainConfig proxy contracts. - Types.ContractSet memory contracts = _proxies(); - ChainAssertions.checkProtocolVersions({ _contracts: contracts, _cfg: cfg, _isProxy: true }); - ChainAssertions.checkSuperchainConfig({ _contracts: contracts, _cfg: cfg, _isProxy: true, _isPaused: false }); - - // Then replace the ProtocolVersions proxy with the implementation address and run assertions on it. - contracts.ProtocolVersions = mustGetAddress("ProtocolVersions"); - ChainAssertions.checkProtocolVersions({ _contracts: contracts, _cfg: cfg, _isProxy: false }); - // Finally replace the SuperchainConfig proxy with the implementation address and run assertions on it. - contracts.SuperchainConfig = mustGetAddress("SuperchainConfig"); - ChainAssertions.checkSuperchainConfig({ _contracts: contracts, _cfg: cfg, _isPaused: false, _isProxy: false }); + // Deploy the SuperchainConfigProxy + deployERC1967Proxy("SuperchainConfigProxy"); + deploySuperchainConfig(); + initializeSuperchainConfig(); + + // Deploy the ProtocolVersionsProxy + deployERC1967Proxy("ProtocolVersionsProxy"); + deployProtocolVersions(); + initializeProtocolVersions(); } - /// @notice Deploy all of the implementations - function deployImplementations(bool _isInterop) public { - require(_isInterop == cfg.useInterop(), "Deploy: Interop setting mismatch."); + /// @notice Deploys the CeloSuperchainConfig, which is currently used to + /// enable a dual Guardian setup, i.e. having the Celo L1 system be + /// pausable by both the global Superchain Guardian (whenever the + /// SuperchainConfig contract is paused) and a new Celo Guardian + /// role. + function setupCeloSuperchainConfig() public { + console.log("Setting up CeloSuperchainConfig"); + + // Deploy the CeloSuperchainConfigProxy + deployERC1967Proxy("CeloSuperchainConfigProxy"); + deployCeloSuperchainConfig(); + initializeCeloSuperchainConfig(); + } - console.log("Deploying implementations"); - DeployImplementations di = new DeployImplementations(); - (DeployImplementationsInput dii, DeployImplementationsOutput dio) = di.etchIOContracts(); - - dii.set(dii.withdrawalDelaySeconds.selector, cfg.faultGameWithdrawalDelay()); - dii.set(dii.minProposalSizeBytes.selector, cfg.preimageOracleMinProposalSize()); - dii.set(dii.challengePeriodSeconds.selector, cfg.preimageOracleChallengePeriod()); - dii.set(dii.proofMaturityDelaySeconds.selector, cfg.proofMaturityDelaySeconds()); - dii.set(dii.disputeGameFinalityDelaySeconds.selector, cfg.disputeGameFinalityDelaySeconds()); - dii.set(dii.mipsVersion.selector, Config.useMultithreadedCannon() ? 2 : 1); - string memory release = "dev"; - dii.set(dii.release.selector, release); - dii.set( - dii.standardVersionsToml.selector, string.concat(vm.projectRoot(), "/test/fixtures/standard-versions.toml") - ); - dii.set(dii.superchainConfigProxy.selector, mustGetAddress("SuperchainConfigProxy")); - dii.set(dii.protocolVersionsProxy.selector, mustGetAddress("ProtocolVersionsProxy")); - dii.set(dii.opcmProxyOwner.selector, cfg.finalSystemOwner()); + /// @notice Deploy a new OP Chain, with an existing SuperchainConfig provided + function setupOpChain(bool _initializeFaultGames) public { + console.log("Deploying OP Chain"); - if (_isInterop) { - di = DeployImplementations(new DeployImplementationsInterop()); + // Ensure that the requisite contracts are deployed + mustGetAddress("SuperchainConfigProxy"); + mustGetAddress("CeloSuperchainConfigProxy"); + mustGetAddress("SystemOwnerSafe"); + mustGetAddress("AddressManager"); + mustGetAddress("ProxyAdmin"); + + deployProxies(); + deployImplementations(); + initializeImplementations(_initializeFaultGames); + + if (_initializeFaultGames) { + setAlphabetFaultGameImplementation({ _allowUpgrade: false }); + setFastFaultGameImplementation({ _allowUpgrade: false }); + setCannonFaultGameImplementation({ _allowUpgrade: false }); + setPermissionedCannonFaultGameImplementation({ _allowUpgrade: false }); + transferDisputeGameFactoryOwnership(); } - di.run(dii, dio); - // Temporary patch for legacy system - if (!cfg.useFaultProofs()) { - deployOptimismPortal(); - deployL2OutputOracle(); - } + transferDelayedWETHOwnership(); + } - save("L1CrossDomainMessenger", address(dio.l1CrossDomainMessengerImpl())); - save("OptimismMintableERC20Factory", address(dio.optimismMintableERC20FactoryImpl())); - save("SystemConfig", address(dio.systemConfigImpl())); - save("L1StandardBridge", address(dio.l1StandardBridgeImpl())); - save("L1ERC721Bridge", address(dio.l1ERC721BridgeImpl())); + /// @notice Deploy the fault games and set the implementations. Initialize AnchorStateRegistry. + function setupFaultGames() public { + // initializeL2OutputOracle(); + setCannonFaultGameImplementation({ _allowUpgrade: false }); + setPermissionedCannonFaultGameImplementation({ _allowUpgrade: false }); + // initializeAnchorStateRegistry(false); + transferDisputeGameFactoryOwnership(); + } - // Fault proofs - save("OptimismPortal2", address(dio.optimismPortalImpl())); - save("DisputeGameFactory", address(dio.disputeGameFactoryImpl())); - save("DelayedWETH", address(dio.delayedWETHImpl())); - save("PreimageOracle", address(dio.preimageOracleSingleton())); - save("Mips", address(dio.mipsSingleton())); - save("OPContractsManagerProxy", address(dio.opcmProxy())); - save("OPContractsManager", address(dio.opcmImpl())); - - Types.ContractSet memory contracts = _impls(); - ChainAssertions.checkL1CrossDomainMessenger({ _contracts: contracts, _vm: vm, _isProxy: false }); - ChainAssertions.checkL1StandardBridge({ _contracts: contracts, _isProxy: false }); - ChainAssertions.checkL1ERC721Bridge({ _contracts: contracts, _isProxy: false }); - ChainAssertions.checkOptimismPortal2({ _contracts: contracts, _cfg: cfg, _isProxy: false }); - ChainAssertions.checkOptimismMintableERC20Factory({ _contracts: contracts, _isProxy: false }); - ChainAssertions.checkDisputeGameFactory({ _contracts: contracts, _expectedOwner: address(0), _isProxy: false }); - ChainAssertions.checkDelayedWETH({ - _contracts: contracts, - _cfg: cfg, - _isProxy: false, - _expectedOwner: address(0) - }); - ChainAssertions.checkPreimageOracle({ - _oracle: IPreimageOracle(address(dio.preimageOracleSingleton())), - _cfg: cfg + function printSetupFaultGames() public { + address l2OutputOracleProxy = mustGetAddress("L2OutputOracleProxy"); + address l2OutputOracle = mustGetAddress("L2OutputOracle"); + + address proxyAdmin = mustGetAddress("ProxyAdmin"); + + console.log("Proxy admin abi"); + console.log(ForgeArtifacts.getAbi("ProxyAdmin")); + + console.log("ProxyAdmin address", proxyAdmin); + console.log(""); + console.log("*************************************************************************"); + console.log("Load ProxyAdmin abi to Gnosis Safe UI, choose function 'upgradeAndCall' and paste in following input parameters"); + console.log("*************************************************************************"); + console.log(""); + + console.log("_proxy:", l2OutputOracleProxy, "// l2OutputOracleProxy address"); + console.log("_implementation:", l2OutputOracle, "// l2OutputOracle implementation address"); + + console.log("_data:"); + console.logBytes(abi.encodeCall( + IL2OutputOracle.initialize, + ( + cfg.l2OutputOracleSubmissionInterval(), + cfg.l2BlockTime(), + cfg.l2OutputOracleStartingBlockNumber(), + cfg.l2OutputOracleStartingTimestamp(), + cfg.l2OutputOracleProposer(), + cfg.l2OutputOracleChallenger(), + cfg.finalizationPeriodSeconds() + ) + )); + + console.log(""); + console.log("########################################################################################"); + console.log(""); + + address anchorStateRegistryProxy = mustGetAddress("AnchorStateRegistryProxy"); + address anchorStateRegistry = mustGetAddress("AnchorStateRegistry"); + ICeloSuperchainConfig superchainConfig = ICeloSuperchainConfig(mustGetAddress("CeloSuperchainConfigProxy")); + + IAnchorStateRegistry.StartingAnchorRoot[] memory roots; + + roots = new IAnchorStateRegistry.StartingAnchorRoot[](2); + roots[0] = IAnchorStateRegistry.StartingAnchorRoot({ + gameType: GameTypes.CANNON, + outputRoot: OutputRoot({ + root: Hash.wrap(cfg.faultGameGenesisOutputRoot()), + l2BlockNumber: cfg.faultGameGenesisBlock() + }) }); - ChainAssertions.checkMIPS({ - _mips: IMIPS(address(dio.mipsSingleton())), - _oracle: IPreimageOracle(address(dio.preimageOracleSingleton())) + roots[1] = IAnchorStateRegistry.StartingAnchorRoot({ + gameType: GameTypes.PERMISSIONED_CANNON, + outputRoot: OutputRoot({ + root: Hash.wrap(cfg.faultGameGenesisOutputRoot()), + l2BlockNumber: cfg.faultGameGenesisBlock() + }) }); - if (_isInterop) { - ChainAssertions.checkSystemConfigInterop({ _contracts: contracts, _cfg: cfg, _isProxy: false }); - } else { - ChainAssertions.checkSystemConfig({ _contracts: contracts, _cfg: cfg, _isProxy: false }); - } + + console.log("*************************************************************************"); + console.log("Load ProxyAdmin abi to Gnosis Safe UI, choose function 'upgradeAndCall' and paste in following input parameters"); + console.log("*************************************************************************"); + console.log(""); + + console.log("_proxy:", anchorStateRegistryProxy, "// anchorStateRegistryProxy address"); + console.log("_implementation:", anchorStateRegistry, "// anchorStateRegistry implementation address"); + + console.log("_data:"); + + console.logBytes(abi.encodeCall(IAnchorStateRegistry.initialize, (roots, superchainConfig))); + + // initializeL2OutputOracle(); + // initializeAnchorStateRegistry(false); } - /// @notice Deploy all of the OP Chain specific contracts - function deployOpChain() public { - console.log("Deploying OP Chain"); + /// @notice Deploy all of the proxies + function deployProxies() public { + console.log("Deploying proxies"); + + deployERC1967Proxy("OptimismPortalProxy"); + deployERC1967Proxy("SystemConfigProxy"); + deployL1StandardBridgeProxy(); + deployL1CrossDomainMessengerProxy(); + deployERC1967Proxy("OptimismMintableERC20FactoryProxy"); + deployERC1967Proxy("L1ERC721BridgeProxy"); + + // Both the DisputeGameFactory and L2OutputOracle proxies are deployed regardless of whether fault proofs is + // enabled to prevent a nastier refactor to the deploy scripts. In the future, the L2OutputOracle will be + // removed. If fault proofs are not enabled, the DisputeGameFactory proxy will be unused. + deployERC1967Proxy("DisputeGameFactoryProxy"); + deployERC1967Proxy("L2OutputOracleProxy"); + deployERC1967Proxy("DelayedWETHProxy"); + deployERC1967Proxy("PermissionedDelayedWETHProxy"); + deployERC1967Proxy("AnchorStateRegistryProxy"); + + transferAddressManagerOwnership(); // to the ProxyAdmin + } - // Ensure that the requisite contracts are deployed - address superchainConfigProxy = mustGetAddress("SuperchainConfigProxy"); - OPContractsManager opcm = OPContractsManager(mustGetAddress("OPContractsManagerProxy")); - - OPContractsManager.DeployInput memory deployInput = getDeployInput(); - OPContractsManager.DeployOutput memory deployOutput = opcm.deploy(deployInput); - - // Save all deploy outputs from the OPCM, in the order they are declared in the DeployOutput struct - save("ProxyAdmin", address(deployOutput.opChainProxyAdmin)); - save("AddressManager", address(deployOutput.addressManager)); - save("L1ERC721BridgeProxy", address(deployOutput.l1ERC721BridgeProxy)); - save("SystemConfigProxy", address(deployOutput.systemConfigProxy)); - save("OptimismMintableERC20FactoryProxy", address(deployOutput.optimismMintableERC20FactoryProxy)); - save("L1StandardBridgeProxy", address(deployOutput.l1StandardBridgeProxy)); - save("L1CrossDomainMessengerProxy", address(deployOutput.l1CrossDomainMessengerProxy)); - - // Fault Proof contracts - save("DisputeGameFactoryProxy", address(deployOutput.disputeGameFactoryProxy)); - save("PermissionedDelayedWETHProxy", address(deployOutput.delayedWETHPermissionedGameProxy)); - save("AnchorStateRegistryProxy", address(deployOutput.anchorStateRegistryProxy)); - save("AnchorStateRegistry", address(deployOutput.anchorStateRegistryImpl)); - save("PermissionedDisputeGame", address(deployOutput.permissionedDisputeGame)); - save("OptimismPortalProxy", address(deployOutput.optimismPortalProxy)); - - // Check if the permissionless game implementation is already set - IDisputeGameFactory factory = IDisputeGameFactory(mustGetAddress("DisputeGameFactoryProxy")); - address permissionlessGameImpl = address(factory.gameImpls(GameTypes.CANNON)); + /// @notice Deploy all of the implementations + function deployImplementations() public { + console.log("Deploying implementations"); + deployL1CrossDomainMessenger(); + deployOptimismMintableERC20Factory(); + deploySystemConfig(); + deployL1StandardBridge(); + deployL1ERC721Bridge(); + deployOptimismPortal(); + deployL2OutputOracle(); + // Fault proofs + deployOptimismPortal2(); + deployDisputeGameFactory(); + deployDelayedWETH(); + deployPreimageOracle(); + deployMips(); + deployAnchorStateRegistry(); + + // Multicall3 + // deployMulticall3(); + } - // Deploy and setup the PermissionlessDelayedWeth not provided by the OPCM. - // If the following require statement is hit, you can delete the block of code after it. - require( - permissionlessGameImpl == address(0), - "Deploy: The PermissionlessDelayedWETH is already set by the OPCM, it is no longer necessary to deploy it separately." - ); - address delayedWETHImpl = mustGetAddress("DelayedWETH"); - address delayedWETHPermissionlessGameProxy = deployERC1967ProxyWithOwner("DelayedWETHProxy", msg.sender); - vm.broadcast(msg.sender); - IProxy(payable(delayedWETHPermissionlessGameProxy)).upgradeToAndCall({ - _implementation: delayedWETHImpl, - _data: abi.encodeCall(IDelayedWETH.initialize, (msg.sender, ISuperchainConfig(superchainConfigProxy))) + function preInitializeOptimismPortalBalance() public broadcast { + address optimismPortalProxy = mustGetAddress("OptimismPortalProxy"); + address storageSetter = mustGetAddress("StorageSetter"); + + // NOTE: the storage slot index should stay the same across versions + // (OptimismPortal, OptimismPortal2, ...) since slot spacers are used + // for legacy storage variables. + // We also assert correctness in a downstream ChainAssertion, + // so changing slot numbers should get detected for coming versions. + uint256 balanceStorageSlot = 61; // slot of _balance variable + + address customGasTokenAddress = Constants.ETHER; + uint256 initialBalance = 0; + customGasTokenAddress = cfg.customGasTokenAddress(); + IERC20 token = IERC20(customGasTokenAddress); + initialBalance = token.balanceOf(optimismPortalProxy); + + _upgradeAndCallViaSafe({ + _proxy: payable(optimismPortalProxy), + _implementation: storageSetter, + _innerCallData: abi.encodeCall(StorageSetter.setUint, (bytes32(balanceStorageSlot), initialBalance)) }); + } - setAlphabetFaultGameImplementation({ _allowUpgrade: false }); - setFastFaultGameImplementation({ _allowUpgrade: false }); - setCannonFaultGameImplementation({ _allowUpgrade: false }); + /// @notice Initialize all of the implementations + function initializeImplementations(bool _initializeAnchorStateRegistry) public { + console.log("Initializing implementations"); - transferDisputeGameFactoryOwnership(); - transferDelayedWETHOwnership(); - transferPermissionedDelayedWETHOwnership(); + if (cfg.useCustomGasToken()) { + setupCustomGasToken(); + save("StorageSetter", deployStorageSetter()); + preInitializeOptimismPortalBalance(); + } + + // Selectively initialize either the original OptimismPortal or the new OptimismPortal2. Since this will upgrade + // the proxy, we cannot initialize both. + if (cfg.useFaultProofs()) { + console.log("Fault proofs enabled. Initializing the OptimismPortal proxy with the OptimismPortal2."); + initializeOptimismPortal2(); + } else { + initializeOptimismPortal(); + } + + initializeSystemConfig(); + initializeL1StandardBridge(); + initializeL1ERC721Bridge(); + initializeOptimismMintableERC20Factory(); + initializeL1CrossDomainMessenger(); + initializeDisputeGameFactory(); + initializeDelayedWETH(); + initializePermissionedDelayedWETH(); + if (_initializeAnchorStateRegistry) { + initializeL2OutputOracle(); + initializeAnchorStateRegistry(true); + } + + ChainAssertions.checkCustomGasTokenOptimismPortal({ _contracts: _proxies(), _cfg: cfg, _isProxy: true }); } /// @notice Add AltDA setup to the OP chain @@ -507,46 +676,96 @@ contract Deploy is Deployer { // Non-Proxied Deployment Functions // //////////////////////////////////////////////////////////////// + /// @notice Deploy the Safe + function deploySafe(string memory _name) public broadcast returns (address addr_) { + address[] memory owners = new address[](0); + addr_ = deploySafe(_name, owners, 1, true); + } + + /// @notice Deploy a new Safe contract. If the keepDeployer option is used to enable further setup actions, then + /// the removeDeployerFromSafe() function should be called on that safe after setup is complete. + /// Note this function does not have the broadcast modifier. + /// @param _name The name of the Safe to deploy. + /// @param _owners The owners of the Safe. + /// @param _threshold The threshold of the Safe. + /// @param _keepDeployer Wether or not the deployer address will be added as an owner of the Safe. + function deploySafe( + string memory _name, + address[] memory _owners, + uint256 _threshold, + bool _keepDeployer + ) + public + returns (address addr_) + { + bytes32 salt = keccak256(abi.encode(_name, _implSalt())); + console.log("Deploying safe: %s with salt %s", _name, vm.toString(salt)); + (SafeProxyFactory safeProxyFactory, Safe safeSingleton) = _getSafeFactory(); + + if (_keepDeployer) { + address[] memory expandedOwners = new address[](_owners.length + 1); + // By always adding msg.sender first we know that the previousOwner will be SENTINEL_OWNERS, which makes it + // easier to call removeOwner later. + expandedOwners[0] = msg.sender; + for (uint256 i = 0; i < _owners.length; i++) { + expandedOwners[i + 1] = _owners[i]; + } + _owners = expandedOwners; + } + + bytes memory initData = abi.encodeCall( + Safe.setup, (_owners, _threshold, address(0), hex"", address(0), address(0), 0, payable(address(0))) + ); + addr_ = address(safeProxyFactory.createProxyWithNonce(address(safeSingleton), initData, uint256(salt))); + + save(_name, addr_); + console.log("New safe: %s deployed at %s\n Note that this safe is owned by the deployer key", _name, addr_); + } + + /// @notice If the keepDeployer option was used with deploySafe(), this function can be used to remove the deployer. + /// Note this function does not have the broadcast modifier. + function removeDeployerFromSafe(string memory _name, uint256 _newThreshold) public { + Safe safe = Safe(mustGetAddress(_name)); + + // The sentinel address is used to mark the start and end of the linked list of owners in the Safe. + address sentinelOwners = address(0x1); + + // Because deploySafe() always adds msg.sender first (if keepDeployer is true), we know that the previousOwner + // will be sentinelOwners. + _callViaSafe({ + _safe: safe, + _target: address(safe), + _data: abi.encodeCall(OwnerManager.removeOwner, (sentinelOwners, msg.sender, _newThreshold)) + }); + console.log("Removed deployer owner from ", _name); + } + /// @notice Deploy the AddressManager function deployAddressManager() public broadcast returns (address addr_) { - // Use create instead of create2 because we need the owner to be set to msg.sender but - // forge will automatically use the create2 factory which messes up the sender. - IAddressManager manager = IAddressManager( - DeployUtils.create1AndSave({ - _save: this, - _name: "AddressManager", - _args: DeployUtils.encodeConstructor(abi.encodeCall(IAddressManager.__constructor__, ())) - }) - ); + console.log("Deploying AddressManager"); + AddressManager manager = new AddressManager(); require(manager.owner() == msg.sender); + + save("AddressManager", address(manager)); + console.log("AddressManager deployed at %s", address(manager)); addr_ = address(manager); } - /// @notice Deploys the ProxyAdmin contract. Should NOT be used for the Superchain. + /// @notice Deploy the ProxyAdmin function deployProxyAdmin() public broadcast returns (address addr_) { - // Deploy the ProxyAdmin contract. - IProxyAdmin admin = IProxyAdmin( - DeployUtils.create2AndSave({ - _save: this, - _salt: _implSalt(), - _name: "ProxyAdmin", - _args: DeployUtils.encodeConstructor(abi.encodeCall(IProxyAdmin.__constructor__, (msg.sender))) - }) - ); - - // Make sure the owner was set to the deployer. + console.log("Deploying ProxyAdmin"); + ProxyAdmin admin = new ProxyAdmin({ _owner: msg.sender }); require(admin.owner() == msg.sender); - // Set the address manager if it is not already set. - IAddressManager addressManager = IAddressManager(mustGetAddress("AddressManager")); - if (admin.addressManager() != addressManager) { - admin.setAddressManager(addressManager); + AddressManager addressManager = AddressManager(mustGetAddress("AddressManager")); + if (address(admin.addressManager()) != address(addressManager)) { + admin.setAddressManager(IAddressManager(address(addressManager))); } - // Make sure the address manager is set properly. - require(admin.addressManager() == addressManager); + require(address(admin.addressManager()) == address(addressManager)); - // Return the address of the deployed contract. + save("ProxyAdmin", address(admin)); + console.log("ProxyAdmin deployed at %s", address(admin)); addr_ = address(admin); } @@ -566,36 +785,26 @@ contract Deploy is Deployer { /// @notice Deploy the L1StandardBridgeProxy using a ChugSplashProxy function deployL1StandardBridgeProxy() public broadcast returns (address addr_) { + console.log("Deploying proxy for L1StandardBridge"); address proxyAdmin = mustGetAddress("ProxyAdmin"); - IL1ChugSplashProxy proxy = IL1ChugSplashProxy( - DeployUtils.create2AndSave({ - _save: this, - _salt: _implSalt(), - _name: "L1ChugSplashProxy", - _nick: "L1StandardBridgeProxy", - _args: DeployUtils.encodeConstructor(abi.encodeCall(IL1ChugSplashProxy.__constructor__, (proxyAdmin))) - }) - ); + L1ChugSplashProxy proxy = new L1ChugSplashProxy(proxyAdmin); + require(EIP1967Helper.getAdmin(address(proxy)) == proxyAdmin); + + save("L1StandardBridgeProxy", address(proxy)); + console.log("L1StandardBridgeProxy deployed at %s", address(proxy)); addr_ = address(proxy); } /// @notice Deploy the L1CrossDomainMessengerProxy using a ResolvedDelegateProxy function deployL1CrossDomainMessengerProxy() public broadcast returns (address addr_) { - IResolvedDelegateProxy proxy = IResolvedDelegateProxy( - DeployUtils.create2AndSave({ - _save: this, - _salt: _implSalt(), - _name: "ResolvedDelegateProxy", - _nick: "L1CrossDomainMessengerProxy", - _args: DeployUtils.encodeConstructor( - abi.encodeCall( - IResolvedDelegateProxy.__constructor__, - (IAddressManager(mustGetAddress("AddressManager")), "OVM_L1CrossDomainMessenger") - ) - ) - }) - ); + console.log("Deploying proxy for L1CrossDomainMessenger"); + AddressManager addressManager = AddressManager(mustGetAddress("AddressManager")); + ResolvedDelegateProxy proxy = new ResolvedDelegateProxy(addressManager, "OVM_L1CrossDomainMessenger"); + + save("L1CrossDomainMessengerProxy", address(proxy)); + console.log("L1CrossDomainMessengerProxy deployed at %s", address(proxy)); + addr_ = address(proxy); } @@ -618,32 +827,27 @@ contract Deploy is Deployer { broadcast returns (address addr_) { - IProxy proxy = IProxy( - DeployUtils.create2AndSave({ - _save: this, - _salt: keccak256(abi.encode(_implSalt(), _name)), - _name: "Proxy", - _nick: _name, - _args: DeployUtils.encodeConstructor(abi.encodeCall(IProxy.__constructor__, (_proxyOwner))) - }) - ); + console.log(string.concat("Deploying ERC1967 proxy for ", _name)); + Proxy proxy = new Proxy({ _admin: _proxyOwner }); + require(EIP1967Helper.getAdmin(address(proxy)) == _proxyOwner); + + save(_name, address(proxy)); + console.log(" at %s", address(proxy)); addr_ = address(proxy); } /// @notice Deploy the DataAvailabilityChallengeProxy function deployDataAvailabilityChallengeProxy() public broadcast returns (address addr_) { + console.log("Deploying proxy for DataAvailabilityChallenge"); address proxyAdmin = mustGetAddress("ProxyAdmin"); - IProxy proxy = IProxy( - DeployUtils.create2AndSave({ - _save: this, - _salt: _implSalt(), - _name: "Proxy", - _nick: "DataAvailabilityChallengeProxy", - _args: DeployUtils.encodeConstructor(abi.encodeCall(IProxy.__constructor__, (proxyAdmin))) - }) - ); + Proxy proxy = new Proxy({ _admin: proxyAdmin }); + require(EIP1967Helper.getAdmin(address(proxy)) == proxyAdmin); + + save("DataAvailabilityChallengeProxy", address(proxy)); + console.log("DataAvailabilityChallengeProxy deployed at %s", address(proxy)); + addr_ = address(proxy); } @@ -651,41 +855,90 @@ contract Deploy is Deployer { // Implementation Deployment Functions // //////////////////////////////////////////////////////////////// + /// @notice Deploy the SuperchainConfig contract + function deploySuperchainConfig() public broadcast { + ISuperchainConfig superchainConfig = ISuperchainConfig(_deploy("SuperchainConfig", hex"")); + + require(superchainConfig.guardian() == address(0)); + bytes32 initialized = vm.load(address(superchainConfig), bytes32(0)); + require(initialized != 0); + } + + /// @notice Deploy the CeloSuperchainConfig contract + function deployCeloSuperchainConfig() public broadcast { + ICeloSuperchainConfig celoSuperchainConfig = ICeloSuperchainConfig(_deploy("CeloSuperchainConfig", hex"")); + + require(celoSuperchainConfig.guardian() == address(0)); + require(celoSuperchainConfig.superchainConfig() == address(0)); + bytes32 initialized = vm.load(address(celoSuperchainConfig), bytes32(0)); + require(initialized != 0); + } + + /// @notice Deploy the L1CrossDomainMessenger + function deployL1CrossDomainMessenger() public broadcast returns (address addr_) { + IL1CrossDomainMessenger messenger = IL1CrossDomainMessenger(_deploy("L1CrossDomainMessenger", hex"")); + + // Override the `L1CrossDomainMessenger` contract to the deployed implementation. This is necessary + // to check the `L1CrossDomainMessenger` implementation alongside dependent contracts, which + // are always proxies. + Types.ContractSet memory contracts = _proxiesUnstrict(); + contracts.L1CrossDomainMessenger = address(messenger); + ChainAssertions.checkL1CrossDomainMessenger({ _contracts: contracts, _vm: vm, _isProxy: false }); + + addr_ = address(messenger); + } + /// @notice Deploy the OptimismPortal function deployOptimismPortal() public broadcast returns (address addr_) { - require(!cfg.useFaultProofs(), "Deploy: FaultProofs OptimismPortal is deployed by OPCM"); - require(!cfg.useInterop(), "Deploy: The legacy OptimismPortal does not support interop"); - - addr_ = DeployUtils.create2AndSave({ - _save: this, - _salt: _implSalt(), - _name: "OptimismPortal", - _args: DeployUtils.encodeConstructor(abi.encodeCall(IOptimismPortal.__constructor__, ())) - }); + if (cfg.useInterop()) { + console.log("Attempting to deploy OptimismPortal with interop, this config is a noop"); + } + + addr_ = _deploy("OptimismPortal", hex""); // Override the `OptimismPortal` contract to the deployed implementation. This is necessary // to check the `OptimismPortal` implementation alongside dependent contracts, which // are always proxies. - Types.ContractSet memory contracts = _proxies(); + Types.ContractSet memory contracts = _proxiesUnstrict(); contracts.OptimismPortal = addr_; ChainAssertions.checkOptimismPortal({ _contracts: contracts, _cfg: cfg, _isProxy: false }); } + /// @notice Deploy the OptimismPortal2 + function deployOptimismPortal2() public broadcast returns (address addr_) { + // Could also verify this inside DeployConfig but doing it here is a bit more reliable. + require( + uint32(cfg.respectedGameType()) == cfg.respectedGameType(), "Deploy: respectedGameType must fit into uint32" + ); + + if (cfg.useInterop()) { + addr_ = _deploy( + "OptimismPortalInterop", + abi.encode(cfg.proofMaturityDelaySeconds(), cfg.disputeGameFinalityDelaySeconds()) + ); + save("OptimismPortal2", addr_); + } else { + addr_ = _deploy( + "OptimismPortal2", abi.encode(cfg.proofMaturityDelaySeconds(), cfg.disputeGameFinalityDelaySeconds()) + ); + } + + // Override the `OptimismPortal2` contract to the deployed implementation. This is necessary + // to check the `OptimismPortal2` implementation alongside dependent contracts, which + // are always proxies. + Types.ContractSet memory contracts = _proxiesUnstrict(); + contracts.OptimismPortal2 = addr_; + ChainAssertions.checkOptimismPortal2({ _contracts: contracts, _cfg: cfg, _isProxy: false }); + } + /// @notice Deploy the L2OutputOracle function deployL2OutputOracle() public broadcast returns (address addr_) { - IL2OutputOracle oracle = IL2OutputOracle( - DeployUtils.create2AndSave({ - _save: this, - _salt: _implSalt(), - _name: "L2OutputOracle", - _args: DeployUtils.encodeConstructor(abi.encodeCall(IL2OutputOracle.__constructor__, ())) - }) - ); + IL2OutputOracle oracle = IL2OutputOracle(_deploy("L2OutputOracle", hex"")); // Override the `L2OutputOracle` contract to the deployed implementation. This is necessary // to check the `L2OutputOracle` implementation alongside dependent contracts, which // are always proxies. - Types.ContractSet memory contracts = _proxies(); + Types.ContractSet memory contracts = _proxiesUnstrict(); contracts.L2OutputOracle = address(oracle); ChainAssertions.checkL2OutputOracle({ _contracts: contracts, @@ -697,53 +950,138 @@ contract Deploy is Deployer { addr_ = address(oracle); } + /// @notice Deploy the OptimismMintableERC20Factory + function deployOptimismMintableERC20Factory() public broadcast returns (address addr_) { + IOptimismMintableERC20Factory factory = + IOptimismMintableERC20Factory(_deploy("OptimismMintableERC20Factory", hex"")); + + // Override the `OptimismMintableERC20Factory` contract to the deployed implementation. This is necessary + // to check the `OptimismMintableERC20Factory` implementation alongside dependent contracts, which + // are always proxies. + Types.ContractSet memory contracts = _proxiesUnstrict(); + contracts.OptimismMintableERC20Factory = address(factory); + ChainAssertions.checkOptimismMintableERC20Factory({ _contracts: contracts, _isProxy: false }); + + addr_ = address(factory); + } + + /// @notice Deploy the DisputeGameFactory + function deployDisputeGameFactory() public broadcast returns (address addr_) { + IDisputeGameFactory factory = IDisputeGameFactory(_deploy("DisputeGameFactory", hex"")); + + // Override the `DisputeGameFactory` contract to the deployed implementation. This is necessary to check the + // `DisputeGameFactory` implementation alongside dependent contracts, which are always proxies. + Types.ContractSet memory contracts = _proxiesUnstrict(); + contracts.DisputeGameFactory = address(factory); + // ChainAssertions.checkDisputeGameFactory({ _contracts: contracts, _expectedOwner: address(0) }); + + addr_ = address(factory); + } + + function deployDelayedWETH() public broadcast returns (address addr_) { + IDelayedWETH weth = IDelayedWETH(payable(_deploy("DelayedWETH", abi.encode(cfg.faultGameWithdrawalDelay())))); + + // Override the `DelayedWETH` contract to the deployed implementation. This is necessary + // to check the `DelayedWETH` implementation alongside dependent contracts, which are + // always proxies. + Types.ContractSet memory contracts = _proxiesUnstrict(); + contracts.DelayedWETH = address(weth); + ChainAssertions.checkDelayedWETH({ + _contracts: contracts, + _cfg: cfg, + _isProxy: false, + _expectedOwner: address(0) + }); + + addr_ = address(weth); + } + + /// @notice Deploy the ProtocolVersions + function deployProtocolVersions() public broadcast returns (address addr_) { + IProtocolVersions versions = IProtocolVersions(_deploy("ProtocolVersions", hex"")); + + // Override the `ProtocolVersions` contract to the deployed implementation. This is necessary + // to check the `ProtocolVersions` implementation alongside dependent contracts, which + // are always proxies. + Types.ContractSet memory contracts = _proxiesUnstrict(); + contracts.ProtocolVersions = address(versions); + ChainAssertions.checkProtocolVersions({ _contracts: contracts, _cfg: cfg, _isProxy: false }); + + addr_ = address(versions); + } + + /// @notice Deploy the PreimageOracle + function deployPreimageOracle() public broadcast returns (address addr_) { + console.log("Deploying PreimageOracle implementation"); + PreimageOracle preimageOracle = new PreimageOracle{ salt: _implSalt() }({ + _minProposalSize: cfg.preimageOracleMinProposalSize(), + _challengePeriod: cfg.preimageOracleChallengePeriod() + }); + save("PreimageOracle", address(preimageOracle)); + console.log("PreimageOracle deployed at %s", address(preimageOracle)); + + addr_ = address(preimageOracle); + } + /// @notice Deploy Mips VM. Deploys either MIPS or MIPS2 depending on the environment function deployMips() public broadcast returns (address addr_) { - addr_ = DeployUtils.create2AndSave({ - _save: this, - _salt: _implSalt(), - _name: Config.useMultithreadedCannon() ? "MIPS2" : "MIPS", - _args: DeployUtils.encodeConstructor( - abi.encodeCall(IMIPS2.__constructor__, (IPreimageOracle(mustGetAddress("PreimageOracle")))) - ) - }); + if (Config.useMultithreadedCannon()) { + addr_ = _deployMips2(); + } else { + addr_ = _deployMips(); + } save("Mips", address(addr_)); } + /// @notice Deploy MIPS + function _deployMips() internal returns (address addr_) { + console.log("Deploying Mips implementation"); + MIPS mips = new MIPS{ salt: _implSalt() }(IPreimageOracle(mustGetAddress("PreimageOracle"))); + console.log("MIPS deployed at %s", address(mips)); + addr_ = address(mips); + } + + /// @notice Deploy MIPS2 + function _deployMips2() internal returns (address addr_) { + console.log("Deploying Mips2 implementation"); + MIPS2 mips2 = new MIPS2{ salt: _implSalt() }(IPreimageOracle(mustGetAddress("PreimageOracle"))); + console.log("MIPS2 deployed at %s", address(mips2)); + addr_ = address(mips2); + } + /// @notice Deploy the AnchorStateRegistry function deployAnchorStateRegistry() public broadcast returns (address addr_) { - IAnchorStateRegistry anchorStateRegistry = IAnchorStateRegistry( - DeployUtils.create2AndSave({ - _save: this, - _salt: _implSalt(), - _name: "AnchorStateRegistry", - _args: DeployUtils.encodeConstructor( - abi.encodeCall( - IAnchorStateRegistry.__constructor__, - (IDisputeGameFactory(mustGetAddress("DisputeGameFactoryProxy"))) - ) - ) - }) - ); + IAnchorStateRegistry anchorStateRegistry = + IAnchorStateRegistry(_deploy("AnchorStateRegistry", abi.encode(mustGetAddress("DisputeGameFactoryProxy")))); addr_ = address(anchorStateRegistry); } + /// @notice Deploy the SystemConfig + function deploySystemConfig() public broadcast returns (address addr_) { + if (cfg.useInterop()) { + addr_ = _deploy("SystemConfigInterop", hex""); + save("SystemConfig", addr_); + } else { + addr_ = _deploy("SystemConfig", hex""); + } + + // Override the `SystemConfig` contract to the deployed implementation. This is necessary + // to check the `SystemConfig` implementation alongside dependent contracts, which + // are always proxies. + Types.ContractSet memory contracts = _proxiesUnstrict(); + contracts.SystemConfig = addr_; + ChainAssertions.checkSystemConfig({ _contracts: contracts, _cfg: cfg, _isProxy: false }); + } + /// @notice Deploy the L1StandardBridge function deployL1StandardBridge() public broadcast returns (address addr_) { - IL1StandardBridge bridge = IL1StandardBridge( - DeployUtils.create2AndSave({ - _save: this, - _salt: _implSalt(), - _name: "L1StandardBridge", - _args: DeployUtils.encodeConstructor(abi.encodeCall(IL1StandardBridge.__constructor__, ())) - }) - ); + IL1StandardBridge bridge = IL1StandardBridge(payable(_deploy("L1StandardBridge", hex""))); // Override the `L1StandardBridge` contract to the deployed implementation. This is necessary // to check the `L1StandardBridge` implementation alongside dependent contracts, which // are always proxies. - Types.ContractSet memory contracts = _proxies(); + Types.ContractSet memory contracts = _proxiesUnstrict(); contracts.L1StandardBridge = address(bridge); ChainAssertions.checkL1StandardBridge({ _contracts: contracts, _isProxy: false }); @@ -752,19 +1090,12 @@ contract Deploy is Deployer { /// @notice Deploy the L1ERC721Bridge function deployL1ERC721Bridge() public broadcast returns (address addr_) { - IL1ERC721Bridge bridge = IL1ERC721Bridge( - DeployUtils.create2AndSave({ - _save: this, - _salt: _implSalt(), - _name: "L1ERC721Bridge", - _args: DeployUtils.encodeConstructor(abi.encodeCall(IL1ERC721Bridge.__constructor__, ())) - }) - ); + IL1ERC721Bridge bridge = IL1ERC721Bridge(_deploy("L1ERC721Bridge", hex"")); // Override the `L1ERC721Bridge` contract to the deployed implementation. This is necessary // to check the `L1ERC721Bridge` implementation alongside dependent contracts, which // are always proxies. - Types.ContractSet memory contracts = _proxies(); + Types.ContractSet memory contracts = _proxiesUnstrict(); contracts.L1ERC721Bridge = address(bridge); ChainAssertions.checkL1ERC721Bridge({ _contracts: contracts, _isProxy: false }); @@ -774,8 +1105,8 @@ contract Deploy is Deployer { /// @notice Transfer ownership of the address manager to the ProxyAdmin function transferAddressManagerOwnership() public broadcast { - console.log("Transferring AddressManager ownership to IProxyAdmin"); - IAddressManager addressManager = IAddressManager(mustGetAddress("AddressManager")); + console.log("Transferring AddressManager ownership to ProxyAdmin"); + AddressManager addressManager = AddressManager(mustGetAddress("AddressManager")); address owner = addressManager.owner(); address proxyAdmin = mustGetAddress("ProxyAdmin"); if (owner != proxyAdmin) { @@ -788,14 +1119,8 @@ contract Deploy is Deployer { /// @notice Deploy the DataAvailabilityChallenge function deployDataAvailabilityChallenge() public broadcast returns (address addr_) { - IDataAvailabilityChallenge dac = IDataAvailabilityChallenge( - DeployUtils.create2AndSave({ - _save: this, - _salt: _implSalt(), - _name: "DataAvailabilityChallenge", - _args: DeployUtils.encodeConstructor(abi.encodeCall(IDataAvailabilityChallenge.__constructor__, ())) - }) - ); + IDataAvailabilityChallenge dac = + IDataAvailabilityChallenge(payable(_deploy("DataAvailabilityChallenge", hex""))); addr_ = address(dac); } @@ -803,6 +1128,177 @@ contract Deploy is Deployer { // Initialize Functions // //////////////////////////////////////////////////////////////// + /// @notice Initialize the SuperchainConfig + function initializeSuperchainConfig() public broadcast { + address payable superchainConfigProxy = mustGetAddress("SuperchainConfigProxy"); + address payable superchainConfig = mustGetAddress("SuperchainConfig"); + _upgradeAndCallViaSafe({ + _proxy: superchainConfigProxy, + _implementation: superchainConfig, + _innerCallData: abi.encodeCall(ISuperchainConfig.initialize, (cfg.superchainConfigGuardian(), false)) + }); + + ChainAssertions.checkSuperchainConfig({ _contracts: _proxiesUnstrict(), _cfg: cfg, _isPaused: false }); + } + + /// @notice Initialize the CeloSuperchainConfig + function initializeCeloSuperchainConfig() public broadcast { + address payable superchainConfigProxy = mustGetAddress("SuperchainConfigProxy"); + address payable celoSuperchainConfigProxy = mustGetAddress("CeloSuperchainConfigProxy"); + address payable celoSuperchainConfig = mustGetAddress("CeloSuperchainConfig"); + _upgradeAndCallViaSafe({ + _proxy: celoSuperchainConfigProxy, + _implementation: celoSuperchainConfig, + _innerCallData: abi.encodeCall( + ICeloSuperchainConfig.initialize, (cfg.superchainConfigGuardian(), false, superchainConfigProxy) + ) + }); + + ChainAssertions.checkCeloSuperchainConfig({ _contracts: _proxiesUnstrict(), _cfg: cfg, _isPaused: false }); + } + + /// @notice Initialize the DisputeGameFactory + function initializeDisputeGameFactory() public broadcast { + console.log("Upgrading and initializing DisputeGameFactory proxy"); + address disputeGameFactoryProxy = mustGetAddress("DisputeGameFactoryProxy"); + address disputeGameFactory = mustGetAddress("DisputeGameFactory"); + + _upgradeAndCallViaSafe({ + _proxy: payable(disputeGameFactoryProxy), + _implementation: disputeGameFactory, + _innerCallData: abi.encodeCall(IDisputeGameFactory.initialize, (msg.sender)) + }); + + string memory version = IDisputeGameFactory(disputeGameFactoryProxy).version(); + console.log("DisputeGameFactory version: %s", version); + + // ChainAssertions.checkDisputeGameFactory({ _contracts: _proxiesUnstrict(), _expectedOwner: msg.sender }); + } + + function initializeDelayedWETH() public broadcast { + console.log("Upgrading and initializing DelayedWETH proxy"); + address delayedWETHProxy = mustGetAddress("DelayedWETHProxy"); + address delayedWETH = mustGetAddress("DelayedWETH"); + address celoSuperchainConfigProxy = mustGetAddress("CeloSuperchainConfigProxy"); + + _upgradeAndCallViaSafe({ + _proxy: payable(delayedWETHProxy), + _implementation: delayedWETH, + _innerCallData: abi.encodeCall( + IDelayedWETH.initialize, (msg.sender, ICeloSuperchainConfig(celoSuperchainConfigProxy)) + ) + }); + + string memory version = IDelayedWETH(payable(delayedWETHProxy)).version(); + console.log("DelayedWETH version: %s", version); + + ChainAssertions.checkDelayedWETH({ + _contracts: _proxiesUnstrict(), + _cfg: cfg, + _isProxy: true, + _expectedOwner: msg.sender + }); + } + + function initializePermissionedDelayedWETH() public broadcast { + console.log("Upgrading and initializing permissioned DelayedWETH proxy"); + address delayedWETHProxy = mustGetAddress("PermissionedDelayedWETHProxy"); + address delayedWETH = mustGetAddress("DelayedWETH"); + address celoSuperchainConfigProxy = mustGetAddress("CeloSuperchainConfigProxy"); + + _upgradeAndCallViaSafe({ + _proxy: payable(delayedWETHProxy), + _implementation: delayedWETH, + _innerCallData: abi.encodeCall( + IDelayedWETH.initialize, (msg.sender, ICeloSuperchainConfig(celoSuperchainConfigProxy)) + ) + }); + + string memory version = IDelayedWETH(payable(delayedWETHProxy)).version(); + console.log("DelayedWETH version: %s", version); + + ChainAssertions.checkPermissionedDelayedWETH({ + _contracts: _proxiesUnstrict(), + _cfg: cfg, + _isProxy: true, + _expectedOwner: msg.sender + }); + } + + /// @notice Initialize the AnchorStateRegistry with a known root anchor + /// @param _deployTestingGames Whether to deploy the testing Fualt Game implementations) + function initializeAnchorStateRegistry(bool _deployTestingGames) public broadcast { + console.log("Upgrading and initializing AnchorStateRegistry proxy"); + address anchorStateRegistryProxy = mustGetAddress("AnchorStateRegistryProxy"); + address anchorStateRegistry = mustGetAddress("AnchorStateRegistry"); + ICeloSuperchainConfig superchainConfig = ICeloSuperchainConfig(mustGetAddress("CeloSuperchainConfigProxy")); + + IAnchorStateRegistry.StartingAnchorRoot[] memory roots; + if (_deployTestingGames) { + roots = new IAnchorStateRegistry.StartingAnchorRoot[](5); + roots[0] = IAnchorStateRegistry.StartingAnchorRoot({ + gameType: GameTypes.CANNON, + outputRoot: OutputRoot({ + root: Hash.wrap(cfg.faultGameGenesisOutputRoot()), + l2BlockNumber: cfg.faultGameGenesisBlock() + }) + }); + roots[1] = IAnchorStateRegistry.StartingAnchorRoot({ + gameType: GameTypes.PERMISSIONED_CANNON, + outputRoot: OutputRoot({ + root: Hash.wrap(cfg.faultGameGenesisOutputRoot()), + l2BlockNumber: cfg.faultGameGenesisBlock() + }) + }); + roots[2] = IAnchorStateRegistry.StartingAnchorRoot({ + gameType: GameTypes.ALPHABET, + outputRoot: OutputRoot({ + root: Hash.wrap(cfg.faultGameGenesisOutputRoot()), + l2BlockNumber: cfg.faultGameGenesisBlock() + }) + }); + roots[3] = IAnchorStateRegistry.StartingAnchorRoot({ + gameType: GameTypes.ASTERISC, + outputRoot: OutputRoot({ + root: Hash.wrap(cfg.faultGameGenesisOutputRoot()), + l2BlockNumber: cfg.faultGameGenesisBlock() + }) + }); + roots[4] = IAnchorStateRegistry.StartingAnchorRoot({ + gameType: GameTypes.FAST, + outputRoot: OutputRoot({ + root: Hash.wrap(cfg.faultGameGenesisOutputRoot()), + l2BlockNumber: cfg.faultGameGenesisBlock() + }) + }); + } else { + roots = new IAnchorStateRegistry.StartingAnchorRoot[](2); + roots[0] = IAnchorStateRegistry.StartingAnchorRoot({ + gameType: GameTypes.CANNON, + outputRoot: OutputRoot({ + root: Hash.wrap(cfg.faultGameGenesisOutputRoot()), + l2BlockNumber: cfg.faultGameGenesisBlock() + }) + }); + roots[1] = IAnchorStateRegistry.StartingAnchorRoot({ + gameType: GameTypes.PERMISSIONED_CANNON, + outputRoot: OutputRoot({ + root: Hash.wrap(cfg.faultGameGenesisOutputRoot()), + l2BlockNumber: cfg.faultGameGenesisBlock() + }) + }); + } + + _upgradeAndCallViaSafe({ + _proxy: payable(anchorStateRegistryProxy), + _implementation: anchorStateRegistry, + _innerCallData: abi.encodeCall(IAnchorStateRegistry.initialize, (roots, superchainConfig)) + }); + + string memory version = IAnchorStateRegistry(payable(anchorStateRegistryProxy)).version(); + console.log("AnchorStateRegistry version: %s", version); + } + /// @notice Initialize the SystemConfig function initializeSystemConfig() public broadcast { console.log("Upgrading and initializing SystemConfig proxy"); @@ -816,11 +1312,10 @@ contract Deploy is Deployer { customGasTokenAddress = cfg.customGasTokenAddress(); } - IProxyAdmin proxyAdmin = IProxyAdmin(payable(mustGetAddress("ProxyAdmin"))); - proxyAdmin.upgradeAndCall({ + _upgradeAndCallViaSafe({ _proxy: payable(systemConfigProxy), _implementation: systemConfig, - _data: abi.encodeCall( + _innerCallData: abi.encodeCall( ISystemConfig.initialize, ( cfg.finalSystemOwner(), @@ -851,17 +1346,155 @@ contract Deploy is Deployer { ChainAssertions.checkSystemConfig({ _contracts: _proxies(), _cfg: cfg, _isProxy: true }); } + /// @notice Initialize the L1StandardBridge + function initializeL1StandardBridge() public broadcast { + console.log("Upgrading and initializing L1StandardBridge proxy"); + ProxyAdmin proxyAdmin = ProxyAdmin(mustGetAddress("ProxyAdmin")); + address l1StandardBridgeProxy = mustGetAddress("L1StandardBridgeProxy"); + address l1StandardBridge = mustGetAddress("L1StandardBridge"); + address l1CrossDomainMessengerProxy = mustGetAddress("L1CrossDomainMessengerProxy"); + address superchainConfigProxy = mustGetAddress("CeloSuperchainConfigProxy"); + address systemConfigProxy = mustGetAddress("SystemConfigProxy"); + + uint256 proxyType = uint256(proxyAdmin.proxyType(l1StandardBridgeProxy)); + Safe safe = Safe(mustGetAddress("SystemOwnerSafe")); + if (proxyType != uint256(ProxyAdmin.ProxyType.CHUGSPLASH)) { + _callViaSafe({ + _safe: safe, + _target: address(proxyAdmin), + _data: abi.encodeCall(ProxyAdmin.setProxyType, (l1StandardBridgeProxy, ProxyAdmin.ProxyType.CHUGSPLASH)) + }); + } + require(uint256(proxyAdmin.proxyType(l1StandardBridgeProxy)) == uint256(ProxyAdmin.ProxyType.CHUGSPLASH)); + + _upgradeAndCallViaSafe({ + _proxy: payable(l1StandardBridgeProxy), + _implementation: l1StandardBridge, + _innerCallData: abi.encodeCall( + IL1StandardBridge.initialize, + ( + ICrossDomainMessenger(l1CrossDomainMessengerProxy), + ICeloSuperchainConfig(superchainConfigProxy), + ISystemConfig(systemConfigProxy) + ) + ) + }); + + string memory version = IL1StandardBridge(payable(l1StandardBridgeProxy)).version(); + console.log("L1StandardBridge version: %s", version); + + ChainAssertions.checkL1StandardBridge({ _contracts: _proxies(), _isProxy: true }); + } + + /// @notice Initialize the L1ERC721Bridge + function initializeL1ERC721Bridge() public broadcast { + console.log("Upgrading and initializing L1ERC721Bridge proxy"); + address l1ERC721BridgeProxy = mustGetAddress("L1ERC721BridgeProxy"); + address l1ERC721Bridge = mustGetAddress("L1ERC721Bridge"); + address l1CrossDomainMessengerProxy = mustGetAddress("L1CrossDomainMessengerProxy"); + address superchainConfigProxy = mustGetAddress("CeloSuperchainConfigProxy"); + + _upgradeAndCallViaSafe({ + _proxy: payable(l1ERC721BridgeProxy), + _implementation: l1ERC721Bridge, + _innerCallData: abi.encodeCall( + IL1ERC721Bridge.initialize, + (ICrossDomainMessenger(payable(l1CrossDomainMessengerProxy)), ICeloSuperchainConfig(superchainConfigProxy)) + ) + }); + + IL1ERC721Bridge bridge = IL1ERC721Bridge(l1ERC721BridgeProxy); + string memory version = bridge.version(); + console.log("L1ERC721Bridge version: %s", version); + + ChainAssertions.checkL1ERC721Bridge({ _contracts: _proxies(), _isProxy: true }); + } + + /// @notice Initialize the OptimismMintableERC20Factory + function initializeOptimismMintableERC20Factory() public broadcast { + console.log("Upgrading and initializing OptimismMintableERC20Factory proxy"); + address optimismMintableERC20FactoryProxy = mustGetAddress("OptimismMintableERC20FactoryProxy"); + address optimismMintableERC20Factory = mustGetAddress("OptimismMintableERC20Factory"); + address l1StandardBridgeProxy = mustGetAddress("L1StandardBridgeProxy"); + + _upgradeAndCallViaSafe({ + _proxy: payable(optimismMintableERC20FactoryProxy), + _implementation: optimismMintableERC20Factory, + _innerCallData: abi.encodeCall(IOptimismMintableERC20Factory.initialize, (l1StandardBridgeProxy)) + }); + + IOptimismMintableERC20Factory factory = IOptimismMintableERC20Factory(optimismMintableERC20FactoryProxy); + string memory version = factory.version(); + console.log("OptimismMintableERC20Factory version: %s", version); + + ChainAssertions.checkOptimismMintableERC20Factory({ _contracts: _proxies(), _isProxy: true }); + } + + /// @notice initializeL1CrossDomainMessenger + function initializeL1CrossDomainMessenger() public broadcast { + console.log("Upgrading and initializing L1CrossDomainMessenger proxy"); + ProxyAdmin proxyAdmin = ProxyAdmin(mustGetAddress("ProxyAdmin")); + address l1CrossDomainMessengerProxy = mustGetAddress("L1CrossDomainMessengerProxy"); + address l1CrossDomainMessenger = mustGetAddress("L1CrossDomainMessenger"); + address superchainConfigProxy = mustGetAddress("CeloSuperchainConfigProxy"); + address optimismPortalProxy = mustGetAddress("OptimismPortalProxy"); + address systemConfigProxy = mustGetAddress("SystemConfigProxy"); + + uint256 proxyType = uint256(proxyAdmin.proxyType(l1CrossDomainMessengerProxy)); + Safe safe = Safe(mustGetAddress("SystemOwnerSafe")); + if (proxyType != uint256(ProxyAdmin.ProxyType.RESOLVED)) { + _callViaSafe({ + _safe: safe, + _target: address(proxyAdmin), + _data: abi.encodeCall(ProxyAdmin.setProxyType, (l1CrossDomainMessengerProxy, ProxyAdmin.ProxyType.RESOLVED)) + }); + } + require(uint256(proxyAdmin.proxyType(l1CrossDomainMessengerProxy)) == uint256(ProxyAdmin.ProxyType.RESOLVED)); + + string memory contractName = "OVM_L1CrossDomainMessenger"; + string memory implName = proxyAdmin.implementationName(l1CrossDomainMessenger); + if (keccak256(bytes(contractName)) != keccak256(bytes(implName))) { + _callViaSafe({ + _safe: safe, + _target: address(proxyAdmin), + _data: abi.encodeCall(ProxyAdmin.setImplementationName, (l1CrossDomainMessengerProxy, contractName)) + }); + } + require( + keccak256(bytes(proxyAdmin.implementationName(l1CrossDomainMessengerProxy))) + == keccak256(bytes(contractName)) + ); + + _upgradeAndCallViaSafe({ + _proxy: payable(l1CrossDomainMessengerProxy), + _implementation: l1CrossDomainMessenger, + _innerCallData: abi.encodeCall( + IL1CrossDomainMessenger.initialize, + ( + ICeloSuperchainConfig(superchainConfigProxy), + IOptimismPortal(payable(optimismPortalProxy)), + ISystemConfig(systemConfigProxy) + ) + ) + }); + + IL1CrossDomainMessenger messenger = IL1CrossDomainMessenger(l1CrossDomainMessengerProxy); + string memory version = messenger.version(); + console.log("L1CrossDomainMessenger version: %s", version); + + ChainAssertions.checkL1CrossDomainMessenger({ _contracts: _proxies(), _vm: vm, _isProxy: true }); + } + /// @notice Initialize the L2OutputOracle function initializeL2OutputOracle() public broadcast { console.log("Upgrading and initializing L2OutputOracle proxy"); address l2OutputOracleProxy = mustGetAddress("L2OutputOracleProxy"); address l2OutputOracle = mustGetAddress("L2OutputOracle"); - IProxyAdmin proxyAdmin = IProxyAdmin(payable(mustGetAddress("ProxyAdmin"))); - proxyAdmin.upgradeAndCall({ + _upgradeAndCallViaSafe({ _proxy: payable(l2OutputOracleProxy), _implementation: l2OutputOracle, - _data: abi.encodeCall( + _innerCallData: abi.encodeCall( IL2OutputOracle.initialize, ( cfg.l2OutputOracleSubmissionInterval(), @@ -890,23 +1523,21 @@ contract Deploy is Deployer { /// @notice Initialize the OptimismPortal function initializeOptimismPortal() public broadcast { console.log("Upgrading and initializing OptimismPortal proxy"); - require(!cfg.useFaultProofs(), "Deploy: FaultProofs OptimismPortal is initialized by OPCM"); address optimismPortalProxy = mustGetAddress("OptimismPortalProxy"); - address systemConfigProxy = mustGetAddress("SystemConfigProxy"); - address superchainConfigProxy = mustGetAddress("SuperchainConfigProxy"); address optimismPortal = mustGetAddress("OptimismPortal"); address l2OutputOracleProxy = mustGetAddress("L2OutputOracleProxy"); + address systemConfigProxy = mustGetAddress("SystemConfigProxy"); + address superchainConfigProxy = mustGetAddress("CeloSuperchainConfigProxy"); - IProxyAdmin proxyAdmin = IProxyAdmin(payable(mustGetAddress("ProxyAdmin"))); - proxyAdmin.upgradeAndCall({ + _upgradeAndCallViaSafe({ _proxy: payable(optimismPortalProxy), _implementation: optimismPortal, - _data: abi.encodeCall( + _innerCallData: abi.encodeCall( IOptimismPortal.initialize, ( IL2OutputOracle(l2OutputOracleProxy), ISystemConfig(systemConfigProxy), - ISuperchainConfig(superchainConfigProxy) + ICeloSuperchainConfig(superchainConfigProxy) ) ) }); @@ -918,22 +1549,77 @@ contract Deploy is Deployer { ChainAssertions.checkOptimismPortal({ _contracts: _proxies(), _cfg: cfg, _isProxy: true }); } + /// @notice Initialize the OptimismPortal2 + function initializeOptimismPortal2() public broadcast { + console.log("Upgrading and initializing OptimismPortal2 proxy"); + address optimismPortalProxy = mustGetAddress("OptimismPortalProxy"); + address optimismPortal2 = mustGetAddress("OptimismPortal2"); + address disputeGameFactoryProxy = mustGetAddress("DisputeGameFactoryProxy"); + address systemConfigProxy = mustGetAddress("SystemConfigProxy"); + address superchainConfigProxy = mustGetAddress("CeloSuperchainConfigProxy"); + + _upgradeAndCallViaSafe({ + _proxy: payable(optimismPortalProxy), + _implementation: optimismPortal2, + _innerCallData: abi.encodeCall( + IOptimismPortal2.initialize, + ( + IDisputeGameFactory(disputeGameFactoryProxy), + ISystemConfig(systemConfigProxy), + ICeloSuperchainConfig(superchainConfigProxy), + GameType.wrap(uint32(cfg.respectedGameType())) + ) + ) + }); + + IOptimismPortal2 portal = IOptimismPortal2(payable(optimismPortalProxy)); + string memory version = portal.version(); + console.log("OptimismPortal2 version: %s", version); + + ChainAssertions.checkOptimismPortal2({ _contracts: _proxies(), _cfg: cfg, _isProxy: true }); + } + + function initializeProtocolVersions() public broadcast { + console.log("Upgrading and initializing ProtocolVersions proxy"); + address protocolVersionsProxy = mustGetAddress("ProtocolVersionsProxy"); + address protocolVersions = mustGetAddress("ProtocolVersions"); + + address finalSystemOwner = cfg.finalSystemOwner(); + uint256 requiredProtocolVersion = cfg.requiredProtocolVersion(); + uint256 recommendedProtocolVersion = cfg.recommendedProtocolVersion(); + + _upgradeAndCallViaSafe({ + _proxy: payable(protocolVersionsProxy), + _implementation: protocolVersions, + _innerCallData: abi.encodeCall( + IProtocolVersions.initialize, + ( + finalSystemOwner, + ProtocolVersion.wrap(requiredProtocolVersion), + ProtocolVersion.wrap(recommendedProtocolVersion) + ) + ) + }); + + IProtocolVersions versions = IProtocolVersions(protocolVersionsProxy); + string memory version = versions.version(); + console.log("ProtocolVersions version: %s", version); + + ChainAssertions.checkProtocolVersions({ _contracts: _proxiesUnstrict(), _cfg: cfg, _isProxy: true }); + } + /// @notice Transfer ownership of the DisputeGameFactory contract to the final system owner function transferDisputeGameFactoryOwnership() public broadcast { console.log("Transferring DisputeGameFactory ownership to Safe"); IDisputeGameFactory disputeGameFactory = IDisputeGameFactory(mustGetAddress("DisputeGameFactoryProxy")); address owner = disputeGameFactory.owner(); - address finalSystemOwner = cfg.finalSystemOwner(); - if (owner != finalSystemOwner) { - disputeGameFactory.transferOwnership(finalSystemOwner); - console.log("DisputeGameFactory ownership transferred to final system owner at: %s", finalSystemOwner); + address safe = mustGetAddress("SystemOwnerSafe"); + if (owner != safe) { + disputeGameFactory.transferOwnership(safe); + console.log("DisputeGameFactory ownership transferred to Safe at: %s", safe); } - ChainAssertions.checkDisputeGameFactory({ - _contracts: _proxies(), - _expectedOwner: finalSystemOwner, - _isProxy: true - }); + // ChainAssertions.checkDisputeGameFactory({ _contracts: _proxies(), _expectedOwner: safe }); } /// @notice Transfer ownership of the DelayedWETH contract to the final system owner @@ -942,17 +1628,12 @@ contract Deploy is Deployer { IDelayedWETH weth = IDelayedWETH(mustGetAddress("DelayedWETHProxy")); address owner = weth.owner(); - address finalSystemOwner = cfg.finalSystemOwner(); - if (owner != finalSystemOwner) { - weth.transferOwnership(finalSystemOwner); - console.log("DelayedWETH ownership transferred to final system owner at: %s", finalSystemOwner); + address safe = mustGetAddress("SystemOwnerSafe"); + if (owner != safe) { + weth.transferOwnership(safe); + console.log("DelayedWETH ownership transferred to Safe at: %s", safe); } - ChainAssertions.checkDelayedWETH({ - _contracts: _proxies(), - _cfg: cfg, - _isProxy: true, - _expectedOwner: finalSystemOwner - }); + ChainAssertions.checkDelayedWETH({ _contracts: _proxies(), _cfg: cfg, _isProxy: true, _expectedOwner: safe }); } /// @notice Transfer ownership of the permissioned DelayedWETH contract to the final system owner @@ -961,16 +1642,16 @@ contract Deploy is Deployer { IDelayedWETH weth = IDelayedWETH(mustGetAddress("PermissionedDelayedWETHProxy")); address owner = weth.owner(); - address finalSystemOwner = cfg.finalSystemOwner(); - if (owner != finalSystemOwner) { - weth.transferOwnership(finalSystemOwner); - console.log("DelayedWETH ownership transferred to final system owner at: %s", finalSystemOwner); + address safe = mustGetAddress("SystemOwnerSafe"); + if (owner != safe) { + weth.transferOwnership(safe); + console.log("DelayedWETH ownership transferred to Safe at: %s", safe); } ChainAssertions.checkPermissionedDelayedWETH({ _contracts: _proxies(), _cfg: cfg, _isProxy: true, - _expectedOwner: finalSystemOwner + _expectedOwner: safe }); } @@ -1000,9 +1681,7 @@ contract Deploy is Deployer { commands[1] = "-c"; commands[2] = string.concat("[[ -f ", filePath, " ]] && echo \"present\""); if (Process.run(commands).length == 0) { - revert( - "Deploy: cannon prestate dump not found, generate it with `make cannon-prestate` in the monorepo root" - ); + revert("Cannon prestate dump not found, generate it with `make cannon-prestate` in the monorepo root."); } commands[2] = string.concat("cat ", filePath, " | jq -r .pre"); mipsAbsolutePrestate_ = Claim.wrap(abi.decode(Process.run(commands), (bytes32))); @@ -1023,7 +1702,7 @@ contract Deploy is Deployer { commands[2] = string.concat("[[ -f ", filePath, " ]] && echo \"present\""); if (Process.run(commands).length == 0) { revert( - "Deploy: MT-Cannon prestate dump not found, generate it with `make cannon-prestate-mt` in the monorepo root" + "MT-Cannon prestate dump not found, generate it with `make cannon-prestate-mt` in the monorepo root." ); } commands[2] = string.concat("cat ", filePath, " | jq -r .pre"); @@ -1062,7 +1741,7 @@ contract Deploy is Deployer { IDisputeGameFactory factory = IDisputeGameFactory(mustGetAddress("DisputeGameFactoryProxy")); IDelayedWETH weth = IDelayedWETH(mustGetAddress("PermissionedDelayedWETHProxy")); - // Deploys and sets the Permissioned FaultDisputeGame implementation in the factory. + // Set the Cannon FaultDisputeGame implementation in the factory. _setFaultGameImplementation({ _factory: factory, _allowUpgrade: _allowUpgrade, @@ -1108,17 +1787,7 @@ contract Deploy is Deployer { IDelayedWETH weth = IDelayedWETH(mustGetAddress("DelayedWETHProxy")); Claim outputAbsolutePrestate = Claim.wrap(bytes32(cfg.faultGameAbsolutePrestate())); - IPreimageOracle fastOracle = IPreimageOracle( - DeployUtils.create2AndSave({ - _save: this, - _salt: _implSalt(), - _name: "PreimageOracle", - _nick: "FastPreimageOracle", - _args: DeployUtils.encodeConstructor( - abi.encodeCall(IPreimageOracle.__constructor__, (cfg.preimageOracleMinProposalSize(), 0)) - ) - }) - ); + PreimageOracle fastOracle = new PreimageOracle(cfg.preimageOracleMinProposalSize(), 0); _setFaultGameImplementation({ _factory: factory, _allowUpgrade: _allowUpgrade, @@ -1127,7 +1796,7 @@ contract Deploy is Deployer { weth: weth, gameType: GameTypes.FAST, absolutePrestate: outputAbsolutePrestate, - faultVm: IBigStepper(new AlphabetVM(outputAbsolutePrestate, fastOracle)), + faultVm: IBigStepper(new AlphabetVM(outputAbsolutePrestate, IPreimageOracle(address(fastOracle)))), // The max depth for the alphabet trace is always 3. Add 1 because split depth is fully inclusive. maxGameDepth: cfg.faultGameSplitDepth() + 3 + 1, maxClockDuration: Duration.wrap(0) // Resolvable immediately @@ -1152,44 +1821,58 @@ contract Deploy is Deployer { } uint32 rawGameType = GameType.unwrap(_params.gameType); - - // Redefine _param variable to avoid stack too deep error during compilation - FaultDisputeGameParams memory _params_ = _params; - require( - rawGameType != GameTypes.PERMISSIONED_CANNON.raw(), "Deploy: Permissioned Game should be deployed by OPCM" - ); - _factory.setImplementation( - _params_.gameType, - IDisputeGame( - DeployUtils.create2AndSave({ - _save: this, - _salt: _implSalt(), - _name: "FaultDisputeGame", - _nick: string.concat("FaultDisputeGame_", vm.toString(rawGameType)), - _args: DeployUtils.encodeConstructor( - abi.encodeCall( - IFaultDisputeGame.__constructor__, - ( - _params_.gameType, - _params_.absolutePrestate, - _params_.maxGameDepth, - cfg.faultGameSplitDepth(), - Duration.wrap(uint64(cfg.faultGameClockExtension())), - _params_.maxClockDuration, - _params_.faultVm, - _params_.weth, - IAnchorStateRegistry(mustGetAddress("AnchorStateRegistryProxy")), - cfg.l2ChainID() - ) + if (rawGameType != GameTypes.PERMISSIONED_CANNON.raw()) { + _factory.setImplementation( + _params.gameType, + IDisputeGame( + _deploy( + "FaultDisputeGame", + string.concat("FaultDisputeGame_", vm.toString(rawGameType)), + abi.encode( + _params.gameType, + _params.absolutePrestate, + _params.maxGameDepth, + cfg.faultGameSplitDepth(), + cfg.faultGameClockExtension(), + _params.maxClockDuration, + _params.faultVm, + _params.weth, + IAnchorStateRegistry(mustGetAddress("AnchorStateRegistryProxy")), + cfg.l2ChainID() ) ) - }) - ) - ); + ) + ); + } else { + _factory.setImplementation( + _params.gameType, + IDisputeGame( + _deploy( + "PermissionedDisputeGame", + abi.encode( + _params.gameType, + _params.absolutePrestate, + _params.maxGameDepth, + cfg.faultGameSplitDepth(), + cfg.faultGameClockExtension(), + _params.maxClockDuration, + _params.faultVm, + _params.weth, + _params.anchorStateRegistry, + cfg.l2ChainID(), + cfg.l2OutputOracleProposer(), + cfg.l2OutputOracleChallenger() + ) + ) + ) + ); + } string memory gameTypeString; if (rawGameType == GameTypes.CANNON.raw()) { gameTypeString = "Cannon"; + } else if (rawGameType == GameTypes.PERMISSIONED_CANNON.raw()) { + gameTypeString = "PermissionedCannon"; } else if (rawGameType == GameTypes.ALPHABET.raw()) { gameTypeString = "Alphabet"; } else { @@ -1215,11 +1898,10 @@ contract Deploy is Deployer { uint256 daBondSize = cfg.daBondSize(); uint256 daResolverRefundPercentage = cfg.daResolverRefundPercentage(); - IProxyAdmin proxyAdmin = IProxyAdmin(payable(mustGetAddress("ProxyAdmin"))); - proxyAdmin.upgradeAndCall({ + _upgradeAndCallViaSafe({ _proxy: payable(dataAvailabilityChallengeProxy), _implementation: dataAvailabilityChallenge, - _data: abi.encodeCall( + _innerCallData: abi.encodeCall( IDataAvailabilityChallenge.initialize, (finalSystemOwner, daChallengeWindow, daResolveWindow, daBondSize, daResolverRefundPercentage) ) @@ -1236,59 +1918,84 @@ contract Deploy is Deployer { require(dac.resolverRefundPercentage() == daResolverRefundPercentage); } - /// @notice Get the DeployInput struct to use for testing - function getDeployInput() public view returns (OPContractsManager.DeployInput memory) { - OutputRoot memory testOutputRoot = OutputRoot({ - root: Hash.wrap(cfg.faultGameGenesisOutputRoot()), - l2BlockNumber: cfg.faultGameGenesisBlock() - }); - IAnchorStateRegistry.StartingAnchorRoot[] memory startingAnchorRoots = - new IAnchorStateRegistry.StartingAnchorRoot[](5); - startingAnchorRoots[0] = - IAnchorStateRegistry.StartingAnchorRoot({ gameType: GameTypes.CANNON, outputRoot: testOutputRoot }); - startingAnchorRoots[1] = IAnchorStateRegistry.StartingAnchorRoot({ - gameType: GameTypes.PERMISSIONED_CANNON, - outputRoot: testOutputRoot - }); - startingAnchorRoots[2] = - IAnchorStateRegistry.StartingAnchorRoot({ gameType: GameTypes.ASTERISC, outputRoot: testOutputRoot }); - startingAnchorRoots[3] = - IAnchorStateRegistry.StartingAnchorRoot({ gameType: GameTypes.FAST, outputRoot: testOutputRoot }); - startingAnchorRoots[4] = - IAnchorStateRegistry.StartingAnchorRoot({ gameType: GameTypes.ALPHABET, outputRoot: testOutputRoot }); - string memory saltMixer = "salt mixer"; - return OPContractsManager.DeployInput({ - roles: OPContractsManager.Roles({ - opChainProxyAdminOwner: msg.sender, - systemConfigOwner: cfg.finalSystemOwner(), - batcher: cfg.batchSenderAddress(), - unsafeBlockSigner: cfg.p2pSequencerAddress(), - proposer: cfg.l2OutputOracleProposer(), - challenger: cfg.l2OutputOracleChallenger() - }), - basefeeScalar: cfg.basefeeScalar(), - blobBasefeeScalar: cfg.blobbasefeeScalar(), - l2ChainId: cfg.l2ChainID(), - startingAnchorRoots: abi.encode(startingAnchorRoots), - saltMixer: saltMixer, - gasLimit: uint64(cfg.l2GenesisBlockGasLimit()), - disputeGameType: GameTypes.PERMISSIONED_CANNON, - disputeAbsolutePrestate: Claim.wrap(bytes32(cfg.faultGameAbsolutePrestate())), - disputeMaxGameDepth: cfg.faultGameMaxDepth(), - disputeSplitDepth: cfg.faultGameSplitDepth(), - disputeClockExtension: Duration.wrap(uint64(cfg.faultGameClockExtension())), - disputeMaxClockDuration: Duration.wrap(uint64(cfg.faultGameMaxClockDuration())) - }); + /// @notice Deploys a contract via CREATE2. + /// @param _name The name of the contract. + /// @param _constructorParams The constructor parameters. + function _deploy(string memory _name, bytes memory _constructorParams) internal returns (address addr_) { + return _deploy(_name, _name, _constructorParams); } - function resetInitializedProxy(string memory _contractName) internal { - console.log("resetting initialized value on %s Proxy", _contractName); - address proxy = mustGetAddress(string.concat(_contractName, "Proxy")); - StorageSlot memory slot = ForgeArtifacts.getInitializedSlot(_contractName); - bytes32 slotVal = vm.load(proxy, bytes32(vm.parseUint(slot.slot))); - uint256 value = uint256(slotVal); - value = value & ~(0xFF << (slot.offset * 8)); - slotVal = bytes32(value); - vm.store(proxy, bytes32(vm.parseUint(slot.slot)), slotVal); + /// @notice Deploys a contract via CREATE2. + /// @param _name The name of the contract. + /// @param _nickname The nickname of the contract. + /// @param _constructorParams The constructor parameters. + function _deploy( + string memory _name, + string memory _nickname, + bytes memory _constructorParams + ) + internal + returns (address addr_) + { + console.log("Deploying %s", _nickname); + bytes32 salt = _implSalt(); + bytes memory initCode = abi.encodePacked(vm.getCode(_name), _constructorParams); + address preComputedAddress = vm.computeCreate2Address(salt, keccak256(initCode)); + require(preComputedAddress.code.length == 0, "Deploy: contract already deployed"); + assembly { + addr_ := create2(0, add(initCode, 0x20), mload(initCode), salt) + } + require(addr_ != address(0), "deployment failed"); + save(_nickname, addr_); + console.log("%s deployed at %s", _nickname, addr_); } + + function setupCustomGasToken() internal { + if (cfg.useCustomGasToken() && cfg.customGasTokenAddress() == address(0)) { + deployERC1967Proxy("CustomGasTokenProxy"); + + console.log("Setting up Custom gas token"); + deployCustomGasToken(); + initializeCustomGasToken(); + + address proxyAddress = mustGetAddress("CustomGasTokenProxy"); + cfg.setUseCustomGasToken(proxyAddress); + } + } + + function deployCustomGasToken() public broadcast returns (address addr_) { + console.log("Deploying CustomGasToken implementation"); + + CeloTokenL1 customGasToken = new CeloTokenL1{ salt: _implSalt() }(); + + save("CustomGasToken", address(customGasToken)); + console.log("CustomGasToken deployed at %s", address(customGasToken)); + addr_ = address(customGasToken); + } + + /// @notice Initialize the CustomGasToken + function initializeCustomGasToken() public broadcast { + console.log("Upgrading and initializing CustomGasToken proxy"); + address customGasTokenProxyAddress = mustGetAddress("CustomGasTokenProxy"); + address customGasTokenAddress = mustGetAddress("CustomGasToken"); + address portalProxyAddress = mustGetAddress("OptimismPortalProxy"); + + _upgradeAndCallViaSafe({ + _proxy: payable(customGasTokenProxyAddress), + _implementation: customGasTokenAddress, + _innerCallData: abi.encodeCall(CeloTokenL1.initialize, (portalProxyAddress)) + }); + + ChainAssertions.checkCeloTokenL1({ _contracts: _proxies(), _isProxy: false }); + } + + // function deployMulticall3() internal onlyDevnet returns (address addr_) { + // // Necessary to be deployed on the L1 for viems withdraw logic + // // Only necessary on local devnet, since on the common public testnets + // // the multicall3 is already deployed. + // console.log("Deploying up Multicall3 contact"); + // Multicall3 mc3 = new Multicall3(); + // addr_ = address(mc3); + // save("Multicall3", addr_); + // } } diff --git a/packages/contracts-bedrock/scripts/deploy/DeployConfig.s.sol b/packages/contracts-bedrock/scripts/deploy/DeployConfig.s.sol index 69341dd3e8742..120bc805c086d 100644 --- a/packages/contracts-bedrock/scripts/deploy/DeployConfig.s.sol +++ b/packages/contracts-bedrock/scripts/deploy/DeployConfig.s.sol @@ -1,9 +1,11 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.15; +import { AddressAliasHelper } from "src/vendor/AddressAliasHelper.sol"; import { Script } from "forge-std/Script.sol"; import { console2 as console } from "forge-std/console2.sol"; import { stdJson } from "forge-std/StdJson.sol"; +import { LibString } from "@solady/utils/LibString.sol"; import { Executables } from "scripts/libraries/Executables.sol"; import { Process } from "scripts/libraries/Process.sol"; import { Config, Fork, ForkUtils } from "scripts/libraries/Config.sol"; @@ -92,6 +94,11 @@ contract DeployConfig is Script { bool public useInterop; + bool public deployCeloContracts; + address public externalSuperchainConfig; + + bool public proxyAdminOwnerIsMultisig; + function read(string memory _path) public { console.log("DeployConfig: reading file %s", _path); try vm.readFile(_path) returns (string memory data_) { @@ -177,6 +184,34 @@ contract DeployConfig is Script { customGasTokenAddress = _readOr(_json, "$.customGasTokenAddress", address(0)); useInterop = _readOr(_json, "$.useInterop", false); + + deployCeloContracts = _readOr(_json, "$.deployCeloContracts", false); + externalSuperchainConfig = _readOr(_json, "$.externalSuperchainConfig", address(0)); + + proxyAdminOwnerIsMultisig = stdJson.readBool(_json, "$.proxyAdminOwnerIsMultisig"); + + verifyProxyAdminOwners(); + } + + /// @notice Performs a check on the ProxyAdmin owner addresses accross L1 and L2, based on the + /// `proxyAdminOwnerIsMultisig` parameter of the config. + /// Specifically: + /// - `finalSystemOwner` is the L1 ProxyAdmin owner address (used in Deploy.s.sol). + /// - `proxyAdminOwner` is the L2 ProxyAdmin owner address (used in L2Genesis.s.sol). + /// - If `proxyAdminOwnerIsMultisig` is true, assumes that both systems are owned by the same L1 + /// smart contract multisig, so the L2 owner should be the aliased verision of the L1 owner + /// address. + /// - If `proxyAdminOwnerIsMultisig` is false, assumes that both systems are owned by the same + /// EOA, so both owner addresses should be equal. + /// See the following docs for details on address aliasing: + /// https://docs.optimism.io/stack/differences#address-aliasing + function verifyProxyAdminOwners() public view { + if (proxyAdminOwnerIsMultisig) { + address expectedAlias = AddressAliasHelper.applyL1ToL2Alias(finalSystemOwner); + require(expectedAlias == proxyAdminOwner, "Expected proxyAdminOwner to be aliased finalSystemOwner"); + } else { + require(finalSystemOwner == proxyAdminOwner, "Expected finalSystemOwner and proxyAdminOwner to be equal"); + } } function fork() public view returns (Fork fork_) { @@ -241,12 +276,30 @@ contract DeployConfig is Script { fundDevAccounts = _fundDevAccounts; } + /// @notice Allow the `fundDevAccounts` config to be overridden. + function setDeployCeloContracts(bool _deployCeloContracts) public { + deployCeloContracts = _deployCeloContracts; + } + /// @notice Allow the `useCustomGasToken` config to be overridden in testing environments function setUseCustomGasToken(address _token) public { useCustomGasToken = true; customGasTokenAddress = _token; } + /// @notice Allow the `externalSuperchainConfig` config to be overridden in testing environments + function setExternalSuperchainConfig(address _externalSuperchainConfig) public { + externalSuperchainConfig = _externalSuperchainConfig; + } + + /// @notice Allow the ProxyAdmin owner configs (`finalSystemOwner`, `proxyAdminOwner`, and + /// `l2ProxyAdminOwnerVerification`) to be overriden in testing environments. + function setProxyAdminOwnerSettings(address l1Owner, address l2Owner, bool isMultisig) public { + finalSystemOwner = l1Owner; + proxyAdminOwner = l2Owner; + proxyAdminOwnerIsMultisig = isMultisig; + } + function latestGenesisFork() internal view returns (Fork) { if (l2GenesisHoloceneTimeOffset == 0) { return Fork.HOLOCENE; diff --git a/packages/contracts-bedrock/scripts/deploy/DeployDelayedWETH.s.sol b/packages/contracts-bedrock/scripts/deploy/DeployDelayedWETH.s.sol index 979869cb3ad0d..4e3ecf985ea15 100644 --- a/packages/contracts-bedrock/scripts/deploy/DeployDelayedWETH.s.sol +++ b/packages/contracts-bedrock/scripts/deploy/DeployDelayedWETH.s.sol @@ -14,7 +14,7 @@ import { LibString } from "@solady/utils/LibString.sol"; // Interfaces import { IProxy } from "src/universal/interfaces/IProxy.sol"; import { IDelayedWETH } from "src/dispute/interfaces/IDelayedWETH.sol"; -import { ISuperchainConfig } from "src/L1/interfaces/ISuperchainConfig.sol"; +import { ICeloSuperchainConfig } from "src/L1/interfaces/ICeloSuperchainConfig.sol"; /// @title DeployDelayedWETH contract DeployDelayedWETHInput is BaseDeployIO { @@ -22,7 +22,7 @@ contract DeployDelayedWETHInput is BaseDeployIO { string internal _release; string internal _standardVersionsToml; address public _proxyAdmin; - ISuperchainConfig public _superchainConfigProxy; + ICeloSuperchainConfig public _superchainConfigProxy; address public _delayedWethOwner; uint256 public _delayedWethDelay; @@ -41,7 +41,7 @@ contract DeployDelayedWETHInput is BaseDeployIO { _proxyAdmin = _value; } else if (_sel == this.superchainConfigProxy.selector) { require(_value != address(0), "DeployDelayedWETH: superchainConfigProxy cannot be zero address"); - _superchainConfigProxy = ISuperchainConfig(_value); + _superchainConfigProxy = ICeloSuperchainConfig(_value); } else if (_sel == this.delayedWethOwner.selector) { require(_value != address(0), "DeployDelayedWETH: delayedWethOwner cannot be zero address"); _delayedWethOwner = _value; @@ -77,7 +77,7 @@ contract DeployDelayedWETHInput is BaseDeployIO { return _proxyAdmin; } - function superchainConfigProxy() public view returns (ISuperchainConfig) { + function superchainConfigProxy() public view returns (ICeloSuperchainConfig) { require(address(_superchainConfigProxy) != address(0), "DeployDisputeGame: superchainConfigProxy not set"); return _superchainConfigProxy; } diff --git a/packages/contracts-bedrock/scripts/deploy/DeployImplementations.s.sol b/packages/contracts-bedrock/scripts/deploy/DeployImplementations.s.sol index c9048a07dfa39..2bf56cd348564 100644 --- a/packages/contracts-bedrock/scripts/deploy/DeployImplementations.s.sol +++ b/packages/contracts-bedrock/scripts/deploy/DeployImplementations.s.sol @@ -349,7 +349,7 @@ contract DeployImplementationsOutput is BaseDeployIO { require(delayedWETH.owner() == address(0), "DW-10"); require(delayedWETH.delay() == _dii.withdrawalDelaySeconds(), "DW-20"); - require(delayedWETH.config() == ISuperchainConfig(address(0)), "DW-30"); + require(address(delayedWETH.config()) == address(ISuperchainConfig(address(0))), "DW-30"); } function assertValidPreimageOracleSingleton(DeployImplementationsInput _dii) internal view { diff --git a/packages/contracts-bedrock/scripts/deploy/DeployOwnership.s.sol b/packages/contracts-bedrock/scripts/deploy/DeployOwnership.s.sol index e126c39f18c9f..5f408407537cb 100644 --- a/packages/contracts-bedrock/scripts/deploy/DeployOwnership.s.sol +++ b/packages/contracts-bedrock/scripts/deploy/DeployOwnership.s.sol @@ -4,19 +4,14 @@ pragma solidity ^0.8.0; import { console2 as console } from "forge-std/console2.sol"; import { GnosisSafe as Safe } from "safe-contracts/GnosisSafe.sol"; -import { GnosisSafeProxyFactory as SafeProxyFactory } from "safe-contracts/proxies/GnosisSafeProxyFactory.sol"; -import { OwnerManager } from "safe-contracts/base/OwnerManager.sol"; import { ModuleManager } from "safe-contracts/base/ModuleManager.sol"; import { GuardManager } from "safe-contracts/base/GuardManager.sol"; -import { Enum as SafeOps } from "safe-contracts/common/Enum.sol"; - -import { DeployUtils } from "scripts/libraries/DeployUtils.sol"; import { Deployer } from "scripts/deploy/Deployer.sol"; import { LivenessGuard } from "src/safe/LivenessGuard.sol"; import { LivenessModule } from "src/safe/LivenessModule.sol"; import { DeputyGuardianModule } from "src/safe/DeputyGuardianModule.sol"; -import { ISuperchainConfig } from "src/L1/interfaces/ISuperchainConfig.sol"; +import { ICeloSuperchainConfig } from "src/L1/interfaces/ICeloSuperchainConfig.sol"; import { Deploy } from "./Deploy.s.sol"; @@ -43,7 +38,7 @@ struct SecurityCouncilConfig { /// @notice Configuration for the Deputy Guardian Module struct DeputyGuardianModuleConfig { address deputyGuardian; - ISuperchainConfig superchainConfig; + ICeloSuperchainConfig superchainConfig; } /// @notice Configuration for the Guardian Safe. @@ -61,8 +56,8 @@ contract DeployOwnership is Deploy { /// @notice Internal function containing the deploy logic. function _run() internal override { console.log("start of Ownership Deployment"); - // The SuperchainConfig is needed as a constructor argument to the Deputy Guardian Module - deploySuperchainConfig(); + // The CeloSuperchainConfig is needed as a constructor argument to the Deputy Guardian Module + deployCeloSuperchainConfig(); deployFoundationOperationsSafe(); deployFoundationUpgradeSafe(); @@ -91,7 +86,7 @@ contract DeployOwnership is Deploy { safeConfig: SafeConfig({ threshold: 1, owners: exampleGuardianOwners }), deputyGuardianModuleConfig: DeputyGuardianModuleConfig({ deputyGuardian: mustGetAddress("FoundationOperationsSafe"), - superchainConfig: ISuperchainConfig(mustGetAddress("SuperchainConfig")) + superchainConfig: ICeloSuperchainConfig(mustGetAddress("CeloSuperchainConfig")) }) }); } @@ -114,30 +109,30 @@ contract DeployOwnership is Deploy { }); } - /// @notice Make a call from the Safe contract to an arbitrary address with arbitrary data - function _callViaSafe(Safe _safe, address _target, bytes memory _data) internal { - // This is the signature format used when the caller is also the signer. - bytes memory signature = abi.encodePacked(uint256(uint160(msg.sender)), bytes32(0), uint8(1)); - - _safe.execTransaction({ - to: _target, - value: 0, - data: _data, - operation: SafeOps.Operation.Call, - safeTxGas: 0, - baseGas: 0, - gasPrice: 0, - gasToken: address(0), - refundReceiver: payable(address(0)), - signatures: signature - }); - } - - /// @notice Deploy the Safe - function deploySafe(string memory _name) public broadcast returns (address addr_) { - address[] memory owners = new address[](0); - addr_ = deploySafe(_name, owners, 1, true); - } + // /// @notice Make a call from the Safe contract to an arbitrary address with arbitrary data + // function _callViaSafe(Safe _safe, address _target, bytes memory _data) internal { + // // This is the signature format used when the caller is also the signer. + // bytes memory signature = abi.encodePacked(uint256(uint160(msg.sender)), bytes32(0), uint8(1)); + + // _safe.execTransaction({ + // to: _target, + // value: 0, + // data: _data, + // operation: SafeOps.Operation.Call, + // safeTxGas: 0, + // baseGas: 0, + // gasPrice: 0, + // gasToken: address(0), + // refundReceiver: payable(address(0)), + // signatures: signature + // }); + // } + + // /// @notice Deploy the Safe + // function deploySafe(string memory _name) public broadcast returns (address addr_) { + // address[] memory owners = new address[](0); + // addr_ = deploySafe(_name, owners, 1, true); + // } /// @notice Deploy a new Safe contract. If the keepDeployer option is used to enable further setup actions, then /// the removeDeployerFromSafe() function should be called on that safe after setup is complete. @@ -146,80 +141,83 @@ contract DeployOwnership is Deploy { /// @param _owners The owners of the Safe. /// @param _threshold The threshold of the Safe. /// @param _keepDeployer Wether or not the deployer address will be added as an owner of the Safe. - function deploySafe( - string memory _name, - address[] memory _owners, - uint256 _threshold, - bool _keepDeployer - ) - public - returns (address addr_) - { - bytes32 salt = keccak256(abi.encode(_name, _implSalt())); - console.log("Deploying safe: %s with salt %s", _name, vm.toString(salt)); - (SafeProxyFactory safeProxyFactory, Safe safeSingleton) = _getSafeFactory(); - - if (_keepDeployer) { - address[] memory expandedOwners = new address[](_owners.length + 1); - // By always adding msg.sender first we know that the previousOwner will be SENTINEL_OWNERS, which makes it - // easier to call removeOwner later. - expandedOwners[0] = msg.sender; - for (uint256 i = 0; i < _owners.length; i++) { - expandedOwners[i + 1] = _owners[i]; - } - _owners = expandedOwners; - } - - bytes memory initData = abi.encodeCall( - Safe.setup, (_owners, _threshold, address(0), hex"", address(0), address(0), 0, payable(address(0))) - ); - addr_ = address(safeProxyFactory.createProxyWithNonce(address(safeSingleton), initData, uint256(salt))); - - save(_name, addr_); - console.log("New safe: %s deployed at %s\n Note that this safe is owned by the deployer key", _name, addr_); - } + // function deploySafe( + // string memory _name, + // address[] memory _owners, + // uint256 _threshold, + // bool _keepDeployer + // ) + // public + // returns (address addr_) + // { + // bytes32 salt = keccak256(abi.encode(_name, _implSalt())); + // console.log("Deploying safe: %s with salt %s", _name, vm.toString(salt)); + // (SafeProxyFactory safeProxyFactory, Safe safeSingleton) = _getSafeFactory(); + + // if (_keepDeployer) { + // address[] memory expandedOwners = new address[](_owners.length + 1); + // // By always adding msg.sender first we know that the previousOwner will be SENTINEL_OWNERS, which makes + // it + // // easier to call removeOwner later. + // expandedOwners[0] = msg.sender; + // for (uint256 i = 0; i < _owners.length; i++) { + // expandedOwners[i + 1] = _owners[i]; + // } + // _owners = expandedOwners; + // } + + // bytes memory initData = abi.encodeCall( + // Safe.setup, (_owners, _threshold, address(0), hex"", address(0), address(0), 0, payable(address(0))) + // ); + // addr_ = address(safeProxyFactory.createProxyWithNonce(address(safeSingleton), initData, uint256(salt))); + + // save(_name, addr_); + // console.log("New safe: %s deployed at %s\n Note that this safe is owned by the deployer key", _name, + // addr_); + // } /// @notice If the keepDeployer option was used with deploySafe(), this function can be used to remove the deployer. /// Note this function does not have the broadcast modifier. - function removeDeployerFromSafe(string memory _name, uint256 _newThreshold) public { - Safe safe = Safe(mustGetAddress(_name)); - - // The sentinel address is used to mark the start and end of the linked list of owners in the Safe. - address sentinelOwners = address(0x1); - - // Because deploySafe() always adds msg.sender first (if keepDeployer is true), we know that the previousOwner - // will be sentinelOwners. - _callViaSafe({ - _safe: safe, - _target: address(safe), - _data: abi.encodeCall(OwnerManager.removeOwner, (sentinelOwners, msg.sender, _newThreshold)) - }); - console.log("Removed deployer owner from ", _name); - } - - /// @notice Gets the address of the SafeProxyFactory and Safe singleton for use in deploying a new GnosisSafe. - function _getSafeFactory() internal returns (SafeProxyFactory safeProxyFactory_, Safe safeSingleton_) { - if (getAddress("SafeProxyFactory") != address(0)) { - // The SafeProxyFactory is already saved, we can just use it. - safeProxyFactory_ = SafeProxyFactory(getAddress("SafeProxyFactory")); - safeSingleton_ = Safe(getAddress("SafeSingleton")); - return (safeProxyFactory_, safeSingleton_); - } - - // These are the standard create2 deployed contracts. First we'll check if they are deployed, - // if not we'll deploy new ones, though not at these addresses. - address safeProxyFactory = 0xa6B71E26C5e0845f74c812102Ca7114b6a896AB2; - address safeSingleton = 0xd9Db270c1B5E3Bd161E8c8503c55cEABeE709552; - - safeProxyFactory.code.length == 0 - ? safeProxyFactory_ = new SafeProxyFactory() - : safeProxyFactory_ = SafeProxyFactory(safeProxyFactory); - - safeSingleton.code.length == 0 ? safeSingleton_ = new Safe() : safeSingleton_ = Safe(payable(safeSingleton)); - - save("SafeProxyFactory", address(safeProxyFactory_)); - save("SafeSingleton", address(safeSingleton_)); - } + // function removeDeployerFromSafe(string memory _name, uint256 _newThreshold) public { + // Safe safe = Safe(mustGetAddress(_name)); + + // // The sentinel address is used to mark the start and end of the linked list of owners in the Safe. + // address sentinelOwners = address(0x1); + + // // Because deploySafe() always adds msg.sender first (if keepDeployer is true), we know that the + // previousOwner + // // will be sentinelOwners. + // _callViaSafe({ + // _safe: safe, + // _target: address(safe), + // _data: abi.encodeCall(OwnerManager.removeOwner, (sentinelOwners, msg.sender, _newThreshold)) + // }); + // console.log("Removed deployer owner from ", _name); + // } + + // /// @notice Gets the address of the SafeProxyFactory and Safe singleton for use in deploying a new GnosisSafe. + // function _getSafeFactory() internal returns (SafeProxyFactory safeProxyFactory_, Safe safeSingleton_) { + // if (getAddress("SafeProxyFactory") != address(0)) { + // // The SafeProxyFactory is already saved, we can just use it. + // safeProxyFactory_ = SafeProxyFactory(getAddress("SafeProxyFactory")); + // safeSingleton_ = Safe(getAddress("SafeSingleton")); + // return (safeProxyFactory_, safeSingleton_); + // } + + // // These are the standard create2 deployed contracts. First we'll check if they are deployed, + // // if not we'll deploy new ones, though not at these addresses. + // address safeProxyFactory = 0xa6B71E26C5e0845f74c812102Ca7114b6a896AB2; + // address safeSingleton = 0xd9Db270c1B5E3Bd161E8c8503c55cEABeE709552; + + // safeProxyFactory.code.length == 0 + // ? safeProxyFactory_ = new SafeProxyFactory() + // : safeProxyFactory_ = SafeProxyFactory(safeProxyFactory); + + // safeSingleton.code.length == 0 ? safeSingleton_ = new Safe() : safeSingleton_ = Safe(payable(safeSingleton)); + + // save("SafeProxyFactory", address(safeProxyFactory_)); + // save("SafeSingleton", address(safeSingleton_)); + // } /// @notice Deploys a Safe with a configuration similar to that of the Foundation Safe on Mainnet. function deployFoundationOperationsSafe() public broadcast returns (address addr_) { @@ -318,20 +316,20 @@ contract DeployOwnership is Deploy { } /// @notice Deploy the SuperchainConfig contract - function deploySuperchainConfig() public broadcast { - ISuperchainConfig superchainConfig = ISuperchainConfig( - DeployUtils.create2AndSave({ - _save: this, - _salt: _implSalt(), - _name: "SuperchainConfig", - _args: DeployUtils.encodeConstructor(abi.encodeCall(ISuperchainConfig.__constructor__, ())) - }) - ); - - require(superchainConfig.guardian() == address(0)); - bytes32 initialized = vm.load(address(superchainConfig), bytes32(0)); - require(initialized != 0); - } + // function deploySuperchainConfig() public broadcast { + // ISuperchainConfig superchainConfig = ISuperchainConfig( + // DeployUtils.create2AndSave({ + // _save: this, + // _salt: _implSalt(), + // _name: "SuperchainConfig", + // _args: DeployUtils.encodeConstructor(abi.encodeCall(ISuperchainConfig.__constructor__, ())) + // }) + // ); + + // require(superchainConfig.guardian() == address(0)); + // bytes32 initialized = vm.load(address(superchainConfig), bytes32(0)); + // require(initialized != 0); + // } /// @notice Configure the Guardian Safe with the DeputyGuardianModule. function configureGuardianSafe() public broadcast returns (address addr_) { diff --git a/packages/contracts-bedrock/scripts/interfaces/ISystemConfigV0.sol b/packages/contracts-bedrock/scripts/interfaces/ISystemConfigV0.sol new file mode 100644 index 0000000000000..af1397d726452 --- /dev/null +++ b/packages/contracts-bedrock/scripts/interfaces/ISystemConfigV0.sol @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import { IResourceMetering } from "src/L1/interfaces/IResourceMetering.sol"; + +/// @title ISystemConfigV0 +/// @notice Minimal interface of the Legacy SystemConfig containing only getters. +/// Based on +/// https://github.com/ethereum-optimism/optimism/blob/f54a2234f2f350795552011f35f704a3feb56a08/packages/contracts-bedrock/src/L1/SystemConfig.sol +interface ISystemConfigV0 { + function owner() external view returns (address); + function VERSION() external view returns (uint256); + function overhead() external view returns (uint256); + function scalar() external view returns (uint256); + function batcherHash() external view returns (bytes32); + function gasLimit() external view returns (uint64); + function resourceConfig() external view returns (IResourceMetering.ResourceConfig memory); + function unsafeBlockSigner() external view returns (address); +} diff --git a/packages/contracts-bedrock/scripts/libraries/LibStateDiff.sol b/packages/contracts-bedrock/scripts/libraries/LibStateDiff.sol new file mode 100644 index 0000000000000..2b6f38a9afa77 --- /dev/null +++ b/packages/contracts-bedrock/scripts/libraries/LibStateDiff.sol @@ -0,0 +1,114 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.15; + +import { stdJson } from "forge-std/StdJson.sol"; +import { VmSafe } from "forge-std/Vm.sol"; + +/// @title LibStateDiff +/// @author refcell +/// @notice Library to write StateDiff output to json. +library LibStateDiff { + /// @notice Accepts an array of AccountAccess structs from the Vm and encodes them as a json string. + /// @param _accountAccesses Array of AccountAccess structs. + /// @return serialized_ string + function encodeAccountAccesses(VmSafe.AccountAccess[] memory _accountAccesses) + internal + returns (string memory serialized_) + { + string[] memory accountAccesses = new string[](_accountAccesses.length); + for (uint256 i = 0; i < _accountAccesses.length; i++) { + accountAccesses[i] = serializeAccountAccess(_accountAccesses[i]); + } + serialized_ = stdJson.serialize("accountAccessElem", "accountAccesses", accountAccesses); + } + + /// @notice Turns an AccountAccess into a json serialized string + /// @param _accountAccess The AccountAccess to serialize + /// @return serialized_ The json serialized string + function serializeAccountAccess(VmSafe.AccountAccess memory _accountAccess) + internal + returns (string memory serialized_) + { + string memory json = ""; + json = stdJson.serialize("accountAccess", "chainInfo", serializeChainInfo(_accountAccess.chainInfo)); + json = stdJson.serialize("accountAccess", "kind", serializeAccountAccessKind(_accountAccess.kind)); + json = stdJson.serialize("accountAccess", "account", _accountAccess.account); + json = stdJson.serialize("accountAccess", "accessor", _accountAccess.accessor); + json = stdJson.serialize("accountAccess", "initialized", _accountAccess.initialized); + json = stdJson.serialize("accountAccess", "oldBalance", _accountAccess.oldBalance); + json = stdJson.serialize("accountAccess", "newBalance", _accountAccess.newBalance); + json = stdJson.serialize("accountAccess", "deployedCode", _accountAccess.deployedCode); + json = stdJson.serialize("accountAccess", "value", _accountAccess.value); + json = stdJson.serialize("accountAccess", "data", _accountAccess.data); + json = stdJson.serialize("accountAccess", "reverted", _accountAccess.reverted); + json = stdJson.serialize( + "accountAccess", "storageAccesses", serializeStorageAccesses(_accountAccess.storageAccesses) + ); + serialized_ = json; + } + + /// @notice Accepts a VmSafe.ChainInfo struct and encodes it as a json string. + /// @param _chainInfo The ChainInfo struct to serialize + /// @return serialized_ string + function serializeChainInfo(VmSafe.ChainInfo memory _chainInfo) internal returns (string memory serialized_) { + string memory json = ""; + json = stdJson.serialize("chainInfo", "forkId", _chainInfo.forkId); + json = stdJson.serialize("chainInfo", "chainId", _chainInfo.chainId); + serialized_ = json; + } + + /// @notice Turns an AccountAccessKind into a string. + /// @param _kind The AccountAccessKind to serialize + /// @return serialized_ The string representation of the AccountAccessKind + function serializeAccountAccessKind(VmSafe.AccountAccessKind _kind) + internal + pure + returns (string memory serialized_) + { + if (_kind == VmSafe.AccountAccessKind.Call) { + serialized_ = "Call"; + } else if (_kind == VmSafe.AccountAccessKind.DelegateCall) { + serialized_ = "DelegateCall"; + } else if (_kind == VmSafe.AccountAccessKind.CallCode) { + serialized_ = "CallCode"; + } else if (_kind == VmSafe.AccountAccessKind.StaticCall) { + serialized_ = "StaticCall"; + } else if (_kind == VmSafe.AccountAccessKind.Create) { + serialized_ = "Create"; + } else if (_kind == VmSafe.AccountAccessKind.SelfDestruct) { + serialized_ = "SelfDestruct"; + } else { + serialized_ = "Resume"; + } + } + + /// @notice Accepts an array of StorageAccess structs from the Vm and encodes each as a json string. + /// @param _storageAccesses Array of StorageAccess structs. + /// @return serialized_ The list of json serialized StorageAccess structs. + function serializeStorageAccesses(VmSafe.StorageAccess[] memory _storageAccesses) + internal + returns (string[] memory serialized_) + { + serialized_ = new string[](_storageAccesses.length); + for (uint256 i = 0; i < _storageAccesses.length; i++) { + serialized_[i] = serializeStorageAccess(_storageAccesses[i]); + } + } + + /// @notice Turns a StorageAccess into a json serialized string + /// @param _storageAccess The StorageAccess to serialize + /// @return serialized_ The json serialized string + function serializeStorageAccess(VmSafe.StorageAccess memory _storageAccess) + internal + returns (string memory serialized_) + { + string memory json = ""; + json = stdJson.serialize("storageAccess", "account", _storageAccess.account); + json = stdJson.serialize("storageAccess", "slot", _storageAccess.slot); + json = stdJson.serialize("storageAccess", "isWrite", _storageAccess.isWrite); + json = stdJson.serialize("storageAccess", "previousValue", _storageAccess.previousValue); + json = stdJson.serialize("storageAccess", "newValue", _storageAccess.newValue); + json = stdJson.serialize("storageAccess", "reverted", _storageAccess.reverted); + serialized_ = json; + } +} diff --git a/packages/contracts-bedrock/scripts/libraries/Types.sol b/packages/contracts-bedrock/scripts/libraries/Types.sol index db37e19457a8f..e2191dac6f40a 100644 --- a/packages/contracts-bedrock/scripts/libraries/Types.sol +++ b/packages/contracts-bedrock/scripts/libraries/Types.sol @@ -18,6 +18,8 @@ library Types { address L1ERC721Bridge; address ProtocolVersions; address SuperchainConfig; + address CeloSuperchainConfig; address OPContractsManager; + address CustomGasToken; } } diff --git a/packages/contracts-bedrock/scripts/upgrades/holocene/DeployUpgrade.s.sol b/packages/contracts-bedrock/scripts/upgrades/holocene/DeployUpgrade.s.sol index 8203e42b060c6..325bee4e6e06e 100644 --- a/packages/contracts-bedrock/scripts/upgrades/holocene/DeployUpgrade.s.sol +++ b/packages/contracts-bedrock/scripts/upgrades/holocene/DeployUpgrade.s.sol @@ -13,7 +13,7 @@ import { Claim, GameTypes, Duration } from "src/dispute/lib/Types.sol"; // Interfaces import { ISystemConfig } from "src/L1/interfaces/ISystemConfig.sol"; -import { ISuperchainConfig } from "src/L1/interfaces/ISuperchainConfig.sol"; +import { ICeloSuperchainConfig } from "src/L1/interfaces/ICeloSuperchainConfig.sol"; import { IProxy } from "src/universal/interfaces/IProxy.sol"; import { IFaultDisputeGame, @@ -96,7 +96,7 @@ contract DeployUpgrade is Deployer { public { prankDeployment("ProxyAdmin", _proxyAdmin); - prankDeployment("SuperchainConfig", _superchainConfig); + prankDeployment("CeloSuperchainConfig", _superchainConfig); if (_systemConfigImpl != address(0)) prankDeployment("SystemConfig", _systemConfigImpl); if (_mipsImpl != address(0)) prankDeployment("MIPS", _mipsImpl); if (_delayedWETH != address(0)) prankDeployment("DelayedWETH", _delayedWETH); @@ -285,7 +285,7 @@ contract DeployUpgrade is Deployer { address delayedWethOwner = cfg.finalSystemOwner(); address proxyAdmin = mustGetAddress("ProxyAdmin"); address impl = mustGetAddress("DelayedWETH"); - ISuperchainConfig superchainConfig = ISuperchainConfig(mustGetAddress("SuperchainConfig")); + ICeloSuperchainConfig superchainConfig = ICeloSuperchainConfig(mustGetAddress("CeloSuperchainConfig")); string memory finalName = string.concat("DelayedWETHProxy", _variant); // Deploy the implementation and proxy contracts. @@ -318,7 +318,7 @@ contract DeployUpgrade is Deployer { delayedWeth.delay() == cfg.faultGameWithdrawalDelay(), "DeployHoloceneUpgrade: invalid DelayedWETH delay" ); require( - delayedWeth.config() == ISuperchainConfig(mustGetAddress("SuperchainConfig")), + delayedWeth.config() == ICeloSuperchainConfig(mustGetAddress("CeloSuperchainConfig")), "DeployHoloceneUpgrade: invalid DelayedWETH config" ); diff --git a/packages/contracts-bedrock/semver-lock.json b/packages/contracts-bedrock/semver-lock.json index 2075985567132..d1451f301d80b 100644 --- a/packages/contracts-bedrock/semver-lock.json +++ b/packages/contracts-bedrock/semver-lock.json @@ -9,15 +9,15 @@ }, "src/L1/L1CrossDomainMessenger.sol": { "initCodeHash": "0x2e9cb3ceb5e55341b311f0666ef7655df4fafae75afdfbcd701cd9c9b2b017d5", - "sourceCodeHash": "0x848ec3774be17bcc8ba65a23d08e35e979b3f39f9d2ac8a810188f945c69c9ea" + "sourceCodeHash": "0x3c989d5a1161e0987b64ecf368003c24fc6596ce424273b23d45efc60e0bdf16" }, "src/L1/L1ERC721Bridge.sol": { - "initCodeHash": "0xb3bf093ea83a24574a6093bebf5b2aea707355ed8d6702b2b5eb292e75b6ae42", - "sourceCodeHash": "0x289de9f40898b6305deecc6b60cdf566aa6c6a1444f713c3a0af23ea7878207e" + "initCodeHash": "0x7e7a41d1d05daf33ed9cf67674f4a4dbfbaa831e51557d60eeb40e7cc7282489", + "sourceCodeHash": "0x9cee3b7eb081ed6fa112577a9b93b396a53ebc57eeb87c1dd3332899946885ee" }, "src/L1/L1StandardBridge.sol": { "initCodeHash": "0x802f72745bb9a82dc049377bb9cf6b58f35aec388aeb957b28a5e14f28d91bc1", - "sourceCodeHash": "0x24b784645b065a5393a2115a078d67f91eb09afd5e70baf81daf975381f16155" + "sourceCodeHash": "0xa46b42eae448b2a3e3d667d27ed2e49a86140bffdddb462444dde0e971691619" }, "src/L1/L2OutputOracle.sol": { "initCodeHash": "0x1182bfb87c4ab399b912ca7fe18cdbf4b24c414e078fb0a55bd3c44d442d3ed1", @@ -28,15 +28,15 @@ "sourceCodeHash": "0x7bfa6eff76176649fe600303cd60009a0f6e282cbaec55836b5ea1f8875cbeb5" }, "src/L1/OptimismPortal.sol": { - "initCodeHash": "0x152167cfa18635ae4918a6eb3371a599cfa084418c0a652799cdb48bfc0ee0cc", - "sourceCodeHash": "0xbe34b82900d02f71bb0949818eabe49531f7e0d8d8bae01f6dac4a296530d1aa" + "initCodeHash": "0x967905f3ca95f502479fbc239552185608216c159d752e5df8e2f9ceb5ff55c6", + "sourceCodeHash": "0x76c1ee85d67b90eac315e54ba8f7d87a58fb2c1052bbe17a2c7d4f958b121366" }, "src/L1/OptimismPortal2.sol": { - "initCodeHash": "0x218358b48f640b3fcb2d239f00dc1cd3b11517ad46c8e1efa44953d38da63540", - "sourceCodeHash": "0x66ac1212760db53a2bb1839e4cd17dc071d9273b8e6fb80646b79e91b3371c1a" + "initCodeHash": "0x7e2951eb2ed008f72309ade75f9d13729872a238464273d172dff8c5ca2c23ed", + "sourceCodeHash": "0xd3ffe8e291aeb533bb95bb956b4bbc0593a49d18aeef0898900bf7791dbc628d" }, "src/L1/OptimismPortalInterop.sol": { - "initCodeHash": "0x39f66ac74341ec235fbdd0d79546283210bd8ac35a2ab2c4bd36c9722ce18411", + "initCodeHash": "0xd02bb60be351b5c7ced1882e3045f79f2342b73f4977872515996fbc404ba504", "sourceCodeHash": "0xbb98144285b9530e336f957d10b20363b350876597e30fd34821940896a2bae8" }, "src/L1/ProtocolVersions.sol": { @@ -45,7 +45,7 @@ }, "src/L1/SuperchainConfig.sol": { "initCodeHash": "0xfca12d9016c746e5c275b186e0ca40cfd65cf45a5665aab7589a669fea3abb47", - "sourceCodeHash": "0x39489a85bc3a5c8560f82d41b31bf7fe22f5b648f4ed538f61695a73092ea9eb" + "sourceCodeHash": "0x855fab6dffc13df092f6ad7f6ce1feee0ad1a4256d71b21490de6cda552a5a78" }, "src/L1/SystemConfig.sol": { "initCodeHash": "0x387aa0c2e404065420dcd3af7e0ebbb60d23569863e06e79d93b8f1561bab2a7", @@ -157,7 +157,7 @@ }, "src/dispute/AnchorStateRegistry.sol": { "initCodeHash": "0x7bdbf9dc5125c953ea1833ccf0ad0e07d25b6f6c47e23da5374413324a38c5f9", - "sourceCodeHash": "0x1d918a536d9f6c900efdf069e96c2a27bb49340d6d1ebaa92dd6b481835a9a82" + "sourceCodeHash": "0x17ee360c2519a56ee220eb9e9b96bae27acb471700c7229fbb7c3da3a95d408a" }, "src/dispute/DelayedWETH.sol": { "initCodeHash": "0xb31e0ff80fd69bc3f3b7d53f3fa42da4cdae393e41b8816719ce5ebe3d248688", @@ -212,11 +212,11 @@ "sourceCodeHash": "0xd1479c60087f352385b6d5379ef3cc07839f671d617626b4c94ece91da781ef2" }, "src/universal/OptimismMintableERC20.sol": { - "initCodeHash": "0x9cd677275b175812f1d5f90a127dbf7b3592714fd842a7a0de3988d716ca3eac", - "sourceCodeHash": "0x5611d8082f68af566554d7f09640b4b1f0e3efee4da1372b68fc7fc538a35ac7" + "initCodeHash": "0x8b22780d82518fcc3f85649eba2a77fec0ada34e0fe0d75059f3c8221828100a", + "sourceCodeHash": "0x03562a29a82a97cbadcb069c632a0cc21974eeab0b63df3e00accb3acc91355d" }, "src/universal/OptimismMintableERC20Factory.sol": { - "initCodeHash": "0x03ad07bd7f89a29f1850fa8b5d377daf0e1d5aef6cb458a127df520549e8e8e6", + "initCodeHash": "0x319e5c673af0f4e3e9031b4138c4498e9204767f2a43a924d710afbe0c35860e", "sourceCodeHash": "0xdb6ec93782a4a217475195507740794a4f5553b9032e7ba31dc48b81f579a940" }, "src/universal/OptimismMintableERC721.sol": { diff --git a/packages/contracts-bedrock/snapshots/abi/AnchorStateRegistry.json b/packages/contracts-bedrock/snapshots/abi/AnchorStateRegistry.json index c1a0fc693a9a5..d5204dd608939 100644 --- a/packages/contracts-bedrock/snapshots/abi/AnchorStateRegistry.json +++ b/packages/contracts-bedrock/snapshots/abi/AnchorStateRegistry.json @@ -79,7 +79,7 @@ "type": "tuple[]" }, { - "internalType": "contract ISuperchainConfig", + "internalType": "contract ICeloSuperchainConfig", "name": "_superchainConfig", "type": "address" } @@ -107,7 +107,7 @@ "name": "superchainConfig", "outputs": [ { - "internalType": "contract ISuperchainConfig", + "internalType": "contract ICeloSuperchainConfig", "name": "", "type": "address" } diff --git a/packages/contracts-bedrock/snapshots/abi/CalledByVm.json b/packages/contracts-bedrock/snapshots/abi/CalledByVm.json new file mode 100644 index 0000000000000..0637a088a01e8 --- /dev/null +++ b/packages/contracts-bedrock/snapshots/abi/CalledByVm.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/packages/contracts-bedrock/snapshots/abi/CeloRegistry.json b/packages/contracts-bedrock/snapshots/abi/CeloRegistry.json new file mode 100644 index 0000000000000..1f095b33d3bb0 --- /dev/null +++ b/packages/contracts-bedrock/snapshots/abi/CeloRegistry.json @@ -0,0 +1,247 @@ +[ + { + "inputs": [ + { + "internalType": "bool", + "name": "test", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "identifierHash", + "type": "bytes32" + } + ], + "name": "getAddressFor", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "identifierHash", + "type": "bytes32" + } + ], + "name": "getAddressForOrDie", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "identifier", + "type": "string" + } + ], + "name": "getAddressForString", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "identifier", + "type": "string" + } + ], + "name": "getAddressForStringOrDie", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "initialized", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32[]", + "name": "identifierHashes", + "type": "bytes32[]" + }, + { + "internalType": "address", + "name": "sender", + "type": "address" + } + ], + "name": "isOneOf", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "name": "registry", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "identifier", + "type": "string" + }, + { + "internalType": "address", + "name": "addr", + "type": "address" + } + ], + "name": "setAddressFor", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "string", + "name": "identifier", + "type": "string" + }, + { + "indexed": true, + "internalType": "bytes32", + "name": "identifierHash", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "address", + "name": "addr", + "type": "address" + } + ], + "name": "RegistryUpdated", + "type": "event" + } +] \ No newline at end of file diff --git a/packages/contracts-bedrock/snapshots/abi/CeloSuperchainConfig.json b/packages/contracts-bedrock/snapshots/abi/CeloSuperchainConfig.json new file mode 100644 index 0000000000000..595fad17df830 --- /dev/null +++ b/packages/contracts-bedrock/snapshots/abi/CeloSuperchainConfig.json @@ -0,0 +1,242 @@ +[ + { + "inputs": [], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [], + "name": "GUARDIAN_SLOT", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "PAUSED_SLOT", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "SUPERCHAIN_CONFIG_SLOT", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "checkAndPauseIfSuperchainPaused", + "outputs": [ + { + "internalType": "bool", + "name": "paused_", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "guardian", + "outputs": [ + { + "internalType": "address", + "name": "guardian_", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_guardian", + "type": "address" + }, + { + "internalType": "bool", + "name": "_paused", + "type": "bool" + } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_guardian", + "type": "address" + }, + { + "internalType": "bool", + "name": "_paused", + "type": "bool" + }, + { + "internalType": "address", + "name": "_superchainConfig", + "type": "address" + } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "_identifier", + "type": "string" + } + ], + "name": "pause", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "paused", + "outputs": [ + { + "internalType": "bool", + "name": "paused_", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "superchainConfig", + "outputs": [ + { + "internalType": "address", + "name": "superchainConfig_", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "unpause", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "version", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "enum CeloSuperchainConfig.CeloUpdateType", + "name": "updateType", + "type": "uint8" + }, + { + "indexed": false, + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "CeloConfigUpdate", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "enum SuperchainConfig.UpdateType", + "name": "updateType", + "type": "uint8" + }, + { + "indexed": false, + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "ConfigUpdate", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint8", + "name": "version", + "type": "uint8" + } + ], + "name": "Initialized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "string", + "name": "identifier", + "type": "string" + } + ], + "name": "Paused", + "type": "event" + }, + { + "anonymous": false, + "inputs": [], + "name": "Unpaused", + "type": "event" + } +] \ No newline at end of file diff --git a/packages/contracts-bedrock/snapshots/abi/CeloTokenL1.json b/packages/contracts-bedrock/snapshots/abi/CeloTokenL1.json new file mode 100644 index 0000000000000..ea1de6d1e2093 --- /dev/null +++ b/packages/contracts-bedrock/snapshots/abi/CeloTokenL1.json @@ -0,0 +1,298 @@ +[ + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "address", + "name": "spender", + "type": "address" + } + ], + "name": "allowance", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "approve", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "balanceOf", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "decimals", + "outputs": [ + { + "internalType": "uint8", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "subtractedValue", + "type": "uint256" + } + ], + "name": "decreaseAllowance", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "addedValue", + "type": "uint256" + } + ], + "name": "increaseAllowance", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "portalProxyAddress", + "type": "address" + } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "name", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "symbol", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "totalSupply", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "transfer", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "transferFrom", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "Approval", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint8", + "name": "version", + "type": "uint8" + } + ], + "name": "Initialized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "Transfer", + "type": "event" + } +] \ No newline at end of file diff --git a/packages/contracts-bedrock/snapshots/abi/CeloTokenL1Permit.json b/packages/contracts-bedrock/snapshots/abi/CeloTokenL1Permit.json new file mode 100644 index 0000000000000..a98f806362c7e --- /dev/null +++ b/packages/contracts-bedrock/snapshots/abi/CeloTokenL1Permit.json @@ -0,0 +1,531 @@ +[ + { + "inputs": [ + { + "internalType": "address", + "name": "portalProxyAddress", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [], + "name": "DOMAIN_SEPARATOR", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "address", + "name": "spender", + "type": "address" + } + ], + "name": "allowance", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "approve", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "balanceOf", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "decimals", + "outputs": [ + { + "internalType": "uint8", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "eip712Domain", + "outputs": [ + { + "internalType": "bytes1", + "name": "fields", + "type": "bytes1" + }, + { + "internalType": "string", + "name": "name", + "type": "string" + }, + { + "internalType": "string", + "name": "version", + "type": "string" + }, + { + "internalType": "uint256", + "name": "chainId", + "type": "uint256" + }, + { + "internalType": "address", + "name": "verifyingContract", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "salt", + "type": "bytes32" + }, + { + "internalType": "uint256[]", + "name": "extensions", + "type": "uint256[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "name", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "name": "nonces", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "deadline", + "type": "uint256" + }, + { + "internalType": "uint8", + "name": "v", + "type": "uint8" + }, + { + "internalType": "bytes32", + "name": "r", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "s", + "type": "bytes32" + } + ], + "name": "permit", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "symbol", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "totalSupply", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "transfer", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "transferFrom", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "Approval", + "type": "event" + }, + { + "anonymous": false, + "inputs": [], + "name": "EIP712DomainChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "Transfer", + "type": "event" + }, + { + "inputs": [], + "name": "ECDSAInvalidSignature", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "length", + "type": "uint256" + } + ], + "name": "ECDSAInvalidSignatureLength", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "s", + "type": "bytes32" + } + ], + "name": "ECDSAInvalidSignatureS", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "allowance", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "needed", + "type": "uint256" + } + ], + "name": "ERC20InsufficientAllowance", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "balance", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "needed", + "type": "uint256" + } + ], + "name": "ERC20InsufficientBalance", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "approver", + "type": "address" + } + ], + "name": "ERC20InvalidApprover", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "receiver", + "type": "address" + } + ], + "name": "ERC20InvalidReceiver", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "sender", + "type": "address" + } + ], + "name": "ERC20InvalidSender", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + } + ], + "name": "ERC20InvalidSpender", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "deadline", + "type": "uint256" + } + ], + "name": "ERC2612ExpiredSignature", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "signer", + "type": "address" + }, + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "name": "ERC2612InvalidSigner", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "internalType": "uint256", + "name": "currentNonce", + "type": "uint256" + } + ], + "name": "InvalidAccountNonce", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidShortString", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "str", + "type": "string" + } + ], + "name": "StringTooLong", + "type": "error" + } +] \ No newline at end of file diff --git a/packages/contracts-bedrock/snapshots/abi/FeeCurrency.json b/packages/contracts-bedrock/snapshots/abi/FeeCurrency.json new file mode 100644 index 0000000000000..4bdf6bbac31f3 --- /dev/null +++ b/packages/contracts-bedrock/snapshots/abi/FeeCurrency.json @@ -0,0 +1,354 @@ +[ + { + "inputs": [ + { + "internalType": "string", + "name": "name_", + "type": "string" + }, + { + "internalType": "string", + "name": "symbol_", + "type": "string" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "address", + "name": "spender", + "type": "address" + } + ], + "name": "allowance", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "approve", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "balanceOf", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "feeRecipient", + "type": "address" + }, + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "address", + "name": "communityFund", + "type": "address" + }, + { + "internalType": "uint256", + "name": "refund", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "tipTxFee", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "baseTxFee", + "type": "uint256" + } + ], + "name": "creditGasFees", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "debitGasFees", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "decimals", + "outputs": [ + { + "internalType": "uint8", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "subtractedValue", + "type": "uint256" + } + ], + "name": "decreaseAllowance", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "addedValue", + "type": "uint256" + } + ], + "name": "increaseAllowance", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "name", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "symbol", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "totalSupply", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "transfer", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "transferFrom", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "Approval", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "Transfer", + "type": "event" + } +] \ No newline at end of file diff --git a/packages/contracts-bedrock/snapshots/abi/FeeCurrencyDirectory.json b/packages/contracts-bedrock/snapshots/abi/FeeCurrencyDirectory.json new file mode 100644 index 0000000000000..4c4ccb64968e8 --- /dev/null +++ b/packages/contracts-bedrock/snapshots/abi/FeeCurrencyDirectory.json @@ -0,0 +1,246 @@ +[ + { + "inputs": [ + { + "internalType": "bool", + "name": "test", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "currencies", + "outputs": [ + { + "internalType": "address", + "name": "oracle", + "type": "address" + }, + { + "internalType": "uint256", + "name": "intrinsicGas", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getCurrencies", + "outputs": [ + { + "internalType": "address[]", + "name": "", + "type": "address[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "getCurrencyConfig", + "outputs": [ + { + "components": [ + { + "internalType": "address", + "name": "oracle", + "type": "address" + }, + { + "internalType": "uint256", + "name": "intrinsicGas", + "type": "uint256" + } + ], + "internalType": "struct IFeeCurrencyDirectory.CurrencyConfig", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "getExchangeRate", + "outputs": [ + { + "internalType": "uint256", + "name": "numerator", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "denominator", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getVersionNumber", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "initialized", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "index", + "type": "uint256" + } + ], + "name": "removeCurrencies", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "address", + "name": "oracle", + "type": "address" + }, + { + "internalType": "uint256", + "name": "intrinsicGas", + "type": "uint256" + } + ], + "name": "setCurrencyConfig", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + } +] \ No newline at end of file diff --git a/packages/contracts-bedrock/snapshots/abi/FeeHandler.json b/packages/contracts-bedrock/snapshots/abi/FeeHandler.json new file mode 100644 index 0000000000000..a584a53f686d0 --- /dev/null +++ b/packages/contracts-bedrock/snapshots/abi/FeeHandler.json @@ -0,0 +1,813 @@ +[ + { + "inputs": [ + { + "internalType": "bool", + "name": "test", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "stateMutability": "payable", + "type": "receive" + }, + { + "inputs": [], + "name": "FIXED1_UINT", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "MIN_BURN", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "tokenAddress", + "type": "address" + } + ], + "name": "activateToken", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "tokenAddress", + "type": "address" + }, + { + "internalType": "address", + "name": "handlerAddress", + "type": "address" + } + ], + "name": "addToken", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "burnCelo", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "burnFraction", + "outputs": [ + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "celoToBeBurned", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amountToBurn", + "type": "uint256" + } + ], + "name": "dailySellLimitHit", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "tokenAddress", + "type": "address" + } + ], + "name": "deactivateToken", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "tokenAddress", + "type": "address" + } + ], + "name": "distribute", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "distributeAll", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "feeBeneficiary", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getActiveTokens", + "outputs": [ + { + "internalType": "address[]", + "name": "", + "type": "address[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "getPastBurnForToken", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "tokenAddress", + "type": "address" + } + ], + "name": "getTokenActive", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "tokenAddress", + "type": "address" + } + ], + "name": "getTokenCurrentDaySellLimit", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "tokenAddress", + "type": "address" + } + ], + "name": "getTokenDailySellLimit", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "tokenAddress", + "type": "address" + } + ], + "name": "getTokenHandler", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "tokenAddress", + "type": "address" + } + ], + "name": "getTokenMaxSlippage", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "tokenAddress", + "type": "address" + } + ], + "name": "getTokenToDistribute", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getVersionNumber", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "tokenAddress", + "type": "address" + } + ], + "name": "handle", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "handleAll", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_registryAddress", + "type": "address" + }, + { + "internalType": "address", + "name": "newFeeBeneficiary", + "type": "address" + }, + { + "internalType": "uint256", + "name": "newBurnFraction", + "type": "uint256" + }, + { + "internalType": "address[]", + "name": "tokens", + "type": "address[]" + }, + { + "internalType": "address[]", + "name": "handlers", + "type": "address[]" + }, + { + "internalType": "uint256[]", + "name": "newLimits", + "type": "uint256[]" + }, + { + "internalType": "uint256[]", + "name": "newMaxSlippages", + "type": "uint256[]" + } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "initialized", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "lastLimitDay", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "registry", + "outputs": [ + { + "internalType": "contract ICeloRegistry", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "tokenAddress", + "type": "address" + } + ], + "name": "removeToken", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "tokenAddress", + "type": "address" + } + ], + "name": "sell", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "fraction", + "type": "uint256" + } + ], + "name": "setBurnFraction", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "newLimit", + "type": "uint256" + } + ], + "name": "setDailySellLimit", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "beneficiary", + "type": "address" + } + ], + "name": "setFeeBeneficiary", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "tokenAddress", + "type": "address" + }, + { + "internalType": "address", + "name": "handlerAddress", + "type": "address" + } + ], + "name": "setHandler", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "newMax", + "type": "uint256" + } + ], + "name": "setMaxSplippage", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "registryAddress", + "type": "address" + } + ], + "name": "setRegistry", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "transfer", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "fraction", + "type": "uint256" + } + ], + "name": "BurnFractionSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "burning", + "type": "uint256" + } + ], + "name": "DailyLimitHit", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "tokenAddress", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "newLimit", + "type": "uint256" + } + ], + "name": "DailyLimitSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "DailySellLimitUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "newBeneficiary", + "type": "address" + } + ], + "name": "FeeBeneficiarySet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "maxSlippage", + "type": "uint256" + } + ], + "name": "MaxSlippageSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "registryAddress", + "type": "address" + } + ], + "name": "RegistrySet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "SoldAndBurnedToken", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "tokenAddress", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "handlerAddress", + "type": "address" + } + ], + "name": "TokenAdded", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "tokenAddress", + "type": "address" + } + ], + "name": "TokenRemoved", + "type": "event" + } +] \ No newline at end of file diff --git a/packages/contracts-bedrock/snapshots/abi/Freezable.json b/packages/contracts-bedrock/snapshots/abi/Freezable.json new file mode 100644 index 0000000000000..dc8fa7e0f21ca --- /dev/null +++ b/packages/contracts-bedrock/snapshots/abi/Freezable.json @@ -0,0 +1,93 @@ +[ + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "registry", + "outputs": [ + { + "internalType": "contract ICeloRegistry", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "registryAddress", + "type": "address" + } + ], + "name": "setRegistry", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "registryAddress", + "type": "address" + } + ], + "name": "RegistrySet", + "type": "event" + } +] \ No newline at end of file diff --git a/packages/contracts-bedrock/snapshots/abi/GoldToken.json b/packages/contracts-bedrock/snapshots/abi/GoldToken.json new file mode 100644 index 0000000000000..a52ef10b6a528 --- /dev/null +++ b/packages/contracts-bedrock/snapshots/abi/GoldToken.json @@ -0,0 +1,552 @@ +[ + { + "inputs": [ + { + "internalType": "bool", + "name": "test", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "address", + "name": "spender", + "type": "address" + } + ], + "name": "allowance", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "approve", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "name": "balanceOf", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "burn", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "circulatingSupply", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "decimals", + "outputs": [ + { + "internalType": "uint8", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "decreaseAllowance", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "getBurnedAmount", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getVersionNumber", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "increaseAllowance", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "increaseSupply", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "registryAddress", + "type": "address" + } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "initialized", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "mint", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "name", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "registry", + "outputs": [ + { + "internalType": "contract ICeloRegistry", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "registryAddress", + "type": "address" + } + ], + "name": "setRegistry", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "symbol", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [], + "name": "totalSupply", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "transfer", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "transferFrom", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + }, + { + "internalType": "string", + "name": "comment", + "type": "string" + } + ], + "name": "transferWithComment", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "Approval", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "registryAddress", + "type": "address" + } + ], + "name": "RegistrySet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "Transfer", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "string", + "name": "comment", + "type": "string" + } + ], + "name": "TransferComment", + "type": "event" + } +] \ No newline at end of file diff --git a/packages/contracts-bedrock/snapshots/abi/Initializable.json b/packages/contracts-bedrock/snapshots/abi/Initializable.json new file mode 100644 index 0000000000000..aeef476ab67fd --- /dev/null +++ b/packages/contracts-bedrock/snapshots/abi/Initializable.json @@ -0,0 +1,26 @@ +[ + { + "inputs": [ + { + "internalType": "bool", + "name": "testingDeployment", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [], + "name": "initialized", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + } +] \ No newline at end of file diff --git a/packages/contracts-bedrock/snapshots/abi/L1CrossDomainMessenger.json b/packages/contracts-bedrock/snapshots/abi/L1CrossDomainMessenger.json index 22a4353cc6564..33430b9a6513e 100644 --- a/packages/contracts-bedrock/snapshots/abi/L1CrossDomainMessenger.json +++ b/packages/contracts-bedrock/snapshots/abi/L1CrossDomainMessenger.json @@ -180,7 +180,7 @@ { "inputs": [ { - "internalType": "contract ISuperchainConfig", + "internalType": "contract ICeloSuperchainConfig", "name": "_superchainConfig", "type": "address" }, @@ -337,7 +337,7 @@ "name": "superchainConfig", "outputs": [ { - "internalType": "contract ISuperchainConfig", + "internalType": "contract ICeloSuperchainConfig", "name": "", "type": "address" } diff --git a/packages/contracts-bedrock/snapshots/abi/L1ERC721Bridge.json b/packages/contracts-bedrock/snapshots/abi/L1ERC721Bridge.json index 33cd5b0a850b3..727f3d52b9cc6 100644 --- a/packages/contracts-bedrock/snapshots/abi/L1ERC721Bridge.json +++ b/packages/contracts-bedrock/snapshots/abi/L1ERC721Bridge.json @@ -176,7 +176,7 @@ "type": "address" }, { - "internalType": "contract ISuperchainConfig", + "internalType": "contract ICeloSuperchainConfig", "name": "_superchainConfig", "type": "address" } @@ -230,7 +230,7 @@ "name": "superchainConfig", "outputs": [ { - "internalType": "contract ISuperchainConfig", + "internalType": "contract ICeloSuperchainConfig", "name": "", "type": "address" } diff --git a/packages/contracts-bedrock/snapshots/abi/L1StandardBridge.json b/packages/contracts-bedrock/snapshots/abi/L1StandardBridge.json index 45480499fe9f4..5956f92903d34 100644 --- a/packages/contracts-bedrock/snapshots/abi/L1StandardBridge.json +++ b/packages/contracts-bedrock/snapshots/abi/L1StandardBridge.json @@ -422,7 +422,7 @@ "type": "address" }, { - "internalType": "contract ISuperchainConfig", + "internalType": "contract ICeloSuperchainConfig", "name": "_superchainConfig", "type": "address" }, @@ -494,7 +494,7 @@ "name": "superchainConfig", "outputs": [ { - "internalType": "contract ISuperchainConfig", + "internalType": "contract ICeloSuperchainConfig", "name": "", "type": "address" } diff --git a/packages/contracts-bedrock/snapshots/abi/MentoFeeHandlerSeller.json b/packages/contracts-bedrock/snapshots/abi/MentoFeeHandlerSeller.json new file mode 100644 index 0000000000000..7190d528858e5 --- /dev/null +++ b/packages/contracts-bedrock/snapshots/abi/MentoFeeHandlerSeller.json @@ -0,0 +1,350 @@ +[ + { + "inputs": [ + { + "internalType": "bool", + "name": "test", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "stateMutability": "payable", + "type": "receive" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "midPriceNumerator", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "midPriceDenominator", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "maxSlippage", + "type": "uint256" + } + ], + "name": "calculateMinAmount", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [], + "name": "getVersionNumber", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_registryAddress", + "type": "address" + }, + { + "internalType": "address[]", + "name": "tokenAddresses", + "type": "address[]" + }, + { + "internalType": "uint256[]", + "name": "newMininumReports", + "type": "uint256[]" + } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "initialized", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "minimumReports", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "registry", + "outputs": [ + { + "internalType": "contract ICeloRegistry", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "sellTokenAddress", + "type": "address" + }, + { + "internalType": "address", + "name": "buyTokenAddress", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "maxSlippage", + "type": "uint256" + } + ], + "name": "sell", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "tokenAddress", + "type": "address" + }, + { + "internalType": "uint256", + "name": "newMininumReports", + "type": "uint256" + } + ], + "name": "setMinimumReports", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "registryAddress", + "type": "address" + } + ], + "name": "setRegistry", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + } + ], + "name": "transfer", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "tokenAddress", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "minimumReports", + "type": "uint256" + } + ], + "name": "MinimumReportsSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "registryAddress", + "type": "address" + } + ], + "name": "RegistrySet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "soldTokenAddress", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "boughtTokenAddress", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "TokenSold", + "type": "event" + } +] \ No newline at end of file diff --git a/packages/contracts-bedrock/snapshots/abi/MockSortedOracles.json b/packages/contracts-bedrock/snapshots/abi/MockSortedOracles.json new file mode 100644 index 0000000000000..f56f9b579aa57 --- /dev/null +++ b/packages/contracts-bedrock/snapshots/abi/MockSortedOracles.json @@ -0,0 +1,249 @@ +[ + { + "inputs": [], + "name": "DENOMINATOR", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "expired", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "getExchangeRate", + "outputs": [ + { + "internalType": "uint256", + "name": "numerator", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "denominator", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "isOldestReportExpired", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + }, + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "medianRate", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "medianTimestamp", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "numRates", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "numerators", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "numerator", + "type": "uint256" + } + ], + "name": "setMedianRate", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "timestamp", + "type": "uint256" + } + ], + "name": "setMedianTimestamp", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "setMedianTimestampToNow", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "rate", + "type": "uint256" + } + ], + "name": "setNumRates", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "setOldestReportExpired", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +] \ No newline at end of file diff --git a/packages/contracts-bedrock/snapshots/abi/OptimismMintableERC20.json b/packages/contracts-bedrock/snapshots/abi/OptimismMintableERC20.json index 5a5763c73962b..57523467d292e 100644 --- a/packages/contracts-bedrock/snapshots/abi/OptimismMintableERC20.json +++ b/packages/contracts-bedrock/snapshots/abi/OptimismMintableERC20.json @@ -180,6 +180,90 @@ "stateMutability": "nonpayable", "type": "function" }, + { + "inputs": [ + { + "internalType": "address[]", + "name": "recipients", + "type": "address[]" + }, + { + "internalType": "uint256[]", + "name": "amounts", + "type": "uint256[]" + } + ], + "name": "creditGasFees", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "feeRecipient", + "type": "address" + }, + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "address", + "name": "communityFund", + "type": "address" + }, + { + "internalType": "uint256", + "name": "refund", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "tipTxFee", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "baseTxFee", + "type": "uint256" + } + ], + "name": "creditGasFees", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "debitGasFees", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, { "inputs": [], "name": "decimals", diff --git a/packages/contracts-bedrock/snapshots/abi/OptimismPortal.json b/packages/contracts-bedrock/snapshots/abi/OptimismPortal.json index 7ccee328a9841..f511518af59bc 100644 --- a/packages/contracts-bedrock/snapshots/abi/OptimismPortal.json +++ b/packages/contracts-bedrock/snapshots/abi/OptimismPortal.json @@ -189,7 +189,7 @@ "type": "address" }, { - "internalType": "contract ISuperchainConfig", + "internalType": "contract ICeloSuperchainConfig", "name": "_superchainConfig", "type": "address" } @@ -443,7 +443,7 @@ "name": "superchainConfig", "outputs": [ { - "internalType": "contract ISuperchainConfig", + "internalType": "contract ICeloSuperchainConfig", "name": "", "type": "address" } diff --git a/packages/contracts-bedrock/snapshots/abi/OptimismPortal2.json b/packages/contracts-bedrock/snapshots/abi/OptimismPortal2.json index 2f52ed573d37c..1cd9ae73c3e59 100644 --- a/packages/contracts-bedrock/snapshots/abi/OptimismPortal2.json +++ b/packages/contracts-bedrock/snapshots/abi/OptimismPortal2.json @@ -326,7 +326,7 @@ "type": "address" }, { - "internalType": "contract ISuperchainConfig", + "internalType": "contract ICeloSuperchainConfig", "name": "_superchainConfig", "type": "address" }, @@ -648,7 +648,7 @@ "name": "superchainConfig", "outputs": [ { - "internalType": "contract ISuperchainConfig", + "internalType": "contract ICeloSuperchainConfig", "name": "", "type": "address" } diff --git a/packages/contracts-bedrock/snapshots/abi/OptimismPortalInterop.json b/packages/contracts-bedrock/snapshots/abi/OptimismPortalInterop.json index 5b9f72b9446c8..0e4a228882004 100644 --- a/packages/contracts-bedrock/snapshots/abi/OptimismPortalInterop.json +++ b/packages/contracts-bedrock/snapshots/abi/OptimismPortalInterop.json @@ -326,7 +326,7 @@ "type": "address" }, { - "internalType": "contract ISuperchainConfig", + "internalType": "contract ICeloSuperchainConfig", "name": "_superchainConfig", "type": "address" }, @@ -666,7 +666,7 @@ "name": "superchainConfig", "outputs": [ { - "internalType": "contract ISuperchainConfig", + "internalType": "contract ICeloSuperchainConfig", "name": "", "type": "address" } diff --git a/packages/contracts-bedrock/snapshots/abi/SortedOracles.json b/packages/contracts-bedrock/snapshots/abi/SortedOracles.json new file mode 100644 index 0000000000000..12a253c5c08be --- /dev/null +++ b/packages/contracts-bedrock/snapshots/abi/SortedOracles.json @@ -0,0 +1,832 @@ +[ + { + "inputs": [ + { + "internalType": "bool", + "name": "test", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "address", + "name": "oracleAddress", + "type": "address" + } + ], + "name": "addOracle", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "breakerBox", + "outputs": [ + { + "internalType": "contract IBreakerBox", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "deleteEquivalentToken", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "equivalentTokens", + "outputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "getEquivalentToken", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "getExchangeRate", + "outputs": [ + { + "internalType": "uint256", + "name": "numerator", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "denominator", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "getOracles", + "outputs": [ + { + "internalType": "address[]", + "name": "", + "type": "address[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "getRates", + "outputs": [ + { + "internalType": "address[]", + "name": "", + "type": "address[]" + }, + { + "internalType": "uint256[]", + "name": "", + "type": "uint256[]" + }, + { + "internalType": "enum SortedLinkedListWithMedian.MedianRelation[]", + "name": "", + "type": "uint8[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "getTimestamps", + "outputs": [ + { + "internalType": "address[]", + "name": "", + "type": "address[]" + }, + { + "internalType": "uint256[]", + "name": "", + "type": "uint256[]" + }, + { + "internalType": "enum SortedLinkedListWithMedian.MedianRelation[]", + "name": "", + "type": "uint8[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "getTokenReportExpirySeconds", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getVersionNumber", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_reportExpirySeconds", + "type": "uint256" + } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "initialized", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "isOldestReportExpired", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + }, + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "isOracle", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "medianRate", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "medianRateWithoutEquivalentMapping", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "medianTimestamp", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "numRates", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "numTimestamps", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "oracles", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "n", + "type": "uint256" + } + ], + "name": "removeExpiredReports", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "address", + "name": "oracleAddress", + "type": "address" + }, + { + "internalType": "uint256", + "name": "index", + "type": "uint256" + } + ], + "name": "removeOracle", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + }, + { + "internalType": "address", + "name": "lesserKey", + "type": "address" + }, + { + "internalType": "address", + "name": "greaterKey", + "type": "address" + } + ], + "name": "report", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "reportExpirySeconds", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract IBreakerBox", + "name": "newBreakerBox", + "type": "address" + } + ], + "name": "setBreakerBox", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "address", + "name": "equivalentToken", + "type": "address" + } + ], + "name": "setEquivalentToken", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_reportExpirySeconds", + "type": "uint256" + } + ], + "name": "setReportExpiry", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_reportExpirySeconds", + "type": "uint256" + } + ], + "name": "setTokenReportExpiry", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "tokenReportExpirySeconds", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "newBreakerBox", + "type": "address" + } + ], + "name": "BreakerBoxUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "equivalentToken", + "type": "address" + } + ], + "name": "EquivalentTokenSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "MedianUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "oracleAddress", + "type": "address" + } + ], + "name": "OracleAdded", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "oracleAddress", + "type": "address" + } + ], + "name": "OracleRemoved", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "oracle", + "type": "address" + } + ], + "name": "OracleReportRemoved", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "oracle", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "timestamp", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "OracleReported", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "reportExpiry", + "type": "uint256" + } + ], + "name": "ReportExpirySet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "reportExpiry", + "type": "uint256" + } + ], + "name": "TokenReportExpirySet", + "type": "event" + } +] \ No newline at end of file diff --git a/packages/contracts-bedrock/snapshots/abi/StableTokenV2.json b/packages/contracts-bedrock/snapshots/abi/StableTokenV2.json new file mode 100644 index 0000000000000..693b960cea99c --- /dev/null +++ b/packages/contracts-bedrock/snapshots/abi/StableTokenV2.json @@ -0,0 +1,742 @@ +[ + { + "inputs": [ + { + "internalType": "bool", + "name": "disable", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [], + "name": "DOMAIN_SEPARATOR", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "address", + "name": "spender", + "type": "address" + } + ], + "name": "allowance", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "approve", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "balanceOf", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "broker", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "burn", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "feeRecipient", + "type": "address" + }, + { + "internalType": "address", + "name": "gatewayFeeRecipient", + "type": "address" + }, + { + "internalType": "address", + "name": "communityFund", + "type": "address" + }, + { + "internalType": "uint256", + "name": "refund", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "tipTxFee", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "gatewayFee", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "baseTxFee", + "type": "uint256" + } + ], + "name": "creditGasFees", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "debitGasFees", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "decimals", + "outputs": [ + { + "internalType": "uint8", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "subtractedValue", + "type": "uint256" + } + ], + "name": "decreaseAllowance", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "exchange", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "addedValue", + "type": "uint256" + } + ], + "name": "increaseAllowance", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "_name", + "type": "string" + }, + { + "internalType": "string", + "name": "_symbol", + "type": "string" + }, + { + "internalType": "address[]", + "name": "initialBalanceAddresses", + "type": "address[]" + }, + { + "internalType": "uint256[]", + "name": "initialBalanceValues", + "type": "uint256[]" + } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_broker", + "type": "address" + }, + { + "internalType": "address", + "name": "_validators", + "type": "address" + }, + { + "internalType": "address", + "name": "_exchange", + "type": "address" + } + ], + "name": "initializeV2", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "mint", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "name", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "name": "nonces", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "deadline", + "type": "uint256" + }, + { + "internalType": "uint8", + "name": "v", + "type": "uint8" + }, + { + "internalType": "bytes32", + "name": "r", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "s", + "type": "bytes32" + } + ], + "name": "permit", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_broker", + "type": "address" + } + ], + "name": "setBroker", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_exchange", + "type": "address" + } + ], + "name": "setExchange", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_validators", + "type": "address" + } + ], + "name": "setValidators", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "symbol", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "totalSupply", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "transfer", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "transferFrom", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + }, + { + "internalType": "string", + "name": "comment", + "type": "string" + } + ], + "name": "transferWithComment", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "validators", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "Approval", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "broker", + "type": "address" + } + ], + "name": "BrokerUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "exchange", + "type": "address" + } + ], + "name": "ExchangeUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint8", + "name": "version", + "type": "uint8" + } + ], + "name": "Initialized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "Transfer", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "string", + "name": "comment", + "type": "string" + } + ], + "name": "TransferComment", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "validators", + "type": "address" + } + ], + "name": "ValidatorsUpdated", + "type": "event" + } +] \ No newline at end of file diff --git a/packages/contracts-bedrock/snapshots/abi/UniswapFeeHandlerSeller.json b/packages/contracts-bedrock/snapshots/abi/UniswapFeeHandlerSeller.json new file mode 100644 index 0000000000000..19c31c979af28 --- /dev/null +++ b/packages/contracts-bedrock/snapshots/abi/UniswapFeeHandlerSeller.json @@ -0,0 +1,481 @@ +[ + { + "inputs": [ + { + "internalType": "bool", + "name": "test", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "stateMutability": "payable", + "type": "receive" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "midPriceNumerator", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "midPriceDenominator", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "maxSlippage", + "type": "uint256" + } + ], + "name": "calculateMinAmount", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "getRoutersForToken", + "outputs": [ + { + "internalType": "address[]", + "name": "", + "type": "address[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getVersionNumber", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_registryAddress", + "type": "address" + }, + { + "internalType": "address[]", + "name": "tokenAddresses", + "type": "address[]" + }, + { + "internalType": "uint256[]", + "name": "newMininumReports", + "type": "uint256[]" + } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "initialized", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "minimumReports", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "registry", + "outputs": [ + { + "internalType": "contract ICeloRegistry", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "address", + "name": "router", + "type": "address" + } + ], + "name": "removeRouter", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "sellTokenAddress", + "type": "address" + }, + { + "internalType": "address", + "name": "buyTokenAddress", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "maxSlippage", + "type": "uint256" + } + ], + "name": "sell", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "tokenAddress", + "type": "address" + }, + { + "internalType": "uint256", + "name": "newMininumReports", + "type": "uint256" + } + ], + "name": "setMinimumReports", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "registryAddress", + "type": "address" + } + ], + "name": "setRegistry", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "address", + "name": "router", + "type": "address" + } + ], + "name": "setRouter", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + } + ], + "name": "transfer", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "tokenAddress", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "minimumReports", + "type": "uint256" + } + ], + "name": "MinimumReportsSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "tokneAddress", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "router", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "quote", + "type": "uint256" + } + ], + "name": "ReceivedQuote", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "registryAddress", + "type": "address" + } + ], + "name": "RegistrySet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "router", + "type": "address" + } + ], + "name": "RouterAddressRemoved", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "router", + "type": "address" + } + ], + "name": "RouterAddressSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "router", + "type": "address" + } + ], + "name": "RouterUsed", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "soldTokenAddress", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "boughtTokenAddress", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "TokenSold", + "type": "event" + } +] \ No newline at end of file diff --git a/packages/contracts-bedrock/snapshots/abi/UsingRegistry.json b/packages/contracts-bedrock/snapshots/abi/UsingRegistry.json new file mode 100644 index 0000000000000..dc8fa7e0f21ca --- /dev/null +++ b/packages/contracts-bedrock/snapshots/abi/UsingRegistry.json @@ -0,0 +1,93 @@ +[ + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "registry", + "outputs": [ + { + "internalType": "contract ICeloRegistry", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "registryAddress", + "type": "address" + } + ], + "name": "setRegistry", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "registryAddress", + "type": "address" + } + ], + "name": "RegistrySet", + "type": "event" + } +] \ No newline at end of file diff --git a/packages/contracts-bedrock/snapshots/storageLayout/AnchorStateRegistry.json b/packages/contracts-bedrock/snapshots/storageLayout/AnchorStateRegistry.json index 3900f45525844..88aa19d3c25c0 100644 --- a/packages/contracts-bedrock/snapshots/storageLayout/AnchorStateRegistry.json +++ b/packages/contracts-bedrock/snapshots/storageLayout/AnchorStateRegistry.json @@ -25,6 +25,6 @@ "label": "superchainConfig", "offset": 0, "slot": "2", - "type": "contract ISuperchainConfig" + "type": "contract ICeloSuperchainConfig" } ] \ No newline at end of file diff --git a/packages/contracts-bedrock/snapshots/storageLayout/CalledByVm.json b/packages/contracts-bedrock/snapshots/storageLayout/CalledByVm.json new file mode 100644 index 0000000000000..0637a088a01e8 --- /dev/null +++ b/packages/contracts-bedrock/snapshots/storageLayout/CalledByVm.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/packages/contracts-bedrock/snapshots/storageLayout/CeloRegistry.json b/packages/contracts-bedrock/snapshots/storageLayout/CeloRegistry.json new file mode 100644 index 0000000000000..17b0df2bd7f9e --- /dev/null +++ b/packages/contracts-bedrock/snapshots/storageLayout/CeloRegistry.json @@ -0,0 +1,23 @@ +[ + { + "bytes": "20", + "label": "_owner", + "offset": 0, + "slot": "0", + "type": "address" + }, + { + "bytes": "1", + "label": "initialized", + "offset": 20, + "slot": "0", + "type": "bool" + }, + { + "bytes": "32", + "label": "registry", + "offset": 0, + "slot": "1", + "type": "mapping(bytes32 => address)" + } +] \ No newline at end of file diff --git a/packages/contracts-bedrock/snapshots/storageLayout/CeloSuperchainConfig.json b/packages/contracts-bedrock/snapshots/storageLayout/CeloSuperchainConfig.json new file mode 100644 index 0000000000000..70e559b7bf6dc --- /dev/null +++ b/packages/contracts-bedrock/snapshots/storageLayout/CeloSuperchainConfig.json @@ -0,0 +1,16 @@ +[ + { + "bytes": "1", + "label": "_initialized", + "offset": 0, + "slot": "0", + "type": "uint8" + }, + { + "bytes": "1", + "label": "_initializing", + "offset": 1, + "slot": "0", + "type": "bool" + } +] \ No newline at end of file diff --git a/packages/contracts-bedrock/snapshots/storageLayout/CeloTokenL1.json b/packages/contracts-bedrock/snapshots/storageLayout/CeloTokenL1.json new file mode 100644 index 0000000000000..e3c218589ca31 --- /dev/null +++ b/packages/contracts-bedrock/snapshots/storageLayout/CeloTokenL1.json @@ -0,0 +1,65 @@ +[ + { + "bytes": "1", + "label": "_initialized", + "offset": 0, + "slot": "0", + "type": "uint8" + }, + { + "bytes": "1", + "label": "_initializing", + "offset": 1, + "slot": "0", + "type": "bool" + }, + { + "bytes": "1600", + "label": "__gap", + "offset": 0, + "slot": "1", + "type": "uint256[50]" + }, + { + "bytes": "32", + "label": "_balances", + "offset": 0, + "slot": "51", + "type": "mapping(address => uint256)" + }, + { + "bytes": "32", + "label": "_allowances", + "offset": 0, + "slot": "52", + "type": "mapping(address => mapping(address => uint256))" + }, + { + "bytes": "32", + "label": "_totalSupply", + "offset": 0, + "slot": "53", + "type": "uint256" + }, + { + "bytes": "32", + "label": "_name", + "offset": 0, + "slot": "54", + "type": "string" + }, + { + "bytes": "32", + "label": "_symbol", + "offset": 0, + "slot": "55", + "type": "string" + }, + { + "bytes": "1440", + "label": "__gap", + "offset": 0, + "slot": "56", + "type": "uint256[45]" + } +] \ No newline at end of file diff --git a/packages/contracts-bedrock/snapshots/storageLayout/CeloTokenL1Permit.json b/packages/contracts-bedrock/snapshots/storageLayout/CeloTokenL1Permit.json new file mode 100644 index 0000000000000..d9c210e06ab30 --- /dev/null +++ b/packages/contracts-bedrock/snapshots/storageLayout/CeloTokenL1Permit.json @@ -0,0 +1,58 @@ +[ + { + "bytes": "32", + "label": "_balances", + "offset": 0, + "slot": "0", + "type": "mapping(address => uint256)" + }, + { + "bytes": "32", + "label": "_allowances", + "offset": 0, + "slot": "1", + "type": "mapping(address => mapping(address => uint256))" + }, + { + "bytes": "32", + "label": "_totalSupply", + "offset": 0, + "slot": "2", + "type": "uint256" + }, + { + "bytes": "32", + "label": "_name", + "offset": 0, + "slot": "3", + "type": "string" + }, + { + "bytes": "32", + "label": "_symbol", + "offset": 0, + "slot": "4", + "type": "string" + }, + { + "bytes": "32", + "label": "_nameFallback", + "offset": 0, + "slot": "5", + "type": "string" + }, + { + "bytes": "32", + "label": "_versionFallback", + "offset": 0, + "slot": "6", + "type": "string" + }, + { + "bytes": "32", + "label": "_nonces", + "offset": 0, + "slot": "7", + "type": "mapping(address => uint256)" + } +] \ No newline at end of file diff --git a/packages/contracts-bedrock/snapshots/storageLayout/FeeCurrency.json b/packages/contracts-bedrock/snapshots/storageLayout/FeeCurrency.json new file mode 100644 index 0000000000000..418a98546cf77 --- /dev/null +++ b/packages/contracts-bedrock/snapshots/storageLayout/FeeCurrency.json @@ -0,0 +1,37 @@ +[ + { + "bytes": "32", + "label": "_balances", + "offset": 0, + "slot": "0", + "type": "mapping(address => uint256)" + }, + { + "bytes": "32", + "label": "_allowances", + "offset": 0, + "slot": "1", + "type": "mapping(address => mapping(address => uint256))" + }, + { + "bytes": "32", + "label": "_totalSupply", + "offset": 0, + "slot": "2", + "type": "uint256" + }, + { + "bytes": "32", + "label": "_name", + "offset": 0, + "slot": "3", + "type": "string" + }, + { + "bytes": "32", + "label": "_symbol", + "offset": 0, + "slot": "4", + "type": "string" + } +] \ No newline at end of file diff --git a/packages/contracts-bedrock/snapshots/storageLayout/FeeCurrencyDirectory.json b/packages/contracts-bedrock/snapshots/storageLayout/FeeCurrencyDirectory.json new file mode 100644 index 0000000000000..61ccdc5fb1511 --- /dev/null +++ b/packages/contracts-bedrock/snapshots/storageLayout/FeeCurrencyDirectory.json @@ -0,0 +1,30 @@ +[ + { + "bytes": "1", + "label": "initialized", + "offset": 0, + "slot": "0", + "type": "bool" + }, + { + "bytes": "20", + "label": "_owner", + "offset": 1, + "slot": "0", + "type": "address" + }, + { + "bytes": "32", + "label": "currencies", + "offset": 0, + "slot": "1", + "type": "mapping(address => struct IFeeCurrencyDirectory.CurrencyConfig)" + }, + { + "bytes": "32", + "label": "currencyList", + "offset": 0, + "slot": "2", + "type": "address[]" + } +] \ No newline at end of file diff --git a/packages/contracts-bedrock/snapshots/storageLayout/FeeHandler.json b/packages/contracts-bedrock/snapshots/storageLayout/FeeHandler.json new file mode 100644 index 0000000000000..468bb7dc38921 --- /dev/null +++ b/packages/contracts-bedrock/snapshots/storageLayout/FeeHandler.json @@ -0,0 +1,72 @@ +[ + { + "bytes": "20", + "label": "_owner", + "offset": 0, + "slot": "0", + "type": "address" + }, + { + "bytes": "1", + "label": "initialized", + "offset": 20, + "slot": "0", + "type": "bool" + }, + { + "bytes": "20", + "label": "registry", + "offset": 0, + "slot": "1", + "type": "contract ICeloRegistry" + }, + { + "bytes": "32", + "label": "_status", + "offset": 0, + "slot": "2", + "type": "uint256" + }, + { + "bytes": "32", + "label": "lastLimitDay", + "offset": 0, + "slot": "3", + "type": "uint256" + }, + { + "bytes": "32", + "label": "burnFraction", + "offset": 0, + "slot": "4", + "type": "struct FixidityLib.Fraction" + }, + { + "bytes": "20", + "label": "feeBeneficiary", + "offset": 0, + "slot": "5", + "type": "address" + }, + { + "bytes": "32", + "label": "celoToBeBurned", + "offset": 0, + "slot": "6", + "type": "uint256" + }, + { + "bytes": "32", + "label": "tokenStates", + "offset": 0, + "slot": "7", + "type": "mapping(address => struct FeeHandler.TokenState)" + }, + { + "bytes": "64", + "label": "activeTokens", + "offset": 0, + "slot": "8", + "type": "struct EnumerableSet.AddressSet" + } +] \ No newline at end of file diff --git a/packages/contracts-bedrock/snapshots/storageLayout/Freezable.json b/packages/contracts-bedrock/snapshots/storageLayout/Freezable.json new file mode 100644 index 0000000000000..fb89bbc7e1ab3 --- /dev/null +++ b/packages/contracts-bedrock/snapshots/storageLayout/Freezable.json @@ -0,0 +1,16 @@ +[ + { + "bytes": "20", + "label": "_owner", + "offset": 0, + "slot": "0", + "type": "address" + }, + { + "bytes": "20", + "label": "registry", + "offset": 0, + "slot": "1", + "type": "contract ICeloRegistry" + } +] \ No newline at end of file diff --git a/packages/contracts-bedrock/snapshots/storageLayout/GoldToken.json b/packages/contracts-bedrock/snapshots/storageLayout/GoldToken.json new file mode 100644 index 0000000000000..67b349856d86c --- /dev/null +++ b/packages/contracts-bedrock/snapshots/storageLayout/GoldToken.json @@ -0,0 +1,37 @@ +[ + { + "bytes": "1", + "label": "initialized", + "offset": 0, + "slot": "0", + "type": "bool" + }, + { + "bytes": "20", + "label": "_owner", + "offset": 1, + "slot": "0", + "type": "address" + }, + { + "bytes": "20", + "label": "registry", + "offset": 0, + "slot": "1", + "type": "contract ICeloRegistry" + }, + { + "bytes": "32", + "label": "totalSupply_", + "offset": 0, + "slot": "2", + "type": "uint256" + }, + { + "bytes": "32", + "label": "allowed", + "offset": 0, + "slot": "3", + "type": "mapping(address => mapping(address => uint256))" + } +] \ No newline at end of file diff --git a/packages/contracts-bedrock/snapshots/storageLayout/Initializable.json b/packages/contracts-bedrock/snapshots/storageLayout/Initializable.json new file mode 100644 index 0000000000000..b29972a4de8eb --- /dev/null +++ b/packages/contracts-bedrock/snapshots/storageLayout/Initializable.json @@ -0,0 +1,9 @@ +[ + { + "bytes": "1", + "label": "initialized", + "offset": 0, + "slot": "0", + "type": "bool" + } +] \ No newline at end of file diff --git a/packages/contracts-bedrock/snapshots/storageLayout/L1CrossDomainMessenger.json b/packages/contracts-bedrock/snapshots/storageLayout/L1CrossDomainMessenger.json index c68ec541baba9..c41cd55934480 100644 --- a/packages/contracts-bedrock/snapshots/storageLayout/L1CrossDomainMessenger.json +++ b/packages/contracts-bedrock/snapshots/storageLayout/L1CrossDomainMessenger.json @@ -130,7 +130,7 @@ "label": "superchainConfig", "offset": 0, "slot": "251", - "type": "contract ISuperchainConfig" + "type": "contract ICeloSuperchainConfig" }, { "bytes": "20", diff --git a/packages/contracts-bedrock/snapshots/storageLayout/L1ERC721Bridge.json b/packages/contracts-bedrock/snapshots/storageLayout/L1ERC721Bridge.json index 2c14ad25904bb..80b5fc16e88fe 100644 --- a/packages/contracts-bedrock/snapshots/storageLayout/L1ERC721Bridge.json +++ b/packages/contracts-bedrock/snapshots/storageLayout/L1ERC721Bridge.json @@ -53,6 +53,6 @@ "label": "superchainConfig", "offset": 0, "slot": "50", - "type": "contract ISuperchainConfig" + "type": "contract ICeloSuperchainConfig" } ] \ No newline at end of file diff --git a/packages/contracts-bedrock/snapshots/storageLayout/L1StandardBridge.json b/packages/contracts-bedrock/snapshots/storageLayout/L1StandardBridge.json index 5562a214e4fef..c4fb27ed9152f 100644 --- a/packages/contracts-bedrock/snapshots/storageLayout/L1StandardBridge.json +++ b/packages/contracts-bedrock/snapshots/storageLayout/L1StandardBridge.json @@ -60,7 +60,7 @@ "label": "superchainConfig", "offset": 0, "slot": "50", - "type": "contract ISuperchainConfig" + "type": "contract ICeloSuperchainConfig" }, { "bytes": "20", diff --git a/packages/contracts-bedrock/snapshots/storageLayout/MentoFeeHandlerSeller.json b/packages/contracts-bedrock/snapshots/storageLayout/MentoFeeHandlerSeller.json new file mode 100644 index 0000000000000..a66c44056e6d0 --- /dev/null +++ b/packages/contracts-bedrock/snapshots/storageLayout/MentoFeeHandlerSeller.json @@ -0,0 +1,30 @@ +[ + { + "bytes": "20", + "label": "_owner", + "offset": 0, + "slot": "0", + "type": "address" + }, + { + "bytes": "1", + "label": "initialized", + "offset": 20, + "slot": "0", + "type": "bool" + }, + { + "bytes": "20", + "label": "registry", + "offset": 0, + "slot": "1", + "type": "contract ICeloRegistry" + }, + { + "bytes": "32", + "label": "minimumReports", + "offset": 0, + "slot": "2", + "type": "mapping(address => uint256)" + } +] \ No newline at end of file diff --git a/packages/contracts-bedrock/snapshots/storageLayout/MockSortedOracles.json b/packages/contracts-bedrock/snapshots/storageLayout/MockSortedOracles.json new file mode 100644 index 0000000000000..c44ef116af950 --- /dev/null +++ b/packages/contracts-bedrock/snapshots/storageLayout/MockSortedOracles.json @@ -0,0 +1,30 @@ +[ + { + "bytes": "32", + "label": "numerators", + "offset": 0, + "slot": "0", + "type": "mapping(address => uint256)" + }, + { + "bytes": "32", + "label": "medianTimestamp", + "offset": 0, + "slot": "1", + "type": "mapping(address => uint256)" + }, + { + "bytes": "32", + "label": "numRates", + "offset": 0, + "slot": "2", + "type": "mapping(address => uint256)" + }, + { + "bytes": "32", + "label": "expired", + "offset": 0, + "slot": "3", + "type": "mapping(address => bool)" + } +] \ No newline at end of file diff --git a/packages/contracts-bedrock/snapshots/storageLayout/OptimismPortal.json b/packages/contracts-bedrock/snapshots/storageLayout/OptimismPortal.json index d129ef1b87e30..e207f83c2fcc6 100644 --- a/packages/contracts-bedrock/snapshots/storageLayout/OptimismPortal.json +++ b/packages/contracts-bedrock/snapshots/storageLayout/OptimismPortal.json @@ -60,7 +60,7 @@ "label": "superchainConfig", "offset": 1, "slot": "53", - "type": "contract ISuperchainConfig" + "type": "contract ICeloSuperchainConfig" }, { "bytes": "20", diff --git a/packages/contracts-bedrock/snapshots/storageLayout/OptimismPortal2.json b/packages/contracts-bedrock/snapshots/storageLayout/OptimismPortal2.json index 0fdd65b3e88fb..ffa1d1861573d 100644 --- a/packages/contracts-bedrock/snapshots/storageLayout/OptimismPortal2.json +++ b/packages/contracts-bedrock/snapshots/storageLayout/OptimismPortal2.json @@ -60,7 +60,7 @@ "label": "superchainConfig", "offset": 1, "slot": "53", - "type": "contract ISuperchainConfig" + "type": "contract ICeloSuperchainConfig" }, { "bytes": "20", diff --git a/packages/contracts-bedrock/snapshots/storageLayout/OptimismPortalInterop.json b/packages/contracts-bedrock/snapshots/storageLayout/OptimismPortalInterop.json index 0fdd65b3e88fb..ffa1d1861573d 100644 --- a/packages/contracts-bedrock/snapshots/storageLayout/OptimismPortalInterop.json +++ b/packages/contracts-bedrock/snapshots/storageLayout/OptimismPortalInterop.json @@ -60,7 +60,7 @@ "label": "superchainConfig", "offset": 1, "slot": "53", - "type": "contract ISuperchainConfig" + "type": "contract ICeloSuperchainConfig" }, { "bytes": "20", diff --git a/packages/contracts-bedrock/snapshots/storageLayout/SortedOracles.json b/packages/contracts-bedrock/snapshots/storageLayout/SortedOracles.json new file mode 100644 index 0000000000000..e1e5e1736aff6 --- /dev/null +++ b/packages/contracts-bedrock/snapshots/storageLayout/SortedOracles.json @@ -0,0 +1,72 @@ +[ + { + "bytes": "20", + "label": "_owner", + "offset": 0, + "slot": "0", + "type": "address" + }, + { + "bytes": "1", + "label": "initialized", + "offset": 20, + "slot": "0", + "type": "bool" + }, + { + "bytes": "32", + "label": "rates", + "offset": 0, + "slot": "1", + "type": "mapping(address => struct SortedLinkedListWithMedian.List)" + }, + { + "bytes": "32", + "label": "timestamps", + "offset": 0, + "slot": "2", + "type": "mapping(address => struct SortedLinkedListWithMedian.List)" + }, + { + "bytes": "32", + "label": "isOracle", + "offset": 0, + "slot": "3", + "type": "mapping(address => mapping(address => bool))" + }, + { + "bytes": "32", + "label": "oracles", + "offset": 0, + "slot": "4", + "type": "mapping(address => address[])" + }, + { + "bytes": "32", + "label": "reportExpirySeconds", + "offset": 0, + "slot": "5", + "type": "uint256" + }, + { + "bytes": "32", + "label": "tokenReportExpirySeconds", + "offset": 0, + "slot": "6", + "type": "mapping(address => uint256)" + }, + { + "bytes": "20", + "label": "breakerBox", + "offset": 0, + "slot": "7", + "type": "contract IBreakerBox" + }, + { + "bytes": "32", + "label": "equivalentTokens", + "offset": 0, + "slot": "8", + "type": "mapping(address => struct SortedOracles.EquivalentToken)" + } +] \ No newline at end of file diff --git a/packages/contracts-bedrock/snapshots/storageLayout/StableTokenV2.json b/packages/contracts-bedrock/snapshots/storageLayout/StableTokenV2.json new file mode 100644 index 0000000000000..eea3cafe6e902 --- /dev/null +++ b/packages/contracts-bedrock/snapshots/storageLayout/StableTokenV2.json @@ -0,0 +1,142 @@ +[ + { + "bytes": "1", + "label": "_initialized", + "offset": 0, + "slot": "0", + "type": "uint8" + }, + { + "bytes": "1", + "label": "_initializing", + "offset": 1, + "slot": "0", + "type": "bool" + }, + { + "bytes": "1600", + "label": "__gap", + "offset": 0, + "slot": "1", + "type": "uint256[50]" + }, + { + "bytes": "32", + "label": "_balances", + "offset": 0, + "slot": "51", + "type": "mapping(address => uint256)" + }, + { + "bytes": "32", + "label": "_allowances", + "offset": 0, + "slot": "52", + "type": "mapping(address => mapping(address => uint256))" + }, + { + "bytes": "32", + "label": "_totalSupply", + "offset": 0, + "slot": "53", + "type": "uint256" + }, + { + "bytes": "32", + "label": "_name", + "offset": 0, + "slot": "54", + "type": "string" + }, + { + "bytes": "32", + "label": "_symbol", + "offset": 0, + "slot": "55", + "type": "string" + }, + { + "bytes": "1440", + "label": "__gap", + "offset": 0, + "slot": "56", + "type": "uint256[45]" + }, + { + "bytes": "32", + "label": "_HASHED_NAME", + "offset": 0, + "slot": "101", + "type": "bytes32" + }, + { + "bytes": "32", + "label": "_HASHED_VERSION", + "offset": 0, + "slot": "102", + "type": "bytes32" + }, + { + "bytes": "1600", + "label": "__gap", + "offset": 0, + "slot": "103", + "type": "uint256[50]" + }, + { + "bytes": "32", + "label": "_nonces", + "offset": 0, + "slot": "153", + "type": "mapping(address => struct CountersUpgradeable.Counter)" + }, + { + "bytes": "32", + "label": "_PERMIT_TYPEHASH_DEPRECATED_SLOT", + "offset": 0, + "slot": "154", + "type": "bytes32" + }, + { + "bytes": "1568", + "label": "__gap", + "offset": 0, + "slot": "155", + "type": "uint256[49]" + }, + { + "bytes": "20", + "label": "_owner", + "offset": 0, + "slot": "204", + "type": "address" + }, + { + "bytes": "1568", + "label": "__gap", + "offset": 0, + "slot": "205", + "type": "uint256[49]" + }, + { + "bytes": "20", + "label": "validators", + "offset": 0, + "slot": "254", + "type": "address" + }, + { + "bytes": "20", + "label": "broker", + "offset": 0, + "slot": "255", + "type": "address" + }, + { + "bytes": "20", + "label": "exchange", + "offset": 0, + "slot": "256", + "type": "address" + } +] \ No newline at end of file diff --git a/packages/contracts-bedrock/snapshots/storageLayout/UniswapFeeHandlerSeller.json b/packages/contracts-bedrock/snapshots/storageLayout/UniswapFeeHandlerSeller.json new file mode 100644 index 0000000000000..3688a3204dec1 --- /dev/null +++ b/packages/contracts-bedrock/snapshots/storageLayout/UniswapFeeHandlerSeller.json @@ -0,0 +1,37 @@ +[ + { + "bytes": "20", + "label": "_owner", + "offset": 0, + "slot": "0", + "type": "address" + }, + { + "bytes": "1", + "label": "initialized", + "offset": 20, + "slot": "0", + "type": "bool" + }, + { + "bytes": "20", + "label": "registry", + "offset": 0, + "slot": "1", + "type": "contract ICeloRegistry" + }, + { + "bytes": "32", + "label": "minimumReports", + "offset": 0, + "slot": "2", + "type": "mapping(address => uint256)" + }, + { + "bytes": "32", + "label": "routerAddresses", + "offset": 0, + "slot": "3", + "type": "mapping(address => struct EnumerableSet.AddressSet)" + } +] \ No newline at end of file diff --git a/packages/contracts-bedrock/snapshots/storageLayout/UsingRegistry.json b/packages/contracts-bedrock/snapshots/storageLayout/UsingRegistry.json new file mode 100644 index 0000000000000..fb89bbc7e1ab3 --- /dev/null +++ b/packages/contracts-bedrock/snapshots/storageLayout/UsingRegistry.json @@ -0,0 +1,16 @@ +[ + { + "bytes": "20", + "label": "_owner", + "offset": 0, + "slot": "0", + "type": "address" + }, + { + "bytes": "20", + "label": "registry", + "offset": 0, + "slot": "1", + "type": "contract ICeloRegistry" + } +] \ No newline at end of file diff --git a/packages/contracts-bedrock/src/L1/CeloSuperchainConfig.sol b/packages/contracts-bedrock/src/L1/CeloSuperchainConfig.sol new file mode 100644 index 0000000000000..748aef77a4607 --- /dev/null +++ b/packages/contracts-bedrock/src/L1/CeloSuperchainConfig.sol @@ -0,0 +1,153 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.15; + +import { Initializable } from "@openzeppelin/contracts/proxy/utils/Initializable.sol"; +import { ISemver } from "src/universal/interfaces/ISemver.sol"; +import { ISuperchainConfig } from "./interfaces/ISuperchainConfig.sol"; +import { Storage } from "src/libraries/Storage.sol"; + +/// @custom:proxied true +/// @custom:audit none This contracts is not yet audited. +/// @title CeloSuperchainConfig +/// @notice The CeloSuperchainConfig contract is used to manage values that are +/// typically part of the global superchain configuration, but potentially need to +/// be handled differently by Celo. +contract CeloSuperchainConfig is Initializable, ISemver { + /// @notice Enum representing different types of updates. + /// @custom:value GUARDIAN Represents an update to the guardian. + /// @custom:value SUPERCHAIN_CONFIG Represents an update to the SuperchainConfig address. + enum UpdateType { + GUARDIAN, + SUPERCHAIN_CONFIG + } + + /// @notice Whether or not the Celo system is paused. + bytes32 public constant PAUSED_SLOT = bytes32(uint256(keccak256("celoSuperchainConfig.paused")) - 1); + + /// @notice The address of the guardian, which can pause withdrawals from the System. + /// It can only be modified by an upgrade. + bytes32 public constant GUARDIAN_SLOT = bytes32(uint256(keccak256("celoSuperchainConfig.guardian")) - 1); + + /// @notice The address of the global OP Superchain SuperchainConfig contract. + /// It can only be modified by an upgrade. + bytes32 public constant SUPERCHAIN_CONFIG_SLOT = + bytes32(uint256(keccak256("celoSuperchainConfig.superchainConfig")) - 1); + /// @notice Emitted when the pause is triggered. + /// @param identifier A string helping to identify provenance of the pause transaction. + + event Paused(string identifier); + + /// @notice Emitted when the pause is lifted. + event Unpaused(); + + /// @notice Emitted when configuration of the Celo-specific portion of the + /// config is updated. + /// @param updateType Type of update. + /// @param data Encoded update data. + event ConfigUpdate(UpdateType indexed updateType, bytes data); + + /// @notice Semantic version. + /// @custom:semver 1.0.0-beta.1 + string public constant version = "1.0.0-beta.1"; + + /// @notice Constructs the CeloSuperchainConfig contract. + constructor() { + initialize({ _guardian: address(0), _paused: false, _superchainConfig: address(0) }); + } + + /// @notice Initializer. + /// @param _guardian Address of the guardian, can pause the OptimismPortal. + /// @param _paused Initial paused status. + /// @param _superchainConfig Address of the global SuperchainConfig. + function initialize(address _guardian, bool _paused, address _superchainConfig) public initializer { + _setGuardian(_guardian); + _setSuperchainConfig(_superchainConfig); + if (_paused) { + _pause("Initializer paused"); + } else if (_superchainConfig != address(0)) { + checkAndPauseIfSuperchainPaused(); + } + } + + /// @notice Checks whether the Celo system should be paused, while also + /// propagating the paused value from Superchain to Celo if + /// necessary. + function checkAndPauseIfSuperchainPaused() public returns (bool paused_) { + address superchainConfig_ = superchainConfig(); + if (superchainConfig_ != address(0) && ISuperchainConfig(superchainConfig_).paused()) { + _pause("Superchain paused"); + return true; + } + + return paused(); + } + + /// @notice Getter for the address of the global SuperchainConfig. + function superchainConfig() public view returns (address superchainConfig_) { + superchainConfig_ = Storage.getAddress(SUPERCHAIN_CONFIG_SLOT); + } + + /// @notice Getter for the guardian address. + function guardian() public view returns (address guardian_) { + guardian_ = Storage.getAddress(GUARDIAN_SLOT); + } + + /// @notice Getter for the current paused status, which depends both on the + /// local paused value, and the paused status of Superchain. + function paused() public view returns (bool paused_) { + paused_ = celoPaused(); + if (paused_) { + return paused_; + } + + if (superchainConfig() != address(0)) { + paused_ = ISuperchainConfig(superchainConfig()).paused(); + } + + return paused_; + } + + /// @notice Pauses withdrawals. + /// @param _identifier (Optional) A string to identify provenance of the pause transaction. + function pause(string memory _identifier) external { + require(msg.sender == guardian(), "CeloSuperchainConfig: only guardian can pause"); + _pause(_identifier); + } + + /// @notice Pauses withdrawals. + /// @param _identifier (Optional) A string to identify provenance of the pause transaction. + function _pause(string memory _identifier) internal { + Storage.setBool(PAUSED_SLOT, true); + emit Paused(_identifier); + } + + /// @notice Unpauses withdrawals. + function unpause() external { + require(msg.sender == guardian(), "CeloSuperchainConfig: only guardian can unpause"); + Storage.setBool(PAUSED_SLOT, false); + emit Unpaused(); + } + + /// @notice Getter for the current local paused value. Note that the actual paused status of the + /// Celo system also depends on the paused status of the Superchain. Use `paused()` to + /// get that. + function celoPaused() public view returns (bool paused_) { + paused_ = Storage.getBool(PAUSED_SLOT); + } + + /// @notice Sets the guardian address. This is only callable during initialization, so an upgrade + /// will be required to change the guardian. + /// @param _guardian The new guardian address. + function _setGuardian(address _guardian) internal { + Storage.setAddress(GUARDIAN_SLOT, _guardian); + emit ConfigUpdate(UpdateType.GUARDIAN, abi.encode(_guardian)); + } + + /// @notice Sets the global SuperchainConfig address. This is only callable + /// during initialization, so an upgrade will be required to change this address. + /// @param _superchainConfig The new SuperchainConfig address. + function _setSuperchainConfig(address _superchainConfig) internal { + Storage.setAddress(SUPERCHAIN_CONFIG_SLOT, _superchainConfig); + emit ConfigUpdate(UpdateType.SUPERCHAIN_CONFIG, abi.encode(_superchainConfig)); + } +} diff --git a/packages/contracts-bedrock/src/L1/L1CrossDomainMessenger.sol b/packages/contracts-bedrock/src/L1/L1CrossDomainMessenger.sol index 27be4a7332fa2..b2c262f6e7084 100644 --- a/packages/contracts-bedrock/src/L1/L1CrossDomainMessenger.sol +++ b/packages/contracts-bedrock/src/L1/L1CrossDomainMessenger.sol @@ -9,7 +9,7 @@ import { Predeploys } from "src/libraries/Predeploys.sol"; // Interfaces import { ISemver } from "src/universal/interfaces/ISemver.sol"; -import { ISuperchainConfig } from "src/L1/interfaces/ISuperchainConfig.sol"; +import { ICeloSuperchainConfig } from "src/L1/interfaces/ICeloSuperchainConfig.sol"; import { ISystemConfig } from "src/L1/interfaces/ISystemConfig.sol"; import { IOptimismPortal } from "src/L1/interfaces/IOptimismPortal.sol"; @@ -20,7 +20,7 @@ import { IOptimismPortal } from "src/L1/interfaces/IOptimismPortal.sol"; /// interface instead of interacting with lower-level contracts directly. contract L1CrossDomainMessenger is CrossDomainMessenger, ISemver { /// @notice Contract of the SuperchainConfig. - ISuperchainConfig public superchainConfig; + ICeloSuperchainConfig public superchainConfig; /// @notice Contract of the OptimismPortal. /// @custom:network-specific @@ -36,7 +36,7 @@ contract L1CrossDomainMessenger is CrossDomainMessenger, ISemver { /// @notice Constructs the L1CrossDomainMessenger contract. constructor() CrossDomainMessenger() { initialize({ - _superchainConfig: ISuperchainConfig(address(0)), + _superchainConfig: ICeloSuperchainConfig(address(0)), _portal: IOptimismPortal(payable(address(0))), _systemConfig: ISystemConfig(address(0)) }); @@ -47,7 +47,7 @@ contract L1CrossDomainMessenger is CrossDomainMessenger, ISemver { /// @param _portal Contract of the OptimismPortal contract on this network. /// @param _systemConfig Contract of the SystemConfig contract on this network. function initialize( - ISuperchainConfig _superchainConfig, + ICeloSuperchainConfig _superchainConfig, IOptimismPortal _portal, ISystemConfig _systemConfig ) diff --git a/packages/contracts-bedrock/src/L1/L1ERC721Bridge.sol b/packages/contracts-bedrock/src/L1/L1ERC721Bridge.sol index bd9b31c1e5893..748f7a5db3158 100644 --- a/packages/contracts-bedrock/src/L1/L1ERC721Bridge.sol +++ b/packages/contracts-bedrock/src/L1/L1ERC721Bridge.sol @@ -11,7 +11,7 @@ import { Predeploys } from "src/libraries/Predeploys.sol"; import { IERC721 } from "@openzeppelin/contracts/token/ERC721/IERC721.sol"; import { ISemver } from "src/universal/interfaces/ISemver.sol"; import { ICrossDomainMessenger } from "src/universal/interfaces/ICrossDomainMessenger.sol"; -import { ISuperchainConfig } from "src/L1/interfaces/ISuperchainConfig.sol"; +import { ICeloSuperchainConfig } from "src/L1/interfaces/ICeloSuperchainConfig.sol"; import { IL2ERC721Bridge } from "src/L2/interfaces/IL2ERC721Bridge.sol"; /// @custom:proxied true @@ -25,7 +25,7 @@ contract L1ERC721Bridge is ERC721Bridge, ISemver { mapping(address => mapping(address => mapping(uint256 => bool))) public deposits; /// @notice Address of the SuperchainConfig contract. - ISuperchainConfig public superchainConfig; + ICeloSuperchainConfig public superchainConfig; /// @notice Semantic version. /// @custom:semver 2.2.0-beta.1 @@ -33,13 +33,16 @@ contract L1ERC721Bridge is ERC721Bridge, ISemver { /// @notice Constructs the L1ERC721Bridge contract. constructor() ERC721Bridge() { - initialize({ _messenger: ICrossDomainMessenger(address(0)), _superchainConfig: ISuperchainConfig(address(0)) }); + initialize({ + _messenger: ICrossDomainMessenger(address(0)), + _superchainConfig: ICeloSuperchainConfig(address(0)) + }); } /// @notice Initializes the contract. /// @param _messenger Contract of the CrossDomainMessenger on this network. /// @param _superchainConfig Contract of the SuperchainConfig contract on this network. - function initialize(ICrossDomainMessenger _messenger, ISuperchainConfig _superchainConfig) public initializer { + function initialize(ICrossDomainMessenger _messenger, ICeloSuperchainConfig _superchainConfig) public initializer { superchainConfig = _superchainConfig; __ERC721Bridge_init({ _messenger: _messenger, _otherBridge: ERC721Bridge(payable(Predeploys.L2_ERC721_BRIDGE)) }); } diff --git a/packages/contracts-bedrock/src/L1/L1StandardBridge.sol b/packages/contracts-bedrock/src/L1/L1StandardBridge.sol index 54cc833fe6730..e74a15bf74baf 100644 --- a/packages/contracts-bedrock/src/L1/L1StandardBridge.sol +++ b/packages/contracts-bedrock/src/L1/L1StandardBridge.sol @@ -10,7 +10,7 @@ import { Predeploys } from "src/libraries/Predeploys.sol"; // Interfaces import { ISemver } from "src/universal/interfaces/ISemver.sol"; import { ICrossDomainMessenger } from "src/universal/interfaces/ICrossDomainMessenger.sol"; -import { ISuperchainConfig } from "src/L1/interfaces/ISuperchainConfig.sol"; +import { ICeloSuperchainConfig } from "src/L1/interfaces/ICeloSuperchainConfig.sol"; import { ISystemConfig } from "src/L1/interfaces/ISystemConfig.sol"; /// @custom:proxied true @@ -79,7 +79,7 @@ contract L1StandardBridge is StandardBridge, ISemver { string public constant version = "2.2.1-beta.2"; /// @notice Address of the SuperchainConfig contract. - ISuperchainConfig public superchainConfig; + ICeloSuperchainConfig public superchainConfig; /// @notice Address of the SystemConfig contract. ISystemConfig public systemConfig; @@ -88,7 +88,7 @@ contract L1StandardBridge is StandardBridge, ISemver { constructor() StandardBridge() { initialize({ _messenger: ICrossDomainMessenger(address(0)), - _superchainConfig: ISuperchainConfig(address(0)), + _superchainConfig: ICeloSuperchainConfig(address(0)), _systemConfig: ISystemConfig(address(0)) }); } @@ -98,7 +98,7 @@ contract L1StandardBridge is StandardBridge, ISemver { /// @param _superchainConfig Contract for the SuperchainConfig on this network. function initialize( ICrossDomainMessenger _messenger, - ISuperchainConfig _superchainConfig, + ICeloSuperchainConfig _superchainConfig, ISystemConfig _systemConfig ) public diff --git a/packages/contracts-bedrock/src/L1/OptimismPortal.sol b/packages/contracts-bedrock/src/L1/OptimismPortal.sol index 2f9f53787581c..70f2e7faadab1 100644 --- a/packages/contracts-bedrock/src/L1/OptimismPortal.sol +++ b/packages/contracts-bedrock/src/L1/OptimismPortal.sol @@ -34,7 +34,7 @@ import { ISemver } from "src/universal/interfaces/ISemver.sol"; import { IL2OutputOracle } from "src/L1/interfaces/IL2OutputOracle.sol"; import { ISystemConfig } from "src/L1/interfaces/ISystemConfig.sol"; import { IResourceMetering } from "src/L1/interfaces/IResourceMetering.sol"; -import { ISuperchainConfig } from "src/L1/interfaces/ISuperchainConfig.sol"; +import { ICeloSuperchainConfig } from "src/L1/interfaces/ICeloSuperchainConfig.sol"; import { IL1Block } from "src/L2/interfaces/IL1Block.sol"; /// @custom:proxied true @@ -82,7 +82,7 @@ contract OptimismPortal is Initializable, ResourceMetering, ISemver { bool private spacer_53_0_1; /// @notice Contract of the Superchain Config. - ISuperchainConfig public superchainConfig; + ICeloSuperchainConfig public superchainConfig; /// @notice Contract of the L2OutputOracle. /// @custom:network-specific @@ -156,7 +156,7 @@ contract OptimismPortal is Initializable, ResourceMetering, ISemver { initialize({ _l2Oracle: IL2OutputOracle(address(0)), _systemConfig: ISystemConfig(address(0)), - _superchainConfig: ISuperchainConfig(address(0)) + _superchainConfig: ICeloSuperchainConfig(address(0)) }); } @@ -167,7 +167,7 @@ contract OptimismPortal is Initializable, ResourceMetering, ISemver { function initialize( IL2OutputOracle _l2Oracle, ISystemConfig _systemConfig, - ISuperchainConfig _superchainConfig + ICeloSuperchainConfig _superchainConfig ) public initializer diff --git a/packages/contracts-bedrock/src/L1/OptimismPortal2.sol b/packages/contracts-bedrock/src/L1/OptimismPortal2.sol index 985711ed18e55..76524a60c38fa 100644 --- a/packages/contracts-bedrock/src/L1/OptimismPortal2.sol +++ b/packages/contracts-bedrock/src/L1/OptimismPortal2.sol @@ -41,7 +41,7 @@ import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { ISemver } from "src/universal/interfaces/ISemver.sol"; import { ISystemConfig } from "src/L1/interfaces/ISystemConfig.sol"; import { IResourceMetering } from "src/L1/interfaces/IResourceMetering.sol"; -import { ISuperchainConfig } from "src/L1/interfaces/ISuperchainConfig.sol"; +import { ICeloSuperchainConfig } from "src/L1/interfaces/ICeloSuperchainConfig.sol"; import { IDisputeGameFactory } from "src/dispute/interfaces/IDisputeGameFactory.sol"; import { IDisputeGame } from "src/dispute/interfaces/IDisputeGame.sol"; import { IL1Block } from "src/L2/interfaces/IL1Block.sol"; @@ -98,7 +98,7 @@ contract OptimismPortal2 is Initializable, ResourceMetering, ISemver { bool private spacer_53_0_1; /// @notice Contract of the Superchain Config. - ISuperchainConfig public superchainConfig; + ICeloSuperchainConfig public superchainConfig; /// @custom:legacy /// @custom:spacer l2Oracle @@ -196,7 +196,7 @@ contract OptimismPortal2 is Initializable, ResourceMetering, ISemver { initialize({ _disputeGameFactory: IDisputeGameFactory(address(0)), _systemConfig: ISystemConfig(address(0)), - _superchainConfig: ISuperchainConfig(address(0)), + _superchainConfig: ICeloSuperchainConfig(address(0)), _initialRespectedGameType: GameType.wrap(0) }); } @@ -208,7 +208,7 @@ contract OptimismPortal2 is Initializable, ResourceMetering, ISemver { function initialize( IDisputeGameFactory _disputeGameFactory, ISystemConfig _systemConfig, - ISuperchainConfig _superchainConfig, + ICeloSuperchainConfig _superchainConfig, GameType _initialRespectedGameType ) public diff --git a/packages/contracts-bedrock/src/L1/interfaces/ICeloSuperchainConfig.sol b/packages/contracts-bedrock/src/L1/interfaces/ICeloSuperchainConfig.sol new file mode 100644 index 0000000000000..ef96ea52b490b --- /dev/null +++ b/packages/contracts-bedrock/src/L1/interfaces/ICeloSuperchainConfig.sol @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +interface ICeloSuperchainConfig { + enum UpdateType { + GUARDIAN, + SUPERCHAIN_CONFIG + } + + event ConfigUpdate(UpdateType indexed updateType, bytes data); + event Paused(string identifier); + event Unpaused(); + + function GUARDIAN_SLOT() external view returns (bytes32); + function PAUSED_SLOT() external view returns (bytes32); + function SUPERCHAIN_CONFIG_SLOT() external view returns (bytes32); + function guardian() external view returns (address guardian_); + function initialize(address _guardian, bool _paused, address _superchainConfig) external; + function superchainConfig() external view returns (address superchainConfig_); + function pause(string memory _identifier) external; + function paused() external view returns (bool paused_); + function unpause() external; + function checkAndPauseIfSuperchainPaused() external returns (bool paused_); + + function __constructor__() external; +} diff --git a/packages/contracts-bedrock/src/L1/interfaces/IL1CrossDomainMessenger.sol b/packages/contracts-bedrock/src/L1/interfaces/IL1CrossDomainMessenger.sol index 8a6de84e2c9df..c808ad5ba5a92 100644 --- a/packages/contracts-bedrock/src/L1/interfaces/IL1CrossDomainMessenger.sol +++ b/packages/contracts-bedrock/src/L1/interfaces/IL1CrossDomainMessenger.sol @@ -2,20 +2,20 @@ pragma solidity ^0.8.0; import { ICrossDomainMessenger } from "src/universal/interfaces/ICrossDomainMessenger.sol"; -import { ISuperchainConfig } from "src/L1/interfaces/ISuperchainConfig.sol"; +import { ICeloSuperchainConfig } from "src/L1/interfaces/ICeloSuperchainConfig.sol"; import { IOptimismPortal } from "src/L1/interfaces/IOptimismPortal.sol"; import { ISystemConfig } from "src/L1/interfaces/ISystemConfig.sol"; interface IL1CrossDomainMessenger is ICrossDomainMessenger { function PORTAL() external view returns (IOptimismPortal); function initialize( - ISuperchainConfig _superchainConfig, + ICeloSuperchainConfig _superchainConfig, IOptimismPortal _portal, ISystemConfig _systemConfig ) external; function portal() external view returns (IOptimismPortal); - function superchainConfig() external view returns (ISuperchainConfig); + function superchainConfig() external view returns (ICeloSuperchainConfig); function systemConfig() external view returns (ISystemConfig); function version() external view returns (string memory); diff --git a/packages/contracts-bedrock/src/L1/interfaces/IL1ERC721Bridge.sol b/packages/contracts-bedrock/src/L1/interfaces/IL1ERC721Bridge.sol index 51356bc8d3468..0138978cb36e6 100644 --- a/packages/contracts-bedrock/src/L1/interfaces/IL1ERC721Bridge.sol +++ b/packages/contracts-bedrock/src/L1/interfaces/IL1ERC721Bridge.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.0; import { IERC721Bridge } from "src/universal/interfaces/IERC721Bridge.sol"; import { ICrossDomainMessenger } from "src/universal/interfaces/ICrossDomainMessenger.sol"; -import { ISuperchainConfig } from "src/L1/interfaces/ISuperchainConfig.sol"; +import { ICeloSuperchainConfig } from "src/L1/interfaces/ICeloSuperchainConfig.sol"; interface IL1ERC721Bridge is IERC721Bridge { function bridgeERC721( @@ -33,9 +33,9 @@ interface IL1ERC721Bridge is IERC721Bridge { bytes memory _extraData ) external; - function initialize(ICrossDomainMessenger _messenger, ISuperchainConfig _superchainConfig) external; + function initialize(ICrossDomainMessenger _messenger, ICeloSuperchainConfig _superchainConfig) external; function paused() external view returns (bool); - function superchainConfig() external view returns (ISuperchainConfig); + function superchainConfig() external view returns (ICeloSuperchainConfig); function version() external view returns (string memory); function __constructor__() external; diff --git a/packages/contracts-bedrock/src/L1/interfaces/IL1StandardBridge.sol b/packages/contracts-bedrock/src/L1/interfaces/IL1StandardBridge.sol index 816436cf1084a..3686459cb0a5c 100644 --- a/packages/contracts-bedrock/src/L1/interfaces/IL1StandardBridge.sol +++ b/packages/contracts-bedrock/src/L1/interfaces/IL1StandardBridge.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.0; import { IStandardBridge } from "src/universal/interfaces/IStandardBridge.sol"; import { ICrossDomainMessenger } from "src/universal/interfaces/ICrossDomainMessenger.sol"; -import { ISuperchainConfig } from "src/L1/interfaces/ISuperchainConfig.sol"; +import { ICeloSuperchainConfig } from "src/L1/interfaces/ICeloSuperchainConfig.sol"; import { ISystemConfig } from "src/L1/interfaces/ISystemConfig.sol"; interface IL1StandardBridge is IStandardBridge { @@ -64,12 +64,12 @@ interface IL1StandardBridge is IStandardBridge { payable; function initialize( ICrossDomainMessenger _messenger, - ISuperchainConfig _superchainConfig, + ICeloSuperchainConfig _superchainConfig, ISystemConfig _systemConfig ) external; function l2TokenBridge() external view returns (address); - function superchainConfig() external view returns (ISuperchainConfig); + function superchainConfig() external view returns (ICeloSuperchainConfig); function systemConfig() external view returns (ISystemConfig); function version() external view returns (string memory); diff --git a/packages/contracts-bedrock/src/L1/interfaces/IOptimismPortal.sol b/packages/contracts-bedrock/src/L1/interfaces/IOptimismPortal.sol index b9035a6e51432..b5a142b25a2d3 100644 --- a/packages/contracts-bedrock/src/L1/interfaces/IOptimismPortal.sol +++ b/packages/contracts-bedrock/src/L1/interfaces/IOptimismPortal.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.0; import { Types } from "src/libraries/Types.sol"; import { ISystemConfig } from "src/L1/interfaces/ISystemConfig.sol"; -import { ISuperchainConfig } from "src/L1/interfaces/ISuperchainConfig.sol"; +import { ICeloSuperchainConfig } from "src/L1/interfaces/ICeloSuperchainConfig.sol"; import { IL2OutputOracle } from "src/L1/interfaces/IL2OutputOracle.sol"; interface IOptimismPortal { @@ -58,7 +58,7 @@ interface IOptimismPortal { function initialize( IL2OutputOracle _l2Oracle, ISystemConfig _systemConfig, - ISuperchainConfig _superchainConfig + ICeloSuperchainConfig _superchainConfig ) external; function isOutputFinalized(uint256 _l2OutputIndex) external view returns (bool); @@ -79,7 +79,7 @@ interface IOptimismPortal { view returns (bytes32 outputRoot, uint128 timestamp, uint128 l2OutputIndex); // nosemgrep function setGasPayingToken(address _token, uint8 _decimals, bytes32 _name, bytes32 _symbol) external; - function superchainConfig() external view returns (ISuperchainConfig); + function superchainConfig() external view returns (ICeloSuperchainConfig); function systemConfig() external view returns (ISystemConfig); function version() external pure returns (string memory); diff --git a/packages/contracts-bedrock/src/L1/interfaces/IOptimismPortal2.sol b/packages/contracts-bedrock/src/L1/interfaces/IOptimismPortal2.sol index 91f09d714314d..b5fbecf4dfbbd 100644 --- a/packages/contracts-bedrock/src/L1/interfaces/IOptimismPortal2.sol +++ b/packages/contracts-bedrock/src/L1/interfaces/IOptimismPortal2.sol @@ -6,7 +6,7 @@ import { GameType, Timestamp } from "src/dispute/lib/LibUDT.sol"; import { IDisputeGame } from "src/dispute/interfaces/IDisputeGame.sol"; import { IDisputeGameFactory } from "src/dispute/interfaces/IDisputeGameFactory.sol"; import { ISystemConfig } from "src/L1/interfaces/ISystemConfig.sol"; -import { ISuperchainConfig } from "src/L1/interfaces/ISuperchainConfig.sol"; +import { ICeloSuperchainConfig } from "src/L1/interfaces/ICeloSuperchainConfig.sol"; interface IOptimismPortal2 { error AlreadyFinalized(); @@ -81,7 +81,7 @@ interface IOptimismPortal2 { function initialize( IDisputeGameFactory _disputeGameFactory, ISystemConfig _systemConfig, - ISuperchainConfig _superchainConfig, + ICeloSuperchainConfig _superchainConfig, GameType _initialRespectedGameType ) external; @@ -110,7 +110,7 @@ interface IOptimismPortal2 { function respectedGameTypeUpdatedAt() external view returns (uint64); function setGasPayingToken(address _token, uint8 _decimals, bytes32 _name, bytes32 _symbol) external; function setRespectedGameType(GameType _gameType) external; - function superchainConfig() external view returns (ISuperchainConfig); + function superchainConfig() external view returns (ICeloSuperchainConfig); function systemConfig() external view returns (ISystemConfig); function version() external pure returns (string memory); diff --git a/packages/contracts-bedrock/src/L1/interfaces/IOptimismPortalInterop.sol b/packages/contracts-bedrock/src/L1/interfaces/IOptimismPortalInterop.sol index 521c7232e125b..c7deede1f0b9c 100644 --- a/packages/contracts-bedrock/src/L1/interfaces/IOptimismPortalInterop.sol +++ b/packages/contracts-bedrock/src/L1/interfaces/IOptimismPortalInterop.sol @@ -6,7 +6,7 @@ import { GameType, Timestamp } from "src/dispute/lib/LibUDT.sol"; import { IDisputeGame } from "src/dispute/interfaces/IDisputeGame.sol"; import { IDisputeGameFactory } from "src/dispute/interfaces/IDisputeGameFactory.sol"; import { ISystemConfig } from "src/L1/interfaces/ISystemConfig.sol"; -import { ISuperchainConfig } from "src/L1/interfaces/ISuperchainConfig.sol"; +import { ICeloSuperchainConfig } from "src/L1/interfaces/ICeloSuperchainConfig.sol"; import { ConfigType } from "src/L2/L1BlockInterop.sol"; interface IOptimismPortalInterop { @@ -82,7 +82,7 @@ interface IOptimismPortalInterop { function initialize( IDisputeGameFactory _disputeGameFactory, ISystemConfig _systemConfig, - ISuperchainConfig _superchainConfig, + ICeloSuperchainConfig _superchainConfig, GameType _initialRespectedGameType ) external; @@ -112,7 +112,7 @@ interface IOptimismPortalInterop { function setConfig(ConfigType _type, bytes memory _value) external; function setGasPayingToken(address _token, uint8 _decimals, bytes32 _name, bytes32 _symbol) external; function setRespectedGameType(GameType _gameType) external; - function superchainConfig() external view returns (ISuperchainConfig); + function superchainConfig() external view returns (ICeloSuperchainConfig); function systemConfig() external view returns (ISystemConfig); function version() external pure returns (string memory); diff --git a/packages/contracts-bedrock/src/celo/AbstractFeeCurrency.sol b/packages/contracts-bedrock/src/celo/AbstractFeeCurrency.sol new file mode 100644 index 0000000000000..d3ec984324deb --- /dev/null +++ b/packages/contracts-bedrock/src/celo/AbstractFeeCurrency.sol @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; + +abstract contract AbstractFeeCurrency is ERC20 { + modifier onlyVm() { + require(msg.sender == address(0), "Only VM can call"); + _; + } + + function debitGasFees(address from, uint256 value) external onlyVm { + _burn(from, value); + } + + // New function signature, will be used when all fee currencies have migrated + function creditGasFees(address[] calldata recipients, uint256[] calldata amounts) external onlyVm { + require(recipients.length == amounts.length, "Recipients and amounts must be the same length."); + + for (uint256 i = 0; i < recipients.length; i++) { + _mint(recipients[i], amounts[i]); + } + } + + // Old function signature for backwards compatibility + function creditGasFees( + address from, + address feeRecipient, + address, // gatewayFeeRecipient, unused + address communityFund, + uint256 refund, + uint256 tipTxFee, + uint256, // gatewayFee, unused + uint256 baseTxFee + ) + external + onlyVm + { + // Calling the new creditGasFees would make sense here, but that is not + // possible due to its calldata arguments. + _mint(from, refund); + _mint(feeRecipient, tipTxFee); + _mint(communityFund, baseTxFee); + } +} diff --git a/packages/contracts-bedrock/src/celo/CalledByVm.sol b/packages/contracts-bedrock/src/celo/CalledByVm.sol new file mode 100644 index 0000000000000..1c6e9eee2a671 --- /dev/null +++ b/packages/contracts-bedrock/src/celo/CalledByVm.sol @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.0; + +contract CalledByVm { + modifier onlyVm() { + require(msg.sender == address(0), "Only VM can call"); + _; + } +} diff --git a/packages/contracts-bedrock/src/celo/CeloPredeploys.sol b/packages/contracts-bedrock/src/celo/CeloPredeploys.sol new file mode 100644 index 0000000000000..2ca38a8457606 --- /dev/null +++ b/packages/contracts-bedrock/src/celo/CeloPredeploys.sol @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +/// @title CeloPredeploys +/// @notice Contains constant addresses for protocol contracts that are pre-deployed to the L2 system. +library CeloPredeploys { + address internal constant CELO_REGISTRY = 0x000000000000000000000000000000000000ce10; + address internal constant GOLD_TOKEN = 0x471EcE3750Da237f93B8E339c536989b8978a438; + address internal constant FEE_HANDLER = 0xcD437749E43A154C07F3553504c68fBfD56B8778; + address internal constant MENTO_FEE_HANDLER_SELLER = 0x4eFa274B7e33476C961065000D58ee09F7921A74; + address internal constant UNISWAP_FEE_HANDLER_SELLER = 0xD3aeE28548Dbb65DF03981f0dC0713BfCBd10a97; + address internal constant SORTED_ORACLES = 0xefB84935239dAcdecF7c5bA76d8dE40b077B7b33; + address internal constant ADDRESS_SORTED_LINKED_LIST_WITH_MEDIAN = 0xED477A99035d0c1e11369F1D7A4e587893cc002B; + address internal constant FEE_CURRENCY = 0x4200000000000000000000000000000000001022; + address internal constant FEE_CURRENCY_DIRECTORY = 0x9212Fb72ae65367A7c887eC4Ad9bE310BAC611BF; + address internal constant cUSD = 0x765DE816845861e75A25fCA122bb6898B8B1282a; + + /// @notice Returns the name of the predeploy at the given address. + function getName(address _addr) internal pure returns (string memory out_) { + // require(isPredeployNamespace(_addr), "Predeploys: address must be a predeploy"); + + if (_addr == CELO_REGISTRY) return "CeloRegistry"; + if (_addr == GOLD_TOKEN) return "GoldToken"; + if (_addr == FEE_HANDLER) return "FeeHandler"; + if (_addr == MENTO_FEE_HANDLER_SELLER) return "MentoFeeHandlerSeller"; + if (_addr == UNISWAP_FEE_HANDLER_SELLER) return "UniswapFeeHandlerSeller"; + if (_addr == SORTED_ORACLES) return "SortedOracles"; + if (_addr == ADDRESS_SORTED_LINKED_LIST_WITH_MEDIAN) return "AddressSortedLinkedListWithMedian"; + if (_addr == FEE_CURRENCY) return "FeeCurrency"; + if (_addr == FEE_CURRENCY_DIRECTORY) return "FeeCurrencyDirectory"; + if (_addr == cUSD) return "cUSD"; + + revert("Predeploys: unnamed predeploy"); + } +} diff --git a/packages/contracts-bedrock/src/celo/CeloRegistry.sol b/packages/contracts-bedrock/src/celo/CeloRegistry.sol new file mode 100644 index 0000000000000..5afdc79c091c1 --- /dev/null +++ b/packages/contracts-bedrock/src/celo/CeloRegistry.sol @@ -0,0 +1,95 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "../../lib/openzeppelin-contracts/contracts/access/Ownable.sol"; + +import "./interfaces/ICeloRegistry.sol"; +import "./Initializable.sol"; + +/** + * @title Routes identifiers to addresses. + */ +contract CeloRegistry is ICeloRegistry, Ownable, Initializable { + mapping(bytes32 => address) public registry; + + event RegistryUpdated(string identifier, bytes32 indexed identifierHash, address indexed addr); + + /** + * @notice Sets initialized == true on implementation contracts + * @param test Set to true to skip implementation initialization + */ + constructor(bool test) Initializable(test) { } + + /** + * @notice Used in place of the constructor to allow the contract to be upgradable via proxy. + */ + function initialize() external initializer { + _transferOwnership(msg.sender); + } + + /** + * @notice Associates the given address with the given identifier. + * @param identifier Identifier of contract whose address we want to set. + * @param addr Address of contract. + */ + function setAddressFor(string calldata identifier, address addr) external onlyOwner { + bytes32 identifierHash = keccak256(abi.encodePacked(identifier)); + registry[identifierHash] = addr; + emit RegistryUpdated(identifier, identifierHash, addr); + } + + /** + * @notice Gets address associated with the given identifierHash. + * @param identifierHash Identifier hash of contract whose address we want to look up. + * @dev Throws if address not set. + */ + function getAddressForOrDie(bytes32 identifierHash) external view returns (address) { + require(registry[identifierHash] != address(0), "identifier has no registry entry"); + return registry[identifierHash]; + } + + /** + * @notice Gets address associated with the given identifierHash. + * @param identifierHash Identifier hash of contract whose address we want to look up. + */ + function getAddressFor(bytes32 identifierHash) external view returns (address) { + return registry[identifierHash]; + } + + /** + * @notice Gets address associated with the given identifier. + * @param identifier Identifier of contract whose address we want to look up. + * @dev Throws if address not set. + */ + function getAddressForStringOrDie(string calldata identifier) external view returns (address) { + bytes32 identifierHash = keccak256(abi.encodePacked(identifier)); + require(registry[identifierHash] != address(0), "identifier has no registry entry"); + return registry[identifierHash]; + } + + /** + * @notice Gets address associated with the given identifier. + * @param identifier Identifier of contract whose address we want to look up. + */ + function getAddressForString(string calldata identifier) external view returns (address) { + bytes32 identifierHash = keccak256(abi.encodePacked(identifier)); + return registry[identifierHash]; + } + + /** + * @notice Iterates over provided array of identifiers, getting the address for each. + * Returns true if `sender` matches the address of one of the provided identifiers. + * @param identifierHashes Array of hashes of approved identifiers. + * @param sender Address in question to verify membership. + * @return True if `sender` corresponds to the address of any of `identifiers` + * registry entries. + */ + function isOneOf(bytes32[] calldata identifierHashes, address sender) external view returns (bool) { + for (uint256 i = 0; i < identifierHashes.length; i++) { + if (registry[identifierHashes[i]] == sender) { + return true; + } + } + return false; + } +} diff --git a/packages/contracts-bedrock/src/celo/CeloTokenL1.sol b/packages/contracts-bedrock/src/celo/CeloTokenL1.sol new file mode 100644 index 0000000000000..81c0af956caa8 --- /dev/null +++ b/packages/contracts-bedrock/src/celo/CeloTokenL1.sol @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import { ERC20Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol"; + +contract CeloTokenL1 is ERC20Upgradeable { + string constant NAME = "Celo"; + string constant SYMBOL = "CELO"; + uint256 constant TOTAL_MARKET_CAP = 1000000000e18; // 1 billion CELO + + constructor() { + _disableInitializers(); + } + + function initialize(address portalProxyAddress) external initializer { + __ERC20_init(NAME, SYMBOL); + _mint(portalProxyAddress, TOTAL_MARKET_CAP); + } +} diff --git a/packages/contracts-bedrock/src/celo/CeloTokenL1Permit.sol b/packages/contracts-bedrock/src/celo/CeloTokenL1Permit.sol new file mode 100644 index 0000000000000..2d095866ba422 --- /dev/null +++ b/packages/contracts-bedrock/src/celo/CeloTokenL1Permit.sol @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import { ERC20Permit } from "@openzeppelin/contracts-v5/token/ERC20/extensions/ERC20Permit.sol"; +import { ERC20 } from "@openzeppelin/contracts-v5/token/ERC20/ERC20.sol"; + +contract CeloTokenL1Permit is ERC20Permit { + string constant NAME = "Celo"; + string constant SYMBOL = "CELO"; + uint256 constant TOTAL_MARKET_CAP = 1000000000e18; // 1 billion CELO + + constructor(address portalProxyAddress) ERC20(NAME, SYMBOL) ERC20Permit(NAME) { + _mint(portalProxyAddress, TOTAL_MARKET_CAP); + } +} diff --git a/packages/contracts-bedrock/src/celo/FeeCurrencyDirectory.sol b/packages/contracts-bedrock/src/celo/FeeCurrencyDirectory.sol new file mode 100644 index 0000000000000..21fc7ff3181a1 --- /dev/null +++ b/packages/contracts-bedrock/src/celo/FeeCurrencyDirectory.sol @@ -0,0 +1,91 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "./Initializable.sol"; +import "./interfaces/IOracle.sol"; +import "./interfaces/IFeeCurrencyDirectory.sol"; +import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; + +contract FeeCurrencyDirectory is IFeeCurrencyDirectory, Initializable, Ownable { + mapping(address => CurrencyConfig) public currencies; + address[] private currencyList; + + constructor(bool test) Initializable(test) { } + + /** + * @notice Initializes the contract with the owner set. + */ + function initialize() public initializer { + _transferOwnership(msg.sender); + } + + /** + * @notice Sets the currency configuration for a token. + * @dev This action can only be performed by the contract owner. + * @param token The token address. + * @param oracle The oracle address for price fetching. + * @param intrinsicGas The intrinsic gas value for transactions. + */ + function setCurrencyConfig(address token, address oracle, uint256 intrinsicGas) external onlyOwner { + require(oracle != address(0), "Oracle address cannot be zero"); + require(intrinsicGas > 0, "Intrinsic gas cannot be zero"); + require(currencies[token].oracle == address(0), "Currency already in the directory"); + + currencies[token] = CurrencyConfig({ oracle: oracle, intrinsicGas: intrinsicGas }); + currencyList.push(token); + } + + /** + * @notice Removes a token from the directory. + * @dev This action can only be performed by the contract owner. + * @param token The token address to remove. + * @param index The index in the list of directory currencies. + */ + function removeCurrencies(address token, uint256 index) external onlyOwner { + require(index < currencyList.length, "Index out of bounds"); + require(currencyList[index] == token, "Index does not match token"); + + delete currencies[token]; + currencyList[index] = currencyList[currencyList.length - 1]; + currencyList.pop(); + } + + /** + * @notice Returns the list of all currency addresses. + * @return An array of addresses. + */ + function getCurrencies() public view returns (address[] memory) { + return currencyList; + } + + /** + * @notice Returns the configuration for a currency. + * @param token The address of the token. + * @return Currency configuration of the token. + */ + function getCurrencyConfig(address token) public view returns (CurrencyConfig memory) { + return currencies[token]; + } + + /** + * @notice Retrieves exchange rate between token and CELO. + * @param token The token address whose price is to be fetched. + * @return numerator The exchange rate numerator. + * @return denominator The exchange rate denominator. + */ + function getExchangeRate(address token) public view returns (uint256 numerator, uint256 denominator) { + require(currencies[token].oracle != address(0), "Currency not in the directory"); + (numerator, denominator) = IOracle(currencies[token].oracle).getExchangeRate(token); + } + + /** + * @notice Returns the storage, major, minor, and patch version of the contract. + * @return Storage version of the contract. + * @return Major version of the contract. + * @return Minor version of the contract. + * @return Patch version of the contract. + */ + function getVersionNumber() external pure returns (uint256, uint256, uint256, uint256) { + return (1, 1, 0, 0); + } +} diff --git a/packages/contracts-bedrock/src/celo/FeeHandler.sol b/packages/contracts-bedrock/src/celo/FeeHandler.sol new file mode 100644 index 0000000000000..e42cbd83f3c87 --- /dev/null +++ b/packages/contracts-bedrock/src/celo/FeeHandler.sol @@ -0,0 +1,543 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "../../lib/openzeppelin-contracts/contracts/utils/math/Math.sol"; +import "../../lib/openzeppelin-contracts/contracts/access/Ownable.sol"; +import "../../lib/openzeppelin-contracts/contracts/utils/structs/EnumerableSet.sol"; +import "../../lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; + +import "./UsingRegistry.sol"; +import "./common/Freezable.sol"; +import "./common/FixidityLib.sol"; +import "./common/Initializable.sol"; + +import "./common/interfaces/IFeeHandler.sol"; +import "./common/interfaces/IFeeHandlerSeller.sol"; + +// TODO move to IStableToken when it adds method getExchangeRegistryId +import "./interfaces/IStableTokenMento.sol"; +import "./common/interfaces/ICeloVersionedContract.sol"; +import "./common/interfaces/ICeloToken.sol"; +import "./stability/interfaces/ISortedOracles.sol"; + +// Using the minimal required signatures in the interfaces so more contracts could be compatible +import { ReentrancyGuard } from "@openzeppelin/contracts/security/ReentrancyGuard.sol"; + +// An implementation of FeeHandler as described in CIP-52 +// See https://github.com/celo-org/celo-proposals/blob/master/CIPs/cip-0052.md +contract FeeHandler is + Ownable, + Initializable, + UsingRegistry, + ICeloVersionedContract, + Freezable, + IFeeHandler, + ReentrancyGuard +{ + using FixidityLib for FixidityLib.Fraction; + using EnumerableSet for EnumerableSet.AddressSet; + + uint256 public constant FIXED1_UINT = 1000000000000000000000000; // TODO move to FIX and add check + + // Min units that can be burned + uint256 public constant MIN_BURN = 200; + + // last day the daily limits were updated + uint256 public lastLimitDay; + + FixidityLib.Fraction public burnFraction; // 80% + + address public feeBeneficiary; + + uint256 public celoToBeBurned; + + // This mapping can not be public because it contains a FixidityLib.Fraction + // and that'd be only supported with experimental features in this + // compiler version + mapping(address => TokenState) private tokenStates; + + struct TokenState { + address handler; + FixidityLib.Fraction maxSlippage; + // Max amounts that can be burned in a day for a token + uint256 dailySellLimit; + // Max amounts that can be burned today for a token + uint256 currentDaySellLimit; + uint256 toDistribute; + // Historical amounts burned by this contract + uint256 pastBurn; + } + + EnumerableSet.AddressSet private activeTokens; + + event SoldAndBurnedToken(address token, uint256 value); + event DailyLimitSet(address tokenAddress, uint256 newLimit); + event DailyLimitHit(address token, uint256 burning); + event MaxSlippageSet(address token, uint256 maxSlippage); + event DailySellLimitUpdated(uint256 amount); + event FeeBeneficiarySet(address newBeneficiary); + event BurnFractionSet(uint256 fraction); + event TokenAdded(address tokenAddress, address handlerAddress); + event TokenRemoved(address tokenAddress); + + /** + * @notice Sets initialized == true on implementation contracts. + * @param test Set to true to skip implementation initialisation. + */ + constructor(bool test) Initializable(test) { } + + /** + * @notice Used in place of the constructor to allow the contract to be upgradable via proxy. + */ + function initialize( + address _registryAddress, + address newFeeBeneficiary, + uint256 newBurnFraction, + address[] calldata tokens, + address[] calldata handlers, + uint256[] calldata newLimits, + uint256[] calldata newMaxSlippages + ) + external + initializer + { + require(tokens.length == handlers.length, "handlers length should match tokens length"); + require(tokens.length == newLimits.length, "limits length should match tokens length"); + require(tokens.length == newMaxSlippages.length, "maxSlippage length should match tokens length"); + + _transferOwnership(msg.sender); + setRegistry(_registryAddress); + _setFeeBeneficiary(newFeeBeneficiary); + _setBurnFraction(newBurnFraction); + + for (uint256 i = 0; i < tokens.length; i++) { + _addToken(tokens[i], handlers[i]); + _setDailySellLimit(tokens[i], newLimits[i]); + _setMaxSplippage(tokens[i], newMaxSlippages[i]); + } + } + + // Without this the contract cant receive Celo as native transfer + receive() external payable { } + + /** + * @dev Returns the handler address for the specified token. + * @param tokenAddress The address of the token for which to return the handler. + * @return The address of the handler contract for the specified token. + */ + function getTokenHandler(address tokenAddress) external view returns (address) { + return tokenStates[tokenAddress].handler; + } + + /** + * @dev Returns a boolean indicating whether the specified token is active or not. + * @param tokenAddress The address of the token for which to retrieve the active status. + * @return A boolean representing the active status of the specified token. + */ + function getTokenActive(address tokenAddress) external view returns (bool) { + return activeTokens.contains(tokenAddress); + } + + /** + * @dev Returns the maximum slippage percentage for the specified token. + * @param tokenAddress The address of the token for which to retrieve the maximum + * slippage percentage. + * @return The maximum slippage percentage as a uint256 value. + */ + function getTokenMaxSlippage(address tokenAddress) external view returns (uint256) { + return FixidityLib.unwrap(tokenStates[tokenAddress].maxSlippage); + } + + /** + * @dev Returns the daily burn limit for the specified token. + * @param tokenAddress The address of the token for which to retrieve the daily burn limit. + * @return The daily burn limit as a uint256 value. + */ + function getTokenDailySellLimit(address tokenAddress) external view returns (uint256) { + return tokenStates[tokenAddress].dailySellLimit; + } + + /** + * @dev Returns the current daily sell limit for the specified token. + * @param tokenAddress The address of the token for which to retrieve the current daily limit. + * @return The current daily limit as a uint256 value. + */ + function getTokenCurrentDaySellLimit(address tokenAddress) external view returns (uint256) { + return tokenStates[tokenAddress].currentDaySellLimit; + } + + /** + * @dev Returns the amount of tokens available to distribute for the specified token. + * @param tokenAddress The address of the token for which to retrieve the amount of + * tokens available to distribute. + * @return The amount of tokens available to distribute as a uint256 value. + */ + function getTokenToDistribute(address tokenAddress) external view returns (uint256) { + return tokenStates[tokenAddress].toDistribute; + } + + function getActiveTokens() public view returns (address[] memory) { + return activeTokens.values(); + } + + /** + * @dev Sets the fee beneficiary address to the specified address. + * @param beneficiary The address to set as the fee beneficiary. + */ + function setFeeBeneficiary(address beneficiary) external onlyOwner { + return _setFeeBeneficiary(beneficiary); + } + + function _setFeeBeneficiary(address beneficiary) private { + feeBeneficiary = beneficiary; + emit FeeBeneficiarySet(beneficiary); + } + + /** + * @dev Sets the burn fraction to the specified value. + * @param fraction The value to set as the burn fraction. + */ + function setBurnFraction(uint256 fraction) external onlyOwner { + return _setBurnFraction(fraction); + } + + function _setBurnFraction(uint256 newFraction) private { + FixidityLib.Fraction memory fraction = FixidityLib.wrap(newFraction); + require(FixidityLib.lte(fraction, FixidityLib.fixed1()), "Burn fraction must be less than or equal to 1"); + burnFraction = fraction; + emit BurnFractionSet(newFraction); + } + + /** + * @dev Sets the burn fraction to the specified value. Token has to have a handler set. + * @param tokenAddress The address of the token to sell + */ + function sell(address tokenAddress) external { + return _sell(tokenAddress); + } + + /** + * @dev Adds a new token to the contract with the specified token and handler addresses. + * @param tokenAddress The address of the token to add. + * @param handlerAddress The address of the handler contract for the specified token. + */ + function addToken(address tokenAddress, address handlerAddress) external onlyOwner { + _addToken(tokenAddress, handlerAddress); + } + + function _addToken(address tokenAddress, address handlerAddress) private { + require(handlerAddress != address(0), "Can't set handler to zero"); + TokenState storage tokenState = tokenStates[tokenAddress]; + tokenState.handler = handlerAddress; + + activeTokens.add(tokenAddress); + emit TokenAdded(tokenAddress, handlerAddress); + } + + /** + * @notice Allows the owner to activate a specified token. + * @param tokenAddress The address of the token to be activated. + */ + function activateToken(address tokenAddress) external onlyOwner { + _activateToken(tokenAddress); + } + + function _activateToken(address tokenAddress) private { + TokenState storage tokenState = tokenStates[tokenAddress]; + require( + tokenState.handler != address(0) || tokenAddress == registry.getAddressForOrDie(GOLD_TOKEN_REGISTRY_ID), + "Handler has to be set to activate token" + ); + activeTokens.add(tokenAddress); + } + + /** + * @dev Deactivates the specified token by marking it as inactive. + * @param tokenAddress The address of the token to deactivate. + */ + function deactivateToken(address tokenAddress) external onlyOwner { + _deactivateToken(tokenAddress); + } + + function _deactivateToken(address tokenAddress) private { + activeTokens.remove(tokenAddress); + } + + /** + * @notice Allows the owner to set a handler contract for a specified token. + * @param tokenAddress The address of the token to set the handler for. + * @param handlerAddress The address of the handler contract to be set. + */ + function setHandler(address tokenAddress, address handlerAddress) external onlyOwner { + _setHandler(tokenAddress, handlerAddress); + } + + function _setHandler(address tokenAddress, address handlerAddress) private { + require(handlerAddress != address(0), "Can't set handler to zero, use deactivateToken"); + TokenState storage tokenState = tokenStates[tokenAddress]; + tokenState.handler = handlerAddress; + } + + function removeToken(address tokenAddress) external onlyOwner { + _removeToken(tokenAddress); + } + + function _removeToken(address tokenAddress) private { + _deactivateToken(tokenAddress); + TokenState storage tokenState = tokenStates[tokenAddress]; + tokenState.handler = address(0); + emit TokenRemoved(tokenAddress); + } + + function _sell(address tokenAddress) private onlyWhenNotFrozen nonReentrant { + IERC20 token = IERC20(tokenAddress); + + TokenState storage tokenState = tokenStates[tokenAddress]; + require(tokenState.handler != address(0), "Handler has to be set to sell token"); + require(FixidityLib.unwrap(tokenState.maxSlippage) != 0, "Max slippage has to be set to sell token"); + FixidityLib.Fraction memory balanceToProcess = + FixidityLib.newFixed(token.balanceOf(address(this)) - tokenState.toDistribute); + + uint256 balanceToBurn = (burnFraction.multiply(balanceToProcess).fromFixed()); + + tokenState.toDistribute = tokenState.toDistribute + balanceToProcess.fromFixed() - balanceToBurn; + + // small numbers cause rounding errors and zero case should be skipped + if (balanceToBurn < MIN_BURN) { + return; + } + + if (dailySellLimitHit(tokenAddress, balanceToBurn)) { + // in case the limit is hit, burn the max possible + balanceToBurn = tokenState.currentDaySellLimit; + emit DailyLimitHit(tokenAddress, balanceToBurn); + } + + token.transfer(tokenState.handler, balanceToBurn); + IFeeHandlerSeller handler = IFeeHandlerSeller(tokenState.handler); + + uint256 celoReceived = handler.sell( + tokenAddress, + registry.getAddressForOrDie(GOLD_TOKEN_REGISTRY_ID), + balanceToBurn, + FixidityLib.unwrap(tokenState.maxSlippage) + ); + + celoToBeBurned = celoToBeBurned + celoReceived; + tokenState.pastBurn = tokenState.pastBurn + balanceToBurn; + updateLimits(tokenAddress, balanceToBurn); + + emit SoldAndBurnedToken(tokenAddress, balanceToBurn); + } + + /** + * @dev Distributes the available tokens for the specified token address to the fee beneficiary. + * @param tokenAddress The address of the token for which to distribute the available tokens. + */ + function distribute(address tokenAddress) external { + return _distribute(tokenAddress); + } + + function _distribute(address tokenAddress) private onlyWhenNotFrozen nonReentrant { + require(feeBeneficiary != address(0), "Can't distribute to the zero address"); + IERC20 token = IERC20(tokenAddress); + uint256 tokenBalance = token.balanceOf(address(this)); + + TokenState storage tokenState = tokenStates[tokenAddress]; + require( + tokenState.handler != address(0) || tokenAddress == registry.getAddressForOrDie(GOLD_TOKEN_REGISTRY_ID), + "Handler has to be set to sell token" + ); + + // safty check to avoid a revert due balance + uint256 balanceToDistribute = Math.min(tokenBalance, tokenState.toDistribute); + + if (balanceToDistribute == 0) { + // don't distribute with zero balance + return; + } + + token.transfer(feeBeneficiary, balanceToDistribute); + tokenState.toDistribute = tokenState.toDistribute - balanceToDistribute; + } + + /** + * @notice Returns the storage, major, minor, and patch version of the contract. + * @return Storage version of the contract. + * @return Major version of the contract. + * @return Minor version of the contract. + * @return Patch version of the contract. + */ + function getVersionNumber() external pure returns (uint256, uint256, uint256, uint256) { + return (1, 1, 0, 0); + } + + /** + * @notice Allows owner to set max slippage for a token. + * @param token Address of the token to set. + * @param newMax New sllipage to set, as Fixidity fraction. + */ + function setMaxSplippage(address token, uint256 newMax) external onlyOwner { + _setMaxSplippage(token, newMax); + } + + function _setMaxSplippage(address token, uint256 newMax) private { + TokenState storage tokenState = tokenStates[token]; + require(newMax != 0, "Cannot set max slippage to zero"); + tokenState.maxSlippage = FixidityLib.wrap(newMax); + require( + FixidityLib.lte(tokenState.maxSlippage, FixidityLib.fixed1()), "Splippage must be less than or equal to 1" + ); + emit MaxSlippageSet(token, newMax); + } + + /** + * @notice Allows owner to set the daily burn limit for a token. + * @param token Address of the token to set. + * @param newLimit The new limit to set, in the token units. + */ + function setDailySellLimit(address token, uint256 newLimit) external onlyOwner { + _setDailySellLimit(token, newLimit); + } + + function _setDailySellLimit(address token, uint256 newLimit) private { + TokenState storage tokenState = tokenStates[token]; + tokenState.dailySellLimit = newLimit; + emit DailyLimitSet(token, newLimit); + } + + /** + * @dev Burns CELO tokens according to burnFraction. + */ + function burnCelo() external { + return _burnCelo(); + } + + /** + * @dev Distributes the available tokens for all registered tokens to the feeBeneficiary. + */ + function distributeAll() external { + return _distributeAll(); + } + + function _distributeAll() private { + for (uint256 i = 0; i < EnumerableSet.length(activeTokens); i++) { + address token = activeTokens.at(i); + _distribute(token); + } + // distribute Celo + _distribute(registry.getAddressForOrDie(GOLD_TOKEN_REGISTRY_ID)); + } + + /** + * @dev Distributes the available tokens for all registered tokens to the feeBeneficiary. + */ + function handleAll() external { + return _handleAll(); + } + + function _handleAll() private { + for (uint256 i = 0; i < EnumerableSet.length(activeTokens); i++) { + // calling _handle would trigger may burn Celo and distributions + // that can be just batched at the end + address token = activeTokens.at(i); + _sell(token); + } + _distributeAll(); // distributes Celo as well + _burnCelo(); + } + + /** + * @dev Distributes the the token for to the feeBeneficiary. + */ + function handle(address tokenAddress) external { + return _handle(tokenAddress); + } + + function _handle(address tokenAddress) private { + // Celo doesn't have to be exchanged for anything + if (tokenAddress != registry.getAddressForOrDie(GOLD_TOKEN_REGISTRY_ID)) { + _sell(tokenAddress); + } + _burnCelo(); + _distribute(tokenAddress); + _distribute(registry.getAddressForOrDie(GOLD_TOKEN_REGISTRY_ID)); + } + + /** + * @notice Burns all the Celo balance of this contract. + */ + function _burnCelo() private { + TokenState storage tokenState = tokenStates[registry.getAddressForOrDie(GOLD_TOKEN_REGISTRY_ID)]; + ICeloToken celo = ICeloToken(registry.getAddressForOrDie(GOLD_TOKEN_REGISTRY_ID)); + + uint256 balanceOfCelo = address(this).balance; + + uint256 balanceToProcess = balanceOfCelo - tokenState.toDistribute - celoToBeBurned; + uint256 currentBalanceToBurn = FixidityLib.newFixed(balanceToProcess).multiply(burnFraction).fromFixed(); + uint256 totalBalanceToBurn = currentBalanceToBurn + celoToBeBurned; + celo.burn(totalBalanceToBurn); + + celoToBeBurned = 0; + tokenState.toDistribute = tokenState.toDistribute + balanceToProcess - currentBalanceToBurn; + } + + /** + * @param token The address of the token to query. + * @return The amount burned for a token. + */ + function getPastBurnForToken(address token) external view returns (uint256) { + return tokenStates[token].pastBurn; + } + + /** + * @param token The address of the token to query. + * @param amountToBurn The amount of the token to burn. + * @return Returns true if burning amountToBurn would exceed the daily limit. + */ + function dailySellLimitHit(address token, uint256 amountToBurn) public returns (bool) { + TokenState storage tokenState = tokenStates[token]; + + if (tokenState.dailySellLimit == 0) { + // if no limit set, assume uncapped + return false; + } + + uint256 currentDay = block.timestamp / 1 days; + // Pattern borrowed from Reserve.sol + if (currentDay > lastLimitDay) { + lastLimitDay = currentDay; + tokenState.currentDaySellLimit = tokenState.dailySellLimit; + } + + return amountToBurn >= tokenState.currentDaySellLimit; + } + + /** + * @notice Updates the current day limit for a token. + * @param token The address of the token to query. + * @param amountBurned the amount of the token that was burned. + */ + function updateLimits(address token, uint256 amountBurned) private { + TokenState storage tokenState = tokenStates[token]; + + if (tokenState.dailySellLimit == 0) { + // if no limit set, assume uncapped + return; + } + tokenState.currentDaySellLimit = tokenState.currentDaySellLimit - amountBurned; + emit DailySellLimitUpdated(amountBurned); + } + + /** + * @notice Allows owner to transfer tokens of this contract. It's meant for governance to + * trigger use cases not contemplated in this contract. + * @param token The address of the token to transfer. + * @param recipient The address of the recipient to transfer the tokens to. + * @param value The amount of tokens to transfer. + * @return A boolean indicating whether the transfer was successful or not. + */ + function transfer(address token, address recipient, uint256 value) external onlyOwner returns (bool) { + return IERC20(token).transfer(recipient, value); + } +} diff --git a/packages/contracts-bedrock/src/celo/FeeHandlerSeller.sol b/packages/contracts-bedrock/src/celo/FeeHandlerSeller.sol new file mode 100644 index 0000000000000..bfcc2a07aa6cb --- /dev/null +++ b/packages/contracts-bedrock/src/celo/FeeHandlerSeller.sol @@ -0,0 +1,92 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.0; + +import "./common/FixidityLib.sol"; +import "../../lib/openzeppelin-contracts/contracts/access/Ownable.sol"; +import "../../lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; +import "./UsingRegistry.sol"; +import "./common/Initializable.sol"; + +// Abstract class for a FeeHandlerSeller, as defined in CIP-52 +// https://github.com/celo-org/celo-proposals/blob/master/CIPs/cip-0052.md +abstract contract FeeHandlerSeller is Ownable, Initializable, UsingRegistry { + using FixidityLib for FixidityLib.Fraction; + + // Address of the token + // Minimal number of reports in SortedOracles contract + mapping(address => uint256) public minimumReports; + + event MinimumReportsSet(address tokenAddress, uint256 minimumReports); + event TokenSold(address soldTokenAddress, address boughtTokenAddress, uint256 amount); + + constructor(bool testingDeployment) Initializable(testingDeployment) { } + + function initialize( + address _registryAddress, + address[] calldata tokenAddresses, + uint256[] calldata newMininumReports + ) + external + initializer + { + _transferOwnership(msg.sender); + setRegistry(_registryAddress); + + for (uint256 i = 0; i < tokenAddresses.length; i++) { + _setMinimumReports(tokenAddresses[i], newMininumReports[i]); + } + } + + /** + * @notice Allows owner to set the minimum number of reports required. + * @param newMininumReports The new update minimum number of reports required. + */ + function setMinimumReports(address tokenAddress, uint256 newMininumReports) public onlyOwner { + _setMinimumReports(tokenAddress, newMininumReports); + } + + function _setMinimumReports(address tokenAddress, uint256 newMininumReports) internal { + minimumReports[tokenAddress] = newMininumReports; + emit MinimumReportsSet(tokenAddress, newMininumReports); + } + + /** + * @dev Calculates the minimum amount of tokens that should be received for the specified + * amount with the given mid-price and maximum slippage. + * @param midPriceNumerator The numerator of the mid-price for the token pair. + * @param midPriceDenominator The denominator of the mid-price for the token pair. + * @param amount The amount of tokens to be exchanged. + * @param maxSlippage The maximum slippage percentage as a fraction of the mid-price. + * @return The minimum amount of tokens that should be received as a uint256 value. + */ + function calculateMinAmount( + uint256 midPriceNumerator, + uint256 midPriceDenominator, + uint256 amount, + uint256 maxSlippage // as fraction + ) + public + pure + returns (uint256) + { + FixidityLib.Fraction memory maxSlippageFraction = FixidityLib.wrap(maxSlippage); + + FixidityLib.Fraction memory price = FixidityLib.newFixedFraction(midPriceNumerator, midPriceDenominator); + FixidityLib.Fraction memory amountFraction = FixidityLib.newFixed(amount); + FixidityLib.Fraction memory totalAmount = price.multiply(amountFraction); + + return totalAmount.subtract(price.multiply(maxSlippageFraction).multiply(amountFraction)).fromFixed(); + } + + /** + * @notice Allows owner to transfer tokens of this contract. It's meant for governance to + * trigger use cases not contemplated in this contract. + * @param token The address of the token to transfer. + * @param amount The amount of tokens to transfer. + * @param to The address of the recipient to transfer the tokens to. + * @return A boolean indicating whether the transfer was successful or not. + */ + function transfer(address token, uint256 amount, address to) external onlyOwner returns (bool) { + return IERC20(token).transfer(to, amount); + } +} diff --git a/packages/contracts-bedrock/src/celo/GoldToken.sol b/packages/contracts-bedrock/src/celo/GoldToken.sol new file mode 100644 index 0000000000000..96ab28c0615cd --- /dev/null +++ b/packages/contracts-bedrock/src/celo/GoldToken.sol @@ -0,0 +1,272 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "../../lib/openzeppelin-contracts/contracts/access/Ownable.sol"; +import "../../lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; + +import "./UsingRegistry.sol"; +import "./CalledByVm.sol"; +import "./Initializable.sol"; +import "./interfaces/ICeloToken.sol"; +import "./common/interfaces/ICeloVersionedContract.sol"; + +contract GoldToken is Initializable, CalledByVm, UsingRegistry, IERC20, ICeloToken, ICeloVersionedContract { + // Address of the TRANSFER precompiled contract. + // solhint-disable state-visibility + address constant TRANSFER = address(0xff - 2); + string constant NAME = "Celo native asset"; + string constant SYMBOL = "CELO"; + uint8 constant DECIMALS = 18; + uint256 internal totalSupply_; + // solhint-enable state-visibility + + mapping(address => mapping(address => uint256)) internal allowed; + + // Burn address is 0xdEaD because truffle is having buggy behaviour with the zero address + address constant BURN_ADDRESS = address(0x000000000000000000000000000000000000dEaD); + + event TransferComment(string comment); + + /** + * @notice Sets initialized == true on implementation contracts + * @param test Set to true to skip implementation initialization + */ + constructor(bool test) Initializable(test) { } + + /** + * @notice Returns the storage, major, minor, and patch version of the contract. + * @return Storage version of the contract. + * @return Major version of the contract. + * @return Minor version of the contract. + * @return Patch version of the contract. + */ + function getVersionNumber() external pure returns (uint256, uint256, uint256, uint256) { + return (1, 1, 2, 0); + } + + /** + * @notice Used in place of the constructor to allow the contract to be upgradable via proxy. + * @param registryAddress Address of the Registry contract. + */ + function initialize(address registryAddress) external initializer { + totalSupply_ = 0; + _transferOwnership(msg.sender); + setRegistry(registryAddress); + } + + /** + * @notice Transfers CELO from one address to another. + * @param to The address to transfer CELO to. + * @param value The amount of CELO to transfer. + * @return True if the transaction succeeds. + */ + // solhint-disable-next-line no-simple-event-func-name + function transfer(address to, uint256 value) external returns (bool) { + return _transferWithCheck(to, value); + } + + /** + * @notice Transfers CELO from one address to another with a comment. + * @param to The address to transfer CELO to. + * @param value The amount of CELO to transfer. + * @param comment The transfer comment + * @return True if the transaction succeeds. + */ + function transferWithComment(address to, uint256 value, string calldata comment) external returns (bool) { + bool succeeded = _transferWithCheck(to, value); + emit TransferComment(comment); + return succeeded; + } + + /** + * @notice This function allows a user to burn a specific amount of tokens. + * Burning is implemented by sending tokens to the burn address. + * @param value: The amount of CELO to burn. + * @return True if burn was successful. + */ + function burn(uint256 value) external returns (bool) { + // not using transferWithCheck as the burn address can potentially be the zero address + return _transfer(BURN_ADDRESS, value); + } + + /** + * @notice Approve a user to transfer CELO on behalf of another user. + * @param spender The address which is being approved to spend CELO. + * @param value The amount of CELO approved to the spender. + * @return True if the transaction succeeds. + */ + function approve(address spender, uint256 value) external returns (bool) { + require(spender != address(0), "cannot set allowance for 0"); + allowed[msg.sender][spender] = value; + emit Approval(msg.sender, spender, value); + return true; + } + + /** + * @notice Increases the allowance of another user. + * @param spender The address which is being approved to spend CELO. + * @param value The increment of the amount of CELO approved to the spender. + * @return True if the transaction succeeds. + */ + function increaseAllowance(address spender, uint256 value) external returns (bool) { + require(spender != address(0), "cannot set allowance for 0"); + uint256 oldValue = allowed[msg.sender][spender]; + uint256 newValue = oldValue + value; + allowed[msg.sender][spender] = newValue; + emit Approval(msg.sender, spender, newValue); + return true; + } + + /** + * @notice Decreases the allowance of another user. + * @param spender The address which is being approved to spend CELO. + * @param value The decrement of the amount of CELO approved to the spender. + * @return True if the transaction succeeds. + */ + function decreaseAllowance(address spender, uint256 value) external returns (bool) { + uint256 oldValue = allowed[msg.sender][spender]; + uint256 newValue = oldValue - value; + allowed[msg.sender][spender] = newValue; + emit Approval(msg.sender, spender, newValue); + return true; + } + + /** + * @notice Transfers CELO from one address to another on behalf of a user. + * @param from The address to transfer CELO from. + * @param to The address to transfer CELO to. + * @param value The amount of CELO to transfer. + * @return True if the transaction succeeds. + */ + function transferFrom(address from, address to, uint256 value) external returns (bool) { + require(to != address(0), "transfer attempted to reserved address 0x0"); + require(value <= balanceOf(from), "transfer value exceeded balance of sender"); + require(value <= allowed[from][msg.sender], "transfer value exceeded sender's allowance for spender"); + + bool success; + (success,) = TRANSFER.call{ value: 0, gas: gasleft() }(abi.encode(from, to, value)); + require(success, "CELO transfer failed"); + + allowed[from][msg.sender] = allowed[from][msg.sender] - value; + emit Transfer(from, to, value); + return true; + } + + /** + * @notice Mints new CELO and gives it to 'to'. + * @param to The account for which to mint tokens. + * @param value The amount of CELO to mint. + */ + function mint(address to, uint256 value) external onlyVm returns (bool) { + if (value == 0) { + return true; + } + + require(to != address(0), "mint attempted to reserved address 0x0"); + totalSupply_ = totalSupply_ + value; + + bool success; + (success,) = TRANSFER.call{ value: 0, gas: gasleft() }(abi.encode(address(0), to, value)); + require(success, "CELO transfer failed"); + + emit Transfer(address(0), to, value); + return true; + } + + /** + * @return The name of the CELO token. + */ + function name() external pure returns (string memory) { + return NAME; + } + + /** + * @return The symbol of the CELO token. + */ + function symbol() external pure returns (string memory) { + return SYMBOL; + } + + /** + * @return The number of decimal places to which CELO is divisible. + */ + function decimals() external pure returns (uint8) { + return DECIMALS; + } + + /** + * @return The total amount of CELO in existence, including what the burn address holds. + */ + function totalSupply() external view returns (uint256) { + return totalSupply_; + } + + /** + * @return The total amount of CELO in existence, not including what the burn address holds. + */ + function circulatingSupply() external view returns (uint256) { + return totalSupply_ - getBurnedAmount() - balanceOf(address(0)); + } + + /** + * @notice Gets the amount of owner's CELO allowed to be spent by spender. + * @param owner The owner of the CELO. + * @param spender The spender of the CELO. + * @return The amount of CELO owner is allowing spender to spend. + */ + function allowance(address owner, address spender) external view returns (uint256) { + return allowed[owner][spender]; + } + + /** + * @notice Increases the variable for total amount of CELO in existence. + * @param amount The amount to increase counter by + */ + function increaseSupply(uint256 amount) external onlyVm { + totalSupply_ = totalSupply_ + amount; + } + + /** + * @notice Gets the amount of CELO that has been burned. + * @return The total amount of Celo that has been sent to the burn address. + */ + function getBurnedAmount() public view returns (uint256) { + return balanceOf(BURN_ADDRESS); + } + + /** + * @notice Gets the balance of the specified address. + * @param owner The address to query the balance of. + * @return The balance of the specified address. + */ + function balanceOf(address owner) public view returns (uint256) { + return owner.balance; + } + + /** + * @notice internal CELO transfer from one address to another. + * @param to The address to transfer CELO to. + * @param value The amount of CELO to transfer. + * @return True if the transaction succeeds. + */ + function _transfer(address to, uint256 value) internal returns (bool) { + require(value <= balanceOf(msg.sender), "transfer value exceeded balance of sender"); + + bool success; + (success,) = TRANSFER.call{ value: 0, gas: gasleft() }(abi.encode(msg.sender, to, value)); + require(success, "CELO transfer failed"); + emit Transfer(msg.sender, to, value); + return true; + } + + /** + * @notice Internal CELO transfer from one address to another. + * @param to The address to transfer CELO to. Zero address will revert. + * @param value The amount of CELO to transfer. + * @return True if the transaction succeeds. + */ + function _transferWithCheck(address to, uint256 value) internal returns (bool) { + require(to != address(0), "transfer attempted to reserved address 0x0"); + return _transfer(to, value); + } +} diff --git a/packages/contracts-bedrock/src/celo/Initializable.sol b/packages/contracts-bedrock/src/celo/Initializable.sol new file mode 100644 index 0000000000000..296ff255d4475 --- /dev/null +++ b/packages/contracts-bedrock/src/celo/Initializable.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.0; + +contract Initializable { + bool public initialized; + + modifier initializer() { + require(!initialized, "contract already initialized"); + initialized = true; + _; + } + + constructor(bool testingDeployment) { + if (!testingDeployment) { + initialized = true; + } + } +} diff --git a/packages/contracts-bedrock/src/celo/MentoFeeHandlerSeller.sol b/packages/contracts-bedrock/src/celo/MentoFeeHandlerSeller.sol new file mode 100644 index 0000000000000..58f5ec89de48c --- /dev/null +++ b/packages/contracts-bedrock/src/celo/MentoFeeHandlerSeller.sol @@ -0,0 +1,85 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "../../lib/openzeppelin-contracts/contracts/access/Ownable.sol"; +import "../../lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; + +import "./interfaces/IStableTokenMento.sol"; + +import "./common/interfaces/IFeeHandlerSeller.sol"; +import "./stability/interfaces/ISortedOracles.sol"; +import "./common/FixidityLib.sol"; +import "./common/Initializable.sol"; + +import "./FeeHandlerSeller.sol"; + +// An implementation of FeeHandlerSeller supporting interfaces compatible with +// Mento +// See https://github.com/celo-org/celo-proposals/blob/master/CIPs/cip-0052.md +contract MentoFeeHandlerSeller is FeeHandlerSeller { + using FixidityLib for FixidityLib.Fraction; + + /** + * @notice Sets initialized == true on implementation contracts. + * @param test Set to true to skip implementation initialisation. + */ + constructor(bool test) FeeHandlerSeller(test) { } + + // without this line the contract can't receive native Celo transfers + receive() external payable { } + + /** + * @notice Returns the storage, major, minor, and patch version of the contract. + * @return Storage version of the contract. + * @return Major version of the contract. + * @return Minor version of the contract. + * @return Patch version of the contract. + */ + function getVersionNumber() external pure returns (uint256, uint256, uint256, uint256) { + return (1, 1, 0, 0); + } + + function sell( + address sellTokenAddress, + address buyTokenAddress, + uint256 amount, + uint256 maxSlippage // as fraction, + ) + external + returns (uint256) + { + require( + buyTokenAddress == registry.getAddressForOrDie(GOLD_TOKEN_REGISTRY_ID), "Buy token can only be gold token" + ); + + IStableTokenMento stableToken = IStableTokenMento(sellTokenAddress); + require(amount <= stableToken.balanceOf(address(this)), "Balance of token to burn not enough"); + + address exchangeAddress = registry.getAddressForOrDie(stableToken.getExchangeRegistryId()); + + IExchange exchange = IExchange(exchangeAddress); + + uint256 minAmount = 0; + + ISortedOracles sortedOracles = getSortedOracles(); + + require( + sortedOracles.numRates(sellTokenAddress) >= minimumReports[sellTokenAddress], + "Number of reports for token not enough" + ); + + (uint256 rateNumerator, uint256 rateDenominator) = sortedOracles.medianRate(sellTokenAddress); + minAmount = calculateMinAmount(rateNumerator, rateDenominator, amount, maxSlippage); + + // TODO an upgrade would be to compare using routers as well + stableToken.approve(exchangeAddress, amount); + exchange.sell(amount, minAmount, false); + + IERC20 goldToken = getGoldToken(); + uint256 celoAmount = goldToken.balanceOf(address(this)); + goldToken.transfer(msg.sender, celoAmount); + + emit TokenSold(sellTokenAddress, buyTokenAddress, amount); + return celoAmount; + } +} diff --git a/packages/contracts-bedrock/src/celo/StableTokenV2.sol b/packages/contracts-bedrock/src/celo/StableTokenV2.sol new file mode 100644 index 0000000000000..703cb5bb060b2 --- /dev/null +++ b/packages/contracts-bedrock/src/celo/StableTokenV2.sol @@ -0,0 +1,336 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import { ERC20PermitUpgradeable } from + "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/draft-ERC20PermitUpgradeable.sol"; +import { ERC20Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol"; +import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; + +import { IStableTokenV2 } from "./interfaces/IStableToken.sol"; +import { CalledByVm } from "./CalledByVm.sol"; + +/** + * @title ERC20 token with minting and burning permissioned to a broker and validators. + */ +contract StableTokenV2 is IStableTokenV2, ERC20PermitUpgradeable, CalledByVm, OwnableUpgradeable { + address public validators; + address public broker; + address public exchange; + + event TransferComment(string comment); + event BrokerUpdated(address broker); + event ValidatorsUpdated(address validators); + event ExchangeUpdated(address exchange); + + /** + * @dev Restricts a function so it can only be executed by an address that's allowed to mint. + * Currently that's the broker, validators, or exchange. + */ + modifier onlyMinter() { + address sender = _msgSender(); + require(sender == broker || sender == validators || sender == exchange, "StableTokenV2: not allowed to mint"); + _; + } + + /** + * @dev Restricts a function so it can only be executed by an address that's allowed to burn. + * Currently that's the broker or exchange. + */ + modifier onlyBurner() { + address sender = _msgSender(); + require(sender == broker || sender == exchange, "StableTokenV2: not allowed to burn"); + _; + } + + /** + * @notice The constructor for the StableTokenV2 contract. + * @dev Should be called with disable=true in deployments when + * it's accessed through a Proxy. + * Call this with disable=false during testing, when used + * without a proxy. + * @param disable Set to true to run `_disableInitializers()` inherited from + * openzeppelin-contracts-upgradeable/Initializable.sol + */ + constructor(bool disable) { + if (disable) { + _disableInitializers(); + } + } + + /** + * @notice Initializes a StableTokenV2. + * It keeps the same signature as the original initialize() function + * in legacy/StableToken.sol + * @param _name The name of the stable token (English) + * @param _symbol A short symbol identifying the token (e.g. "cUSD") + * @param initialBalanceAddresses Array of addresses with an initial balance. + * @param initialBalanceValues Array of balance values corresponding to initialBalanceAddresses. + * deprecated-param exchangeIdentifier String identifier of exchange in registry (for specific fiat pairs) + */ + function initialize( + // slither-disable-start shadowing-local + string calldata _name, + string calldata _symbol, + // slither-disable-end shadowing-local + address[] calldata initialBalanceAddresses, + uint256[] calldata initialBalanceValues + ) + external + initializer + { + __ERC20_init_unchained(_name, _symbol); + __ERC20Permit_init(_symbol); + _transferOwnership(_msgSender()); + + require(initialBalanceAddresses.length == initialBalanceValues.length, "Array length mismatch"); + for (uint256 i = 0; i < initialBalanceAddresses.length; i += 1) { + _mint(initialBalanceAddresses[i], initialBalanceValues[i]); + } + } + + /** + * @notice Initializes a StableTokenV2 contract + * when upgrading from legacy/StableToken.sol. + * It sets the addresses that were previously read from the Registry. + * It runs the ERC20PermitUpgradeable initializer. + * @dev This function is only callable once. + * @param _broker The address of the Broker contract. + * @param _validators The address of the Validators contract. + * @param _exchange The address of the Exchange contract. + */ + function initializeV2( + address _broker, + address _validators, + address _exchange + ) + external + reinitializer(2) + onlyOwner + { + _setBroker(_broker); + _setValidators(_validators); + _setExchange(_exchange); + __ERC20Permit_init(symbol()); + } + + /** + * @notice Sets the address of the Broker contract. + * @dev This function is only callable by the owner. + * @param _broker The address of the Broker contract. + */ + function setBroker(address _broker) external onlyOwner { + _setBroker(_broker); + } + + /** + * @notice Sets the address of the Validators contract. + * @dev This function is only callable by the owner. + * @param _validators The address of the Validators contract. + */ + function setValidators(address _validators) external onlyOwner { + _setValidators(_validators); + } + + /** + * @notice Sets the address of the Exchange contract. + * @dev This function is only callable by the owner. + * @param _exchange The address of the Exchange contract. + */ + function setExchange(address _exchange) external onlyOwner { + _setExchange(_exchange); + } + + /** + * @notice Transfer token for a specified address + * @param to The address to transfer to. + * @param value The amount to be transferred. + * @param comment The transfer comment. + * @return True if the transaction succeeds. + */ + function transferWithComment(address to, uint256 value, string calldata comment) external returns (bool) { + emit TransferComment(comment); + return transfer(to, value); + } + + /** + * @notice Mints new StableToken and gives it to 'to'. + * @param to The account for which to mint tokens. + * @param value The amount of StableToken to mint. + */ + function mint(address to, uint256 value) external onlyMinter returns (bool) { + _mint(to, value); + return true; + } + + /** + * @notice Burns StableToken from the balance of msg.sender. + * @param value The amount of StableToken to burn. + */ + function burn(uint256 value) external onlyBurner returns (bool) { + _burn(msg.sender, value); + return true; + } + + /** + * @notice Set the address of the Broker contract and emit an event + * @param _broker The address of the Broker contract. + */ + function _setBroker(address _broker) internal { + broker = _broker; + emit BrokerUpdated(_broker); + } + + /** + * @notice Set the address of the Validators contract and emit an event + * @param _validators The address of the Validators contract. + */ + function _setValidators(address _validators) internal { + validators = _validators; + emit ValidatorsUpdated(_validators); + } + + /** + * @notice Set the address of the Exchange contract and emit an event + * @param _exchange The address of the Exchange contract. + */ + function _setExchange(address _exchange) internal { + exchange = _exchange; + emit ExchangeUpdated(_exchange); + } + + /// @inheritdoc ERC20Upgradeable + function transferFrom( + address from, + address to, + uint256 amount + ) + public + override(ERC20Upgradeable, IStableTokenV2) + returns (bool) + { + return ERC20Upgradeable.transferFrom(from, to, amount); + } + + /// @inheritdoc ERC20Upgradeable + function transfer(address to, uint256 amount) public override(ERC20Upgradeable, IStableTokenV2) returns (bool) { + return ERC20Upgradeable.transfer(to, amount); + } + + /// @inheritdoc ERC20Upgradeable + function balanceOf(address account) public view override(ERC20Upgradeable, IStableTokenV2) returns (uint256) { + return ERC20Upgradeable.balanceOf(account); + } + + /// @inheritdoc ERC20Upgradeable + function approve( + address spender, + uint256 amount + ) + public + override(ERC20Upgradeable, IStableTokenV2) + returns (bool) + { + return ERC20Upgradeable.approve(spender, amount); + } + + /// @inheritdoc ERC20Upgradeable + function allowance( + address owner, + address spender + ) + public + view + override(ERC20Upgradeable, IStableTokenV2) + returns (uint256) + { + return ERC20Upgradeable.allowance(owner, spender); + } + + /// @inheritdoc ERC20Upgradeable + function totalSupply() public view override(ERC20Upgradeable, IStableTokenV2) returns (uint256) { + return ERC20Upgradeable.totalSupply(); + } + + /// @inheritdoc ERC20PermitUpgradeable + function permit( + address owner, + address spender, + uint256 value, + uint256 deadline, + uint8 v, + bytes32 r, + bytes32 s + ) + public + override(ERC20PermitUpgradeable, IStableTokenV2) + { + ERC20PermitUpgradeable.permit(owner, spender, value, deadline, v, r, s); + } + + /** + * @notice Reserve balance for making payments for gas in this StableToken currency. + * @param from The account to reserve balance from + * @param value The amount of balance to reserve + * @dev Note that this function is called by the protocol when paying for tx fees in this + * currency. After the tx is executed, gas is refunded to the sender and credited to the + * various tx fee recipients via a call to `creditGasFees`. + */ + function debitGasFees(address from, uint256 value) external onlyVm { + _burn(from, value); + } + + /** + * @notice Alternative function to credit balance after making payments + * for gas in this StableToken currency. + * @param from The account to debit balance from + * @param feeRecipient Coinbase address + * @param gatewayFeeRecipient Gateway address + * @param communityFund Community fund address + * @param refund amount to be refunded by the VM + * @param tipTxFee Coinbase fee + * @param baseTxFee Community fund fee + * @param gatewayFee Gateway fee + * @dev Note that this function is called by the protocol when paying for tx fees in this + * currency. Before the tx is executed, gas is debited from the sender via a call to + * `debitGasFees`. + */ + function creditGasFees( + address from, + address feeRecipient, + address gatewayFeeRecipient, + address communityFund, + uint256 refund, + uint256 tipTxFee, + uint256 gatewayFee, + uint256 baseTxFee + ) + external + onlyVm + { + // slither-disable-next-line uninitialized-local + uint256 amountToBurn; + _mint(from, refund + tipTxFee + gatewayFee + baseTxFee); + + if (feeRecipient != address(0)) { + _transfer(from, feeRecipient, tipTxFee); + } else if (tipTxFee > 0) { + amountToBurn += tipTxFee; + } + + if (gatewayFeeRecipient != address(0)) { + _transfer(from, gatewayFeeRecipient, gatewayFee); + } else if (gatewayFee > 0) { + amountToBurn += gatewayFee; + } + + if (communityFund != address(0)) { + _transfer(from, communityFund, baseTxFee); + } else if (baseTxFee > 0) { + amountToBurn += baseTxFee; + } + + if (amountToBurn > 0) { + _burn(from, amountToBurn); + } + } +} diff --git a/packages/contracts-bedrock/src/celo/UniswapFeeHandlerSeller.sol b/packages/contracts-bedrock/src/celo/UniswapFeeHandlerSeller.sol new file mode 100644 index 0000000000000..279e022bbac33 --- /dev/null +++ b/packages/contracts-bedrock/src/celo/UniswapFeeHandlerSeller.sol @@ -0,0 +1,193 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "../../lib/openzeppelin-contracts/contracts/utils/math/Math.sol"; +import "../../lib/openzeppelin-contracts/contracts/access/Ownable.sol"; +import "../../lib/openzeppelin-contracts/contracts/utils/structs/EnumerableSet.sol"; +import "../../lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; + +import "./UsingRegistry.sol"; + +import "./common/interfaces/IFeeHandlerSeller.sol"; +import "./stability/interfaces/ISortedOracles.sol"; +import "./common/FixidityLib.sol"; +import "./common/Initializable.sol"; +import "./FeeHandlerSeller.sol"; + +import "./uniswap/interfaces/IUniswapV2RouterMin.sol"; +import "./uniswap/interfaces/IUniswapV2FactoryMin.sol"; + +// An implementation of FeeHandlerSeller supporting interfaces compatible with +// Uniswap V2 API +// See https://github.com/celo-org/celo-proposals/blob/master/CIPs/cip-0052.md +contract UniswapFeeHandlerSeller is FeeHandlerSeller { + using FixidityLib for FixidityLib.Fraction; + using EnumerableSet for EnumerableSet.AddressSet; + + uint256 constant MAX_TIMESTAMP_BLOCK_EXCHANGE = 20; + uint256 constant MAX_NUMBER_ROUTERS_PER_TOKEN = 3; + mapping(address => EnumerableSet.AddressSet) private routerAddresses; + + event ReceivedQuote(address indexed tokneAddress, address indexed router, uint256 quote); + event RouterUsed(address router); + event RouterAddressSet(address token, address router); + event RouterAddressRemoved(address token, address router); + + /** + * @notice Sets initialized == true on implementation contracts. + * @param test Set to true to skip implementation initialisation. + */ + constructor(bool test) FeeHandlerSeller(test) { } + + // without this line the contract can't receive native Celo transfers + receive() external payable { } + + /** + * @notice Returns the storage, major, minor, and patch version of the contract. + * @return Storage version of the contract. + * @return Major version of the contract. + * @return Minor version of the contract. + * @return Patch version of the contract. + */ + function getVersionNumber() external pure returns (uint256, uint256, uint256, uint256) { + return (1, 1, 0, 0); + } + + /** + * @notice Allows owner to set the router for a token. + * @param token Address of the token to set. + * @param router The new router. + */ + function setRouter(address token, address router) external onlyOwner { + _setRouter(token, router); + } + + function _setRouter(address token, address router) private { + require(router != address(0), "Router can't be address zero"); + routerAddresses[token].add(router); + require(routerAddresses[token].values().length <= MAX_NUMBER_ROUTERS_PER_TOKEN, "Max number of routers reached"); + emit RouterAddressSet(token, router); + } + + /** + * @notice Allows owner to remove a router for a token. + * @param token Address of the token. + * @param router Address of the router to remove. + */ + function removeRouter(address token, address router) external onlyOwner { + routerAddresses[token].remove(router); + emit RouterAddressRemoved(token, router); + } + + /** + * @notice Get the list of routers for a token. + * @param token The address of the token to query. + * @return An array of all the allowed router. + */ + function getRoutersForToken(address token) external view returns (address[] memory) { + return routerAddresses[token].values(); + } + + /** + * @dev Calculates the minimum amount of tokens that can be received for a given amount of sell tokens, + * taking into account the slippage and the rates of the sell token and CELO token on the Uniswap V2 pair. + * @param sellTokenAddress The address of the sell token. + * @param maxSlippage The maximum slippage allowed. + * @param amount The amount of sell tokens to be traded. + * @param bestRouter The Uniswap V2 router with the best price. + * @return The minimum amount of tokens that can be received. + */ + function calculateAllMinAmount( + address sellTokenAddress, + uint256 maxSlippage, + uint256 amount, + IUniswapV2RouterMin bestRouter + ) + private + view + returns (uint256) + { + ISortedOracles sortedOracles = getSortedOracles(); + uint256 minReports = minimumReports[sellTokenAddress]; + + require(sortedOracles.numRates(sellTokenAddress) >= minReports, "Number of reports for token not enough"); + + uint256 minimalSortedOracles = 0; + // if minimumReports for this token is zero, assume the check is not needed + if (minReports > 0) { + (uint256 rateNumerator, uint256 rateDenominator) = sortedOracles.medianRate(sellTokenAddress); + + minimalSortedOracles = calculateMinAmount(rateNumerator, rateDenominator, amount, maxSlippage); + } + + IERC20 celoToken = getGoldToken(); + address pair = IUniswapV2FactoryMin(bestRouter.factory()).getPair(sellTokenAddress, address(celoToken)); + uint256 minAmountPair = + calculateMinAmount(IERC20(sellTokenAddress).balanceOf(pair), celoToken.balanceOf(pair), amount, maxSlippage); + + return Math.max(minAmountPair, minimalSortedOracles); + } + + // This function explicitly defines few variables because it was getting error "stack too deep" + function sell( + address sellTokenAddress, + address buyTokenAddress, + uint256 amount, + uint256 maxSlippage // as fraction, + ) + external + returns (uint256) + { + require( + buyTokenAddress == registry.getAddressForOrDie(GOLD_TOKEN_REGISTRY_ID), "Buy token can only be gold token" + ); + + require(routerAddresses[sellTokenAddress].values().length > 0, "routerAddresses should be non empty"); + + // An improvement to this function would be to allow the user to pass a path as argument + // and if it generates a better outcome that the ones enabled that gets used + // and the user gets a reward + + IERC20 celoToken = getGoldToken(); + + IUniswapV2RouterMin bestRouter; + uint256 bestRouterQuote = 0; + + address[] memory path = new address[](2); + + path[0] = sellTokenAddress; + path[1] = address(celoToken); + + for (uint256 i = 0; i < routerAddresses[sellTokenAddress].values().length; i++) { + address poolAddress = routerAddresses[sellTokenAddress].at(i); + IUniswapV2RouterMin router = IUniswapV2RouterMin(poolAddress); + + // Using the second return value becuase it's the last argument, + // the previous values show how many tokens are exchanged in each path + // so the first value would be equivalent to balanceToBurn + uint256 wouldGet = router.getAmountsOut(amount, path)[1]; + + emit ReceivedQuote(sellTokenAddress, poolAddress, wouldGet); + if (wouldGet > bestRouterQuote) { + bestRouterQuote = wouldGet; + bestRouter = router; + } + } + + require(bestRouterQuote != 0, "Can't exchange with zero quote"); + + uint256 minAmount = 0; + minAmount = calculateAllMinAmount(sellTokenAddress, maxSlippage, amount, bestRouter); + + IERC20(sellTokenAddress).approve(address(bestRouter), amount); + bestRouter.swapExactTokensForTokens( + amount, minAmount, path, address(this), block.timestamp + MAX_TIMESTAMP_BLOCK_EXCHANGE + ); + + uint256 celoAmount = celoToken.balanceOf(address(this)); + celoToken.transfer(msg.sender, celoAmount); + emit RouterUsed(address(bestRouter)); + emit TokenSold(sellTokenAddress, buyTokenAddress, amount); + return celoAmount; + } +} diff --git a/packages/contracts-bedrock/src/celo/UsingRegistry.sol b/packages/contracts-bedrock/src/celo/UsingRegistry.sol new file mode 100644 index 0000000000000..7461a631ed12f --- /dev/null +++ b/packages/contracts-bedrock/src/celo/UsingRegistry.sol @@ -0,0 +1,120 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "../../lib/openzeppelin-contracts/contracts/access/Ownable.sol"; +import "../../lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; + +import "./interfaces/IAccounts.sol"; +import "./interfaces/IFreezer.sol"; +import "./interfaces/ICeloRegistry.sol"; + +import "./governance/interfaces/IElection.sol"; +import "./governance/interfaces/IGovernance.sol"; +import "./governance/interfaces/ILockedGold.sol"; +import "./governance/interfaces/IValidators.sol"; + +import "./identity/interfaces/IRandom.sol"; +import "./identity/interfaces/IAttestations.sol"; + +import "./stability/interfaces/ISortedOracles.sol"; + +import "./mento/interfaces/IExchange.sol"; +import "./mento/interfaces/IReserve.sol"; +import "./mento/interfaces/IStableToken.sol"; + +contract UsingRegistry is Ownable { + event RegistrySet(address indexed registryAddress); + + // solhint-disable state-visibility + bytes32 constant ACCOUNTS_REGISTRY_ID = keccak256(abi.encodePacked("Accounts")); + bytes32 constant ATTESTATIONS_REGISTRY_ID = keccak256(abi.encodePacked("Attestations")); + bytes32 constant DOWNTIME_SLASHER_REGISTRY_ID = keccak256(abi.encodePacked("DowntimeSlasher")); + bytes32 constant DOUBLE_SIGNING_SLASHER_REGISTRY_ID = keccak256(abi.encodePacked("DoubleSigningSlasher")); + bytes32 constant ELECTION_REGISTRY_ID = keccak256(abi.encodePacked("Election")); + bytes32 constant EXCHANGE_REGISTRY_ID = keccak256(abi.encodePacked("Exchange")); + bytes32 constant FREEZER_REGISTRY_ID = keccak256(abi.encodePacked("Freezer")); + bytes32 constant GOLD_TOKEN_REGISTRY_ID = keccak256(abi.encodePacked("GoldToken")); + bytes32 constant GOVERNANCE_REGISTRY_ID = keccak256(abi.encodePacked("Governance")); + bytes32 constant GOVERNANCE_SLASHER_REGISTRY_ID = keccak256(abi.encodePacked("GovernanceSlasher")); + bytes32 constant LOCKED_GOLD_REGISTRY_ID = keccak256(abi.encodePacked("LockedGold")); + bytes32 constant RESERVE_REGISTRY_ID = keccak256(abi.encodePacked("Reserve")); + bytes32 constant RANDOM_REGISTRY_ID = keccak256(abi.encodePacked("Random")); + bytes32 constant SORTED_ORACLES_REGISTRY_ID = keccak256(abi.encodePacked("SortedOracles")); + bytes32 constant STABLE_TOKEN_REGISTRY_ID = keccak256(abi.encodePacked("StableToken")); + bytes32 constant VALIDATORS_REGISTRY_ID = keccak256(abi.encodePacked("Validators")); + // solhint-enable state-visibility + + ICeloRegistry public registry; + + modifier onlyRegisteredContract(bytes32 identifierHash) { + require(registry.getAddressForOrDie(identifierHash) == msg.sender, "only registered contract"); + _; + } + + modifier onlyRegisteredContracts(bytes32[] memory identifierHashes) { + require(registry.isOneOf(identifierHashes, msg.sender), "only registered contracts"); + _; + } + + /** + * @notice Updates the address pointing to a Registry contract. + * @param registryAddress The address of a registry contract for routing to other contracts. + */ + function setRegistry(address registryAddress) public onlyOwner { + require(registryAddress != address(0), "Cannot register the null address"); + registry = ICeloRegistry(registryAddress); + emit RegistrySet(registryAddress); + } + + function getAccounts() internal view returns (IAccounts) { + return IAccounts(registry.getAddressForOrDie(ACCOUNTS_REGISTRY_ID)); + } + + function getAttestations() internal view returns (IAttestations) { + return IAttestations(registry.getAddressForOrDie(ATTESTATIONS_REGISTRY_ID)); + } + + function getElection() internal view returns (IElection) { + return IElection(registry.getAddressForOrDie(ELECTION_REGISTRY_ID)); + } + + function getExchange() internal view returns (IExchange) { + return IExchange(registry.getAddressForOrDie(EXCHANGE_REGISTRY_ID)); + } + + function getFreezer() internal view returns (IFreezer) { + return IFreezer(registry.getAddressForOrDie(FREEZER_REGISTRY_ID)); + } + + function getGoldToken() internal view returns (IERC20) { + return IERC20(registry.getAddressForOrDie(GOLD_TOKEN_REGISTRY_ID)); + } + + function getGovernance() internal view returns (IGovernance) { + return IGovernance(registry.getAddressForOrDie(GOVERNANCE_REGISTRY_ID)); + } + + function getLockedGold() internal view returns (ILockedGold) { + return ILockedGold(registry.getAddressForOrDie(LOCKED_GOLD_REGISTRY_ID)); + } + + function getRandom() internal view returns (IRandom) { + return IRandom(registry.getAddressForOrDie(RANDOM_REGISTRY_ID)); + } + + function getReserve() internal view returns (IReserve) { + return IReserve(registry.getAddressForOrDie(RESERVE_REGISTRY_ID)); + } + + function getSortedOracles() internal view returns (ISortedOracles) { + return ISortedOracles(registry.getAddressForOrDie(SORTED_ORACLES_REGISTRY_ID)); + } + + function getStableToken() internal view returns (IStableToken) { + return IStableToken(registry.getAddressForOrDie(STABLE_TOKEN_REGISTRY_ID)); + } + + function getValidators() internal view returns (IValidators) { + return IValidators(registry.getAddressForOrDie(VALIDATORS_REGISTRY_ID)); + } +} diff --git a/packages/contracts-bedrock/src/celo/common/FixidityLib.sol b/packages/contracts-bedrock/src/celo/common/FixidityLib.sol new file mode 100644 index 0000000000000..d64dfaa1088e0 --- /dev/null +++ b/packages/contracts-bedrock/src/celo/common/FixidityLib.sol @@ -0,0 +1,288 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.0; + +/** + * @title FixidityLib + * @author Gadi Guy, Alberto Cuesta Canada + * @notice This library provides fixed point arithmetic with protection against + * overflow. + * All operations are done with uint256 and the operands must have been created + * with any of the newFrom* functions, which shift the comma digits() to the + * right and check for limits, or with wrap() which expects a number already + * in the internal representation of a fraction. + * When using this library be sure to use maxNewFixed() as the upper limit for + * creation of fixed point numbers. + * @dev All contained functions are pure and thus marked internal to be inlined + * on consuming contracts at compile time for gas efficiency. + */ +library FixidityLib { + struct Fraction { + uint256 value; + } + + /** + * @notice Number of positions that the comma is shifted to the right. + */ + function digits() internal pure returns (uint8) { + return 24; + } + + uint256 private constant FIXED1_UINT = 1000000000000000000000000; + + /** + * @notice This is 1 in the fixed point units used in this library. + * @dev Test fixed1() equals 10^digits() + * Hardcoded to 24 digits. + */ + function fixed1() internal pure returns (Fraction memory) { + return Fraction(FIXED1_UINT); + } + + /** + * @notice Wrap a uint256 that represents a 24-decimal fraction in a Fraction + * struct. + * @param x Number that already represents a 24-decimal fraction. + * @return A Fraction struct with contents x. + */ + function wrap(uint256 x) internal pure returns (Fraction memory) { + return Fraction(x); + } + + /** + * @notice Unwraps the uint256 inside of a Fraction struct. + */ + function unwrap(Fraction memory x) internal pure returns (uint256) { + return x.value; + } + + /** + * @notice The amount of decimals lost on each multiplication operand. + * @dev Test mulPrecision() equals sqrt(fixed1) + */ + function mulPrecision() internal pure returns (uint256) { + return 1000000000000; + } + + /** + * @notice Maximum value that can be converted to fixed point. Optimize for deployment. + * @dev + * Test maxNewFixed() equals maxUint256() / fixed1() + */ + function maxNewFixed() internal pure returns (uint256) { + return 115792089237316195423570985008687907853269984665640564; + } + + /** + * @notice Converts a uint256 to fixed point Fraction + * @dev Test newFixed(0) returns 0 + * Test newFixed(1) returns fixed1() + * Test newFixed(maxNewFixed()) returns maxNewFixed() * fixed1() + * Test newFixed(maxNewFixed()+1) fails + */ + function newFixed(uint256 x) internal pure returns (Fraction memory) { + require(x <= maxNewFixed(), "can't create fixidity number larger than maxNewFixed()"); + return Fraction(x * FIXED1_UINT); + } + + /** + * @notice Converts a uint256 in the fixed point representation of this + * library to a non decimal. All decimal digits will be truncated. + */ + function fromFixed(Fraction memory x) internal pure returns (uint256) { + return x.value / FIXED1_UINT; + } + + /** + * @notice Converts two uint256 representing a fraction to fixed point units, + * equivalent to multiplying dividend and divisor by 10^digits(). + * @param numerator numerator must be <= maxNewFixed() + * @param denominator denominator must be <= maxNewFixed() and denominator can't be 0 + * @dev + * Test newFixedFraction(1,0) fails + * Test newFixedFraction(0,1) returns 0 + * Test newFixedFraction(1,1) returns fixed1() + * Test newFixedFraction(1,fixed1()) returns 1 + */ + function newFixedFraction(uint256 numerator, uint256 denominator) internal pure returns (Fraction memory) { + Fraction memory convertedNumerator = newFixed(numerator); + Fraction memory convertedDenominator = newFixed(denominator); + return divide(convertedNumerator, convertedDenominator); + } + + /** + * @notice Returns the integer part of a fixed point number. + * @dev + * Test integer(0) returns 0 + * Test integer(fixed1()) returns fixed1() + * Test integer(newFixed(maxNewFixed())) returns maxNewFixed()*fixed1() + */ + function integer(Fraction memory x) internal pure returns (Fraction memory) { + return Fraction((x.value / FIXED1_UINT) * FIXED1_UINT); // Can't overflow + } + + /** + * @notice Returns the fractional part of a fixed point number. + * In the case of a negative number the fractional is also negative. + * @dev + * Test fractional(0) returns 0 + * Test fractional(fixed1()) returns 0 + * Test fractional(fixed1()-1) returns 10^24-1 + */ + function fractional(Fraction memory x) internal pure returns (Fraction memory) { + return Fraction(x.value - (x.value / FIXED1_UINT) * FIXED1_UINT); // Can't overflow + } + + /** + * @notice x+y. + * @dev The maximum value that can be safely used as an addition operator is defined as + * maxFixedAdd = maxUint256()-1 / 2, or + * 57896044618658097711785492504343953926634992332820282019728792003956564819967. + * Test add(maxFixedAdd,maxFixedAdd) equals maxFixedAdd + maxFixedAdd + * Test add(maxFixedAdd+1,maxFixedAdd+1) throws + */ + function add(Fraction memory x, Fraction memory y) internal pure returns (Fraction memory) { + uint256 z = x.value + y.value; + require(z >= x.value, "add overflow detected"); + return Fraction(z); + } + + /** + * @notice x-y. + * @dev + * Test subtract(6, 10) fails + */ + function subtract(Fraction memory x, Fraction memory y) internal pure returns (Fraction memory) { + require(x.value >= y.value, "substraction underflow detected"); + return Fraction(x.value - y.value); + } + + /** + * @notice x*y. If any of the operators is higher than the max multiplier value it + * might overflow. + * @dev The maximum value that can be safely used as a multiplication operator + * (maxFixedMul) is calculated as sqrt(maxUint256()*fixed1()), + * or 340282366920938463463374607431768211455999999999999 + * Test multiply(0,0) returns 0 + * Test multiply(maxFixedMul,0) returns 0 + * Test multiply(0,maxFixedMul) returns 0 + * Test multiply(fixed1()/mulPrecision(),fixed1()*mulPrecision()) returns fixed1() + * Test multiply(maxFixedMul,maxFixedMul) is around maxUint256() + * Test multiply(maxFixedMul+1,maxFixedMul+1) fails + */ + function multiply(Fraction memory x, Fraction memory y) internal pure returns (Fraction memory) { + if (x.value == 0 || y.value == 0) return Fraction(0); + if (y.value == FIXED1_UINT) return x; + if (x.value == FIXED1_UINT) return y; + + // Separate into integer and fractional parts + // x = x1 + x2, y = y1 + y2 + uint256 x1 = integer(x).value / FIXED1_UINT; + uint256 x2 = fractional(x).value; + uint256 y1 = integer(y).value / FIXED1_UINT; + uint256 y2 = fractional(y).value; + + // (x1 + x2) * (y1 + y2) = (x1 * y1) + (x1 * y2) + (x2 * y1) + (x2 * y2) + uint256 x1y1 = x1 * y1; + if (x1 != 0) require(x1y1 / x1 == y1, "overflow x1y1 detected"); + + // x1y1 needs to be multiplied back by fixed1 + // solium-disable-next-line mixedcase + uint256 fixed_x1y1 = x1y1 * FIXED1_UINT; + if (x1y1 != 0) require(fixed_x1y1 / x1y1 == FIXED1_UINT, "overflow x1y1 * fixed1 detected"); + x1y1 = fixed_x1y1; + + uint256 x2y1 = x2 * y1; + if (x2 != 0) require(x2y1 / x2 == y1, "overflow x2y1 detected"); + + uint256 x1y2 = x1 * y2; + if (x1 != 0) require(x1y2 / x1 == y2, "overflow x1y2 detected"); + + x2 = x2 / mulPrecision(); + y2 = y2 / mulPrecision(); + uint256 x2y2 = x2 * y2; + if (x2 != 0) require(x2y2 / x2 == y2, "overflow x2y2 detected"); + + // result = fixed1() * x1 * y1 + x1 * y2 + x2 * y1 + x2 * y2 / fixed1(); + Fraction memory result = Fraction(x1y1); + result = add(result, Fraction(x2y1)); // Add checks for overflow + result = add(result, Fraction(x1y2)); // Add checks for overflow + result = add(result, Fraction(x2y2)); // Add checks for overflow + return result; + } + + /** + * @notice 1/x + * @dev + * Test reciprocal(0) fails + * Test reciprocal(fixed1()) returns fixed1() + * Test reciprocal(fixed1()*fixed1()) returns 1 // Testing how the fractional is truncated + * Test reciprocal(1+fixed1()*fixed1()) returns 0 // Testing how the fractional is truncated + * Test reciprocal(newFixedFraction(1, 1e24)) returns newFixed(1e24) + */ + function reciprocal(Fraction memory x) internal pure returns (Fraction memory) { + require(x.value != 0, "can't call reciprocal(0)"); + return Fraction((FIXED1_UINT * FIXED1_UINT) / x.value); // Can't overflow + } + + /** + * @notice x/y. If the dividend is higher than the max dividend value, it + * might overflow. You can use multiply(x,reciprocal(y)) instead. + * @dev The maximum value that can be safely used as a dividend (maxNewFixed) is defined as + * divide(maxNewFixed,newFixedFraction(1,fixed1())) is around maxUint256(). + * This yields the value 115792089237316195423570985008687907853269984665640564. + * Test maxNewFixed equals maxUint256()/fixed1() + * Test divide(maxNewFixed,1) equals maxNewFixed*(fixed1) + * Test divide(maxNewFixed+1,multiply(mulPrecision(),mulPrecision())) throws + * Test divide(fixed1(),0) fails + * Test divide(maxNewFixed,1) = maxNewFixed*(10^digits()) + * Test divide(maxNewFixed+1,1) throws + */ + function divide(Fraction memory x, Fraction memory y) internal pure returns (Fraction memory) { + require(y.value != 0, "can't divide by 0"); + uint256 X = x.value * FIXED1_UINT; + require(X / FIXED1_UINT == x.value, "overflow at divide"); + return Fraction(X / y.value); + } + + /** + * @notice x > y + */ + function gt(Fraction memory x, Fraction memory y) internal pure returns (bool) { + return x.value > y.value; + } + + /** + * @notice x >= y + */ + function gte(Fraction memory x, Fraction memory y) internal pure returns (bool) { + return x.value >= y.value; + } + + /** + * @notice x < y + */ + function lt(Fraction memory x, Fraction memory y) internal pure returns (bool) { + return x.value < y.value; + } + + /** + * @notice x <= y + */ + function lte(Fraction memory x, Fraction memory y) internal pure returns (bool) { + return x.value <= y.value; + } + + /** + * @notice x == y + */ + function equals(Fraction memory x, Fraction memory y) internal pure returns (bool) { + return x.value == y.value; + } + + /** + * @notice x <= 1 + */ + function isProperFraction(Fraction memory x) internal pure returns (bool) { + return lte(x, fixed1()); + } +} diff --git a/packages/contracts-bedrock/src/celo/common/Freezable.sol b/packages/contracts-bedrock/src/celo/common/Freezable.sol new file mode 100644 index 0000000000000..4fa2754c2a4e8 --- /dev/null +++ b/packages/contracts-bedrock/src/celo/common/Freezable.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "../UsingRegistry.sol"; + +contract Freezable is UsingRegistry { + // onlyWhenNotFrozen functions can only be called when `frozen` is false, otherwise they will + // revert. + modifier onlyWhenNotFrozen() { + require(!getFreezer().isFrozen(address(this)), "can't call when contract is frozen"); + _; + } +} diff --git a/packages/contracts-bedrock/src/celo/common/Initializable.sol b/packages/contracts-bedrock/src/celo/common/Initializable.sol new file mode 100644 index 0000000000000..524ca6a10e7a9 --- /dev/null +++ b/packages/contracts-bedrock/src/celo/common/Initializable.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +contract Initializable { + bool public initialized; + + constructor(bool testingDeployment) { + if (!testingDeployment) { + initialized = true; + } + } + + modifier initializer() { + require(!initialized, "contract already initialized"); + initialized = true; + _; + } +} diff --git a/packages/contracts-bedrock/src/celo/common/interfaces/ICeloToken.sol b/packages/contracts-bedrock/src/celo/common/interfaces/ICeloToken.sol new file mode 100644 index 0000000000000..29c1416fbafee --- /dev/null +++ b/packages/contracts-bedrock/src/celo/common/interfaces/ICeloToken.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.0; + +/** + * @title This interface describes the non- ERC20 shared interface for all Celo Tokens, and + * in the absence of interface inheritance is intended as a companion to IERC20.sol. + */ +interface ICeloToken { + function transferWithComment(address, uint256, string calldata) external returns (bool); + function name() external view returns (string memory); + function symbol() external view returns (string memory); + function decimals() external view returns (uint8); + function burn(uint256 value) external returns (bool); +} diff --git a/packages/contracts-bedrock/src/celo/common/interfaces/ICeloVersionedContract.sol b/packages/contracts-bedrock/src/celo/common/interfaces/ICeloVersionedContract.sol new file mode 100644 index 0000000000000..ebad613f1521b --- /dev/null +++ b/packages/contracts-bedrock/src/celo/common/interfaces/ICeloVersionedContract.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.0; + +interface ICeloVersionedContract { + /** + * @notice Returns the storage, major, minor, and patch version of the contract. + * @return Storage version of the contract. + * @return Major version of the contract. + * @return Minor version of the contract. + * @return Patch version of the contract. + */ + function getVersionNumber() external pure returns (uint256, uint256, uint256, uint256); +} diff --git a/packages/contracts-bedrock/src/celo/common/interfaces/IFeeHandler.sol b/packages/contracts-bedrock/src/celo/common/interfaces/IFeeHandler.sol new file mode 100644 index 0000000000000..f0ac43340bff7 --- /dev/null +++ b/packages/contracts-bedrock/src/celo/common/interfaces/IFeeHandler.sol @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.0; + +import "../FixidityLib.sol"; + +interface IFeeHandler { + // sets the portion of the fee that should be burned. + function setBurnFraction(uint256 fraction) external; + + function addToken(address tokenAddress, address handlerAddress) external; + function removeToken(address tokenAddress) external; + + function setHandler(address tokenAddress, address handlerAddress) external; + + // marks token to be handled in "handleAll()) + function activateToken(address tokenAddress) external; + function deactivateToken(address tokenAddress) external; + + function sell(address tokenAddress) external; + + // calls exchange(tokenAddress), and distribute(tokenAddress) + function handle(address tokenAddress) external; + + // main entrypoint for a burn, iterates over token and calles handle + function handleAll() external; + + // Sends the balance of token at tokenAddress to feesBeneficiary, + // according to the entry tokensToDistribute[tokenAddress] + function distribute(address tokenAddress) external; + + // burns the balance of Celo in the contract minus the entry of tokensToDistribute[CeloAddress] + function burnCelo() external; + + // calls distribute for all the nonCeloTokens + function distributeAll() external; + + // in case some funds need to be returned or moved to another contract + function transfer(address token, address recipient, uint256 value) external returns (bool); +} diff --git a/packages/contracts-bedrock/src/celo/common/interfaces/IFeeHandlerSeller.sol b/packages/contracts-bedrock/src/celo/common/interfaces/IFeeHandlerSeller.sol new file mode 100644 index 0000000000000..716cd8b0c8e98 --- /dev/null +++ b/packages/contracts-bedrock/src/celo/common/interfaces/IFeeHandlerSeller.sol @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.0; + +import "../FixidityLib.sol"; + +interface IFeeHandlerSeller { + function sell( + address sellTokenAddress, + address buyTokenAddress, + uint256 amount, + uint256 minAmount + ) + external + returns (uint256); + // in case some funds need to be returned or moved to another contract + function transfer(address token, uint256 amount, address to) external returns (bool); +} diff --git a/packages/contracts-bedrock/src/celo/common/linkedlists/AddressSortedLinkedList.sol b/packages/contracts-bedrock/src/celo/common/linkedlists/AddressSortedLinkedList.sol new file mode 100644 index 0000000000000..c2d39cf696a0c --- /dev/null +++ b/packages/contracts-bedrock/src/celo/common/linkedlists/AddressSortedLinkedList.sol @@ -0,0 +1,267 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "@openzeppelin/contracts/utils/math/Math.sol"; + +import "./SortedLinkedList.sol"; + +/** + * @title Maintains a sorted list of unsigned ints keyed by address. + */ +library AddressSortedLinkedList { + using SortedLinkedList for SortedLinkedList.List; + + /** + * @notice Inserts an element into a doubly linked list. + * @param list A storage pointer to the underlying list. + * @param key The key of the element to insert. + * @param value The element value. + * @param lesserKey The key of the element less than the element to insert. + * @param greaterKey The key of the element greater than the element to insert. + */ + function insert( + SortedLinkedList.List storage list, + address key, + uint256 value, + address lesserKey, + address greaterKey + ) + public + { + list.insert(toBytes(key), value, toBytes(lesserKey), toBytes(greaterKey)); + } + + /** + * @notice Removes an element from the doubly linked list. + * @param list A storage pointer to the underlying list. + * @param key The key of the element to remove. + */ + function remove(SortedLinkedList.List storage list, address key) public { + list.remove(toBytes(key)); + } + + /** + * @notice Updates an element in the list. + * @param list A storage pointer to the underlying list. + * @param key The element key. + * @param value The element value. + * @param lesserKey The key of the element will be just left of `key` after the update. + * @param greaterKey The key of the element will be just right of `key` after the update. + * @dev Note that only one of "lesserKey" or "greaterKey" needs to be correct to reduce friction. + */ + function update( + SortedLinkedList.List storage list, + address key, + uint256 value, + address lesserKey, + address greaterKey + ) + public + { + list.update(toBytes(key), value, toBytes(lesserKey), toBytes(greaterKey)); + } + + /** + * @notice Returns whether or not a particular key is present in the sorted list. + * @param list A storage pointer to the underlying list. + * @param key The element key. + * @return Whether or not the key is in the sorted list. + */ + function contains(SortedLinkedList.List storage list, address key) public view returns (bool) { + return list.contains(toBytes(key)); + } + + /** + * @notice Returns the value for a particular key in the sorted list. + * @param list A storage pointer to the underlying list. + * @param key The element key. + * @return The element value. + */ + function getValue(SortedLinkedList.List storage list, address key) public view returns (uint256) { + return list.getValue(toBytes(key)); + } + + /** + * @notice Gets all elements from the doubly linked list. + * @return Array of all keys in the list. + * @return Values corresponding to keys, which will be ordered largest to smallest. + */ + function getElements(SortedLinkedList.List storage list) public view returns (address[] memory, uint256[] memory) { + bytes32[] memory byteKeys = list.getKeys(); + address[] memory keys = new address[](byteKeys.length); + uint256[] memory values = new uint256[](byteKeys.length); + for (uint256 i = 0; i < byteKeys.length; i = i + 1) { + keys[i] = toAddress(byteKeys[i]); + values[i] = list.values[byteKeys[i]]; + } + return (keys, values); + } + + /** + * @notice Returns the minimum of `max` and the number of elements in the list > threshold. + * @param list A storage pointer to the underlying list. + * @param threshold The number that the element must exceed to be included. + * @param max The maximum number returned by this function. + * @return The minimum of `max` and the number of elements in the list > threshold. + */ + function numElementsGreaterThan( + SortedLinkedList.List storage list, + uint256 threshold, + uint256 max + ) + public + view + returns (uint256) + { + uint256 revisedMax = Math.min(max, list.list.numElements); + bytes32 key = list.list.head; + for (uint256 i = 0; i < revisedMax; i = i + 1) { + if (list.getValue(key) < threshold) { + return i; + } + key = list.list.elements[key].previousKey; + } + return revisedMax; + } + + /** + * @notice Returns the N greatest elements of the list. + * @param list A storage pointer to the underlying list. + * @param n The number of elements to return. + * @return The keys of the greatest elements. + */ + function headN(SortedLinkedList.List storage list, uint256 n) public view returns (address[] memory) { + bytes32[] memory byteKeys = list.headN(n); + address[] memory keys = new address[](n); + for (uint256 i = 0; i < n; i = i + 1) { + keys[i] = toAddress(byteKeys[i]); + } + return keys; + } + + /** + * @notice Gets all element keys from the doubly linked list. + * @param list A storage pointer to the underlying list. + * @return All element keys from head to tail. + */ + function getKeys(SortedLinkedList.List storage list) public view returns (address[] memory) { + return headN(list, list.list.numElements); + } + + /** + * @notice Returns the number of elements in the list. + * @param list A storage pointer to the underlying list. + * @return The number of elements in the list. + */ + function getNumElements(SortedLinkedList.List storage list) public view returns (uint256) { + return list.list.numElements; + } + + /** + * @notice Returns the key of the first element in the list. + * @param list A storage pointer to the underlying list. + * @return The key of the first element in the list. + */ + function getHead(SortedLinkedList.List storage list) public view returns (address) { + return toAddress(list.list.head); + } + + /** + * @notice Returns the key of the last element in the list. + * @param list A storage pointer to the underlying list. + * @return The key of the last element in the list. + */ + function getTail(SortedLinkedList.List storage list) public view returns (address) { + return toAddress(list.list.tail); + } + + /** + * @notice Gets lesser and greater for address that has increased it's value. + * @param list A storage pointer to the underlying list. + * @param group The original address. + * @param newValue New value that has to be bigger or equal than the previous one. + * @param loopLimit The max limit of loops that will be executed. + */ + function getLesserAndGreaterOfAddressThatIncreasedValue( + SortedLinkedList.List storage list, + address group, + uint256 newValue, + uint256 loopLimit + ) + public + view + returns (address previous, address next) + { + (, previous, next) = get(list, group); + + while (next != address(0) && loopLimit != 0 && newValue > getValue(list, next)) { + previous = next; + (,, next) = get(list, previous); + loopLimit--; + } + + if (loopLimit == 0) { + return (address(0), address(0)); + } + } + + /** + * @notice Gets lesser and greater for address that has decreased it's value. + * @param list A storage pointer to the underlying list. + * @param group The original address. + * @param newValue New value that has to be smaller or equal than the previous one. + * @param loopLimit The max limit of loops that will be executed. + */ + function getLesserAndGreaterOfAddressThatDecreasedValue( + SortedLinkedList.List storage list, + address group, + uint256 newValue, + uint256 loopLimit + ) + public + view + returns (address previous, address next) + { + (, previous, next) = get(list, group); + while (previous != address(0) && loopLimit != 0 && newValue < getValue(list, previous)) { + next = previous; + (, previous,) = get(list, next); + loopLimit--; + } + if (loopLimit == 0) { + return (address(0), address(0)); + } + } + + function toBytes(address a) public pure returns (bytes32) { + return bytes32(uint256(uint160(a)) << 96); + } + + function toAddress(bytes32 b) public pure returns (address) { + return address(uint160(uint256(b) >> 96)); + } + + /** + * @notice Returns Element based on key. + * @param list A storage pointer to the underlying list. + * @param key The element key. + * @return exists Whether or not the key exists. + * @return previousKey Previous key. + * @return nextKey Next key. + */ + function get( + SortedLinkedList.List storage list, + address key + ) + internal + view + returns (bool exists, address previousKey, address nextKey) + { + LinkedList.Element memory element = list.get(toBytes(key)); + exists = element.exists; + if (element.exists) { + previousKey = toAddress(element.previousKey); + nextKey = toAddress(element.nextKey); + } + } +} diff --git a/packages/contracts-bedrock/src/celo/common/linkedlists/AddressSortedLinkedListWithMedian.sol b/packages/contracts-bedrock/src/celo/common/linkedlists/AddressSortedLinkedListWithMedian.sol new file mode 100644 index 0000000000000..01f2b77e6a065 --- /dev/null +++ b/packages/contracts-bedrock/src/celo/common/linkedlists/AddressSortedLinkedListWithMedian.sol @@ -0,0 +1,160 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "./SortedLinkedListWithMedian.sol"; + +/** + * @title Maintains a sorted list of unsigned ints keyed by address. + */ +library AddressSortedLinkedListWithMedian { + using SortedLinkedListWithMedian for SortedLinkedListWithMedian.List; + + function toBytes(address a) public pure returns (bytes32) { + return bytes32(uint256(uint160(a)) << 96); + } + + function toAddress(bytes32 b) public pure returns (address) { + return address(uint160(uint256(b) >> 96)); + } + + /** + * @notice Inserts an element into a doubly linked list. + * @param list A storage pointer to the underlying list. + * @param key The key of the element to insert. + * @param value The element value. + * @param lesserKey The key of the element less than the element to insert. + * @param greaterKey The key of the element greater than the element to insert. + */ + function insert( + SortedLinkedListWithMedian.List storage list, + address key, + uint256 value, + address lesserKey, + address greaterKey + ) + public + { + list.insert(toBytes(key), value, toBytes(lesserKey), toBytes(greaterKey)); + } + + /** + * @notice Removes an element from the doubly linked list. + * @param list A storage pointer to the underlying list. + * @param key The key of the element to remove. + */ + function remove(SortedLinkedListWithMedian.List storage list, address key) public { + list.remove(toBytes(key)); + } + + /** + * @notice Updates an element in the list. + * @param list A storage pointer to the underlying list. + * @param key The element key. + * @param value The element value. + * @param lesserKey The key of the element will be just left of `key` after the update. + * @param greaterKey The key of the element will be just right of `key` after the update. + * @dev Note that only one of "lesserKey" or "greaterKey" needs to be correct to reduce friction. + */ + function update( + SortedLinkedListWithMedian.List storage list, + address key, + uint256 value, + address lesserKey, + address greaterKey + ) + public + { + list.update(toBytes(key), value, toBytes(lesserKey), toBytes(greaterKey)); + } + + /** + * @notice Returns whether or not a particular key is present in the sorted list. + * @param list A storage pointer to the underlying list. + * @param key The element key. + * @return Whether or not the key is in the sorted list. + */ + function contains(SortedLinkedListWithMedian.List storage list, address key) public view returns (bool) { + return list.contains(toBytes(key)); + } + + /** + * @notice Returns the value for a particular key in the sorted list. + * @param list A storage pointer to the underlying list. + * @param key The element key. + * @return The element value. + */ + function getValue(SortedLinkedListWithMedian.List storage list, address key) public view returns (uint256) { + return list.getValue(toBytes(key)); + } + + /** + * @notice Returns the median value of the sorted list. + * @param list A storage pointer to the underlying list. + * @return The median value. + */ + function getMedianValue(SortedLinkedListWithMedian.List storage list) public view returns (uint256) { + return list.getValue(list.median); + } + + /** + * @notice Returns the key of the first element in the list. + * @param list A storage pointer to the underlying list. + * @return The key of the first element in the list. + */ + function getHead(SortedLinkedListWithMedian.List storage list) external view returns (address) { + return toAddress(list.getHead()); + } + + /** + * @notice Returns the key of the median element in the list. + * @param list A storage pointer to the underlying list. + * @return The key of the median element in the list. + */ + function getMedian(SortedLinkedListWithMedian.List storage list) external view returns (address) { + return toAddress(list.getMedian()); + } + + /** + * @notice Returns the key of the last element in the list. + * @param list A storage pointer to the underlying list. + * @return The key of the last element in the list. + */ + function getTail(SortedLinkedListWithMedian.List storage list) external view returns (address) { + return toAddress(list.getTail()); + } + + /** + * @notice Returns the number of elements in the list. + * @param list A storage pointer to the underlying list. + * @return The number of elements in the list. + */ + function getNumElements(SortedLinkedListWithMedian.List storage list) external view returns (uint256) { + return list.getNumElements(); + } + + /** + * @notice Gets all elements from the doubly linked list. + * @param list A storage pointer to the underlying list. + * @return Array of all keys in the list. + * @return Values corresponding to keys, which will be ordered largest to smallest. + * @return Array of relations to median of corresponding list elements. + */ + function getElements(SortedLinkedListWithMedian.List storage list) + public + view + returns (address[] memory, uint256[] memory, SortedLinkedListWithMedian.MedianRelation[] memory) + { + bytes32[] memory byteKeys = list.getKeys(); + address[] memory keys = new address[](byteKeys.length); + uint256[] memory values = new uint256[](byteKeys.length); + // prettier-ignore + SortedLinkedListWithMedian.MedianRelation[] memory relations = + new SortedLinkedListWithMedian.MedianRelation[](keys.length); + for (uint256 i = 0; i < byteKeys.length; i++) { + keys[i] = toAddress(byteKeys[i]); + values[i] = list.getValue(byteKeys[i]); + relations[i] = list.relation[byteKeys[i]]; + } + return (keys, values, relations); + } +} diff --git a/packages/contracts-bedrock/src/celo/common/linkedlists/LinkedList.sol b/packages/contracts-bedrock/src/celo/common/linkedlists/LinkedList.sol new file mode 100644 index 0000000000000..3a64805c5a583 --- /dev/null +++ b/packages/contracts-bedrock/src/celo/common/linkedlists/LinkedList.sol @@ -0,0 +1,162 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +/** + * @title Maintains a doubly linked list keyed by bytes32. + * @dev Following the `next` pointers will lead you to the head, rather than the tail. + */ +library LinkedList { + struct Element { + bytes32 previousKey; + bytes32 nextKey; + bool exists; + } + + struct List { + bytes32 head; + bytes32 tail; + uint256 numElements; + mapping(bytes32 => Element) elements; + } + + /** + * @notice Inserts an element into a doubly linked list. + * @param list A storage pointer to the underlying list. + * @param key The key of the element to insert. + * @param previousKey The key of the element that comes before the element to insert. + * @param nextKey The key of the element that comes after the element to insert. + */ + function insert(List storage list, bytes32 key, bytes32 previousKey, bytes32 nextKey) internal { + require(key != bytes32(0), "Key must be defined"); + require(!contains(list, key), "Can't insert an existing element"); + require(previousKey != key && nextKey != key, "Key cannot be the same as previousKey or nextKey"); + + Element storage element = list.elements[key]; + element.exists = true; + + if (list.numElements == 0) { + list.tail = key; + list.head = key; + } else { + require(previousKey != bytes32(0) || nextKey != bytes32(0), "Either previousKey or nextKey must be defined"); + + element.previousKey = previousKey; + element.nextKey = nextKey; + + if (previousKey != bytes32(0)) { + require(contains(list, previousKey), "If previousKey is defined, it must exist in the list"); + Element storage previousElement = list.elements[previousKey]; + require(previousElement.nextKey == nextKey, "previousKey must be adjacent to nextKey"); + previousElement.nextKey = key; + } else { + list.tail = key; + } + + if (nextKey != bytes32(0)) { + require(contains(list, nextKey), "If nextKey is defined, it must exist in the list"); + Element storage nextElement = list.elements[nextKey]; + require(nextElement.previousKey == previousKey, "previousKey must be adjacent to nextKey"); + nextElement.previousKey = key; + } else { + list.head = key; + } + } + + list.numElements = list.numElements + 1; + } + + /** + * @notice Inserts an element at the tail of the doubly linked list. + * @param list A storage pointer to the underlying list. + * @param key The key of the element to insert. + */ + function push(List storage list, bytes32 key) internal { + insert(list, key, bytes32(0), list.tail); + } + + /** + * @notice Removes an element from the doubly linked list. + * @param list A storage pointer to the underlying list. + * @param key The key of the element to remove. + */ + function remove(List storage list, bytes32 key) internal { + Element storage element = list.elements[key]; + require(key != bytes32(0) && contains(list, key), "key not in list"); + if (element.previousKey != bytes32(0)) { + Element storage previousElement = list.elements[element.previousKey]; + previousElement.nextKey = element.nextKey; + } else { + list.tail = element.nextKey; + } + + if (element.nextKey != bytes32(0)) { + Element storage nextElement = list.elements[element.nextKey]; + nextElement.previousKey = element.previousKey; + } else { + list.head = element.previousKey; + } + + delete list.elements[key]; + list.numElements = list.numElements - 1; + } + + /** + * @notice Updates an element in the list. + * @param list A storage pointer to the underlying list. + * @param key The element key. + * @param previousKey The key of the element that comes before the updated element. + * @param nextKey The key of the element that comes after the updated element. + */ + function update(List storage list, bytes32 key, bytes32 previousKey, bytes32 nextKey) internal { + require(key != bytes32(0) && key != previousKey && key != nextKey && contains(list, key), "key on in list"); + remove(list, key); + insert(list, key, previousKey, nextKey); + } + + /** + * @notice Returns whether or not a particular key is present in the sorted list. + * @param list A storage pointer to the underlying list. + * @param key The element key. + * @return Whether or not the key is in the sorted list. + */ + function contains(List storage list, bytes32 key) internal view returns (bool) { + return list.elements[key].exists; + } + + /** + * @notice Returns Element based on key. + * @param list A storage pointer to the underlying list. + * @param key The element key. + * @return Whether or not the key is in the sorted list. + */ + function get(List storage list, bytes32 key) internal view returns (Element memory) { + return list.elements[key]; + } + + /** + * @notice Returns the keys of the N elements at the head of the list. + * @param list A storage pointer to the underlying list. + * @param n The number of elements to return. + * @return The keys of the N elements at the head of the list. + * @dev Reverts if n is greater than the number of elements in the list. + */ + function headN(List storage list, uint256 n) internal view returns (bytes32[] memory) { + require(n <= list.numElements, "not enough elements"); + bytes32[] memory keys = new bytes32[](n); + bytes32 key = list.head; + for (uint256 i = 0; i < n; i = i + 1) { + keys[i] = key; + key = list.elements[key].previousKey; + } + return keys; + } + + /** + * @notice Gets all element keys from the doubly linked list. + * @param list A storage pointer to the underlying list. + * @return All element keys from head to tail. + */ + function getKeys(List storage list) internal view returns (bytes32[] memory) { + return headN(list, list.numElements); + } +} diff --git a/packages/contracts-bedrock/src/celo/common/linkedlists/SortedLinkedList.sol b/packages/contracts-bedrock/src/celo/common/linkedlists/SortedLinkedList.sol new file mode 100644 index 0000000000000..207bc03653464 --- /dev/null +++ b/packages/contracts-bedrock/src/celo/common/linkedlists/SortedLinkedList.sol @@ -0,0 +1,218 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "./LinkedList.sol"; + +/** + * @title Maintains a sorted list of unsigned ints keyed by bytes32. + */ +library SortedLinkedList { + using LinkedList for LinkedList.List; + + struct List { + LinkedList.List list; + mapping(bytes32 => uint256) values; + } + + /** + * @notice Inserts an element into a doubly linked list. + * @param list A storage pointer to the underlying list. + * @param key The key of the element to insert. + * @param value The element value. + * @param lesserKey The key of the element less than the element to insert. + * @param greaterKey The key of the element greater than the element to insert. + */ + function insert(List storage list, bytes32 key, uint256 value, bytes32 lesserKey, bytes32 greaterKey) internal { + require(key != bytes32(0) && key != lesserKey && key != greaterKey && !contains(list, key), "invalid key"); + require( + (lesserKey != bytes32(0) || greaterKey != bytes32(0)) || list.list.numElements == 0, + "greater and lesser key zero" + ); + require(contains(list, lesserKey) || lesserKey == bytes32(0), "invalid lesser key"); + require(contains(list, greaterKey) || greaterKey == bytes32(0), "invalid greater key"); + (lesserKey, greaterKey) = getLesserAndGreater(list, value, lesserKey, greaterKey); + list.list.insert(key, lesserKey, greaterKey); + list.values[key] = value; + } + + /** + * @notice Removes an element from the doubly linked list. + * @param list A storage pointer to the underlying list. + * @param key The key of the element to remove. + */ + function remove(List storage list, bytes32 key) internal { + list.list.remove(key); + list.values[key] = 0; + } + + /** + * @notice Updates an element in the list. + * @param list A storage pointer to the underlying list. + * @param key The element key. + * @param value The element value. + * @param lesserKey The key of the element will be just left of `key` after the update. + * @param greaterKey The key of the element will be just right of `key` after the update. + * @dev Note that only one of "lesserKey" or "greaterKey" needs to be correct to reduce friction. + */ + function update(List storage list, bytes32 key, uint256 value, bytes32 lesserKey, bytes32 greaterKey) internal { + remove(list, key); + insert(list, key, value, lesserKey, greaterKey); + } + + /** + * @notice Inserts an element at the tail of the doubly linked list. + * @param list A storage pointer to the underlying list. + * @param key The key of the element to insert. + */ + function push(List storage list, bytes32 key) internal { + insert(list, key, 0, bytes32(0), list.list.tail); + } + + /** + * @notice Removes N elements from the head of the list and returns their keys. + * @param list A storage pointer to the underlying list. + * @param n The number of elements to pop. + * @return The keys of the popped elements. + */ + function popN(List storage list, uint256 n) internal returns (bytes32[] memory) { + require(n <= list.list.numElements, "not enough elements"); + bytes32[] memory keys = new bytes32[](n); + for (uint256 i = 0; i < n; i = i + 1) { + bytes32 key = list.list.head; + keys[i] = key; + remove(list, key); + } + return keys; + } + + /** + * @notice Returns whether or not a particular key is present in the sorted list. + * @param list A storage pointer to the underlying list. + * @param key The element key. + * @return Whether or not the key is in the sorted list. + */ + function contains(List storage list, bytes32 key) internal view returns (bool) { + return list.list.contains(key); + } + + /** + * @notice Returns Element based on key. + * @param list A storage pointer to the underlying list. + * @param key The element key. + * @return Whether or not the key is in the sorted list. + */ + function get(List storage list, bytes32 key) internal view returns (LinkedList.Element memory) { + return list.list.get(key); + } + + /** + * @notice Returns the value for a particular key in the sorted list. + * @param list A storage pointer to the underlying list. + * @param key The element key. + * @return The element value. + */ + function getValue(List storage list, bytes32 key) internal view returns (uint256) { + return list.values[key]; + } + + /** + * @notice Gets all elements from the doubly linked list. + * @param list A storage pointer to the underlying list. + * @return Array of all keys in the list. + * @return Values corresponding to keys, which will be ordered largest to smallest. + */ + function getElements(List storage list) internal view returns (bytes32[] memory, uint256[] memory) { + bytes32[] memory keys = getKeys(list); + uint256[] memory values = new uint256[](keys.length); + for (uint256 i = 0; i < keys.length; i = i + 1) { + values[i] = list.values[keys[i]]; + } + return (keys, values); + } + + /** + * @notice Gets all element keys from the doubly linked list. + * @param list A storage pointer to the underlying list. + * @return All element keys from head to tail. + */ + function getKeys(List storage list) internal view returns (bytes32[] memory) { + return list.list.getKeys(); + } + + /** + * @notice Returns first N greatest elements of the list. + * @param list A storage pointer to the underlying list. + * @param n The number of elements to return. + * @return The keys of the first n elements. + * @dev Reverts if n is greater than the number of elements in the list. + */ + function headN(List storage list, uint256 n) internal view returns (bytes32[] memory) { + return list.list.headN(n); + } + + /** + * @notice Returns the keys of the elements greaterKey than and less than the provided value. + * @param list A storage pointer to the underlying list. + * @param value The element value. + * @param lesserKey The key of the element which could be just left of the new value. + * @param greaterKey The key of the element which could be just right of the new value. + * @return The correct lesserKey keys. + * @return The correct greaterKey keys. + */ + function getLesserAndGreater( + List storage list, + uint256 value, + bytes32 lesserKey, + bytes32 greaterKey + ) + private + view + returns (bytes32, bytes32) + { + // Check for one of the following conditions and fail if none are met: + // 1. The value is less than the current lowest value + // 2. The value is greater than the current greatest value + // 3. The value is just greater than the value for `lesserKey` + // 4. The value is just less than the value for `greaterKey` + if (lesserKey == bytes32(0) && isValueBetween(list, value, lesserKey, list.list.tail)) { + return (lesserKey, list.list.tail); + } else if (greaterKey == bytes32(0) && isValueBetween(list, value, list.list.head, greaterKey)) { + return (list.list.head, greaterKey); + } else if ( + lesserKey != bytes32(0) && isValueBetween(list, value, lesserKey, list.list.elements[lesserKey].nextKey) + ) { + return (lesserKey, list.list.elements[lesserKey].nextKey); + } else if ( + greaterKey != bytes32(0) + && isValueBetween(list, value, list.list.elements[greaterKey].previousKey, greaterKey) + ) { + return (list.list.elements[greaterKey].previousKey, greaterKey); + } + + require(false, "get lesser and greater failure"); + return (0, 0); + } + + /** + * @notice Returns whether or not a given element is between two other elements. + * @param list A storage pointer to the underlying list. + * @param value The element value. + * @param lesserKey The key of the element whose value should be lesserKey. + * @param greaterKey The key of the element whose value should be greaterKey. + * @return True if the given element is between the two other elements. + */ + function isValueBetween( + List storage list, + uint256 value, + bytes32 lesserKey, + bytes32 greaterKey + ) + private + view + returns (bool) + { + bool isLesser = lesserKey == bytes32(0) || list.values[lesserKey] <= value; + bool isGreater = greaterKey == bytes32(0) || list.values[greaterKey] >= value; + return isLesser && isGreater; + } +} diff --git a/packages/contracts-bedrock/src/celo/common/linkedlists/SortedLinkedListWithMedian.sol b/packages/contracts-bedrock/src/celo/common/linkedlists/SortedLinkedListWithMedian.sol new file mode 100644 index 0000000000000..98513d434b7cc --- /dev/null +++ b/packages/contracts-bedrock/src/celo/common/linkedlists/SortedLinkedListWithMedian.sol @@ -0,0 +1,253 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "./LinkedList.sol"; +import "./SortedLinkedList.sol"; + +/** + * @title Maintains a sorted list of unsigned ints keyed by bytes32. + */ +library SortedLinkedListWithMedian { + using SortedLinkedList for SortedLinkedList.List; + + enum MedianAction { + None, + Lesser, + Greater + } + + enum MedianRelation { + Undefined, + Lesser, + Greater, + Equal + } + + struct List { + SortedLinkedList.List list; + bytes32 median; + mapping(bytes32 => MedianRelation) relation; + } + + /** + * @notice Inserts an element into a doubly linked list. + * @param list A storage pointer to the underlying list. + * @param key The key of the element to insert. + * @param value The element value. + * @param lesserKey The key of the element less than the element to insert. + * @param greaterKey The key of the element greater than the element to insert. + */ + function insert(List storage list, bytes32 key, uint256 value, bytes32 lesserKey, bytes32 greaterKey) internal { + list.list.insert(key, value, lesserKey, greaterKey); + LinkedList.Element storage element = list.list.list.elements[key]; + + MedianAction action = MedianAction.None; + if (list.list.list.numElements == 1) { + list.median = key; + list.relation[key] = MedianRelation.Equal; + } else if (list.list.list.numElements % 2 == 1) { + // When we have an odd number of elements, and the element that we inserted is less than + // the previous median, we need to slide the median down one element, since we had previously + // selected the greater of the two middle elements. + if (element.previousKey == bytes32(0) || list.relation[element.previousKey] == MedianRelation.Lesser) { + action = MedianAction.Lesser; + list.relation[key] = MedianRelation.Lesser; + } else { + list.relation[key] = MedianRelation.Greater; + } + } else { + // When we have an even number of elements, and the element that we inserted is greater than + // the previous median, we need to slide the median up one element, since we always select + // the greater of the two middle elements. + if (element.nextKey == bytes32(0) || list.relation[element.nextKey] == MedianRelation.Greater) { + action = MedianAction.Greater; + list.relation[key] = MedianRelation.Greater; + } else { + list.relation[key] = MedianRelation.Lesser; + } + } + updateMedian(list, action); + } + + /** + * @notice Removes an element from the doubly linked list. + * @param list A storage pointer to the underlying list. + * @param key The key of the element to remove. + */ + function remove(List storage list, bytes32 key) internal { + MedianAction action = MedianAction.None; + if (list.list.list.numElements == 0) { + list.median = bytes32(0); + } else if (list.list.list.numElements % 2 == 0) { + // When we have an even number of elements, we always choose the higher of the two medians. + // Thus, if the element we're removing is greaterKey than or equal to the median we need to + // slide the median left by one. + if (list.relation[key] == MedianRelation.Greater || list.relation[key] == MedianRelation.Equal) { + action = MedianAction.Lesser; + } + } else { + // When we don't have an even number of elements, we just choose the median value. + // Thus, if the element we're removing is less than or equal to the median, we need to slide + // median right by one. + if (list.relation[key] == MedianRelation.Lesser || list.relation[key] == MedianRelation.Equal) { + action = MedianAction.Greater; + } + } + updateMedian(list, action); + + list.list.remove(key); + } + + /** + * @notice Updates an element in the list. + * @param list A storage pointer to the underlying list. + * @param key The element key. + * @param value The element value. + * @param lesserKey The key of the element will be just left of `key` after the update. + * @param greaterKey The key of the element will be just right of `key` after the update. + * @dev Note that only one of "lesserKey" or "greaterKey" needs to be correct to reduce friction. + */ + function update(List storage list, bytes32 key, uint256 value, bytes32 lesserKey, bytes32 greaterKey) internal { + remove(list, key); + insert(list, key, value, lesserKey, greaterKey); + } + + /** + * @notice Inserts an element at the tail of the doubly linked list. + * @param list A storage pointer to the underlying list. + * @param key The key of the element to insert. + */ + function push(List storage list, bytes32 key) internal { + insert(list, key, 0, bytes32(0), list.list.list.tail); + } + + /** + * @notice Removes N elements from the head of the list and returns their keys. + * @param list A storage pointer to the underlying list. + * @param n The number of elements to pop. + * @return The keys of the popped elements. + */ + function popN(List storage list, uint256 n) internal returns (bytes32[] memory) { + require(n <= list.list.list.numElements, "not enough elements"); + bytes32[] memory keys = new bytes32[](n); + for (uint256 i = 0; i < n; i++) { + bytes32 key = list.list.list.head; + keys[i] = key; + remove(list, key); + } + return keys; + } + + /** + * @notice Returns whether or not a particular key is present in the sorted list. + * @param list A storage pointer to the underlying list. + * @param key The element key. + * @return Whether or not the key is in the sorted list. + */ + function contains(List storage list, bytes32 key) internal view returns (bool) { + return list.list.contains(key); + } + + /** + * @notice Returns the value for a particular key in the sorted list. + * @param list A storage pointer to the underlying list. + * @param key The element key. + * @return The element value. + */ + function getValue(List storage list, bytes32 key) internal view returns (uint256) { + return list.list.values[key]; + } + + /** + * @notice Returns the median value of the sorted list. + * @param list A storage pointer to the underlying list. + * @return The median value. + */ + function getMedianValue(List storage list) internal view returns (uint256) { + return getValue(list, list.median); + } + + /** + * @notice Returns the key of the first element in the list. + * @param list A storage pointer to the underlying list. + * @return The key of the first element in the list. + */ + function getHead(List storage list) internal view returns (bytes32) { + return list.list.list.head; + } + + /** + * @notice Returns the key of the median element in the list. + * @param list A storage pointer to the underlying list. + * @return The key of the median element in the list. + */ + function getMedian(List storage list) internal view returns (bytes32) { + return list.median; + } + + /** + * @notice Returns the key of the last element in the list. + * @param list A storage pointer to the underlying list. + * @return The key of the last element in the list. + */ + function getTail(List storage list) internal view returns (bytes32) { + return list.list.list.tail; + } + + /** + * @notice Returns the number of elements in the list. + * @param list A storage pointer to the underlying list. + * @return The number of elements in the list. + */ + function getNumElements(List storage list) internal view returns (uint256) { + return list.list.list.numElements; + } + + /** + * @notice Gets all elements from the doubly linked list. + * @param list A storage pointer to the underlying list. + * @return Array of all keys in the list. + * @return Values corresponding to keys, which will be ordered largest to smallest. + * @return Array of relations to median of corresponding list elements. + */ + function getElements(List storage list) + internal + view + returns (bytes32[] memory, uint256[] memory, MedianRelation[] memory) + { + bytes32[] memory keys = getKeys(list); + uint256[] memory values = new uint256[](keys.length); + MedianRelation[] memory relations = new MedianRelation[](keys.length); + for (uint256 i = 0; i < keys.length; i++) { + values[i] = list.list.values[keys[i]]; + relations[i] = list.relation[keys[i]]; + } + return (keys, values, relations); + } + + /** + * @notice Gets all element keys from the doubly linked list. + * @param list A storage pointer to the underlying list. + * @return All element keys from head to tail. + */ + function getKeys(List storage list) internal view returns (bytes32[] memory) { + return list.list.getKeys(); + } + + /** + * @notice Moves the median pointer right or left of its current value. + * @param list A storage pointer to the underlying list. + * @param action Which direction to move the median pointer. + */ + function updateMedian(List storage list, MedianAction action) private { + LinkedList.Element storage previousMedian = list.list.list.elements[list.median]; + if (action == MedianAction.Lesser) { + list.relation[list.median] = MedianRelation.Greater; + list.median = previousMedian.previousKey; + } else if (action == MedianAction.Greater) { + list.relation[list.median] = MedianRelation.Lesser; + list.median = previousMedian.nextKey; + } + list.relation[list.median] = MedianRelation.Equal; + } +} diff --git a/packages/contracts-bedrock/src/celo/governance/interfaces/IElection.sol b/packages/contracts-bedrock/src/celo/governance/interfaces/IElection.sol new file mode 100644 index 0000000000000..a08df4038179d --- /dev/null +++ b/packages/contracts-bedrock/src/celo/governance/interfaces/IElection.sol @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.0; + +interface IElection { + function electValidatorSigners() external view returns (address[] memory); + function electNValidatorSigners(uint256, uint256) external view returns (address[] memory); + function vote(address, uint256, address, address) external returns (bool); + function activate(address) external returns (bool); + function revokeActive(address, uint256, address, address, uint256) external returns (bool); + function revokeAllActive(address, address, address, uint256) external returns (bool); + function revokePending(address, uint256, address, address, uint256) external returns (bool); + function markGroupIneligible(address) external; + function markGroupEligible(address, address, address) external; + function allowedToVoteOverMaxNumberOfGroups(address) external returns (bool); + function forceDecrementVotes( + address, + uint256, + address[] calldata, + address[] calldata, + uint256[] calldata + ) + external + returns (uint256); + function setAllowedToVoteOverMaxNumberOfGroups(bool flag) external; + + // view functions + function getElectableValidators() external view returns (uint256, uint256); + function getElectabilityThreshold() external view returns (uint256); + function getNumVotesReceivable(address) external view returns (uint256); + function getTotalVotes() external view returns (uint256); + function getActiveVotes() external view returns (uint256); + function getTotalVotesByAccount(address) external view returns (uint256); + function getPendingVotesForGroupByAccount(address, address) external view returns (uint256); + function getActiveVotesForGroupByAccount(address, address) external view returns (uint256); + function getTotalVotesForGroupByAccount(address, address) external view returns (uint256); + function getActiveVoteUnitsForGroupByAccount(address, address) external view returns (uint256); + function getTotalVotesForGroup(address) external view returns (uint256); + function getActiveVotesForGroup(address) external view returns (uint256); + function getPendingVotesForGroup(address) external view returns (uint256); + function getGroupEligibility(address) external view returns (bool); + function getGroupEpochRewards(address, uint256, uint256[] calldata) external view returns (uint256); + function getGroupsVotedForByAccount(address) external view returns (address[] memory); + function getEligibleValidatorGroups() external view returns (address[] memory); + function getTotalVotesForEligibleValidatorGroups() external view returns (address[] memory, uint256[] memory); + function getCurrentValidatorSigners() external view returns (address[] memory); + function canReceiveVotes(address, uint256) external view returns (bool); + function hasActivatablePendingVotes(address, address) external view returns (bool); + function validatorSignerAddressFromCurrentSet(uint256 index) external view returns (address); + function numberValidatorsInCurrentSet() external view returns (uint256); + + // only owner + function setElectableValidators(uint256, uint256) external returns (bool); + function setMaxNumGroupsVotedFor(uint256) external returns (bool); + function setElectabilityThreshold(uint256) external returns (bool); + + // only VM + function distributeEpochRewards(address, uint256, address, address) external; +} diff --git a/packages/contracts-bedrock/src/celo/governance/interfaces/IGovernance.sol b/packages/contracts-bedrock/src/celo/governance/interfaces/IGovernance.sol new file mode 100644 index 0000000000000..3c5228e29cbef --- /dev/null +++ b/packages/contracts-bedrock/src/celo/governance/interfaces/IGovernance.sol @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.0; + +interface IGovernance { + function votePartially( + uint256 proposalId, + uint256 index, + uint256 yesVotes, + uint256 noVotes, + uint256 abstainVotes + ) + external + returns (bool); + + function isVoting(address) external view returns (bool); + function getAmountOfGoldUsedForVoting(address account) external view returns (uint256); + + function getProposal(uint256 proposalId) + external + view + returns (address, uint256, uint256, uint256, string memory, uint256, bool); + + function getReferendumStageDuration() external view returns (uint256); +} diff --git a/packages/contracts-bedrock/src/celo/governance/interfaces/ILockedGold.sol b/packages/contracts-bedrock/src/celo/governance/interfaces/ILockedGold.sol new file mode 100644 index 0000000000000..4f90373500c2f --- /dev/null +++ b/packages/contracts-bedrock/src/celo/governance/interfaces/ILockedGold.sol @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.0; + +interface ILockedGold { + function lock() external payable; + function incrementNonvotingAccountBalance(address, uint256) external; + function decrementNonvotingAccountBalance(address, uint256) external; + function getAccountTotalLockedGold(address) external view returns (uint256); + function getTotalLockedGold() external view returns (uint256); + function getPendingWithdrawals(address) external view returns (uint256[] memory, uint256[] memory); + function getPendingWithdrawal(address account, uint256 index) external view returns (uint256, uint256); + function getTotalPendingWithdrawals(address) external view returns (uint256); + function unlock(uint256) external; + function relock(uint256, uint256) external; + function withdraw(uint256) external; + function slash( + address account, + uint256 penalty, + address reporter, + uint256 reward, + address[] calldata lessers, + address[] calldata greaters, + uint256[] calldata indices + ) + external; + function isSlasher(address) external view returns (bool); + function unlockingPeriod() external view returns (uint256); + function getAccountNonvotingLockedGold(address account) external view returns (uint256); +} diff --git a/packages/contracts-bedrock/src/celo/governance/interfaces/IReleaseGold.sol b/packages/contracts-bedrock/src/celo/governance/interfaces/IReleaseGold.sol new file mode 100644 index 0000000000000..a55eb3a4d4d11 --- /dev/null +++ b/packages/contracts-bedrock/src/celo/governance/interfaces/IReleaseGold.sol @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.0; + +interface IReleaseGold { + function transfer(address, uint256) external; + function unlockGold(uint256) external; + function withdrawLockedGold(uint256) external; + function authorizeVoteSigner(address payable, uint8, bytes32, bytes32) external; + function authorizeValidatorSigner(address payable, uint8, bytes32, bytes32) external; + function authorizeValidatorSignerWithPublicKey(address payable, uint8, bytes32, bytes32, bytes calldata) external; + function authorizeValidatorSignerWithKeys( + address payable, + uint8, + bytes32, + bytes32, + bytes calldata, + bytes calldata, + bytes calldata + ) + external; + function authorizeAttestationSigner(address payable, uint8, bytes32, bytes32) external; + function revokeActive(address, uint256, address, address, uint256) external; + function revokePending(address, uint256, address, address, uint256) external; + + // view functions + function getTotalBalance() external view returns (uint256); + function getRemainingTotalBalance() external view returns (uint256); + function getRemainingUnlockedBalance() external view returns (uint256); + function getRemainingLockedBalance() external view returns (uint256); + function getCurrentReleasedTotalAmount() external view returns (uint256); + function isRevoked() external view returns (bool); + + // only beneficiary + function setCanExpire(bool) external; + function withdraw(uint256) external; + function lockGold(uint256) external; + function relockGold(uint256, uint256) external; + function setAccount(string calldata, bytes calldata, address, uint8, bytes32, bytes32) external; + function createAccount() external; + function setAccountName(string calldata) external; + function setAccountWalletAddress(address, uint8, bytes32, bytes32) external; + function setAccountDataEncryptionKey(bytes calldata) external; + function setAccountMetadataURL(string calldata) external; + + // only owner + function setBeneficiary(address payable) external; + + // only release owner + function setLiquidityProvision() external; + function setMaxDistribution(uint256) external; + function refundAndFinalize() external; + function revoke() external; + function expire() external; +} diff --git a/packages/contracts-bedrock/src/celo/governance/interfaces/IValidators.sol b/packages/contracts-bedrock/src/celo/governance/interfaces/IValidators.sol new file mode 100644 index 0000000000000..fb708af9c0ec5 --- /dev/null +++ b/packages/contracts-bedrock/src/celo/governance/interfaces/IValidators.sol @@ -0,0 +1,85 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.0; + +interface IValidators { + function registerValidator(bytes calldata, bytes calldata, bytes calldata) external returns (bool); + function deregisterValidator(uint256) external returns (bool); + function affiliate(address) external returns (bool); + function deaffiliate() external returns (bool); + function updateBlsPublicKey(bytes calldata, bytes calldata) external returns (bool); + function registerValidatorGroup(uint256) external returns (bool); + function deregisterValidatorGroup(uint256) external returns (bool); + function addMember(address) external returns (bool); + function addFirstMember(address, address, address) external returns (bool); + function removeMember(address) external returns (bool); + function reorderMember(address, address, address) external returns (bool); + function updateCommission() external; + function setNextCommissionUpdate(uint256) external; + function resetSlashingMultiplier() external; + + // only owner + function setCommissionUpdateDelay(uint256) external; + function setMaxGroupSize(uint256) external returns (bool); + function setMembershipHistoryLength(uint256) external returns (bool); + function setValidatorScoreParameters(uint256, uint256) external returns (bool); + function setGroupLockedGoldRequirements(uint256, uint256) external returns (bool); + function setValidatorLockedGoldRequirements(uint256, uint256) external returns (bool); + function setSlashingMultiplierResetPeriod(uint256) external; + + // view functions + function getMaxGroupSize() external view returns (uint256); + function getCommissionUpdateDelay() external view returns (uint256); + function getValidatorScoreParameters() external view returns (uint256, uint256); + function getMembershipHistory(address) + external + view + returns (uint256[] memory, address[] memory, uint256, uint256); + function calculateEpochScore(uint256) external view returns (uint256); + function calculateGroupEpochScore(uint256[] calldata) external view returns (uint256); + function getAccountLockedGoldRequirement(address) external view returns (uint256); + function meetsAccountLockedGoldRequirements(address) external view returns (bool); + function getValidatorBlsPublicKeyFromSigner(address) external view returns (bytes memory); + function getValidator(address account) + external + view + returns (bytes memory, bytes memory, address, uint256, address); + function getValidatorGroup(address) + external + view + returns (address[] memory, uint256, uint256, uint256, uint256[] memory, uint256, uint256); + function getGroupNumMembers(address) external view returns (uint256); + function getTopGroupValidators(address, uint256) external view returns (address[] memory); + function getGroupsNumMembers(address[] calldata accounts) external view returns (uint256[] memory); + function getNumRegisteredValidators() external view returns (uint256); + function groupMembershipInEpoch(address, uint256, uint256) external view returns (address); + + // only registered contract + function updateEcdsaPublicKey(address, address, bytes calldata) external returns (bool); + function updatePublicKeys( + address, + address, + bytes calldata, + bytes calldata, + bytes calldata + ) + external + returns (bool); + function getValidatorLockedGoldRequirements() external view returns (uint256, uint256); + function getGroupLockedGoldRequirements() external view returns (uint256, uint256); + function getRegisteredValidators() external view returns (address[] memory); + function getRegisteredValidatorSigners() external view returns (address[] memory); + function getRegisteredValidatorGroups() external view returns (address[] memory); + function isValidatorGroup(address) external view returns (bool); + function isValidator(address) external view returns (bool); + function getValidatorGroupSlashingMultiplier(address) external view returns (uint256); + function getMembershipInLastEpoch(address) external view returns (address); + function getMembershipInLastEpochFromSigner(address) external view returns (address); + + // only VM + function updateValidatorScoreFromSigner(address, uint256) external; + function distributeEpochPaymentsFromSigner(address, uint256) external returns (uint256); + + // only slasher + function forceDeaffiliateIfValidator(address) external; + function halveSlashingMultiplier(address) external; +} diff --git a/packages/contracts-bedrock/src/celo/identity/interfaces/IAttestations.sol b/packages/contracts-bedrock/src/celo/identity/interfaces/IAttestations.sol new file mode 100644 index 0000000000000..a034025c9bde6 --- /dev/null +++ b/packages/contracts-bedrock/src/celo/identity/interfaces/IAttestations.sol @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.0; + +interface IAttestations { + function revoke(bytes32, uint256) external; + function withdraw(address) external; + + // view functions + function getUnselectedRequest(bytes32, address) external view returns (uint32, uint32, address); + function getAttestationIssuers(bytes32, address) external view returns (address[] memory); + function getAttestationStats(bytes32, address) external view returns (uint32, uint32); + function batchGetAttestationStats(bytes32[] calldata) + external + view + returns (uint256[] memory, address[] memory, uint64[] memory, uint64[] memory); + function getAttestationState(bytes32, address, address) external view returns (uint8, uint32, address); + function getCompletableAttestations( + bytes32, + address + ) + external + view + returns (uint32[] memory, address[] memory, uint256[] memory, bytes memory); + function getAttestationRequestFee(address) external view returns (uint256); + function getMaxAttestations() external view returns (uint256); + function validateAttestationCode(bytes32, address, uint8, bytes32, bytes32) external view returns (address); + function lookupAccountsForIdentifier(bytes32) external view returns (address[] memory); + function requireNAttestationsRequested(bytes32, address, uint32) external view; + + // only owner + function setAttestationRequestFee(address, uint256) external; + function setAttestationExpiryBlocks(uint256) external; + function setSelectIssuersWaitBlocks(uint256) external; + function setMaxAttestations(uint256) external; +} diff --git a/packages/contracts-bedrock/src/celo/identity/interfaces/IEscrow.sol b/packages/contracts-bedrock/src/celo/identity/interfaces/IEscrow.sol new file mode 100644 index 0000000000000..3aaba6f2f2a1b --- /dev/null +++ b/packages/contracts-bedrock/src/celo/identity/interfaces/IEscrow.sol @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +interface IEscrow { + function transfer( + bytes32 identifier, + address token, + uint256 value, + uint256 expirySeconds, + address paymentId, + uint256 minAttestations + ) + external + returns (bool); + function transferWithTrustedIssuers( + bytes32 identifier, + address token, + uint256 value, + uint256 expirySeconds, + address paymentId, + uint256 minAttestations, + address[] calldata trustedIssuers + ) + external + returns (bool); + function withdraw(address paymentID, uint8 v, bytes32 r, bytes32 s) external returns (bool); + function revoke(address paymentID) external returns (bool); + + // view functions + function getReceivedPaymentIds(bytes32 identifier) external view returns (address[] memory); + function getSentPaymentIds(address sender) external view returns (address[] memory); + function getTrustedIssuersPerPayment(address paymentId) external view returns (address[] memory); + function getDefaultTrustedIssuers() external view returns (address[] memory); + function MAX_TRUSTED_ISSUERS_PER_PAYMENT() external view returns (uint256); + + // onlyOwner functions + function addDefaultTrustedIssuer(address trustedIssuer) external; + function removeDefaultTrustedIssuer(address trustedIssuer, uint256 index) external; +} diff --git a/packages/contracts-bedrock/src/celo/identity/interfaces/IFederatedAttestations.sol b/packages/contracts-bedrock/src/celo/identity/interfaces/IFederatedAttestations.sol new file mode 100644 index 0000000000000..90b5fd7d0c2b2 --- /dev/null +++ b/packages/contracts-bedrock/src/celo/identity/interfaces/IFederatedAttestations.sol @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +interface IFederatedAttestations { + function registerAttestationAsIssuer(bytes32 identifier, address account, uint64 issuedOn) external; + function registerAttestation( + bytes32 identifier, + address issuer, + address account, + address signer, + uint64 issuedOn, + uint8 v, + bytes32 r, + bytes32 s + ) + external; + function revokeAttestation(bytes32 identifier, address issuer, address account) external; + function batchRevokeAttestations( + address issuer, + bytes32[] calldata identifiers, + address[] calldata accounts + ) + external; + + // view functions + function lookupAttestations( + bytes32 identifier, + address[] calldata trustedIssuers + ) + external + view + returns (uint256[] memory, address[] memory, address[] memory, uint64[] memory, uint64[] memory); + function lookupIdentifiers( + address account, + address[] calldata trustedIssuers + ) + external + view + returns (uint256[] memory, bytes32[] memory); + function validateAttestationSig( + bytes32 identifier, + address issuer, + address account, + address signer, + uint64 issuedOn, + uint8 v, + bytes32 r, + bytes32 s + ) + external + view; + function getUniqueAttestationHash( + bytes32 identifier, + address issuer, + address account, + address signer, + uint64 issuedOn + ) + external + pure + returns (bytes32); +} diff --git a/packages/contracts-bedrock/src/celo/identity/interfaces/IOdisPayments.sol b/packages/contracts-bedrock/src/celo/identity/interfaces/IOdisPayments.sol new file mode 100644 index 0000000000000..12370f6fc6922 --- /dev/null +++ b/packages/contracts-bedrock/src/celo/identity/interfaces/IOdisPayments.sol @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +interface IOdisPayments { + function payInCUSD(address account, uint256 value) external; + function totalPaidCUSD(address) external view returns (uint256); +} diff --git a/packages/contracts-bedrock/src/celo/identity/interfaces/IRandom.sol b/packages/contracts-bedrock/src/celo/identity/interfaces/IRandom.sol new file mode 100644 index 0000000000000..ff388bb7261cb --- /dev/null +++ b/packages/contracts-bedrock/src/celo/identity/interfaces/IRandom.sol @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.0; + +interface IRandom { + function revealAndCommit(bytes32, bytes32, address) external; + function randomnessBlockRetentionWindow() external view returns (uint256); + function random() external view returns (bytes32); + function getBlockRandomness(uint256) external view returns (bytes32); +} diff --git a/packages/contracts-bedrock/src/celo/interfaces/IAccounts.sol b/packages/contracts-bedrock/src/celo/interfaces/IAccounts.sol new file mode 100644 index 0000000000000..1732248a15e94 --- /dev/null +++ b/packages/contracts-bedrock/src/celo/interfaces/IAccounts.sol @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.0; + +interface IAccounts { + function isAccount(address) external view returns (bool); + function voteSignerToAccount(address) external view returns (address); + function validatorSignerToAccount(address) external view returns (address); + function attestationSignerToAccount(address) external view returns (address); + function signerToAccount(address) external view returns (address); + function getAttestationSigner(address) external view returns (address); + function getValidatorSigner(address) external view returns (address); + function getVoteSigner(address) external view returns (address); + function hasAuthorizedVoteSigner(address) external view returns (bool); + function hasAuthorizedValidatorSigner(address) external view returns (bool); + function hasAuthorizedAttestationSigner(address) external view returns (bool); + + function setAccountDataEncryptionKey(bytes calldata) external; + function setMetadataURL(string calldata) external; + function setName(string calldata) external; + function setWalletAddress(address, uint8, bytes32, bytes32) external; + function setAccount(string calldata, bytes calldata, address, uint8, bytes32, bytes32) external; + + function getDataEncryptionKey(address) external view returns (bytes memory); + function getWalletAddress(address) external view returns (address); + function getMetadataURL(address) external view returns (string memory); + function batchGetMetadataURL(address[] calldata) external view returns (uint256[] memory, bytes memory); + function getName(address) external view returns (string memory); + + function authorizeVoteSigner(address, uint8, bytes32, bytes32) external; + function authorizeValidatorSigner(address, uint8, bytes32, bytes32) external; + function authorizeValidatorSignerWithPublicKey(address, uint8, bytes32, bytes32, bytes calldata) external; + function authorizeValidatorSignerWithKeys( + address, + uint8, + bytes32, + bytes32, + bytes calldata, + bytes calldata, + bytes calldata + ) + external; + function authorizeAttestationSigner(address, uint8, bytes32, bytes32) external; + function createAccount() external returns (bool); + + function setPaymentDelegation(address, uint256) external; + function getPaymentDelegation(address) external view returns (address, uint256); + function isSigner(address, address, bytes32) external view returns (bool); +} diff --git a/packages/contracts-bedrock/src/celo/interfaces/ICeloRegistry.sol b/packages/contracts-bedrock/src/celo/interfaces/ICeloRegistry.sol new file mode 100644 index 0000000000000..604c6a71f2c73 --- /dev/null +++ b/packages/contracts-bedrock/src/celo/interfaces/ICeloRegistry.sol @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.0; + +interface ICeloRegistry { + function setAddressFor(string calldata, address) external; + function getAddressForOrDie(bytes32) external view returns (address); + function getAddressFor(bytes32) external view returns (address); + function getAddressForStringOrDie(string calldata identifier) external view returns (address); + function getAddressForString(string calldata identifier) external view returns (address); + function isOneOf(bytes32[] calldata, address) external view returns (bool); +} diff --git a/packages/contracts-bedrock/src/celo/interfaces/ICeloToken.sol b/packages/contracts-bedrock/src/celo/interfaces/ICeloToken.sol new file mode 100644 index 0000000000000..29c1416fbafee --- /dev/null +++ b/packages/contracts-bedrock/src/celo/interfaces/ICeloToken.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.0; + +/** + * @title This interface describes the non- ERC20 shared interface for all Celo Tokens, and + * in the absence of interface inheritance is intended as a companion to IERC20.sol. + */ +interface ICeloToken { + function transferWithComment(address, uint256, string calldata) external returns (bool); + function name() external view returns (string memory); + function symbol() external view returns (string memory); + function decimals() external view returns (uint8); + function burn(uint256 value) external returns (bool); +} diff --git a/packages/contracts-bedrock/src/celo/interfaces/ICeloVersionedContract.sol b/packages/contracts-bedrock/src/celo/interfaces/ICeloVersionedContract.sol new file mode 100644 index 0000000000000..ebad613f1521b --- /dev/null +++ b/packages/contracts-bedrock/src/celo/interfaces/ICeloVersionedContract.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.0; + +interface ICeloVersionedContract { + /** + * @notice Returns the storage, major, minor, and patch version of the contract. + * @return Storage version of the contract. + * @return Major version of the contract. + * @return Minor version of the contract. + * @return Patch version of the contract. + */ + function getVersionNumber() external pure returns (uint256, uint256, uint256, uint256); +} diff --git a/packages/contracts-bedrock/src/celo/interfaces/IFeeCurrencyDirectory.sol b/packages/contracts-bedrock/src/celo/interfaces/IFeeCurrencyDirectory.sol new file mode 100644 index 0000000000000..5c6ab9051ccf2 --- /dev/null +++ b/packages/contracts-bedrock/src/celo/interfaces/IFeeCurrencyDirectory.sol @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +interface IFeeCurrencyDirectory { + struct CurrencyConfig { + address oracle; + uint256 intrinsicGas; + } + + /** + * @notice Returns the list of all currency addresses. + * @return An array of addresses. + */ + function getCurrencies() external view returns (address[] memory); + /** + * @notice Returns the configuration for a currency. + * @param token The address of the token. + * @return Currency configuration of the token. + */ + function getCurrencyConfig(address token) external view returns (CurrencyConfig memory); + + /** + * @notice Retrieves exchange rate between token and CELO. + * @param token The token address whose price is to be fetched. + * @return numerator The exchange rate numerator. + * @return denominator The exchange rate denominator. + */ + function getExchangeRate(address token) external view returns (uint256 numerator, uint256 denominator); +} diff --git a/packages/contracts-bedrock/src/celo/interfaces/IFreezer.sol b/packages/contracts-bedrock/src/celo/interfaces/IFreezer.sol new file mode 100644 index 0000000000000..6b3e1802401e9 --- /dev/null +++ b/packages/contracts-bedrock/src/celo/interfaces/IFreezer.sol @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.0; + +interface IFreezer { + function isFrozen(address) external view returns (bool); +} diff --git a/packages/contracts-bedrock/src/celo/interfaces/IMetaTransactionWallet.sol b/packages/contracts-bedrock/src/celo/interfaces/IMetaTransactionWallet.sol new file mode 100644 index 0000000000000..60ed3c8e4cb02 --- /dev/null +++ b/packages/contracts-bedrock/src/celo/interfaces/IMetaTransactionWallet.sol @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.0; + +interface IMetaTransactionWallet { + function setEip712DomainSeparator() external; + function executeMetaTransaction( + address, + uint256, + bytes calldata, + uint8, + bytes32, + bytes32 + ) + external + returns (bytes memory); + function executeTransaction(address, uint256, bytes calldata) external returns (bytes memory); + function executeTransactions( + address[] calldata, + uint256[] calldata, + bytes calldata, + uint256[] calldata + ) + external + returns (bytes memory, uint256[] memory); + + // view functions + function getMetaTransactionDigest(address, uint256, bytes calldata, uint256) external view returns (bytes32); + function getMetaTransactionSigner( + address, + uint256, + bytes calldata, + uint256, + uint8, + bytes32, + bytes32 + ) + external + view + returns (address); + + //only owner + function setSigner(address) external; +} diff --git a/packages/contracts-bedrock/src/celo/interfaces/IMetaTransactionWalletDeployer.sol b/packages/contracts-bedrock/src/celo/interfaces/IMetaTransactionWalletDeployer.sol new file mode 100644 index 0000000000000..790eb7cd6719c --- /dev/null +++ b/packages/contracts-bedrock/src/celo/interfaces/IMetaTransactionWalletDeployer.sol @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.0; + +interface IMetaTransactionWalletDeployer { + function deploy(address, address, bytes calldata) external; +} diff --git a/packages/contracts-bedrock/src/celo/interfaces/IOracle.sol b/packages/contracts-bedrock/src/celo/interfaces/IOracle.sol new file mode 100644 index 0000000000000..b9bd6eeea163d --- /dev/null +++ b/packages/contracts-bedrock/src/celo/interfaces/IOracle.sol @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +/// Possibly not final version +interface IOracle { + function getExchangeRate(address token) external view returns (uint256 numerator, uint256 denominator); +} diff --git a/packages/contracts-bedrock/src/celo/interfaces/IStableToken.sol b/packages/contracts-bedrock/src/celo/interfaces/IStableToken.sol new file mode 100644 index 0000000000000..6b1566a3f3002 --- /dev/null +++ b/packages/contracts-bedrock/src/celo/interfaces/IStableToken.sol @@ -0,0 +1,99 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +pragma solidity ^0.8.0; + +interface IStableTokenV2 { + function totalSupply() external view returns (uint256); + + function balanceOf(address account) external view returns (uint256); + + function transfer(address recipient, uint256 amount) external returns (bool); + + function allowance(address owner, address spender) external view returns (uint256); + + function approve(address spender, uint256 amount) external returns (bool); + + function transferFrom(address sender, address recipient, uint256 amount) external returns (bool); + + function mint(address, uint256) external returns (bool); + + function burn(uint256) external returns (bool); + + function permit( + address owner, + address spender, + uint256 value, + uint256 deadline, + uint8 v, + bytes32 r, + bytes32 s + ) + external; + + /** + * @notice Transfer token for a specified address + * @param to The address to transfer to. + * @param value The amount to be transferred. + * @param comment The transfer comment. + * @return True if the transaction succeeds. + */ + function transferWithComment(address to, uint256 value, string calldata comment) external returns (bool); + + /** + * @notice Initializes a StableTokenV2. + * It keeps the same signature as the original initialize() function + * in legacy/StableToken.sol + * @param _name The name of the stable token (English) + * @param _symbol A short symbol identifying the token (e.g. "cUSD") + * @param initialBalanceAddresses Array of addresses with an initial balance. + * @param initialBalanceValues Array of balance values corresponding to initialBalanceAddresses. + * deprecated-param exchangeIdentifier String identifier of exchange in registry (for specific fiat pairs) + */ + function initialize( + string calldata _name, + string calldata _symbol, + address[] calldata initialBalanceAddresses, + uint256[] calldata initialBalanceValues + ) + external; + + /** + * @notice Initializes a StableTokenV2 contract + * when upgrading from legacy/StableToken.sol. + * It sets the addresses that were previously read from the Registry. + * It runs the ERC20PermitUpgradeable initializer. + * @dev This function is only callable once. + * @param _broker The address of the Broker contract. + * @param _validators The address of the Validators contract. + * @param _exchange The address of the Exchange contract. + */ + function initializeV2(address _broker, address _validators, address _exchange) external; + + /** + * @notice Gets the address of the Broker contract. + */ + function broker() external returns (address); + + /** + * @notice Gets the address of the Validators contract. + */ + function validators() external returns (address); + + /** + * @notice Gets the address of the Exchange contract. + */ + function exchange() external returns (address); + + function debitGasFees(address from, uint256 value) external; + + function creditGasFees( + address from, + address feeRecipient, + address gatewayFeeRecipient, + address communityFund, + uint256 refund, + uint256 tipTxFee, + uint256 gatewayFee, + uint256 baseTxFee + ) + external; +} diff --git a/packages/contracts-bedrock/src/celo/interfaces/IStableTokenMento.sol b/packages/contracts-bedrock/src/celo/interfaces/IStableTokenMento.sol new file mode 100644 index 0000000000000..6011ba4b257f1 --- /dev/null +++ b/packages/contracts-bedrock/src/celo/interfaces/IStableTokenMento.sol @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.0; + +/** + * @title This interface describes the functions specific to Celo Stable Tokens, and in the + * absence of interface inheritance is intended as a companion to IERC20.sol and ICeloToken.sol. + */ +interface IStableTokenMento { + function mint(address, uint256) external returns (bool); + + function burn(uint256) external returns (bool); + + function setInflationParameters(uint256, uint256) external; + + function valueToUnits(uint256) external view returns (uint256); + + function unitsToValue(uint256) external view returns (uint256); + + function getInflationParameters() external view returns (uint256, uint256, uint256, uint256); + + // NOTE: duplicated with IERC20.sol, remove once interface inheritance is supported. + function balanceOf(address) external view returns (uint256); + + function getExchangeRegistryId() external view returns (bytes32); + + function approve(address spender, uint256 value) external returns (bool); +} diff --git a/packages/contracts-bedrock/src/celo/mento/interfaces/IExchange.sol b/packages/contracts-bedrock/src/celo/mento/interfaces/IExchange.sol new file mode 100644 index 0000000000000..f15efbc043fd1 --- /dev/null +++ b/packages/contracts-bedrock/src/celo/mento/interfaces/IExchange.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.0; + +interface IExchange { + function buy(uint256, uint256, bool) external returns (uint256); + + function sell(uint256, uint256, bool) external returns (uint256); + + function exchange(uint256, uint256, bool) external returns (uint256); + + function setUpdateFrequency(uint256) external; + + function getBuyTokenAmount(uint256, bool) external view returns (uint256); + + function getSellTokenAmount(uint256, bool) external view returns (uint256); + + function getBuyAndSellBuckets(bool) external view returns (uint256, uint256); +} diff --git a/packages/contracts-bedrock/src/celo/mento/interfaces/IReserve.sol b/packages/contracts-bedrock/src/celo/mento/interfaces/IReserve.sol new file mode 100644 index 0000000000000..5f90215173cc3 --- /dev/null +++ b/packages/contracts-bedrock/src/celo/mento/interfaces/IReserve.sol @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.0; + +interface IReserve { + function setTobinTaxStalenessThreshold(uint256) external; + + function addToken(address) external returns (bool); + + function removeToken(address, uint256) external returns (bool); + + function transferGold(address payable, uint256) external returns (bool); + + function transferExchangeGold(address payable, uint256) external returns (bool); + + function getReserveGoldBalance() external view returns (uint256); + + function getUnfrozenReserveGoldBalance() external view returns (uint256); + + function getOrComputeTobinTax() external returns (uint256, uint256); + + function getTokens() external view returns (address[] memory); + + function getReserveRatio() external view returns (uint256); + + function addExchangeSpender(address) external; + + function removeExchangeSpender(address, uint256) external; + + function addSpender(address) external; + + function removeSpender(address) external; +} diff --git a/packages/contracts-bedrock/src/celo/mento/interfaces/IStableToken.sol b/packages/contracts-bedrock/src/celo/mento/interfaces/IStableToken.sol new file mode 100644 index 0000000000000..e0d2b26448ad9 --- /dev/null +++ b/packages/contracts-bedrock/src/celo/mento/interfaces/IStableToken.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.0; + +/** + * @title This interface describes the functions specific to Celo Stable Tokens, and in the + * absence of interface inheritance is intended as a companion to IERC20.sol and ICeloToken.sol. + */ +interface IStableToken { + function mint(address, uint256) external returns (bool); + + function burn(uint256) external returns (bool); + + function setInflationParameters(uint256, uint256) external; + + function valueToUnits(uint256) external view returns (uint256); + + function unitsToValue(uint256) external view returns (uint256); + + function getInflationParameters() external view returns (uint256, uint256, uint256, uint256); + + // NOTE: duplicated with IERC20.sol, remove once interface inheritance is supported. + function balanceOf(address) external view returns (uint256); +} diff --git a/packages/contracts-bedrock/src/celo/stability/SortedOracles.sol b/packages/contracts-bedrock/src/celo/stability/SortedOracles.sol new file mode 100644 index 0000000000000..0922cd6f0a482 --- /dev/null +++ b/packages/contracts-bedrock/src/celo/stability/SortedOracles.sol @@ -0,0 +1,466 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +pragma solidity ^0.8.0; + +import "../../../lib/openzeppelin-contracts/contracts/access/Ownable.sol"; +import "../../../lib/openzeppelin-contracts/contracts/utils/math/SafeMath.sol"; + +import "./interfaces/ISortedOracles.sol"; +import "../common/interfaces/ICeloVersionedContract.sol"; +import "./interfaces/IBreakerBox.sol"; + +import "../common/FixidityLib.sol"; +import "../common/Initializable.sol"; +import "../common/linkedlists/AddressSortedLinkedListWithMedian.sol"; +import "../common/linkedlists/SortedLinkedListWithMedian.sol"; +import "./interfaces/IOracle.sol"; + +/** + * @title SortedOracles + * + * @notice This contract stores a collection of exchange rates with CELO + * expressed in units of other assets. The most recent exchange rates + * are gathered off-chain by oracles, who then use the `report` function to + * submit the rates to this contract. Before submitting a rate report, an + * oracle's address must be added to the `isOracle` mapping for a specific + * rateFeedId, with the flag set to true. While submitting a report requires + * an address to be added to the mapping, no additional permissions are needed + * to read the reports, the calculated median rate, or the list of oracles. + * + * @dev A unique rateFeedId identifies each exchange rate. In the initial implementation + * of this contract, the rateFeedId was set as the address of the stable + * asset contract that used the rate. However, this implementation has since + * been updated, and the rateFeedId block.timestamp also refers to an address derived from the + * concatenation other asset symbols. This change enables the contract to store multiple exchange rates for a + * single token. As a result of this change, there may be instances + * where the term "token" is used in the contract code. These useages of the term + * "token" are actually referring to the rateFeedId. + * + */ +contract SortedOracles is ISortedOracles, IOracle, ICeloVersionedContract, Ownable, Initializable { + using SafeMath for uint256; + using AddressSortedLinkedListWithMedian for SortedLinkedListWithMedian.List; + using FixidityLib for FixidityLib.Fraction; + + struct EquivalentToken { + address token; + } + + uint256 private constant FIXED1_UINT = 1e24; + + // Maps a rateFeedID to a sorted list of report values. + mapping(address => SortedLinkedListWithMedian.List) private rates; + // Maps a rateFeedID to a sorted list of report timestamps. + mapping(address => SortedLinkedListWithMedian.List) private timestamps; + mapping(address => mapping(address => bool)) public isOracle; + mapping(address => address[]) public oracles; + + // `reportExpirySeconds` is the fallback value used to determine reporting + // frequency. Initially it was the _only_ value but we later introduced + // the per token mapping in `tokenReportExpirySeconds`. If a token + // doesn't have a value in the mapping (i.e. it's 0), the fallback is used. + // See: #getTokenReportExpirySeconds + uint256 public reportExpirySeconds; + // Maps a rateFeedId to its report expiry time in seconds. + mapping(address => uint256) public tokenReportExpirySeconds; + + IBreakerBox public breakerBox; + // Maps a token address to its equivalent token address. + // Original token will return the median value same as the value of equivalent token. + mapping(address => EquivalentToken) public equivalentTokens; + + event OracleAdded(address indexed token, address indexed oracleAddress); + event OracleRemoved(address indexed token, address indexed oracleAddress); + event OracleReported(address indexed token, address indexed oracle, uint256 timestamp, uint256 value); + event OracleReportRemoved(address indexed token, address indexed oracle); + event MedianUpdated(address indexed token, uint256 value); + event ReportExpirySet(uint256 reportExpiry); + event TokenReportExpirySet(address token, uint256 reportExpiry); + event BreakerBoxUpdated(address indexed newBreakerBox); + event EquivalentTokenSet(address indexed token, address indexed equivalentToken); + + modifier onlyOracle(address token) { + require(isOracle[token][msg.sender], "sender was not an oracle for token addr"); + _; + } + + /** + * @notice Sets initialized == true on implementation contracts + * @param test Set to true to skip implementation initialization + */ + constructor(bool test) Initializable(test) { } + + /** + * @notice Used in place of the constructor to allow the contract to be upgradable via proxy. + * @param _reportExpirySeconds The number of seconds before a report is considered expired. + */ + function initialize(uint256 _reportExpirySeconds) external initializer { + _transferOwnership(msg.sender); + setReportExpiry(_reportExpirySeconds); + } + + /** + * @notice Sets the report expiry parameter for a rateFeedId. + * @param _token The token for which the report expiry is being set. + * @param _reportExpirySeconds The number of seconds before a report is considered expired. + */ + function setTokenReportExpiry(address _token, uint256 _reportExpirySeconds) external onlyOwner { + require(_reportExpirySeconds > 0, "report expiry seconds must be > 0"); + require(_reportExpirySeconds != tokenReportExpirySeconds[_token], "token reportExpirySeconds hasn't changed"); + tokenReportExpirySeconds[_token] = _reportExpirySeconds; + emit TokenReportExpirySet(_token, _reportExpirySeconds); + } + + /** + * @notice Adds a new Oracle for a specified rate feed. + * @param token The token for which the specified oracle is to be added. + * @param oracleAddress The address of the oracle. + */ + function addOracle(address token, address oracleAddress) external onlyOwner { + // solhint-disable-next-line reason-string + require( + token != address(0) && oracleAddress != address(0) && !isOracle[token][oracleAddress], + "token addr was null or oracle addr was null or oracle addr is already an oracle for token addr" + ); + isOracle[token][oracleAddress] = true; + oracles[token].push(oracleAddress); + emit OracleAdded(token, oracleAddress); + } + + /** + * @notice Removes an Oracle from a specified rate feed. + * @param token The token from which the specified oracle is to be removed. + * @param oracleAddress The address of the oracle. + * @param index The index of `oracleAddress` in the list of oracles. + */ + function removeOracle(address token, address oracleAddress, uint256 index) external onlyOwner { + // solhint-disable-next-line reason-string + require( + token != address(0) && oracleAddress != address(0) && oracles[token].length > index + && oracles[token][index] == oracleAddress, + "token addr null or oracle addr null or index of token oracle not mapped to oracle addr" + ); + isOracle[token][oracleAddress] = false; + oracles[token][index] = oracles[token][oracles[token].length.sub(1)]; + oracles[token].pop(); + if (reportExists(token, oracleAddress)) { + removeReport(token, oracleAddress); + } + emit OracleRemoved(token, oracleAddress); + } + + /** + * @notice Removes a report that is expired. + * @param token The token for which the expired report is to be removed. + * @param n The number of expired reports to remove, at most (deterministic upper gas bound). + */ + function removeExpiredReports(address token, uint256 n) external { + require( + token != address(0) && n < timestamps[token].getNumElements(), + "token addr null or trying to remove too many reports" + ); + for (uint256 i = 0; i < n; i = i.add(1)) { + (bool isExpired, address oldestAddress) = isOldestReportExpired(token); + if (isExpired) { + removeReport(token, oldestAddress); + } else { + break; + } + } + } + + /** + * @notice Sets the equivalent token for a token. + * @param token The address of the token. + * @param equivalentToken The address of the equivalent token. + */ + function setEquivalentToken(address token, address equivalentToken) external onlyOwner { + require(token != address(0), "token address cannot be 0"); + require(equivalentToken != address(0), "equivalentToken address cannot be 0"); + equivalentTokens[token] = EquivalentToken(equivalentToken); + emit EquivalentTokenSet(token, equivalentToken); + } + + /** + * @notice Sets the equivalent token for a token. + * @param token The address of the token. + */ + function deleteEquivalentToken(address token) external onlyOwner { + require(token != address(0), "token address cannot be 0"); + delete equivalentTokens[token]; + emit EquivalentTokenSet(token, address(0)); + } + + /** + * @notice Updates an oracle value and the median. + * @param token The token for which the rate is being reported. + * @param value The number of stable asset that equate to one unit of collateral asset, for the + * specified rateFeedId, expressed as a fixidity value. + * @param lesserKey The element which should be just left of the new oracle value. + * @param greaterKey The element which should be just right of the new oracle value. + * @dev Note that only one of `lesserKey` or `greaterKey` needs to be correct to reduce friction. + */ + function report(address token, uint256 value, address lesserKey, address greaterKey) external onlyOracle(token) { + uint256 originalMedian = rates[token].getMedianValue(); + if (rates[token].contains(msg.sender)) { + rates[token].update(msg.sender, value, lesserKey, greaterKey); + + // Rather than update the timestamp, we remove it and re-add it at the + // head of the list later. The reason for this is that we need to handle + // a few different cases: + // 1. This oracle is the only one to report so far. lesserKey = address(0) + // 2. Other oracles have reported since this one's last report. lesserKey = getHead() + // 3. Other oracles have reported, but the most recent is this one. + // lesserKey = key immediately after getHead() + // + // However, if we just remove this timestamp, timestamps[token].getHead() + // does the right thing in all cases. + timestamps[token].remove(msg.sender); + } else { + rates[token].insert(msg.sender, value, lesserKey, greaterKey); + } + timestamps[token].insert( + msg.sender, + // solhint-disable-next-line not-rely-on-time + block.timestamp, + timestamps[token].getHead(), + address(0) + ); + emit OracleReported(token, msg.sender, block.timestamp, value); + uint256 newMedian = rates[token].getMedianValue(); + if (newMedian != originalMedian) { + emit MedianUpdated(token, newMedian); + } + + if (address(breakerBox) != address(0)) { + breakerBox.checkAndSetBreakers(token); + } + } + + /** + * @notice Gets the equivalent token for a token. + * @param token The address of the token. + * @return The address of the equivalent token. + */ + function getEquivalentToken(address token) external view returns (address) { + return (equivalentTokens[token].token); + } + + /** + * @notice Returns the median timestamp. + * @dev Does not take the equivalentTokens mapping into account. + * For that, the underlying token should be queried. + * @param token The token for which the median timestamp is being retrieved. + * @return uint256 The median report timestamp for the specified rateFeedId. + */ + function medianTimestamp(address token) external view returns (uint256) { + return timestamps[token].getMedianValue(); + } + + /** + * @notice Gets all elements from the doubly linked list. + * @dev Does not take the equivalentTokens mapping into account. + * For that, the underlying token should be queried. + * @param token The token for which the timestamps are being retrieved. + * @return keys Keys of nn unpacked list of elements from largest to smallest. + * @return values Values of an unpacked list of elements from largest to smallest. + * @return relations Relations of an unpacked list of elements from largest to smallest. + */ + function getTimestamps(address token) + external + view + returns (address[] memory, uint256[] memory, SortedLinkedListWithMedian.MedianRelation[] memory) + { + return timestamps[token].getElements(); + } + + /** + * @notice Returns the list of oracles for a speficied rateFeedId. + * @dev Does not take the equivalentTokens mapping into account. + * For that, the underlying token should be queried. + * @param token The token for which the oracles are being retrieved. + * @return address[] A list of oracles for the given rateFeedId. + */ + function getOracles(address token) external view returns (address[] memory) { + return oracles[token]; + } + + /** + * @notice Gets all elements from the doubly linked list. + * @dev Does not take the equivalentTokens mapping into account. + * For that, the underlying token should be queried. + * @param token The token for which the rates are being retrieved. + * @return keys Keys of an unpacked list of elements from largest to smallest. + * @return values Values of an unpacked list of elements from largest to smallest. + * @return relations Relations of an unpacked list of elements from largest to smallest. + */ + function getRates(address token) + external + view + returns (address[] memory, uint256[] memory, SortedLinkedListWithMedian.MedianRelation[] memory) + { + return rates[token].getElements(); + } + + /** + * @notice Returns the exchange rate for a specified token. + * @param token The token for which the exchange rate is being retrieved. + * @return numerator uint256 The exchange rate for the specified token. + * @return denominator uint256 The denominator for the exchange rate. + */ + function getExchangeRate(address token) external view returns (uint256 numerator, uint256 denominator) { + (numerator, denominator) = medianRate(token); + } + + /** + * @notice Returns the storage, major, minor, and patch version of the contract. + * @return Storage version of the contract. + * @return Major version of the contract. + * @return Minor version of the contract. + * @return Patch version of the contract. + */ + function getVersionNumber() external pure returns (uint256, uint256, uint256, uint256) { + return (1, 1, 4, 0); + } + + /** + * @notice Sets the report expiry parameter. + * @param _reportExpirySeconds The number of seconds before a report is considered expired. + */ + function setReportExpiry(uint256 _reportExpirySeconds) public onlyOwner { + require(_reportExpirySeconds > 0, "report expiry seconds must be > 0"); + require(_reportExpirySeconds != reportExpirySeconds, "reportExpirySeconds hasn't changed"); + reportExpirySeconds = _reportExpirySeconds; + emit ReportExpirySet(_reportExpirySeconds); + } + + /** + * @notice Sets the address of the BreakerBox. + * @param newBreakerBox The new BreakerBox address. + */ + function setBreakerBox(IBreakerBox newBreakerBox) public onlyOwner { + require(address(newBreakerBox) != address(0), "BreakerBox address must be set"); + breakerBox = newBreakerBox; + emit BreakerBoxUpdated(address(newBreakerBox)); + } + + /** + * @notice Returns the median of the currently stored rates for a specified rateFeedId. + * @dev Please note that this function respects the equivalentToken mapping, and so may + * return the median identified as an equivalent to the supplied rateFeedId. + * @param token The token for which the median value is being retrieved. + * @return uint256 The median exchange rate for rateFeedId (fixidity). + * @return uint256 denominator + */ + function medianRate(address token) public view returns (uint256, uint256) { + EquivalentToken storage equivalentToken = equivalentTokens[token]; + if (equivalentToken.token != address(0)) { + (uint256 equivalentMedianRate, uint256 denominator) = + medianRateWithoutEquivalentMapping(equivalentToken.token); + return (equivalentMedianRate, denominator); + } + + return medianRateWithoutEquivalentMapping(token); + } + + /** + * @notice Returns the number of rates that are currently stored for a specifed rateFeedId. + * @dev Does not take the equivalentTokens mapping into account. + * For that, the underlying token should be queried. + * @param token The token for which the number of rates is being retrieved. + * @return uint256 The number of reported oracle rates stored for the given rateFeedId. + */ + function numRates(address token) public view returns (uint256) { + return rates[token].getNumElements(); + } + + /** + * @notice Check if last report is expired. + * @param token The token for which the expired report is to be checked. + * @return bool A bool indicating if the last report is expired. + * @return address Oracle address of the last report. + */ + function isOldestReportExpired(address token) public view returns (bool, address) { + // solhint-disable-next-line reason-string + require(token != address(0)); + address oldest = timestamps[token].getTail(); + uint256 timestamp = timestamps[token].getValue(oldest); + // solhint-disable-next-line not-rely-on-time + if (block.timestamp.sub(timestamp) >= getTokenReportExpirySeconds(token)) { + return (true, oldest); + } + return (false, oldest); + } + + /** + * @notice Returns the median of the currently stored rates for a specified rateFeedId. + * @dev Does not take the equivalentTokens mapping into account. + * @param token The token for which the median value is being retrieved. + * @return uint256 The median exchange rate for rateFeedId (fixidity). + * @return uint256 denominator + */ + function medianRateWithoutEquivalentMapping(address token) public view returns (uint256, uint256) { + return (rates[token].getMedianValue(), numRates(token) == 0 ? 0 : FIXED1_UINT); + } + + /** + * @notice Returns the number of timestamps. + * @dev Does not take the equivalentTokens mapping into account. + * For that, the underlying token should be queried. + * @param token The token for which the number of timestamps is being retrieved. + * @return uint256 The number of oracle report timestamps for the specified rateFeedId. + */ + function numTimestamps(address token) public view returns (uint256) { + return timestamps[token].getNumElements(); + } + + /** + * @notice Returns the expiry for specified rateFeedId if it exists, if not the default is returned. + * @dev Does not take the equivalentTokens mapping into account. + * For that, the underlying token should be queried. + * @param token The token for which the report expiry is being retrieved. + * @return The report expiry in seconds. + */ + function getTokenReportExpirySeconds(address token) public view returns (uint256) { + if (tokenReportExpirySeconds[token] == 0) { + return reportExpirySeconds; + } + + return tokenReportExpirySeconds[token]; + } + + /** + * @notice Checks if a report exists for a specified rateFeedId from a given oracle. + * @dev Does not take the equivalentTokens mapping into account. + * For that, the underlying token should be queried. + * @param token The token for which the report should be checked. + * @param oracle The oracle whose report should be checked. + * @return bool True if a report exists, false otherwise. + */ + function reportExists(address token, address oracle) internal view returns (bool) { + return rates[token].contains(oracle) && timestamps[token].contains(oracle); + } + + /** + * @notice Removes an oracle value and updates the median. + * @dev Does not take the equivalentTokens mapping into account. + * For that, the underlying token should be queried. + * @param token The token for which the oracle report should be removed. + * @param oracle The oracle whose value should be removed. + * @dev This can be used to delete elements for oracles that have been removed. + * However, a > 1 elements reports list should always be maintained + */ + function removeReport(address token, address oracle) private { + if (numTimestamps(token) == 1 && reportExists(token, oracle)) return; + uint256 originalMedian = rates[token].getMedianValue(); + rates[token].remove(oracle); + timestamps[token].remove(oracle); + emit OracleReportRemoved(token, oracle); + uint256 newMedian = rates[token].getMedianValue(); + if (newMedian != originalMedian) { + emit MedianUpdated(token, newMedian); + if (address(breakerBox) != address(0)) { + breakerBox.checkAndSetBreakers(token); + } + } + } +} diff --git a/packages/contracts-bedrock/src/celo/stability/interfaces/IBreakerBox.sol b/packages/contracts-bedrock/src/celo/stability/interfaces/IBreakerBox.sol new file mode 100644 index 0000000000000..83690dcb2f397 --- /dev/null +++ b/packages/contracts-bedrock/src/celo/stability/interfaces/IBreakerBox.sol @@ -0,0 +1,140 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +/** + * @title Breaker Box Interface + * @notice Defines the basic interface for the Breaker Box + */ +interface IBreakerBox { + /** + * @dev Used to keep track of the status of a breaker for a specific rate feed. + * + * - TradingMode: Represents the trading mode the breaker is in for a rate feed. + * This uses a bitmask approach, meaning each bit represents a + * different trading mode. The final trading mode of the rate feed + * is obtained by applying a logical OR operation to the TradingMode + * of all breakers associated with that rate feed. This allows multiple + * breakers to contribute to the final trading mode simultaneously. + * Possible values: + * 0: bidirectional trading. + * 1: inflow only. + * 2: outflow only. + * 3: trading halted. + * + * - LastUpdatedTime: Records the last time the breaker status was updated. This is + * used to manage cooldown periods before the breaker can be reset. + * + * - Enabled: Indicates whether the breaker is enabled for the associated rate feed. + */ + struct BreakerStatus { + uint8 tradingMode; + uint64 lastUpdatedTime; + bool enabled; + } + + /** + * @notice Emitted when a new breaker is added to the breaker box. + * @param breaker The address of the breaker. + */ + event BreakerAdded(address indexed breaker); + + /** + * @notice Emitted when a breaker is removed from the breaker box. + * @param breaker The address of the breaker. + */ + event BreakerRemoved(address indexed breaker); + + /** + * @notice Emitted when a breaker is tripped by a rate feed. + * @param breaker The address of the breaker. + * @param rateFeedID The address of the rate feed. + */ + event BreakerTripped(address indexed breaker, address indexed rateFeedID); + + /** + * @notice Emitted when a new rate feed is added to the breaker box. + * @param rateFeedID The address of the rate feed. + */ + event RateFeedAdded(address indexed rateFeedID); + + /** + * @notice Emitted when dependencies for a rate feed are set. + * @param rateFeedID The address of the rate feed. + * @param dependencies The addresses of the dependendent rate feeds. + */ + event RateFeedDependenciesSet(address indexed rateFeedID, address[] indexed dependencies); + + /** + * @notice Emitted when a rate feed is removed from the breaker box. + * @param rateFeedID The address of the rate feed. + */ + event RateFeedRemoved(address indexed rateFeedID); + + /** + * @notice Emitted when the trading mode for a rate feed is updated + * @param rateFeedID The address of the rate feed. + * @param tradingMode The new trading mode. + */ + event TradingModeUpdated(address indexed rateFeedID, uint256 tradingMode); + + /** + * @notice Emitted after a reset attempt is successful. + * @param rateFeedID The address of the rate feed. + * @param breaker The address of the breaker. + */ + event ResetSuccessful(address indexed rateFeedID, address indexed breaker); + + /** + * @notice Emitted after a reset attempt fails when the + * rate feed fails the breakers reset criteria. + * @param rateFeedID The address of the rate feed. + * @param breaker The address of the breaker. + */ + event ResetAttemptCriteriaFail(address indexed rateFeedID, address indexed breaker); + + /** + * @notice Emitted after a reset attempt fails when cooldown time has not elapsed. + * @param rateFeedID The address of the rate feed. + * @param breaker The address of the breaker. + */ + event ResetAttemptNotCool(address indexed rateFeedID, address indexed breaker); + + /** + * @notice Emitted when the sortedOracles address is updated. + * @param newSortedOracles The address of the new sortedOracles. + */ + event SortedOraclesUpdated(address indexed newSortedOracles); + + /** + * @notice Emitted when the breaker is enabled or disabled for a rate feed. + * @param breaker The address of the breaker. + * @param rateFeedID The address of the rate feed. + * @param status Indicating the status. + */ + event BreakerStatusUpdated(address breaker, address rateFeedID, bool status); + + /** + * @notice Checks breakers for the rateFeedID and sets correct trading mode + * if any breakers are tripped or need to be reset. + * @param rateFeedID The address of the rate feed to run checks for. + */ + function checkAndSetBreakers(address rateFeedID) external; + + /** + * @notice Retrives an array of all breaker addresses. + */ + function getBreakers() external view returns (address[] memory); + + /** + * @notice Checks if a breaker with the specified address has been added to the breaker box. + * @param breaker The address of the breaker to check; + * @return A bool indicating whether or not the breaker has been added. + */ + function isBreaker(address breaker) external view returns (bool); + + /** + * @notice Gets the trading mode for the specified rateFeedID. + * @param rateFeedID The address of the rate feed to retrieve the trading mode for. + */ + function getRateFeedTradingMode(address rateFeedID) external view returns (uint8 tradingMode); +} diff --git a/packages/contracts-bedrock/src/celo/stability/interfaces/IOracle.sol b/packages/contracts-bedrock/src/celo/stability/interfaces/IOracle.sol new file mode 100644 index 0000000000000..b9bd6eeea163d --- /dev/null +++ b/packages/contracts-bedrock/src/celo/stability/interfaces/IOracle.sol @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +/// Possibly not final version +interface IOracle { + function getExchangeRate(address token) external view returns (uint256 numerator, uint256 denominator); +} diff --git a/packages/contracts-bedrock/src/celo/stability/interfaces/ISortedOracles.sol b/packages/contracts-bedrock/src/celo/stability/interfaces/ISortedOracles.sol new file mode 100644 index 0000000000000..b6a4d4539a359 --- /dev/null +++ b/packages/contracts-bedrock/src/celo/stability/interfaces/ISortedOracles.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +interface ISortedOracles { + function addOracle(address, address) external; + function removeOracle(address, address, uint256) external; + function report(address, uint256, address, address) external; + function removeExpiredReports(address, uint256) external; + function isOldestReportExpired(address token) external view returns (bool, address); + function numRates(address) external view returns (uint256); + function medianRate(address) external view returns (uint256, uint256); + function numTimestamps(address) external view returns (uint256); + function medianTimestamp(address) external view returns (uint256); +} diff --git a/packages/contracts-bedrock/src/celo/testing/FeeCurrency.sol b/packages/contracts-bedrock/src/celo/testing/FeeCurrency.sol new file mode 100644 index 0000000000000..fd00f42c01bbb --- /dev/null +++ b/packages/contracts-bedrock/src/celo/testing/FeeCurrency.sol @@ -0,0 +1,420 @@ +// SPDX-License-Identifier: MIT +// Modified from OpenZeppelin Contracts (last updated v4.7.0) (token/ERC20/ERC20.sol) + +pragma solidity ^0.8.0; + +import "../../../lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; +import "../../../lib/openzeppelin-contracts/contracts/token/ERC20/extensions/IERC20Metadata.sol"; +import "../../../lib/openzeppelin-contracts/contracts/utils/Context.sol"; + +import "../CalledByVm.sol"; + +/** + * @dev Implementation of the {IERC20} interface + Celo debit/creditGasFees. + * + * This implementation is agnostic to the way tokens are created. This means + * that a supply mechanism has to be added in a derived contract using {_mint}. + * For a generic mechanism see {ERC20PresetMinterPauser}. + * + * TIP: For a detailed writeup see our guide + * https://forum.zeppelin.solutions/t/how-to-implement-erc20-supply-mechanisms/226[How + * to implement supply mechanisms]. + * + * We have followed general OpenZeppelin Contracts guidelines: functions revert + * instead returning `false` on failure. This behavior is nonetheless + * conventional and does not conflict with the expectations of ERC20 + * applications. + * + * Additionally, an {Approval} event is emitted on calls to {transferFrom}. + * This allows applications to reconstruct the allowance for all accounts just + * by listening to said events. Other implementations of the EIP may not emit + * these events, as it isn't required by the specification. + * + * Finally, the non-standard {decreaseAllowance} and {increaseAllowance} + * functions have been added to mitigate the well-known issues around setting + * allowances. See {IERC20-approve}. + */ +contract FeeCurrency is Context, IERC20, IERC20Metadata, CalledByVm { + mapping(address => uint256) private _balances; + + mapping(address => mapping(address => uint256)) private _allowances; + + uint256 private _totalSupply; + + string private _name; + string private _symbol; + + /** + * @dev Sets the values for {name} and {symbol}. + * + * The default value of {decimals} is 18. To select a different value for + * {decimals} you should overload it. + * + * All two of these values are immutable: they can only be set once during + * construction. + */ + constructor(string memory name_, string memory symbol_) { + _name = name_; + _symbol = symbol_; + } + + /** + * @dev Returns the name of the token. + */ + function name() public view virtual override returns (string memory) { + return _name; + } + + /** + * @dev Returns the symbol of the token, usually a shorter version of the + * name. + */ + function symbol() public view virtual override returns (string memory) { + return _symbol; + } + + /** + * @dev Returns the number of decimals used to get its user representation. + * For example, if `decimals` equals `2`, a balance of `505` tokens should + * be displayed to a user as `5.05` (`505 / 10 ** 2`). + * + * Tokens usually opt for a value of 18, imitating the relationship between + * Ether and Wei. This is the value {ERC20} uses, unless this function is + * overridden; + * + * NOTE: This information is only used for _display_ purposes: it in + * no way affects any of the arithmetic of the contract, including + * {IERC20-balanceOf} and {IERC20-transfer}. + */ + function decimals() public view virtual override returns (uint8) { + return 18; + } + + /** + * @dev See {IERC20-totalSupply}. + */ + function totalSupply() public view virtual override returns (uint256) { + return _totalSupply; + } + + /** + * @dev See {IERC20-balanceOf}. + */ + function balanceOf(address account) public view virtual override returns (uint256) { + return _balances[account]; + } + + /** + * @dev See {IERC20-transfer}. + * + * Requirements: + * + * - `to` cannot be the zero address. + * - the caller must have a balance of at least `amount`. + */ + function transfer(address to, uint256 amount) public virtual override returns (bool) { + address owner = _msgSender(); + _transfer(owner, to, amount); + return true; + } + + /** + * @dev See {IERC20-allowance}. + */ + function allowance(address owner, address spender) public view virtual override returns (uint256) { + return _allowances[owner][spender]; + } + + /** + * @dev See {IERC20-approve}. + * + * NOTE: If `amount` is the maximum `uint256`, the allowance is not updated on + * `transferFrom`. This is semantically equivalent to an infinite approval. + * + * Requirements: + * + * - `spender` cannot be the zero address. + */ + function approve(address spender, uint256 amount) public virtual override returns (bool) { + address owner = _msgSender(); + _approve(owner, spender, amount); + return true; + } + + /** + * @dev See {IERC20-transferFrom}. + * + * Emits an {Approval} event indicating the updated allowance. This is not + * required by the EIP. See the note at the beginning of {ERC20}. + * + * NOTE: Does not update the allowance if the current allowance + * is the maximum `uint256`. + * + * Requirements: + * + * - `from` and `to` cannot be the zero address. + * - `from` must have a balance of at least `amount`. + * - the caller must have allowance for ``from``'s tokens of at least + * `amount`. + */ + function transferFrom(address from, address to, uint256 amount) public virtual override returns (bool) { + address spender = _msgSender(); + _spendAllowance(from, spender, amount); + _transfer(from, to, amount); + return true; + } + + /** + * @dev Atomically increases the allowance granted to `spender` by the caller. + * + * This is an alternative to {approve} that can be used as a mitigation for + * problems described in {IERC20-approve}. + * + * Emits an {Approval} event indicating the updated allowance. + * + * Requirements: + * + * - `spender` cannot be the zero address. + */ + function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) { + address owner = _msgSender(); + _approve(owner, spender, allowance(owner, spender) + addedValue); + return true; + } + + /** + * @dev Atomically decreases the allowance granted to `spender` by the caller. + * + * This is an alternative to {approve} that can be used as a mitigation for + * problems described in {IERC20-approve}. + * + * Emits an {Approval} event indicating the updated allowance. + * + * Requirements: + * + * - `spender` cannot be the zero address. + * - `spender` must have allowance for the caller of at least + * `subtractedValue`. + */ + function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) { + address owner = _msgSender(); + uint256 currentAllowance = allowance(owner, spender); + require(currentAllowance >= subtractedValue, "ERC20: decreased allowance below zero"); + unchecked { + _approve(owner, spender, currentAllowance - subtractedValue); + } + + return true; + } + + /** + * @dev Moves `amount` of tokens from `from` to `to`. + * + * This internal function is equivalent to {transfer}, and can be used to + * e.g. implement automatic token fees, slashing mechanisms, etc. + * + * Emits a {Transfer} event. + * + * Requirements: + * + * - `from` cannot be the zero address. + * - `to` cannot be the zero address. + * - `from` must have a balance of at least `amount`. + */ + function _transfer(address from, address to, uint256 amount) internal virtual { + require(from != address(0), "ERC20: transfer from the zero address"); + require(to != address(0), "ERC20: transfer to the zero address"); + + _beforeTokenTransfer(from, to, amount); + + uint256 fromBalance = _balances[from]; + require(fromBalance >= amount, "ERC20: transfer amount exceeds balance"); + unchecked { + _balances[from] = fromBalance - amount; + } + _balances[to] += amount; + + emit Transfer(from, to, amount); + + _afterTokenTransfer(from, to, amount); + } + + /** + * @dev Creates `amount` tokens and assigns them to `account`, increasing + * the total supply. + * + * Emits a {Transfer} event with `from` set to the zero address. + * + * Requirements: + * + * - `account` cannot be the zero address. + */ + function _mint(address account, uint256 amount) internal virtual { + require(account != address(0), "ERC20: mint to the zero address"); + + _beforeTokenTransfer(address(0), account, amount); + + _totalSupply += amount; + _balances[account] += amount; + emit Transfer(address(0), account, amount); + + _afterTokenTransfer(address(0), account, amount); + } + + /** + * @dev Destroys `amount` tokens from `account`, reducing the + * total supply. + * + * Emits a {Transfer} event with `to` set to the zero address. + * + * Requirements: + * + * - `account` cannot be the zero address. + * - `account` must have at least `amount` tokens. + */ + function _burn(address account, uint256 amount) internal virtual { + require(account != address(0), "ERC20: burn from the zero address"); + + _beforeTokenTransfer(account, address(0), amount); + + uint256 accountBalance = _balances[account]; + require(accountBalance >= amount, "ERC20: burn amount exceeds balance"); + unchecked { + _balances[account] = accountBalance - amount; + } + _totalSupply -= amount; + + emit Transfer(account, address(0), amount); + + _afterTokenTransfer(account, address(0), amount); + } + + /** + * @dev Sets `amount` as the allowance of `spender` over the `owner` s tokens. + * + * This internal function is equivalent to `approve`, and can be used to + * e.g. set automatic allowances for certain subsystems, etc. + * + * Emits an {Approval} event. + * + * Requirements: + * + * - `owner` cannot be the zero address. + * - `spender` cannot be the zero address. + */ + function _approve(address owner, address spender, uint256 amount) internal virtual { + require(owner != address(0), "ERC20: approve from the zero address"); + require(spender != address(0), "ERC20: approve to the zero address"); + + _allowances[owner][spender] = amount; + emit Approval(owner, spender, amount); + } + + /** + * @dev Updates `owner` s allowance for `spender` based on spent `amount`. + * + * Does not update the allowance amount in case of infinite allowance. + * Revert if not enough allowance is available. + * + * Might emit an {Approval} event. + */ + function _spendAllowance(address owner, address spender, uint256 amount) internal virtual { + uint256 currentAllowance = allowance(owner, spender); + if (currentAllowance != type(uint256).max) { + require(currentAllowance >= amount, "ERC20: insufficient allowance"); + unchecked { + _approve(owner, spender, currentAllowance - amount); + } + } + } + + /** + * @dev Hook that is called before any transfer of tokens. This includes + * minting and burning. + * + * Calling conditions: + * + * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens + * will be transferred to `to`. + * - when `from` is zero, `amount` tokens will be minted for `to`. + * - when `to` is zero, `amount` of ``from``'s tokens will be burned. + * - `from` and `to` are never both zero. + * + * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. + */ + function _beforeTokenTransfer(address from, address to, uint256 amount) internal virtual { } + + /** + * @dev Hook that is called after any transfer of tokens. This includes + * minting and burning. + * + * Calling conditions: + * + * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens + * has been transferred to `to`. + * - when `from` is zero, `amount` tokens have been minted for `to`. + * - when `to` is zero, `amount` of ``from``'s tokens have been burned. + * - `from` and `to` are never both zero. + * + * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. + */ + function _afterTokenTransfer(address from, address to, uint256 amount) internal virtual { } + + /** + * @notice Reserve balance for making payments for gas in this StableToken currency. + * @param from The account to reserve balance from + * @param value The amount of balance to reserve + * @dev Note that this function is called by the protocol when paying for tx fees in this + * currency. After the tx is executed, gas is refunded to the sender and credited to the + * various tx fee recipients via a call to `creditGasFees`. Note too that the events emitted + * by `creditGasFees` reflect the *net* gas fee payments for the transaction. + */ + function debitGasFees(address from, uint256 value) external onlyVm { + _balances[from] -= value; + _totalSupply -= value; + } + + /** + * @notice Alternative function to credit balance after making payments + * for gas in this StableToken currency. + * @param from The account to debit balance from + * @param feeRecipient Coinbase address + * legacy param gatewayFeeRecipient Gateway address (UNUSED!) + * @param communityFund Community fund address + * @param tipTxFee Coinbase fee + * @param baseTxFee Community fund fee + * legacy param gatewayFee Gateway fee (UNUSED!) + * @dev Note that this function is called by the protocol when paying for tx fees in this + * currency. Before the tx is executed, gas is debited from the sender via a call to + * `debitGasFees`. Note too that the events emitted by `creditGasFees` reflect the *net* gas fee + * payments for the transaction. + */ + function creditGasFees( + address from, + address feeRecipient, + address, // gatewayFeeRecipient + address communityFund, + uint256 refund, + uint256 tipTxFee, + uint256, // gatewayFee + uint256 baseTxFee + ) + external + onlyVm + { + _balances[from] += refund; + + refund += _creditGas(from, communityFund, baseTxFee); + refund += _creditGas(from, feeRecipient, tipTxFee); + _totalSupply += refund; + } + + function _creditGas(address from, address to, uint256 value) internal returns (uint256) { + if (to == address(0)) { + return 0; + } + _balances[to] += value; + emit Transfer(from, to, value); + return value; + } +} diff --git a/packages/contracts-bedrock/src/celo/testing/MockSortedOracles.sol b/packages/contracts-bedrock/src/celo/testing/MockSortedOracles.sol new file mode 100644 index 0000000000000..0b724cc352472 --- /dev/null +++ b/packages/contracts-bedrock/src/celo/testing/MockSortedOracles.sol @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import { IOracle } from "../interfaces/IOracle.sol"; + +/** + * @title A mock SortedOracles for testing. + */ +contract MockSortedOracles is IOracle { + uint256 public constant DENOMINATOR = 1000000000000000000000000; + mapping(address => uint256) public numerators; + mapping(address => uint256) public medianTimestamp; + mapping(address => uint256) public numRates; + mapping(address => bool) public expired; + + function setMedianRate(address token, uint256 numerator) external returns (bool) { + numerators[token] = numerator; + return true; + } + + function setMedianTimestamp(address token, uint256 timestamp) external { + medianTimestamp[token] = timestamp; + } + + function setMedianTimestampToNow(address token) external { + // solhint-disable-next-line not-rely-on-time + medianTimestamp[token] = uint128(block.timestamp); + } + + function setNumRates(address token, uint256 rate) external { + numRates[token] = rate; // This change may break something, TODO + } + + function getExchangeRate(address token) external view returns (uint256 numerator, uint256 denominator) { + return medianRate(token); + } + + function medianRate(address token) public view returns (uint256, uint256) { + if (numerators[token] > 0) { + return (numerators[token], DENOMINATOR); + } + return (0, 0); + } + + function isOldestReportExpired(address token) public view returns (bool, address) { + return (expired[token], token); + } + + function setOldestReportExpired(address token) public { + expired[token] = true; + } +} diff --git a/packages/contracts-bedrock/src/celo/uniswap/interfaces/IUniswapV2FactoryMin.sol b/packages/contracts-bedrock/src/celo/uniswap/interfaces/IUniswapV2FactoryMin.sol new file mode 100644 index 0000000000000..22a65ad353675 --- /dev/null +++ b/packages/contracts-bedrock/src/celo/uniswap/interfaces/IUniswapV2FactoryMin.sol @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.0; + +interface IUniswapV2FactoryMin { + function getPair(address tokenA, address tokenB) external view returns (address pair); +} diff --git a/packages/contracts-bedrock/src/celo/uniswap/interfaces/IUniswapV2RouterMin.sol b/packages/contracts-bedrock/src/celo/uniswap/interfaces/IUniswapV2RouterMin.sol new file mode 100644 index 0000000000000..ff41f146050ff --- /dev/null +++ b/packages/contracts-bedrock/src/celo/uniswap/interfaces/IUniswapV2RouterMin.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.0; + +interface IUniswapV2RouterMin { + function factory() external pure returns (address); + function swapExactTokensForTokens( + uint256 amountIn, + uint256 amountOutMin, + address[] calldata path, + address to, + uint256 deadline + ) + external + returns (uint256[] memory amounts); + function getAmountsOut( + uint256 amountIn, + address[] calldata path + ) + external + view + returns (uint256[] memory amounts); +} diff --git a/packages/contracts-bedrock/src/dispute/AnchorStateRegistry.sol b/packages/contracts-bedrock/src/dispute/AnchorStateRegistry.sol index a1b1531523f47..2e4aaab366a0b 100644 --- a/packages/contracts-bedrock/src/dispute/AnchorStateRegistry.sol +++ b/packages/contracts-bedrock/src/dispute/AnchorStateRegistry.sol @@ -14,7 +14,7 @@ import { ISemver } from "src/universal/interfaces/ISemver.sol"; import { IFaultDisputeGame } from "src/dispute/interfaces/IFaultDisputeGame.sol"; import { IDisputeGame } from "src/dispute/interfaces/IDisputeGame.sol"; import { IDisputeGameFactory } from "src/dispute/interfaces/IDisputeGameFactory.sol"; -import { ISuperchainConfig } from "src/L1/interfaces/ISuperchainConfig.sol"; +import { ICeloSuperchainConfig } from "src/L1/interfaces/ICeloSuperchainConfig.sol"; /// @custom:proxied true /// @title AnchorStateRegistry @@ -40,7 +40,7 @@ contract AnchorStateRegistry is Initializable, ISemver { mapping(GameType => OutputRoot) public anchors; /// @notice Address of the SuperchainConfig contract. - ISuperchainConfig public superchainConfig; + ICeloSuperchainConfig public superchainConfig; /// @param _disputeGameFactory DisputeGameFactory address. constructor(IDisputeGameFactory _disputeGameFactory) { @@ -53,7 +53,7 @@ contract AnchorStateRegistry is Initializable, ISemver { /// @param _superchainConfig The address of the SuperchainConfig contract. function initialize( StartingAnchorRoot[] memory _startingAnchorRoots, - ISuperchainConfig _superchainConfig + ICeloSuperchainConfig _superchainConfig ) public initializer diff --git a/packages/contracts-bedrock/src/dispute/DelayedWETH.sol b/packages/contracts-bedrock/src/dispute/DelayedWETH.sol index 3438c22e3679a..f655670985260 100644 --- a/packages/contracts-bedrock/src/dispute/DelayedWETH.sol +++ b/packages/contracts-bedrock/src/dispute/DelayedWETH.sol @@ -7,7 +7,7 @@ import { WETH98 } from "src/universal/WETH98.sol"; // Interfaces import { ISemver } from "src/universal/interfaces/ISemver.sol"; -import { ISuperchainConfig } from "src/L1/interfaces/ISuperchainConfig.sol"; +import { ICeloSuperchainConfig } from "src/L1/interfaces/ICeloSuperchainConfig.sol"; /// @custom:proxied true /// @title DelayedWETH @@ -41,19 +41,19 @@ contract DelayedWETH is OwnableUpgradeable, WETH98, ISemver { /// @notice Withdrawal delay in seconds. uint256 internal immutable DELAY_SECONDS; - /// @notice Address of the SuperchainConfig contract. - ISuperchainConfig public config; + /// @notice Address of the CeloSuperchainConfig contract. + ICeloSuperchainConfig public config; /// @param _delay The delay for withdrawals in seconds. constructor(uint256 _delay) { DELAY_SECONDS = _delay; - initialize({ _owner: address(0), _config: ISuperchainConfig(address(0)) }); + initialize({ _owner: address(0), _config: ICeloSuperchainConfig(address(0)) }); } /// @notice Initializes the contract. /// @param _owner The address of the owner. - /// @param _config Address of the SuperchainConfig contract. - function initialize(address _owner, ISuperchainConfig _config) public initializer { + /// @param _config Address of the CeloSuperchainConfig contract. + function initialize(address _owner, ICeloSuperchainConfig _config) public initializer { __Ownable_init(); _transferOwnership(_owner); config = _config; diff --git a/packages/contracts-bedrock/src/dispute/interfaces/IAnchorStateRegistry.sol b/packages/contracts-bedrock/src/dispute/interfaces/IAnchorStateRegistry.sol index 4de2bb1deab60..0ca2073c6bfd5 100644 --- a/packages/contracts-bedrock/src/dispute/interfaces/IAnchorStateRegistry.sol +++ b/packages/contracts-bedrock/src/dispute/interfaces/IAnchorStateRegistry.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.0; import { IFaultDisputeGame } from "src/dispute/interfaces/IFaultDisputeGame.sol"; import { IDisputeGameFactory } from "src/dispute/interfaces/IDisputeGameFactory.sol"; -import { ISuperchainConfig } from "src/L1/interfaces/ISuperchainConfig.sol"; +import { ICeloSuperchainConfig } from "src/L1/interfaces/ICeloSuperchainConfig.sol"; import "src/dispute/lib/Types.sol"; interface IAnchorStateRegistry { @@ -22,11 +22,11 @@ interface IAnchorStateRegistry { function disputeGameFactory() external view returns (IDisputeGameFactory); function initialize( StartingAnchorRoot[] memory _startingAnchorRoots, - ISuperchainConfig _superchainConfig + ICeloSuperchainConfig _superchainConfig ) external; function setAnchorState(IFaultDisputeGame _game) external; - function superchainConfig() external view returns (ISuperchainConfig); + function superchainConfig() external view returns (ICeloSuperchainConfig); function tryUpdateAnchorState() external; function version() external view returns (string memory); diff --git a/packages/contracts-bedrock/src/dispute/interfaces/IDelayedWETH.sol b/packages/contracts-bedrock/src/dispute/interfaces/IDelayedWETH.sol index 98b221285b560..f68ebd7c68672 100644 --- a/packages/contracts-bedrock/src/dispute/interfaces/IDelayedWETH.sol +++ b/packages/contracts-bedrock/src/dispute/interfaces/IDelayedWETH.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; -import { ISuperchainConfig } from "src/L1/interfaces/ISuperchainConfig.sol"; +import { ICeloSuperchainConfig } from "src/L1/interfaces/ICeloSuperchainConfig.sol"; interface IDelayedWETH { struct WithdrawalRequest { @@ -16,10 +16,10 @@ interface IDelayedWETH { fallback() external payable; receive() external payable; - function config() external view returns (ISuperchainConfig); + function config() external view returns (ICeloSuperchainConfig); function delay() external view returns (uint256); function hold(address _guy, uint256 _wad) external; - function initialize(address _owner, ISuperchainConfig _config) external; + function initialize(address _owner, ICeloSuperchainConfig _config) external; function owner() external view returns (address); function recover(uint256 _wad) external; function transferOwnership(address newOwner) external; // nosemgrep diff --git a/packages/contracts-bedrock/src/safe/DeputyGuardianModule.sol b/packages/contracts-bedrock/src/safe/DeputyGuardianModule.sol index 673cf2a1efb66..9f5f089bbbf38 100644 --- a/packages/contracts-bedrock/src/safe/DeputyGuardianModule.sol +++ b/packages/contracts-bedrock/src/safe/DeputyGuardianModule.sol @@ -12,24 +12,24 @@ import { GameType, Timestamp } from "src/dispute/lib/Types.sol"; // Interfaces import { IAnchorStateRegistry } from "src/dispute/interfaces/IAnchorStateRegistry.sol"; import { IFaultDisputeGame } from "src/dispute/interfaces/IFaultDisputeGame.sol"; -import { ISuperchainConfig } from "src/L1/interfaces/ISuperchainConfig.sol"; +import { ICeloSuperchainConfig } from "src/L1/interfaces/ICeloSuperchainConfig.sol"; import { IOptimismPortal2 } from "src/L1/interfaces/IOptimismPortal2.sol"; import { IDisputeGame } from "src/dispute/interfaces/IDisputeGame.sol"; import { ISemver } from "src/universal/interfaces/ISemver.sol"; /// @title DeputyGuardianModule /// @notice This module is intended to be enabled on the Security Council Safe, which will own the Guardian role in the -/// SuperchainConfig contract. The DeputyGuardianModule should allow a Deputy Guardian to administer any of the -/// actions that the Guardian is authorized to take. The security council can revoke the Deputy Guardian's +/// CeloSuperchainConfig contract. The DeputyGuardianModule should allow a Deputy Guardian to administer any of +/// the actions that the Guardian is authorized to take. The security council can revoke the Deputy Guardian's /// authorization at any time by disabling this module. contract DeputyGuardianModule is ISemver { /// @notice Error message for failed transaction execution error ExecutionFailed(string); - /// @notice Emitted when the SuperchainConfig is paused + /// @notice Emitted when the CeloSuperchainConfig is paused event Paused(string identifier); - /// @notice Emitted when the SuperchainConfig is unpaused + /// @notice Emitted when the CeloSuperchainConfig is unpaused event Unpaused(); /// @notice Emitted when a DisputeGame is blacklisted @@ -41,8 +41,8 @@ contract DeputyGuardianModule is ISemver { /// @notice The Safe contract instance Safe internal immutable SAFE; - /// @notice The SuperchainConfig's address - ISuperchainConfig internal immutable SUPERCHAIN_CONFIG; + /// @notice The CeloSuperchainConfig's address + ICeloSuperchainConfig internal immutable SUPERCHAIN_CONFIG; /// @notice The deputy guardian's address address internal immutable DEPUTY_GUARDIAN; @@ -52,7 +52,7 @@ contract DeputyGuardianModule is ISemver { string public constant version = "2.0.1-beta.4"; // Constructor to initialize the Safe and baseModule instances - constructor(Safe _safe, ISuperchainConfig _superchainConfig, address _deputyGuardian) { + constructor(Safe _safe, ICeloSuperchainConfig _superchainConfig, address _deputyGuardian) { SAFE = _safe; SUPERCHAIN_CONFIG = _superchainConfig; DEPUTY_GUARDIAN = _deputyGuardian; @@ -64,9 +64,9 @@ contract DeputyGuardianModule is ISemver { safe_ = SAFE; } - /// @notice Getter function for the SuperchainConfig's address - /// @return superchainConfig_ The SuperchainConfig's address - function superchainConfig() public view returns (ISuperchainConfig superchainConfig_) { + /// @notice Getter function for the CeloSuperchainConfig's address + /// @return superchainConfig_ The CeloSuperchainConfig's address + function superchainConfig() public view returns (ICeloSuperchainConfig superchainConfig_) { superchainConfig_ = SUPERCHAIN_CONFIG; } @@ -84,7 +84,7 @@ contract DeputyGuardianModule is ISemver { } /// @notice Calls the Security Council Safe's `execTransactionFromModuleReturnData()`, with the arguments - /// necessary to call `pause()` on the `SuperchainConfig` contract. + /// necessary to call `pause()` on the `CeloSuperchainConfig` contract. /// Only the deputy guardian can call this function. function pause() external { _onlyDeputyGuardian(); @@ -99,7 +99,7 @@ contract DeputyGuardianModule is ISemver { } /// @notice Calls the Security Council Safe's `execTransactionFromModuleReturnData()`, with the arguments - /// necessary to call `unpause()` on the `SuperchainConfig` contract. + /// necessary to call `unpause()` on the `CeloSuperchainConfig` contract. /// Only the deputy guardian can call this function. function unpause() external { _onlyDeputyGuardian(); diff --git a/packages/contracts-bedrock/src/safe/interfaces/IDeputyGuardianModule.sol b/packages/contracts-bedrock/src/safe/interfaces/IDeputyGuardianModule.sol index da74cf4dd983b..f51907a8a3378 100644 --- a/packages/contracts-bedrock/src/safe/interfaces/IDeputyGuardianModule.sol +++ b/packages/contracts-bedrock/src/safe/interfaces/IDeputyGuardianModule.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.0; import { IAnchorStateRegistry } from "src/dispute/interfaces/IAnchorStateRegistry.sol"; import { IFaultDisputeGame } from "src/dispute/interfaces/IFaultDisputeGame.sol"; -import { ISuperchainConfig } from "src/L1/interfaces/ISuperchainConfig.sol"; +import { ICeloSuperchainConfig } from "src/L1/interfaces/ICeloSuperchainConfig.sol"; import { IOptimismPortal2 } from "src/L1/interfaces/IOptimismPortal2.sol"; import { IDisputeGame } from "src/dispute/interfaces/IDisputeGame.sol"; import { ISemver } from "src/universal/interfaces/ISemver.sol"; @@ -20,9 +20,9 @@ interface IDeputyGuardianModule is ISemver { event RespectedGameTypeSet(GameType indexed gameType, Timestamp indexed updatedAt); function version() external view returns (string memory); - function __constructor__(Safe _safe, ISuperchainConfig _superchainConfig, address _deputyGuardian) external; + function __constructor__(Safe _safe, ICeloSuperchainConfig _superchainConfig, address _deputyGuardian) external; function safe() external view returns (Safe safe_); - function superchainConfig() external view returns (ISuperchainConfig superchainConfig_); + function superchainConfig() external view returns (ICeloSuperchainConfig superchainConfig_); function deputyGuardian() external view returns (address deputyGuardian_); function pause() external; function unpause() external; diff --git a/packages/contracts-bedrock/src/universal/OptimismMintableERC20.sol b/packages/contracts-bedrock/src/universal/OptimismMintableERC20.sol index 34d601cbb65b0..726df61e4ee6f 100644 --- a/packages/contracts-bedrock/src/universal/OptimismMintableERC20.sol +++ b/packages/contracts-bedrock/src/universal/OptimismMintableERC20.sol @@ -8,6 +8,7 @@ import { IOptimismMintableERC20 } from "src/universal/interfaces/IOptimismMintab import { ILegacyMintableERC20 } from "src/universal/interfaces/ILegacyMintableERC20.sol"; import { ISemver } from "src/universal/interfaces/ISemver.sol"; import { Preinstalls } from "src/libraries/Preinstalls.sol"; +import { AbstractFeeCurrency } from "src/celo/AbstractFeeCurrency.sol"; /// @title OptimismMintableERC20 /// @notice OptimismMintableERC20 is a standard extension of the base ERC20 token contract designed @@ -15,7 +16,7 @@ import { Preinstalls } from "src/libraries/Preinstalls.sol"; /// use an OptimismMintablERC20 as the L2 representation of an L1 token, or vice-versa. /// Designed to be backwards compatible with the older StandardL2ERC20 token which was only /// meant for use on L2. -contract OptimismMintableERC20 is ERC20Permit, ISemver { +contract OptimismMintableERC20 is ERC20Permit, ISemver, AbstractFeeCurrency { /// @notice Address of the corresponding version of this token on the remote chain. address public immutable REMOTE_TOKEN; diff --git a/packages/contracts-bedrock/test/L1/CeloSuperchainConfig.t.sol b/packages/contracts-bedrock/test/L1/CeloSuperchainConfig.t.sol new file mode 100644 index 0000000000000..2137264abd66c --- /dev/null +++ b/packages/contracts-bedrock/test/L1/CeloSuperchainConfig.t.sol @@ -0,0 +1,313 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.15; + +import { CommonTest } from "test/setup/CommonTest.sol"; + +// Target contract dependencies +import { IProxy } from "src/universal/interfaces/IProxy.sol"; + +// Target contract +import { ICeloSuperchainConfig } from "src/L1/interfaces/ICeloSuperchainConfig.sol"; + +import { DeployUtils } from "scripts/libraries/DeployUtils.sol"; + +contract CeloSuperchainConfig_Test_Setup is CommonTest { + address internal celoGuardian; + + function setUp() public override { + super.setUp(); + + celoGuardian = deploy.cfg().superchainConfigGuardian(); + } +} + +contract CeloSuperchainConfig_Init_Test is CeloSuperchainConfig_Test_Setup { + /// @dev Tests that initialization sets the correct values. + function test_initialize_unpaused_succeeds() external view { + assertFalse(celoSuperchainConfig.paused()); + assertEq(celoSuperchainConfig.guardian(), celoGuardian); + assertEq(celoSuperchainConfig.superchainConfig(), address(superchainConfig)); + } + + /// @dev Tests that it can be intialized as paused. + function test_initialize_paused_succeeds() external { + IProxy newProxy = IProxy( + DeployUtils.create1({ + _name: "Proxy", + _args: DeployUtils.encodeConstructor(abi.encodeCall(IProxy.__constructor__, (alice))) + }) + ); + ICeloSuperchainConfig newImpl = ICeloSuperchainConfig( + DeployUtils.create1({ + _name: "CeloSuperchainConfig", + _args: DeployUtils.encodeConstructor(abi.encodeCall(ICeloSuperchainConfig.__constructor__, ())) + }) + ); + + vm.startPrank(alice); + newProxy.upgradeToAndCall( + address(newImpl), + abi.encodeWithSignature("initialize(address,bool,address)", celoGuardian, true, address(superchainConfig)) + ); + + assertTrue(ICeloSuperchainConfig(address(newProxy)).paused()); + assertEq(ICeloSuperchainConfig(address(newProxy)).guardian(), celoGuardian); + assertEq(celoSuperchainConfig.superchainConfig(), address(superchainConfig)); + } + + /// @dev Tests that it will be intialized as paused if Superchain was paused + /// at initialization time. + function test_initialize_whenSuperchainPaused_succeeds() external { + vm.prank(superchainConfig.guardian()); + superchainConfig.pause("identifier"); + assertTrue(superchainConfig.paused()); + + IProxy newProxy = IProxy( + DeployUtils.create1({ + _name: "Proxy", + _args: DeployUtils.encodeConstructor(abi.encodeCall(IProxy.__constructor__, (alice))) + }) + ); + ICeloSuperchainConfig newImpl = ICeloSuperchainConfig( + DeployUtils.create1({ + _name: "CeloSuperchainConfig", + _args: DeployUtils.encodeConstructor(abi.encodeCall(ICeloSuperchainConfig.__constructor__, ())) + }) + ); + + vm.startPrank(alice); + newProxy.upgradeToAndCall( + address(newImpl), + abi.encodeWithSignature("initialize(address,bool,address)", celoGuardian, false, address(superchainConfig)) + ); + vm.stopPrank(); + + vm.prank(superchainConfig.guardian()); + superchainConfig.unpause(); + assertFalse(superchainConfig.paused()); + + assertTrue(ICeloSuperchainConfig(address(newProxy)).paused()); + assertEq(ICeloSuperchainConfig(address(newProxy)).guardian(), celoGuardian); + assertEq(celoSuperchainConfig.superchainConfig(), address(superchainConfig)); + } +} + +contract CeloSuperchainConfig_Pause_TestFail is CeloSuperchainConfig_Test_Setup { + /// @dev Tests that `pause` reverts when called by a non-guardian. + function test_pause_notGuardian_reverts() external { + assertFalse(celoSuperchainConfig.paused()); + + assertTrue(celoSuperchainConfig.guardian() != alice); + vm.expectRevert("CeloSuperchainConfig: only guardian can pause"); + vm.prank(alice); + celoSuperchainConfig.pause("identifier"); + + assertFalse(celoSuperchainConfig.paused()); + } + + /// @dev Tests that `pause` reverts when called by the Superchain Guardian. + /// Currently this test is skipped because the dev Deploy setup ends up + /// using the same address for both Guardian roles. + function test_pause_superchainGuardian_reverts() external { + assertFalse(celoSuperchainConfig.paused()); + + address superchainConfigGuardian = deploy.cfg().superchainConfigGuardian(); + if (superchainConfigGuardian == celoSuperchainConfig.guardian()) { + vm.skip(true); + } + + vm.expectRevert("CeloSuperchainConfig: only guardian can pause"); + vm.prank(superchainConfigGuardian); + celoSuperchainConfig.pause("identifier"); + + assertFalse(celoSuperchainConfig.paused()); + } +} + +contract CeloSuperchainConfig_Pause_Test is CeloSuperchainConfig_Test_Setup { + /// @dev Tests that `pause` successfully pauses when called by the guardian. + function test_pause_succeeds() external { + assertFalse(celoSuperchainConfig.paused()); + + vm.expectEmit(address(celoSuperchainConfig)); + emit Paused("identifier"); + + vm.prank(celoSuperchainConfig.guardian()); + celoSuperchainConfig.pause("identifier"); + + assertTrue(celoSuperchainConfig.paused()); + } +} + +contract CeloSuperchainConfig_Unpause_TestFail is CeloSuperchainConfig_Test_Setup { + /// @dev Tests that `unpause` reverts when called by a non-guardian. + function test_unpause_notGuardian_reverts() external { + vm.prank(celoSuperchainConfig.guardian()); + celoSuperchainConfig.pause("identifier"); + assertEq(celoSuperchainConfig.paused(), true); + + assertTrue(celoSuperchainConfig.guardian() != alice); + vm.expectRevert("CeloSuperchainConfig: only guardian can unpause"); + vm.prank(alice); + celoSuperchainConfig.unpause(); + + assertTrue(celoSuperchainConfig.paused()); + } +} + +contract CeloSuperchainConfig_Unpause_Test is CeloSuperchainConfig_Test_Setup { + /// @dev Tests that `unpause` successfully unpauses + /// when called by the guardian. + function test_unpause_succeeds() external { + vm.startPrank(celoSuperchainConfig.guardian()); + celoSuperchainConfig.pause("identifier"); + assertEq(celoSuperchainConfig.paused(), true); + + vm.expectEmit(address(celoSuperchainConfig)); + emit Unpaused(); + celoSuperchainConfig.unpause(); + + assertFalse(celoSuperchainConfig.paused()); + } + + /// @dev Tests that after `unpause`, if the Superchain is still paused, Celo + /// remains paused anyways. + function test_unpause_whenSuperchainPaused_succeeds() external { + vm.prank(celoSuperchainConfig.guardian()); + celoSuperchainConfig.pause("identifier"); + assertEq(celoSuperchainConfig.paused(), true); + + vm.prank(superchainConfig.guardian()); + superchainConfig.pause("identifier"); + assertTrue(superchainConfig.paused()); + + vm.prank(celoSuperchainConfig.guardian()); + celoSuperchainConfig.unpause(); + + assertEq(celoSuperchainConfig.paused(), true); + } +} + +contract CeloSuperchainConfig_Paused_Test is CeloSuperchainConfig_Test_Setup { + /// @dev Tests that the `paused` getter returns `false` whenever both Celo + /// and Superchain are unpaused. + function test_paused_whenSuperchainUnpaused_succeeds() external view { + assertFalse(superchainConfig.paused()); + bool paused = celoSuperchainConfig.paused(); + assertFalse(paused); + } + + /// @dev Tests that the `paused` getter returns `true` whenever Superchain + /// is paused, even when Celo is unpaused. + function test_paused_whenSuperchainPaused_succeeds() external { + vm.prank(superchainConfig.guardian()); + superchainConfig.pause("identifier"); + assertTrue(superchainConfig.paused()); + + bool paused = celoSuperchainConfig.paused(); + assertTrue(paused); + } + + /// @dev Tests that the `paused` getter returns `true` whenever Celo is paused, + /// even when the Superchain is unpaused. + function test_paused_whenCeloPaused_succeeds() external { + vm.prank(celoGuardian); + celoSuperchainConfig.pause("identifier"); + + assertFalse(superchainConfig.paused()); + + bool paused = celoSuperchainConfig.paused(); + assertTrue(paused); + } + + /// @dev Tests that the `paused` getter returns `true` whenever both Celo + /// and the Superchain are paused. + function test_paused_whenBothPaused_succeeds() external { + vm.prank(superchainConfig.guardian()); + superchainConfig.pause("identifier"); + assertTrue(superchainConfig.paused()); + + vm.prank(celoGuardian); + celoSuperchainConfig.pause("identifier"); + + bool paused = celoSuperchainConfig.paused(); + assertTrue(paused); + } +} + +contract CeloSuperchainConfig_CheckAndPauseIfSuperchainPaused_Test is CeloSuperchainConfig_Test_Setup { + /// @dev Tests that `checkAndPauseIfSuperchainPaused` is a no-op when + /// Superchain is unpaused. + function test_checkAndPauseIfSuperchainPaused_whenSuperchainUnpaused_succeeds() external { + assertFalse(superchainConfig.paused()); + + bool paused = celoSuperchainConfig.checkAndPauseIfSuperchainPaused(); + assertFalse(paused); + + paused = celoSuperchainConfig.paused(); + assertFalse(paused); + } + + /// @dev Tests that `checkAndPauseIfSuperchainPaused` propagatates + /// Superchain's paused status to Celo when the Superchain is paused. + function test_checkAndPauseIfSuperchainPaused_whenSuperchainPaused_succeeds() external { + vm.prank(superchainConfig.guardian()); + superchainConfig.pause("identifier"); + assertTrue(superchainConfig.paused()); + + bool paused = celoSuperchainConfig.checkAndPauseIfSuperchainPaused(); + assertTrue(paused); + + paused = celoSuperchainConfig.paused(); + assertTrue(paused); + + vm.prank(superchainConfig.guardian()); + superchainConfig.unpause(); + assertFalse(superchainConfig.paused()); + + paused = celoSuperchainConfig.paused(); + assertTrue(paused); + } + + /// @dev Tests that `checkAndPauseIfSuperchainPaused` works even if Superchain is not set. + function test_checkAndPauseIfSuperchainPaused_whenSuperchainNotSet() external { + IProxy newProxy = IProxy( + DeployUtils.create1({ + _name: "Proxy", + _args: DeployUtils.encodeConstructor(abi.encodeCall(IProxy.__constructor__, (alice))) + }) + ); + ICeloSuperchainConfig newImpl = ICeloSuperchainConfig( + DeployUtils.create1({ + _name: "CeloSuperchainConfig", + _args: DeployUtils.encodeConstructor(abi.encodeCall(ICeloSuperchainConfig.__constructor__, ())) + }) + ); + + vm.startPrank(alice); + newProxy.upgradeToAndCall( + address(newImpl), + abi.encodeWithSignature("initialize(address,bool,address)", celoGuardian, false, address(0)) + ); + vm.stopPrank(); + + ICeloSuperchainConfig newCeloSuperchainConfig = ICeloSuperchainConfig(address(newProxy)); + + bool paused = newCeloSuperchainConfig.checkAndPauseIfSuperchainPaused(); + assertFalse(paused); + + vm.prank(newCeloSuperchainConfig.guardian()); + newCeloSuperchainConfig.pause("identifier"); + assertTrue(newCeloSuperchainConfig.paused()); + + paused = newCeloSuperchainConfig.checkAndPauseIfSuperchainPaused(); + assertTrue(paused); + + vm.prank(newCeloSuperchainConfig.guardian()); + newCeloSuperchainConfig.unpause(); + assertFalse(newCeloSuperchainConfig.paused()); + + paused = newCeloSuperchainConfig.paused(); + assertFalse(paused); + } +} diff --git a/packages/contracts-bedrock/test/L1/L1CrossDomainMessenger.t.sol b/packages/contracts-bedrock/test/L1/L1CrossDomainMessenger.t.sol index 08a976aa0ec22..ed995527ff39a 100644 --- a/packages/contracts-bedrock/test/L1/L1CrossDomainMessenger.t.sol +++ b/packages/contracts-bedrock/test/L1/L1CrossDomainMessenger.t.sol @@ -14,7 +14,7 @@ import { Encoding } from "src/libraries/Encoding.sol"; // Target contract dependencies import { IL1CrossDomainMessenger } from "src/L1/interfaces/IL1CrossDomainMessenger.sol"; import { IOptimismPortal } from "src/L1/interfaces/IOptimismPortal.sol"; -import { ISuperchainConfig } from "src/L1/interfaces/ISuperchainConfig.sol"; +import { ICeloSuperchainConfig } from "src/L1/interfaces/ICeloSuperchainConfig.sol"; import { ISystemConfig } from "src/L1/interfaces/ISystemConfig.sol"; contract L1CrossDomainMessenger_Test is Bridge_Initializer { @@ -38,13 +38,62 @@ contract L1CrossDomainMessenger_Test is Bridge_Initializer { /// @dev Tests that the proxy is initialized correctly. function test_initialize_succeeds() external view { - assertEq(address(l1CrossDomainMessenger.superchainConfig()), address(superchainConfig)); + assertEq(address(l1CrossDomainMessenger.superchainConfig()), address(celoSuperchainConfig)); assertEq(address(l1CrossDomainMessenger.PORTAL()), address(optimismPortal)); assertEq(address(l1CrossDomainMessenger.portal()), address(optimismPortal)); assertEq(address(l1CrossDomainMessenger.OTHER_MESSENGER()), Predeploys.L2_CROSS_DOMAIN_MESSENGER); assertEq(address(l1CrossDomainMessenger.otherMessenger()), Predeploys.L2_CROSS_DOMAIN_MESSENGER); } + /// @dev Tests that `pause` successfully pauses + /// when called by the GUARDIAN. + function test_pause_succeeds() external { + address guardian = celoSuperchainConfig.guardian(); + + assertEq(l1CrossDomainMessenger.paused(), false); + + vm.expectEmit(address(celoSuperchainConfig)); + emit Paused("identifier"); + + vm.prank(guardian); + celoSuperchainConfig.pause("identifier"); + + assertEq(l1CrossDomainMessenger.paused(), true); + } + + /// @dev Tests that pausing the Superchain successfully pauses + /// when called by the Superchain GUARDIAN. + function test_superchainPause_succeeds() external { + address guardian = superchainConfig.guardian(); + + assertEq(l1CrossDomainMessenger.paused(), false); + + vm.expectEmit(address(superchainConfig)); + emit Paused("identifier"); + + vm.prank(guardian); + superchainConfig.pause("identifier"); + + assertEq(l1CrossDomainMessenger.paused(), true); + } + + /// @dev Tests that `unpause` successfully unpauses + /// when called by the GUARDIAN. + function test_unpause_succeeds() external { + address guardian = celoSuperchainConfig.guardian(); + + vm.prank(guardian); + celoSuperchainConfig.pause("identifier"); + assertEq(l1CrossDomainMessenger.paused(), true); + + vm.expectEmit(address(celoSuperchainConfig)); + emit Unpaused(); + vm.prank(guardian); + celoSuperchainConfig.unpause(); + + assertEq(l1CrossDomainMessenger.paused(), false); + } + /// @dev Tests that the version can be decoded from the message nonce. function test_messageVersion_succeeds() external view { (, uint16 version) = Encoding.decodeVersionedNonce(l1CrossDomainMessenger.messageNonce()); @@ -606,7 +655,7 @@ contract L1CrossDomainMessenger_Test is Bridge_Initializer { /// @dev Tests that the superchain config is called by the messengers paused function function test_pause_callsSuperchainConfig_succeeds() external { - vm.expectCall(address(superchainConfig), abi.encodeCall(ISuperchainConfig.paused, ())); + vm.expectCall(address(superchainConfig), abi.encodeCall(ICeloSuperchainConfig.paused, ())); l1CrossDomainMessenger.paused(); } @@ -773,7 +822,9 @@ contract L1CrossDomainMessenger_ReinitReentryTest is Bridge_Initializer { // call the initializer function l1CrossDomainMessenger.initialize( - ISuperchainConfig(superchainConfig), IOptimismPortal(optimismPortal), ISystemConfig(systemConfig) + ICeloSuperchainConfig(address(superchainConfig)), + IOptimismPortal(optimismPortal), + ISystemConfig(systemConfig) ); // attempt to re-replay the withdrawal diff --git a/packages/contracts-bedrock/test/L1/L1ERC721Bridge.t.sol b/packages/contracts-bedrock/test/L1/L1ERC721Bridge.t.sol index 055ba45d0e1bb..78200cf50381f 100644 --- a/packages/contracts-bedrock/test/L1/L1ERC721Bridge.t.sol +++ b/packages/contracts-bedrock/test/L1/L1ERC721Bridge.t.sol @@ -83,7 +83,7 @@ contract L1ERC721Bridge_Test is Bridge_Initializer { assertEq(address(l1ERC721Bridge.messenger()), address(l1CrossDomainMessenger)); assertEq(address(l1ERC721Bridge.OTHER_BRIDGE()), Predeploys.L2_ERC721_BRIDGE); assertEq(address(l1ERC721Bridge.otherBridge()), Predeploys.L2_ERC721_BRIDGE); - assertEq(address(l1ERC721Bridge.superchainConfig()), address(superchainConfig)); + assertEq(address(l1ERC721Bridge.superchainConfig()), address(celoSuperchainConfig)); } /// @dev Tests that the ERC721 can be bridged successfully. @@ -319,27 +319,57 @@ contract L1ERC721Bridge_Pause_Test is Bridge_Initializer { /// @dev Verifies that the `paused` accessor returns the same value as the `paused` function of the /// `superchainConfig`. function test_paused_succeeds() external view { - assertEq(l1ERC721Bridge.paused(), superchainConfig.paused()); + assertEq(l1ERC721Bridge.paused(), celoSuperchainConfig.paused()); } /// @dev Ensures that the `paused` function of the bridge contract actually calls the `paused` function of the /// `superchainConfig`. function test_pause_callsSuperchainConfig_succeeds() external { - vm.expectCall(address(superchainConfig), abi.encodeCall(ISuperchainConfig.paused, ())); + vm.expectCall(address(celoSuperchainConfig), abi.encodeCall(ISuperchainConfig.paused, ())); l1ERC721Bridge.paused(); } - /// @dev Checks that the `paused` state of the bridge matches the `paused` state of the `superchainConfig` after - /// it's been changed. + /// @dev Checks that the `paused` state of the bridge matches the `paused` state of the + /// `celoSuperchainConfig` after it's been changed. + function test_pause_matchesCeloSuperchainConfig_succeeds() external { + assertFalse(l1StandardBridge.paused()); + assertEq(l1StandardBridge.paused(), celoSuperchainConfig.paused()); + + vm.prank(celoSuperchainConfig.guardian()); + celoSuperchainConfig.pause("identifier"); + + assertTrue(l1StandardBridge.paused()); + assertEq(l1StandardBridge.paused(), celoSuperchainConfig.paused()); + } + + /// @dev Checks that pausing the Superchain SuperchainConfig propagates to + // the paused state of bridge. function test_pause_matchesSuperchainConfig_succeeds() external { assertFalse(l1StandardBridge.paused()); - assertEq(l1StandardBridge.paused(), superchainConfig.paused()); + assertEq(l1StandardBridge.paused(), celoSuperchainConfig.paused()); vm.prank(superchainConfig.guardian()); superchainConfig.pause("identifier"); assertTrue(l1StandardBridge.paused()); - assertEq(l1StandardBridge.paused(), superchainConfig.paused()); + assertEq(l1StandardBridge.paused(), celoSuperchainConfig.paused()); + } + + /// @dev Checks that the `paused` state of the bridge matches the `paused` state of the + /// `celoSuperchainConfig` after it's been paused and unpaused. + function test_unpause_matchesCeloSuperchainConfig_succeeds() external { + assertFalse(l1StandardBridge.paused()); + assertEq(l1StandardBridge.paused(), celoSuperchainConfig.paused()); + + vm.prank(celoSuperchainConfig.guardian()); + celoSuperchainConfig.pause("identifier"); + assertTrue(l1StandardBridge.paused()); + assertEq(l1StandardBridge.paused(), celoSuperchainConfig.paused()); + + vm.prank(celoSuperchainConfig.guardian()); + celoSuperchainConfig.unpause(); + assertFalse(l1StandardBridge.paused()); + assertEq(l1StandardBridge.paused(), celoSuperchainConfig.paused()); } } diff --git a/packages/contracts-bedrock/test/L1/L1StandardBridge.t.sol b/packages/contracts-bedrock/test/L1/L1StandardBridge.t.sol index 760f602df21e1..3c0ba06427883 100644 --- a/packages/contracts-bedrock/test/L1/L1StandardBridge.t.sol +++ b/packages/contracts-bedrock/test/L1/L1StandardBridge.t.sol @@ -26,7 +26,7 @@ contract L1StandardBridge_Getter_Test is Bridge_Initializer { assert(address(l1StandardBridge.OTHER_BRIDGE()) == address(l2StandardBridge)); assert(address(l1StandardBridge.messenger()) == address(l1CrossDomainMessenger)); assert(address(l1StandardBridge.MESSENGER()) == address(l1CrossDomainMessenger)); - assert(l1StandardBridge.superchainConfig() == superchainConfig); + assert(l1StandardBridge.superchainConfig() == celoSuperchainConfig); assert(l1StandardBridge.systemConfig() == systemConfig); } } @@ -48,7 +48,7 @@ contract L1StandardBridge_Initialize_Test is Bridge_Initializer { /// @dev Test that the initialize function sets the correct values. function test_initialize_succeeds() external view { - assertEq(address(l1StandardBridge.superchainConfig()), address(superchainConfig)); + assertEq(address(l1StandardBridge.superchainConfig()), address(celoSuperchainConfig)); assertEq(address(l1StandardBridge.MESSENGER()), address(l1CrossDomainMessenger)); assertEq(address(l1StandardBridge.messenger()), address(l1CrossDomainMessenger)); assertEq(address(l1StandardBridge.OTHER_BRIDGE()), Predeploys.L2_STANDARD_BRIDGE); diff --git a/packages/contracts-bedrock/test/L1/OptimismPortal.t.sol b/packages/contracts-bedrock/test/L1/OptimismPortal.t.sol index 028fc75870439..84b300329dfe3 100644 --- a/packages/contracts-bedrock/test/L1/OptimismPortal.t.sol +++ b/packages/contracts-bedrock/test/L1/OptimismPortal.t.sol @@ -9,9 +9,6 @@ import { CommonTest } from "test/setup/CommonTest.sol"; import { NextImpl } from "test/mocks/NextImpl.sol"; import { EIP1967Helper } from "test/mocks/EIP1967Helper.sol"; -// Contracts -import { SuperchainConfig } from "src/L1/SuperchainConfig.sol"; - // Libraries import { Types } from "src/libraries/Types.sol"; import { Hashing } from "src/libraries/Hashing.sol"; @@ -62,7 +59,7 @@ contract OptimismPortal_Test is CommonTest { assertEq(address(optimismPortal.l2Oracle()), address(l2OutputOracle)); assertEq(address(optimismPortal.systemConfig()), address(systemConfig)); assertEq(optimismPortal.guardian(), guardian); - assertEq(address(optimismPortal.superchainConfig()), address(superchainConfig)); + assertEq(address(optimismPortal.superchainConfig()), address(celoSuperchainConfig)); assertEq(optimismPortal.l2Sender(), Constants.DEFAULT_L2_SENDER); assertEq(optimismPortal.paused(), false); (uint128 prevBaseFee, uint64 prevBoughtGas, uint64 prevBlockNum) = optimismPortal.params(); @@ -78,6 +75,22 @@ contract OptimismPortal_Test is CommonTest { assertEq(optimismPortal.paused(), false); + vm.expectEmit(address(celoSuperchainConfig)); + emit Paused("identifier"); + + vm.prank(guardian); + celoSuperchainConfig.pause("identifier"); + + assertEq(optimismPortal.paused(), true); + } + + /// @dev Tests that pausing the Superchain successfully pauses + /// when called by the Superchain GUARDIAN. + function test_superchainPause_succeeds() external { + address guardian = superchainConfig.guardian(); + + assertEq(optimismPortal.paused(), false); + vm.expectEmit(address(superchainConfig)); emit Paused("identifier"); @@ -92,9 +105,9 @@ contract OptimismPortal_Test is CommonTest { assertEq(optimismPortal.paused(), false); assertTrue(optimismPortal.guardian() != alice); - vm.expectRevert("SuperchainConfig: only guardian can pause"); + vm.expectRevert("CeloSuperchainConfig: only guardian can pause"); vm.prank(alice); - superchainConfig.pause("identifier"); + celoSuperchainConfig.pause("identifier"); assertEq(optimismPortal.paused(), false); } @@ -105,13 +118,13 @@ contract OptimismPortal_Test is CommonTest { address guardian = optimismPortal.guardian(); vm.prank(guardian); - superchainConfig.pause("identifier"); + celoSuperchainConfig.pause("identifier"); assertEq(optimismPortal.paused(), true); - vm.expectEmit(address(superchainConfig)); + vm.expectEmit(address(celoSuperchainConfig)); emit Unpaused(); vm.prank(guardian); - superchainConfig.unpause(); + celoSuperchainConfig.unpause(); assertEq(optimismPortal.paused(), false); } @@ -121,13 +134,13 @@ contract OptimismPortal_Test is CommonTest { address guardian = optimismPortal.guardian(); vm.prank(guardian); - superchainConfig.pause("identifier"); + celoSuperchainConfig.pause("identifier"); assertEq(optimismPortal.paused(), true); assertTrue(optimismPortal.guardian() != alice); - vm.expectRevert("SuperchainConfig: only guardian can unpause"); + vm.expectRevert("CeloSuperchainConfig: only guardian can unpause"); vm.prank(alice); - superchainConfig.unpause(); + celoSuperchainConfig.unpause(); assertEq(optimismPortal.paused(), true); } diff --git a/packages/contracts-bedrock/test/L1/OptimismPortal2.t.sol b/packages/contracts-bedrock/test/L1/OptimismPortal2.t.sol index 8cc56937bd331..188e5e9f0e520 100644 --- a/packages/contracts-bedrock/test/L1/OptimismPortal2.t.sol +++ b/packages/contracts-bedrock/test/L1/OptimismPortal2.t.sol @@ -9,9 +9,6 @@ import { CommonTest } from "test/setup/CommonTest.sol"; import { NextImpl } from "test/mocks/NextImpl.sol"; import { EIP1967Helper } from "test/mocks/EIP1967Helper.sol"; -// Contracts -import { SuperchainConfig } from "src/L1/SuperchainConfig.sol"; - // Libraries import { Types } from "src/libraries/Types.sol"; import { Hashing } from "src/libraries/Hashing.sol"; @@ -65,7 +62,7 @@ contract OptimismPortal2_Test is CommonTest { assertEq(address(optimismPortal2.disputeGameFactory()), address(disputeGameFactory)); assertEq(address(optimismPortal2.systemConfig()), address(systemConfig)); assertEq(optimismPortal2.guardian(), guardian); - assertEq(address(optimismPortal2.superchainConfig()), address(superchainConfig)); + assertEq(address(optimismPortal2.superchainConfig()), address(celoSuperchainConfig)); assertEq(optimismPortal2.l2Sender(), Constants.DEFAULT_L2_SENDER); assertEq(optimismPortal2.paused(), false); assertEq(optimismPortal2.respectedGameType().raw(), deploy.cfg().respectedGameType()); @@ -78,6 +75,22 @@ contract OptimismPortal2_Test is CommonTest { assertEq(optimismPortal2.paused(), false); + vm.expectEmit(address(celoSuperchainConfig)); + emit Paused("identifier"); + + vm.prank(guardian); + celoSuperchainConfig.pause("identifier"); + + assertEq(optimismPortal2.paused(), true); + } + + /// @dev Tests that pausing the Superchain successfully pauses + /// when called by the Superchain GUARDIAN. + function test_superchainPause_succeeds() external { + address guardian = superchainConfig.guardian(); + + assertEq(optimismPortal2.paused(), false); + vm.expectEmit(address(superchainConfig)); emit Paused("identifier"); @@ -92,9 +105,9 @@ contract OptimismPortal2_Test is CommonTest { assertEq(optimismPortal2.paused(), false); assertTrue(optimismPortal2.guardian() != alice); - vm.expectRevert("SuperchainConfig: only guardian can pause"); + vm.expectRevert("CeloSuperchainConfig: only guardian can pause"); vm.prank(alice); - superchainConfig.pause("identifier"); + celoSuperchainConfig.pause("identifier"); assertEq(optimismPortal2.paused(), false); } @@ -105,13 +118,13 @@ contract OptimismPortal2_Test is CommonTest { address guardian = optimismPortal2.guardian(); vm.prank(guardian); - superchainConfig.pause("identifier"); + celoSuperchainConfig.pause("identifier"); assertEq(optimismPortal2.paused(), true); - vm.expectEmit(address(superchainConfig)); + vm.expectEmit(address(celoSuperchainConfig)); emit Unpaused(); vm.prank(guardian); - superchainConfig.unpause(); + celoSuperchainConfig.unpause(); assertEq(optimismPortal2.paused(), false); } @@ -121,13 +134,13 @@ contract OptimismPortal2_Test is CommonTest { address guardian = optimismPortal2.guardian(); vm.prank(guardian); - superchainConfig.pause("identifier"); + celoSuperchainConfig.pause("identifier"); assertEq(optimismPortal2.paused(), true); assertTrue(optimismPortal2.guardian() != alice); - vm.expectRevert("SuperchainConfig: only guardian can unpause"); + vm.expectRevert("CeloSuperchainConfig: only guardian can unpause"); vm.prank(alice); - superchainConfig.unpause(); + celoSuperchainConfig.unpause(); assertEq(optimismPortal2.paused(), true); } @@ -532,7 +545,7 @@ contract OptimismPortal2_FinalizeWithdrawal_Test is CommonTest { /// @dev Tests that `proveWithdrawalTransaction` reverts when paused. function test_proveWithdrawalTransaction_paused_reverts() external { vm.prank(optimismPortal2.guardian()); - superchainConfig.pause("identifier"); + celoSuperchainConfig.pause("identifier"); vm.expectRevert(CallPaused.selector); optimismPortal2.proveWithdrawalTransaction({ @@ -917,7 +930,7 @@ contract OptimismPortal2_FinalizeWithdrawal_Test is CommonTest { /// @dev Tests that `finalizeWithdrawalTransaction` reverts if the contract is paused. function test_finalizeWithdrawalTransaction_paused_reverts() external { vm.prank(optimismPortal2.guardian()); - superchainConfig.pause("identifier"); + celoSuperchainConfig.pause("identifier"); vm.expectRevert(CallPaused.selector); optimismPortal2.finalizeWithdrawalTransaction(_defaultTx); diff --git a/packages/contracts-bedrock/test/L2/L2Genesis.t.sol b/packages/contracts-bedrock/test/L2/L2Genesis.t.sol index 57b22e628ebae..33704f01cc287 100644 --- a/packages/contracts-bedrock/test/L2/L2Genesis.t.sol +++ b/packages/contracts-bedrock/test/L2/L2Genesis.t.sol @@ -179,6 +179,7 @@ contract L2GenesisTest is Test { /// @notice Tests the number of accounts in the genesis setup function _test_allocs_size(string memory _path) internal { genesis.cfg().setFundDevAccounts(false); + genesis.cfg().setDeployCeloContracts(true); genesis.runWithLatestLocal(_dummyL1Deps()); genesis.writeGenesisAllocs(_path); @@ -188,6 +189,7 @@ contract L2GenesisTest is Test { expected += 256; // precompiles expected += 13; // preinstalls expected += 1; // 4788 deployer account + expected += 14; // Celo predeploys // 16 prefunded dev accounts are excluded assertEq(expected, getJSONKeyCount(_path), "key count check"); diff --git a/packages/contracts-bedrock/test/dispute/DelayedWETH.t.sol b/packages/contracts-bedrock/test/dispute/DelayedWETH.t.sol index 6d7a9bea5134d..0860588a0cdd9 100644 --- a/packages/contracts-bedrock/test/dispute/DelayedWETH.t.sol +++ b/packages/contracts-bedrock/test/dispute/DelayedWETH.t.sol @@ -30,7 +30,7 @@ contract DelayedWETH_Initialize_Test is DelayedWETH_Init { /// @dev Tests that initialization is successful. function test_initialize_succeeds() public view { assertEq(delayedWeth.owner(), address(this)); - assertEq(address(delayedWeth.config()), address(superchainConfig)); + assertEq(address(delayedWeth.config()), address(celoSuperchainConfig)); } } @@ -158,7 +158,7 @@ contract DelayedWETH_Withdraw_Test is DelayedWETH_Init { // Pause the contract. address guardian = optimismPortal.guardian(); vm.prank(guardian); - superchainConfig.pause("identifier"); + celoSuperchainConfig.pause("identifier"); // Withdraw fails. vm.expectRevert("DelayedWETH: contract is paused"); diff --git a/packages/contracts-bedrock/test/invariants/CrossDomainMessenger.t.sol b/packages/contracts-bedrock/test/invariants/CrossDomainMessenger.t.sol index 08a0c0027763e..b313613d2aa6f 100644 --- a/packages/contracts-bedrock/test/invariants/CrossDomainMessenger.t.sol +++ b/packages/contracts-bedrock/test/invariants/CrossDomainMessenger.t.sol @@ -58,6 +58,15 @@ contract RelayActor is StdUtils { // gas that is too low to complete the call. uint256 gas = doFail ? bound(minGasLimit, 60_000, 80_000) : xdm.baseGas(_message, minGasLimit); + // HACK! It appears that the usage of CeloSuperchainConfig adds a bit of + // gas to the cost of the relay call. Without an additional stipend, the + // call runs out of gas. + // TODO(m-chrzan): calculate the precise amount of gas that should be + // used here. + if (doFail) { + gas += 10000; + } + // Compute the cross domain message hash and store it in `hashes`. // The `relayMessage` function will always encode the message as a version 1 // message after checking that the V0 hash has not already been relayed. diff --git a/packages/contracts-bedrock/test/safe/DeputyGuardianModule.t.sol b/packages/contracts-bedrock/test/safe/DeputyGuardianModule.t.sol index 8dd1ba970abdd..c661306d43b63 100644 --- a/packages/contracts-bedrock/test/safe/DeputyGuardianModule.t.sol +++ b/packages/contracts-bedrock/test/safe/DeputyGuardianModule.t.sol @@ -41,8 +41,8 @@ contract DeputyGuardianModule_TestInit is CommonTest, SafeTestTools { // Set the Safe as the Guardian of the SuperchainConfig vm.store( - address(superchainConfig), - superchainConfig.GUARDIAN_SLOT(), + address(celoSuperchainConfig), + celoSuperchainConfig.GUARDIAN_SLOT(), bytes32(uint256(uint160(address(safeInstance.safe)))) ); @@ -53,7 +53,7 @@ contract DeputyGuardianModule_TestInit is CommonTest, SafeTestTools { _name: "DeputyGuardianModule", _args: DeployUtils.encodeConstructor( abi.encodeCall( - IDeputyGuardianModule.__constructor__, (safeInstance.safe, superchainConfig, deputyGuardian) + IDeputyGuardianModule.__constructor__, (safeInstance.safe, celoSuperchainConfig, deputyGuardian) ) ) }) @@ -67,14 +67,14 @@ contract DeputyGuardianModule_Getters_Test is DeputyGuardianModule_TestInit { function test_getters_works() external view { assertEq(address(deputyGuardianModule.safe()), address(safeInstance.safe)); assertEq(address(deputyGuardianModule.deputyGuardian()), address(deputyGuardian)); - assertEq(address(deputyGuardianModule.superchainConfig()), address(superchainConfig)); + assertEq(address(deputyGuardianModule.superchainConfig()), address(celoSuperchainConfig)); } } contract DeputyGuardianModule_Pause_Test is DeputyGuardianModule_TestInit { /// @dev Tests that `pause` successfully pauses when called by the deputy guardian. function test_pause_succeeds() external { - vm.expectEmit(address(superchainConfig)); + vm.expectEmit(address(celoSuperchainConfig)); emit Paused("Deputy Guardian"); vm.expectEmit(address(safeInstance.safe)); @@ -85,7 +85,7 @@ contract DeputyGuardianModule_Pause_Test is DeputyGuardianModule_TestInit { vm.prank(address(deputyGuardian)); deputyGuardianModule.pause(); - assertEq(superchainConfig.paused(), true); + assertEq(celoSuperchainConfig.paused(), true); } } @@ -99,8 +99,8 @@ contract DeputyGuardianModule_Pause_TestFail is DeputyGuardianModule_TestInit { /// @dev Tests that when the call from the Safe reverts, the error message is returned. function test_pause_targetReverts_reverts() external { vm.mockCallRevert( - address(superchainConfig), - abi.encodePacked(superchainConfig.pause.selector), + address(celoSuperchainConfig), + abi.encodePacked(celoSuperchainConfig.pause.selector), "SuperchainConfig: pause() reverted" ); @@ -116,12 +116,12 @@ contract DeputyGuardianModule_Unpause_Test is DeputyGuardianModule_TestInit { super.setUp(); vm.prank(address(deputyGuardian)); deputyGuardianModule.pause(); - assertTrue(superchainConfig.paused()); + assertTrue(celoSuperchainConfig.paused()); } /// @dev Tests that `unpause` successfully unpauses when called by the deputy guardian. function test_unpause_succeeds() external { - vm.expectEmit(address(superchainConfig)); + vm.expectEmit(address(celoSuperchainConfig)); emit Unpaused(); vm.expectEmit(address(safeInstance.safe)); @@ -132,7 +132,7 @@ contract DeputyGuardianModule_Unpause_Test is DeputyGuardianModule_TestInit { vm.prank(address(deputyGuardian)); deputyGuardianModule.unpause(); - assertFalse(superchainConfig.paused()); + assertFalse(celoSuperchainConfig.paused()); } } @@ -143,14 +143,14 @@ contract DeputyGuardianModule_Unpause_TestFail is DeputyGuardianModule_Unpause_T function test_unpause_notDeputyGuardian_reverts() external { vm.expectRevert(abi.encodeWithSelector(Unauthorized.selector)); deputyGuardianModule.unpause(); - assertTrue(superchainConfig.paused()); + assertTrue(celoSuperchainConfig.paused()); } /// @dev Tests that when the call from the Safe reverts, the error message is returned. function test_unpause_targetReverts_reverts() external { vm.mockCallRevert( - address(superchainConfig), - abi.encodePacked(superchainConfig.unpause.selector), + address(celoSuperchainConfig), + abi.encodePacked(celoSuperchainConfig.unpause.selector), "SuperchainConfig: unpause reverted" ); diff --git a/packages/contracts-bedrock/test/setup/CommonTest.sol b/packages/contracts-bedrock/test/setup/CommonTest.sol index 679ce200559c9..77eb172da91c5 100644 --- a/packages/contracts-bedrock/test/setup/CommonTest.sol +++ b/packages/contracts-bedrock/test/setup/CommonTest.sol @@ -22,6 +22,7 @@ contract CommonTest is Test, Setup, Events { bool useLegacyContracts; address customGasToken; bool useInteropOverride; + address externalSuperchainConfig; function setUp() public virtual override { alice = makeAddr("alice"); @@ -45,6 +46,9 @@ contract CommonTest is Test, Setup, Events { if (useInteropOverride) { deploy.cfg().setUseInterop(true); } + if (externalSuperchainConfig != address(0)) { + deploy.cfg().setExternalSuperchainConfig(externalSuperchainConfig); + } vm.etch(address(ffi), vm.getDeployedCode("FFIInterface.sol:FFIInterface")); vm.label(address(ffi), "FFIInterface"); @@ -150,4 +154,15 @@ contract CommonTest is Test, Setup, Events { useInteropOverride = true; } + + function enableExternalSuperchainConfig(address _externalSuperchainConfig) public { + // Check if the system has already been deployed, based off of the heuristic that alice and bob have not been + // set by the `setUp` function yet. + if (!(alice == address(0) && bob == address(0))) { + revert("CommonTest: Cannot enable interop after deployment. Consider overriding `setUp`."); + } + + externalSuperchainConfig = _externalSuperchainConfig; + super.withoutSuperchain(); + } } diff --git a/packages/contracts-bedrock/test/setup/ExternalSuperchainConfig.t.sol b/packages/contracts-bedrock/test/setup/ExternalSuperchainConfig.t.sol new file mode 100644 index 0000000000000..57e9a414e76d4 --- /dev/null +++ b/packages/contracts-bedrock/test/setup/ExternalSuperchainConfig.t.sol @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.15; + +import { console } from "forge-std/console.sol"; + +import { DeployConfig } from "scripts/deploy/DeployConfig.s.sol"; +import { SuperchainConfig } from "src/L1/SuperchainConfig.sol"; + +// Testing utilities +import { CommonTest } from "test/setup/CommonTest.sol"; + +contract ExternalSuperchainConfig_Test is CommonTest { + address anAddress; + + function setUp() public override { + anAddress = address(new SuperchainConfig()); + super.enableExternalSuperchainConfig(anAddress); + super.setUp(); + } + + function test_setsSuperchainConfigCorrectly() public { + assertEq(address(superchainConfig), anAddress); + } +} + +contract ExternalSuperchainConfig_TestFail is CommonTest { + function setUp() public override { } + + function test_address0_fails() public { + // With the below `expectRevert` uncommented, the test fails with "next call did not revert + // as expected", even though, when commented, instead the test fails with the expected error + // message. + vm.skip(true); + super.enableExternalSuperchainConfig(address(0)); + //vm.expectRevert("Need to provide the external SuperchainConfig address!"); + super.setUp(); + } +} diff --git a/packages/contracts-bedrock/test/setup/L2ProxyAdminOwnerVerification.t.sol b/packages/contracts-bedrock/test/setup/L2ProxyAdminOwnerVerification.t.sol new file mode 100644 index 0000000000000..ff608c66b99cb --- /dev/null +++ b/packages/contracts-bedrock/test/setup/L2ProxyAdminOwnerVerification.t.sol @@ -0,0 +1,68 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.15; + +import { DeployConfig } from "scripts/deploy/DeployConfig.s.sol"; + +// Testing utilities +import { CommonTest } from "test/setup/CommonTest.sol"; + +contract L2ProxyAdminOwnerVerification_Test is CommonTest { + address constant anOwner = 0xCAfEcAfeCAfECaFeCaFecaFecaFECafECafeCaFe; + address constant anotherOwner = 0xBEeFbeefbEefbeEFbeEfbEEfBEeFbeEfBeEfBeef; + address constant aliasedOwner = 0xDc0FCafeCAFecAfeCafecAfecaFecAFeCAfEdc0F; + + DeployConfig cfg; + + function setUp() public override { + super.setUp(); + cfg = deploy.cfg(); + } + + function test_aliased_succeeds() public { + cfg.setProxyAdminOwnerSettings(anOwner, aliasedOwner, true); + cfg.verifyProxyAdminOwners(); + } + + function test_equal_succeeds() public { + cfg.setProxyAdminOwnerSettings(anOwner, anOwner, false); + cfg.verifyProxyAdminOwners(); + } +} + +contract L2ProxyAdminOwnerVerification_TestFail is CommonTest { + address constant anOwner = 0xCAfEcAfeCAfECaFeCaFecaFecaFECafECafeCaFe; + address constant anotherOwner = 0xBEeFbeefbEefbeEFbeEfbEEfBEeFbeEfBeEfBeef; + address constant aliasedOwner = 0xDc0FCafeCAFecAfeCafecAfecaFecAFeCAfEdc0F; + address constant offByOneOwner = 0xcAFeCAfEcAFECaFecaFECAFeCAfEcAFecAFECAff; + + DeployConfig cfg; + + function setUp() public override { + super.setUp(); + cfg = deploy.cfg(); + } + + function test_aliased_whenSameAddress_fails() public { + cfg.setProxyAdminOwnerSettings(anOwner, anOwner, true); + vm.expectRevert("Expected proxyAdminOwner to be aliased finalSystemOwner"); + cfg.verifyProxyAdminOwners(); + } + + function test_aliased_whenIncorrectAlias_fails() public { + cfg.setProxyAdminOwnerSettings(anOwner, offByOneOwner, true); + vm.expectRevert("Expected proxyAdminOwner to be aliased finalSystemOwner"); + cfg.verifyProxyAdminOwners(); + } + + function test_equal_whenAliased_fails() public { + cfg.setProxyAdminOwnerSettings(anOwner, aliasedOwner, false); + vm.expectRevert("Expected finalSystemOwner and proxyAdminOwner to be equal"); + cfg.verifyProxyAdminOwners(); + } + + function test_equal_whenOffBy1_fails() public { + cfg.setProxyAdminOwnerSettings(anOwner, offByOneOwner, false); + vm.expectRevert("Expected finalSystemOwner and proxyAdminOwner to be equal"); + cfg.verifyProxyAdminOwners(); + } +} diff --git a/packages/contracts-bedrock/test/setup/Setup.sol b/packages/contracts-bedrock/test/setup/Setup.sol index ef2b654b2410d..f0a9e45d4fd7a 100644 --- a/packages/contracts-bedrock/test/setup/Setup.sol +++ b/packages/contracts-bedrock/test/setup/Setup.sol @@ -23,6 +23,7 @@ import { IL1CrossDomainMessenger } from "src/L1/interfaces/IL1CrossDomainMesseng import { IL2OutputOracle } from "src/L1/interfaces/IL2OutputOracle.sol"; import { ISystemConfig } from "src/L1/interfaces/ISystemConfig.sol"; import { ISuperchainConfig } from "src/L1/interfaces/ISuperchainConfig.sol"; +import { ICeloSuperchainConfig } from "src/L1/interfaces/ICeloSuperchainConfig.sol"; import { IDataAvailabilityChallenge } from "src/L1/interfaces/IDataAvailabilityChallenge.sol"; import { IL1StandardBridge } from "src/L1/interfaces/IL1StandardBridge.sol"; import { IProtocolVersions } from "src/L1/interfaces/IProtocolVersions.sol"; @@ -86,6 +87,7 @@ contract Setup { IOptimismMintableERC20Factory l1OptimismMintableERC20Factory; IProtocolVersions protocolVersions; ISuperchainConfig superchainConfig; + ICeloSuperchainConfig celoSuperchainConfig; IDataAvailabilityChallenge dataAvailabilityChallenge; // L2 contracts @@ -112,6 +114,8 @@ contract Setup { IOptimismSuperchainERC20Factory l2OptimismSuperchainERC20Factory = IOptimismSuperchainERC20Factory(Predeploys.OPTIMISM_SUPERCHAIN_ERC20_FACTORY); + bool needsSuperchain = true; + /// @dev Deploys the Deploy contract without including its bytecode in the bytecode /// of this contract by fetching the bytecode dynamically using `vm.getCode()`. /// If the Deploy bytecode is included in this contract, then it will double @@ -141,7 +145,7 @@ contract Setup { hex"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe03601600081602082378035828234f58015156039578182fd5b8082525050506014600cf3" ); - deploy.run(); + deploy.run(needsSuperchain); console.log("Setup: completed L1 deployment, registering addresses now"); optimismPortal = IOptimismPortal(deploy.mustGetAddress("OptimismPortalProxy")); @@ -157,6 +161,7 @@ contract Setup { IOptimismMintableERC20Factory(deploy.mustGetAddress("OptimismMintableERC20FactoryProxy")); protocolVersions = IProtocolVersions(deploy.mustGetAddress("ProtocolVersionsProxy")); superchainConfig = ISuperchainConfig(deploy.mustGetAddress("SuperchainConfigProxy")); + celoSuperchainConfig = ICeloSuperchainConfig(deploy.mustGetAddress("CeloSuperchainConfigProxy")); anchorStateRegistry = IAnchorStateRegistry(deploy.mustGetAddress("AnchorStateRegistryProxy")); vm.label(address(optimismPortal), "OptimismPortal"); @@ -179,6 +184,7 @@ contract Setup { vm.label(address(protocolVersions), "ProtocolVersions"); vm.label(deploy.mustGetAddress("ProtocolVersionsProxy"), "ProtocolVersionsProxy"); vm.label(address(superchainConfig), "SuperchainConfig"); + vm.label(address(celoSuperchainConfig), "CeloSuperchainConfig"); vm.label(deploy.mustGetAddress("SuperchainConfigProxy"), "SuperchainConfigProxy"); vm.label(AddressAliasHelper.applyL1ToL2Alias(address(l1CrossDomainMessenger)), "L1CrossDomainMessenger_aliased"); @@ -265,4 +271,8 @@ contract Setup { function labelPreinstall(address _addr) internal { vm.label(_addr, Preinstalls.getName(_addr)); } + + function withoutSuperchain() internal { + needsSuperchain = false; + } } diff --git a/packages/contracts-bedrock/test/universal/Specs.t.sol b/packages/contracts-bedrock/test/universal/Specs.t.sol index 9c22e178a5f4d..4a9e303c8f4f1 100644 --- a/packages/contracts-bedrock/test/universal/Specs.t.sol +++ b/packages/contracts-bedrock/test/universal/Specs.t.sol @@ -920,6 +920,20 @@ contract Specification_Test is CommonTest { _addSpec({ _name: "LivenessModule", _sel: _getSel("safe()") }); _addSpec({ _name: "LivenessModule", _sel: _getSel("thresholdPercentage()") }); _addSpec({ _name: "LivenessModule", _sel: _getSel("version()") }); + + // CeloSuperchainConfig + _addSpec({ _name: "CeloSuperchainConfig", _sel: _getSel("GUARDIAN_SLOT()") }); + _addSpec({ _name: "CeloSuperchainConfig", _sel: _getSel("PAUSED_SLOT()") }); + _addSpec({ _name: "CeloSuperchainConfig", _sel: _getSel("SUPERCHAIN_CONFIG_SLOT()") }); + _addSpec({ _name: "CeloSuperchainConfig", _sel: _getSel("guardian()") }); + _addSpec({ _name: "CeloSuperchainConfig", _sel: _getSel("superchainConfig()") }); + _addSpec({ _name: "CeloSuperchainConfig", _sel: _getSel("initialize(address,bool,address)") }); + _addSpec({ _name: "CeloSuperchainConfig", _sel: _getSel("pause(string)") }); + _addSpec({ _name: "CeloSuperchainConfig", _sel: _getSel("checkAndPauseIfSuperchainPaused()") }); + _addSpec({ _name: "CeloSuperchainConfig", _sel: _getSel("paused()") }); + _addSpec({ _name: "CeloSuperchainConfig", _sel: _getSel("celoPaused()") }); + _addSpec({ _name: "CeloSuperchainConfig", _sel: _getSel("unpause()") }); + _addSpec({ _name: "CeloSuperchainConfig", _sel: _getSel("version()") }); } /// @dev Computes the selector from a function signature. diff --git a/packages/contracts-bedrock/test/vendor/Initializable.t.sol b/packages/contracts-bedrock/test/vendor/Initializable.t.sol index 5c17987c471d7..c972b2a66326b 100644 --- a/packages/contracts-bedrock/test/vendor/Initializable.t.sol +++ b/packages/contracts-bedrock/test/vendor/Initializable.t.sol @@ -18,7 +18,7 @@ import "scripts/deploy/Deployer.sol"; // Interfaces import { ISystemConfig } from "src/L1/interfaces/ISystemConfig.sol"; import { IResourceMetering } from "src/L1/interfaces/IResourceMetering.sol"; -import { ISuperchainConfig } from "src/L1/interfaces/ISuperchainConfig.sol"; +import { ICeloSuperchainConfig } from "src/L1/interfaces/ICeloSuperchainConfig.sol"; import { ProtocolVersion } from "src/L1/interfaces/IProtocolVersions.sol"; import { IAnchorStateRegistry } from "src/dispute/interfaces/IAnchorStateRegistry.sol"; @@ -74,7 +74,8 @@ contract Initializer_Test is Bridge_Initializer { name: "L1CrossDomainMessenger", target: deploy.mustGetAddress("L1CrossDomainMessenger"), initCalldata: abi.encodeCall( - l1CrossDomainMessenger.initialize, (superchainConfig, optimismPortal, systemConfig) + l1CrossDomainMessenger.initialize, + (ICeloSuperchainConfig(address(superchainConfig)), optimismPortal, systemConfig) ) }) ); @@ -84,7 +85,8 @@ contract Initializer_Test is Bridge_Initializer { name: "L1CrossDomainMessengerProxy", target: address(l1CrossDomainMessenger), initCalldata: abi.encodeCall( - l1CrossDomainMessenger.initialize, (superchainConfig, optimismPortal, systemConfig) + l1CrossDomainMessenger.initialize, + (ICeloSuperchainConfig(address(superchainConfig)), optimismPortal, systemConfig) ) }) ); @@ -109,7 +111,7 @@ contract Initializer_Test is Bridge_Initializer { InitializeableContract({ name: "DelayedWETH", target: deploy.mustGetAddress("DelayedWETH"), - initCalldata: abi.encodeCall(delayedWeth.initialize, (address(0), ISuperchainConfig(address(0)))) + initCalldata: abi.encodeCall(delayedWeth.initialize, (address(0), ICeloSuperchainConfig(address(0)))) }) ); // DelayedWETHProxy @@ -117,7 +119,7 @@ contract Initializer_Test is Bridge_Initializer { InitializeableContract({ name: "DelayedWETHProxy", target: address(delayedWeth), - initCalldata: abi.encodeCall(delayedWeth.initialize, (address(0), ISuperchainConfig(address(0)))) + initCalldata: abi.encodeCall(delayedWeth.initialize, (address(0), ICeloSuperchainConfig(address(0)))) }) ); // L2OutputOracleImpl @@ -141,7 +143,10 @@ contract Initializer_Test is Bridge_Initializer { InitializeableContract({ name: "OptimismPortal", target: deploy.mustGetAddress("OptimismPortal"), - initCalldata: abi.encodeCall(optimismPortal.initialize, (l2OutputOracle, systemConfig, superchainConfig)) + initCalldata: abi.encodeCall( + optimismPortal.initialize, + (l2OutputOracle, systemConfig, ICeloSuperchainConfig(address(superchainConfig))) + ) }) ); // OptimismPortalProxy @@ -149,7 +154,10 @@ contract Initializer_Test is Bridge_Initializer { InitializeableContract({ name: "OptimismPortalProxy", target: address(optimismPortal), - initCalldata: abi.encodeCall(optimismPortal.initialize, (l2OutputOracle, systemConfig, superchainConfig)) + initCalldata: abi.encodeCall( + optimismPortal.initialize, + (l2OutputOracle, systemConfig, ICeloSuperchainConfig(address(superchainConfig))) + ) }) ); // OptimismPortal2Impl @@ -162,7 +170,7 @@ contract Initializer_Test is Bridge_Initializer { ( disputeGameFactory, systemConfig, - superchainConfig, + ICeloSuperchainConfig(address(superchainConfig)), GameType.wrap(uint32(deploy.cfg().respectedGameType())) ) ) @@ -274,7 +282,8 @@ contract Initializer_Test is Bridge_Initializer { name: "L1StandardBridge", target: deploy.mustGetAddress("L1StandardBridge"), initCalldata: abi.encodeCall( - l1StandardBridge.initialize, (l1CrossDomainMessenger, superchainConfig, systemConfig) + l1StandardBridge.initialize, + (l1CrossDomainMessenger, ICeloSuperchainConfig(address(superchainConfig)), systemConfig) ) }) ); @@ -284,7 +293,8 @@ contract Initializer_Test is Bridge_Initializer { name: "L1StandardBridgeProxy", target: address(l1StandardBridge), initCalldata: abi.encodeCall( - l1StandardBridge.initialize, (l1CrossDomainMessenger, superchainConfig, systemConfig) + l1StandardBridge.initialize, + (l1CrossDomainMessenger, ICeloSuperchainConfig(address(superchainConfig)), systemConfig) ) }) ); @@ -309,7 +319,9 @@ contract Initializer_Test is Bridge_Initializer { InitializeableContract({ name: "L1ERC721Bridge", target: deploy.mustGetAddress("L1ERC721Bridge"), - initCalldata: abi.encodeCall(l1ERC721Bridge.initialize, (l1CrossDomainMessenger, superchainConfig)) + initCalldata: abi.encodeCall( + l1ERC721Bridge.initialize, (l1CrossDomainMessenger, ICeloSuperchainConfig(address(superchainConfig))) + ) }) ); // L1ERC721BridgeProxy @@ -317,7 +329,9 @@ contract Initializer_Test is Bridge_Initializer { InitializeableContract({ name: "L1ERC721BridgeProxy", target: address(l1ERC721Bridge), - initCalldata: abi.encodeCall(l1ERC721Bridge.initialize, (l1CrossDomainMessenger, superchainConfig)) + initCalldata: abi.encodeCall( + l1ERC721Bridge.initialize, (l1CrossDomainMessenger, ICeloSuperchainConfig(address(superchainConfig))) + ) }) ); // L2ERC721Bridge @@ -367,7 +381,7 @@ contract Initializer_Test is Bridge_Initializer { target: address(anchorStateRegistry), initCalldata: abi.encodeCall( anchorStateRegistry.initialize, - (new IAnchorStateRegistry.StartingAnchorRoot[](1), ISuperchainConfig(address(0))) + (new IAnchorStateRegistry.StartingAnchorRoot[](1), ICeloSuperchainConfig(address(0))) ) }) ); @@ -378,10 +392,26 @@ contract Initializer_Test is Bridge_Initializer { target: address(anchorStateRegistry), initCalldata: abi.encodeCall( anchorStateRegistry.initialize, - (new IAnchorStateRegistry.StartingAnchorRoot[](1), ISuperchainConfig(address(0))) + (new IAnchorStateRegistry.StartingAnchorRoot[](1), ICeloSuperchainConfig(address(0))) ) }) ); + // CeloSuperchainConfig + contracts.push( + InitializeableContract({ + name: "CeloSuperchainConfig", + target: address(celoSuperchainConfig), + initCalldata: abi.encodeWithSignature("initialize(address,bool,address)", address(0), false, address(0)) + }) + ); + // CeloSuperchainConfigProxy + contracts.push( + InitializeableContract({ + name: "CeloSuperchainConfigProxy", + target: address(celoSuperchainConfig), + initCalldata: abi.encodeWithSignature("initialize(address,bool,address)", address(0), false, address(0)) + }) + ); // Nicknamed contracts. nicknames["OptimismPortal2Proxy"] = "OptimismPortalProxy"; @@ -393,7 +423,7 @@ contract Initializer_Test is Bridge_Initializer { /// 3. The `initialize()` function of each contract cannot be called again. function test_cannotReinitialize_succeeds() public { // Collect exclusions. - string[] memory excludes = new string[](9); + string[] memory excludes = new string[](10); // TODO: Neither of these contracts are labeled properly in the deployment script. Both are // currently being labeled as their non-interop versions. Remove these exclusions once // the deployment script is fixed. @@ -414,6 +444,8 @@ contract Initializer_Test is Bridge_Initializer { excludes[7] = "src/L1/OPContractsManagerInterop.sol"; // The L2OutputOracle is not always deployed (and is no longer being modified) excludes[8] = "src/L1/L2OutputOracle.sol"; + // Celo contracts are not setup the same way as most of the OP L2 Genesis contracts. + excludes[9] = "src/celo/*"; // Get all contract names in the src directory, minus the excluded contracts. string[] memory contractNames = ForgeArtifacts.getContractNames("src/*", excludes);