diff --git a/.circleci/config.yml b/.circleci/config.yml index de393bf981880..3895381961c70 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -35,51 +35,8 @@ parameters: orbs: go: circleci/go@1.8.0 - gcp-cli: circleci/gcp-cli@3.0.1 - slack: circleci/slack@4.10.1 shellcheck: circleci/shellcheck@3.2.0 commands: - gcp-oidc-authenticate: - description: "Authenticate with GCP using a CircleCI OIDC token." - parameters: - project_id: - type: env_var_name - default: GCP_PROJECT_ID - workload_identity_pool_id: - type: env_var_name - default: GCP_WIP_ID - workload_identity_pool_provider_id: - type: env_var_name - default: GCP_WIP_PROVIDER_ID - service_account_email: - type: env_var_name - default: GCP_SERVICE_ACCOUNT_EMAIL - gcp_cred_config_file_path: - type: string - default: /home/circleci/gcp_cred_config.json - oidc_token_file_path: - type: string - default: /home/circleci/oidc_token.json - steps: - - run: - name: "Create OIDC credential configuration" - command: | - # Store OIDC token in temp file - echo $CIRCLE_OIDC_TOKEN > << parameters.oidc_token_file_path >> - # Create a credential configuration for the generated OIDC ID Token - gcloud iam workload-identity-pools create-cred-config \ - "projects/${<< parameters.project_id >>}/locations/global/workloadIdentityPools/${<< parameters.workload_identity_pool_id >>}/providers/${<< parameters.workload_identity_pool_provider_id >>}"\ - --output-file="<< parameters.gcp_cred_config_file_path >>" \ - --service-account="${<< parameters.service_account_email >>}" \ - --credential-source-file=<< parameters.oidc_token_file_path >> - - run: - name: "Authenticate with GCP using OIDC" - command: | - # Configure gcloud to leverage the generated credential configuration - gcloud auth login --brief --cred-file "<< parameters.gcp_cred_config_file_path >>" - # Configure ADC - echo "export GOOGLE_APPLICATION_CREDENTIALS='<< parameters.gcp_cred_config_file_path >>'" | tee -a "$BASH_ENV" - check-changed: description: "Conditionally halts a step if certain modules change" parameters: @@ -110,12 +67,8 @@ commands: type: string default: "" steps: - - slack/notify: - channel: << parameters.channel >> - event: fail - template: basic_fail_1 - branch_pattern: develop - mentions: "<< parameters.mentions >>" + - run: + command: "true" # No notifications setup up for celo-org fork jobs: cannon-go-lint-and-test: @@ -266,238 +219,6 @@ jobs: - "packages/contracts-bedrock/deployments/devnetL1" - notify-failures-on-develop - docker-build: - environment: - DOCKER_BUILDKIT: 1 - parameters: - docker_tags: - description: Docker image tags, comma-separated - type: string - docker_name: - description: "Docker buildx bake target" - type: string - default: "" - registry: - description: Docker registry - type: string - default: "us-docker.pkg.dev" - repo: - description: Docker repo - type: string - default: "oplabs-tools-artifacts/images" - save_image_tag: - description: Save docker image with given tag - type: string - default: "" - platforms: - description: Platforms to build for, comma-separated - type: string - default: "linux/amd64" - publish: - description: Publish the docker image (multi-platform, all tags) - type: boolean - default: false - release: - description: Run the release script - type: boolean - default: false - resource_class: - description: Docker resoruce class - type: string - default: medium - machine: - image: <> - resource_class: "<>" - docker_layer_caching: true # we rely on this for faster builds, and actively warm it up for builds with common stages - steps: - - checkout - - attach_workspace: - at: /tmp/docker_images - - run: - command: mkdir -p /tmp/docker_images - - when: - condition: "<>" - steps: - - gcp-cli/install - - when: - condition: - or: - - "<>" - - "<>" - steps: - - gcp-oidc-authenticate - - run: - name: Build - command: | - # Check to see if DOCKER_HUB_READ_ONLY_TOKEN is set (i.e. we are in repo) before attempting to use secrets. - # Building should work without this read only login, but may get rate limited. - if [[ -v DOCKER_HUB_READ_ONLY_TOKEN ]]; then - echo "$DOCKER_HUB_READ_ONLY_TOKEN" | docker login -u "$DOCKER_HUB_READ_ONLY_USER" --password-stdin - fi - - export REGISTRY="<>" - export REPOSITORY="<>" - export IMAGE_TAGS="$(echo -ne "<>" | sed "s/[^a-zA-Z0-9\n,]/-/g")" - export GIT_COMMIT="$(git rev-parse HEAD)" - export GIT_DATE="$(git show -s --format='%ct')" - export PLATFORMS="<>" - - echo "Checking git tags pointing at $GIT_COMMIT:" - tags_at_commit=$(git tag --points-at $GIT_COMMIT) - echo "Tags at commit:\n$tags_at_commit" - - filtered_tags=$(echo "$tags_at_commit" | grep "^<>/" || true) - echo "Filtered tags: $filtered_tags" - - if [ -z "$filtered_tags" ]; then - export GIT_VERSION="untagged" - else - sorted_tags=$(echo "$filtered_tags" | sed "s/<>\///" | sort -V) - echo "Sorted tags: $sorted_tags" - - # prefer full release tag over "-rc" release candidate tag if both exist - full_release_tag=$(echo "$sorted_tags" | grep -v -- "-rc" || true) - if [ -z "$full_release_tag" ]; then - export GIT_VERSION=$(echo "$sorted_tags" | tail -n 1) - else - export GIT_VERSION=$(echo "$full_release_tag" | tail -n 1) - fi - fi - - echo "Setting GIT_VERSION=$GIT_VERSION" - - # Create, start (bootstrap) and use a *named* docker builder - # This allows us to cross-build multi-platform, - # and naming allows us to use the DLC (docker-layer-cache) - docker buildx create --driver=docker-container --name=buildx-build --bootstrap --use - - DOCKER_OUTPUT_DESTINATION="" - if [ "<>" == "true" ]; then - gcloud auth configure-docker <> - echo "Building for platforms $PLATFORMS and then publishing to registry" - DOCKER_OUTPUT_DESTINATION="--push" - if [ "<>" != "" ]; then - echo "ERROR: cannot save image to docker when publishing to registry" - exit 1 - fi - else - if [ "<>" == "" ]; then - echo "Running $PLATFORMS build without destination (cache warm-up)" - DOCKER_OUTPUT_DESTINATION="" - elif [[ $PLATFORMS == *,* ]]; then - echo "ERROR: cannot perform multi-arch (platforms: $PLATFORMS) build while also loading the result into regular docker" - exit 1 - else - echo "Running single-platform $PLATFORMS build and loading into docker" - DOCKER_OUTPUT_DESTINATION="--load" - fi - fi - - # Let them cook! - docker buildx bake \ - --progress plain \ - --builder=buildx-build \ - -f docker-bake.hcl \ - $DOCKER_OUTPUT_DESTINATION \ - <> - - no_output_timeout: 45m - - when: - condition: "<>" - steps: - - notify-failures-on-develop - - when: - condition: "<>" - steps: - - run: - name: Save - command: | - IMAGE_NAME="<>/<>/<>:<>" - docker save -o /tmp/docker_images/<>.tar $IMAGE_NAME - - persist_to_workspace: - root: /tmp/docker_images - paths: # only write the one file, to avoid concurrent workspace-file additions - - "<>.tar" - - when: - condition: "<>" - steps: - - run: - name: Tag - command: | - ./ops/scripts/ci-docker-tag-op-stack-release.sh <>/<> $CIRCLE_TAG $CIRCLE_SHA1 - - when: - condition: - and: - - or: - - "<>" - - "<>" - - equal: [develop, << pipeline.git.branch >>] - steps: - - gcp-oidc-authenticate: - service_account_email: GCP_SERVICE_ATTESTOR_ACCOUNT_EMAIL - - run: - name: Sign - command: | - git clone https://github.com/ethereum-optimism/binary_signer - cd binary_signer/signer - git checkout tags/v1.0.3 - - IMAGE_PATH="<>/<>/<>:<>" - echo $IMAGE_PATH - pip3 install -r requirements.txt - - python3 ./sign_image.py --command="sign"\ - --attestor-project-name="$ATTESTOR_PROJECT_NAME"\ - --attestor-name="$ATTESTOR_NAME"\ - --image-path="$IMAGE_PATH"\ - --signer-logging-level="INFO"\ - --attestor-key-id="//cloudkms.googleapis.com/v1/projects/$ATTESTOR_PROJECT_NAME/locations/global/keyRings/$ATTESTOR_NAME-key-ring/cryptoKeys/$ATTESTOR_NAME-key/cryptoKeyVersions/1" - - # Verify newly published images (built on AMD machine) will run on ARM - check-cross-platform: - docker: - - image: cimg/base:current - resource_class: arm.medium - parameters: - registry: - description: Docker registry - type: string - default: "us-docker.pkg.dev" - repo: - description: Docker repo - type: string - default: "oplabs-tools-artifacts/images" - op_component: - description: "Name of op-stack component (e.g. op-node)" - type: string - default: "" - docker_tag: - description: "Tag of docker image" - type: string - default: "<>" - steps: - - setup_remote_docker - - run: - name: "Verify Image Platform" - command: | - image_name="<>/<>/<>:<>" - echo "Retrieving Docker image manifest: $image_name" - MANIFEST=$(docker manifest inspect $image_name) - - echo "Verifying 'linux/arm64' is supported..." - SUPPORTED_PLATFORM=$(echo "$MANIFEST" | jq -r '.manifests[] | select(.platform.architecture == "arm64" and .platform.os == "linux")') - echo $SUPPORT_PLATFORM - if [ -z "$SUPPORTED_PLATFORM" ]; then - echo "Platform 'linux/arm64' not supported by this image" - exit 1 - fi - - run: - name: "Pull and run docker image" - command: | - image_name="<>/<>/<>:<>" - docker pull $image_name || exit 1 - docker run $image_name <> --version || exit 1 - contracts-bedrock-coverage: docker: - image: <> @@ -1242,6 +963,9 @@ jobs: - run: name: Test the stack command: make devnet-test + - run: + name: Run Celo e2e tests + command: op-e2e/celo/run_all_tests.sh - run: name: Dump op-node logs command: | @@ -1424,35 +1148,6 @@ jobs: steps: - run: echo Done - fpp-verify: - docker: - - image: cimg/go:1.21 - steps: - - checkout - - run: - name: verify-sepolia - command: | - make verify-sepolia - working_directory: op-program - - notify-failures-on-develop: - mentions: "@proofs-squad" - - op-program-compat: - docker: - - image: <> - steps: - - checkout - - restore_cache: - name: Restore Go modules cache - key: gomod-{{ checksum "go.sum" }} - - restore_cache: - key: golang-build-cache - - run: - name: compat-sepolia - command: | - make verify-compat - working_directory: op-program - check-generated-mocks-op-node: docker: - image: <> @@ -1671,9 +1366,9 @@ workflows: - op-service-rethdb-tests: requires: - go-mod-download - - op-program-compat: + - cannon-prestate: requires: - - op-program-tests + - go-mod-download - bedrock-go-tests: requires: - go-mod-download @@ -1691,503 +1386,20 @@ workflows: - op-dispute-mon-tests - op-conductor-tests - op-program-tests - - op-program-compat - op-service-tests - op-service-rethdb-tests - op-e2e-HTTP-tests - op-e2e-fault-proof-tests - op-e2e-action-tests - - docker-build: - name: op-node-docker-build - docker_name: op-node - docker_tags: <>,<> - save_image_tag: <> # for devnet later - - docker-build: - name: op-batcher-docker-build - docker_name: op-batcher - docker_tags: <>,<> - save_image_tag: <> # for devnet later - - docker-build: - name: op-program-docker-build - docker_name: op-program - docker_tags: <>,<> - # op-program is not part of the devnet, we don't save it. - - docker-build: - name: op-proposer-docker-build - docker_name: op-proposer - docker_tags: <>,<> - save_image_tag: <> # for devnet later - - docker-build: - name: op-challenger-docker-build - docker_name: op-challenger - docker_tags: <>,<> - save_image_tag: <> # for devnet later - - docker-build: - name: op-dispute-mon-docker-build - docker_name: op-dispute-mon - docker_tags: <>,<> - save_image_tag: <> # for devnet later - - docker-build: - name: op-conductor-docker-build - docker_name: op-conductor - docker_tags: <>,<> - # op-conductor is not part of the devnet, we don't save it. - - docker-build: - name: op-heartbeat-docker-build - docker_name: op-heartbeat - docker_tags: <>,<> - # op-heartbeat is not part of the devnet, we don't save it. - - docker-build: - name: da-server-docker-build - docker_name: da-server - docker_tags: <>,<> - save_image_tag: <> # for devnet later - - cannon-prestate: - requires: - - go-mod-download - - devnet: - matrix: - parameters: - variant: ["default", "l2oo", "plasma", "plasma-generic"] - requires: - - pnpm-monorepo - - op-batcher-docker-build - - op-proposer-docker-build - - op-node-docker-build - - op-challenger-docker-build - - da-server-docker-build - - cannon-prestate - check-generated-mocks-op-node - check-generated-mocks-op-service - cannon-go-lint-and-test: requires: - pnpm-monorepo - cannon-build-test-vectors - - shellcheck/check: - name: shell-check - # We don't need the `exclude` key as the orb detects the `.shellcheckrc` - dir: . - ignore-dirs: - ./packages/contracts-bedrock/lib - - release: - when: - not: - equal: [ scheduled_pipeline, << pipeline.trigger_source >> ] - jobs: - - hold: - type: approval - filters: - tags: - only: /^(da-server|proxyd|chain-mon|ci-builder(-rust)?|ufm-[a-z0-9\-]*|op-[a-z0-9\-]*)\/v.*/ - branches: - ignore: /.*/ - - docker-build: - name: op-heartbeat-release - filters: - tags: - only: /^op-heartbeat\/v.*/ - branches: - ignore: /.*/ - docker_name: op-heartbeat - docker_tags: <> - requires: ['hold'] - platforms: "linux/amd64,linux/arm64" - publish: true - release: true - context: - - oplabs-gcr-release - - check-cross-platform: - name: op-heartbeat-cross-platform - op_component: op-heartbeat - requires: - - op-heartbeat-release - - docker-build: - name: op-node-docker-release - filters: - tags: - only: /^op-node\/v.*/ - branches: - ignore: /.*/ - docker_name: op-node - docker_tags: <> - requires: ['hold'] - platforms: "linux/amd64,linux/arm64" - publish: true - release: true - context: - - oplabs-gcr-release - - check-cross-platform: - name: op-node-cross-platform - op_component: op-node - requires: - - op-node-docker-release - - docker-build: - name: op-batcher-docker-release - filters: - tags: - only: /^op-batcher\/v.*/ - branches: - ignore: /.*/ - docker_name: op-batcher - docker_tags: <> - requires: ['hold'] - platforms: "linux/amd64,linux/arm64" - publish: true - release: true - context: - - oplabs-gcr-release - - check-cross-platform: - name: op-batcher-cross-platform - op_component: op-batcher - requires: - - op-batcher-docker-release - - docker-build: - name: op-proposer-docker-release - filters: - tags: - only: /^op-proposer\/v.*/ - branches: - ignore: /.*/ - docker_name: op-proposer - docker_tags: <> - requires: ['hold'] - platforms: "linux/amd64,linux/arm64" - publish: true - release: true - context: - - oplabs-gcr-release - - check-cross-platform: - name: op-proposer-cross-platform - op_component: op-proposer - requires: - - op-proposer-docker-release - - docker-build: - name: op-challenger-docker-release - filters: - tags: - only: /^op-challenger\/v.*/ - branches: - ignore: /.*/ - docker_name: op-challenger - docker_tags: <> - requires: ['hold'] - platforms: "linux/amd64,linux/arm64" - publish: true - release: true - context: - - oplabs-gcr-release - - check-cross-platform: - name: op-challenger-cross-platform - op_component: op-challenger - requires: - - op-challenger-docker-release - - docker-build: - name: op-dispute-mon-docker-release - filters: - tags: - only: /^op-dispute-mon\/v.*/ - branches: - ignore: /.*/ - docker_name: op-dispute-mon - docker_tags: <> - requires: ['hold'] - platforms: "linux/amd64,linux/arm64" - publish: true - release: true - context: - - oplabs-gcr-release - - check-cross-platform: - name: op-dispute-mon-cross-platform - op_component: op-dispute-mon - requires: - - op-dispute-mon-docker-release - - docker-build: - name: op-conductor-docker-release - filters: - tags: - only: /^op-conductor\/v.*/ - branches: - ignore: /.*/ - docker_name: op-conductor - docker_tags: <> - requires: ['hold'] - platforms: "linux/amd64,linux/arm64" - publish: true - release: true - context: - - oplabs-gcr-release - - check-cross-platform: - name: op-conductor-cross-platform - op_component: op-conductor - requires: - - op-conductor-docker-release - - docker-build: - name: da-server-docker-release - filters: - tags: - only: /^da-server\/v.*/ - branches: - ignore: /.*/ - docker_name: da-server - docker_tags: <> - requires: ['hold'] - platforms: "linux/amd64,linux/arm64" - publish: true - release: true - context: - - oplabs-gcr-release - - check-cross-platform: - name: da-server-cross-platform - op_component: da-server - requires: - - da-server-docker-release - - docker-build: - name: op-ufm-docker-release - filters: - tags: - only: /^op-ufm\/v.*/ - branches: - ignore: /.*/ - docker_name: op-ufm - docker_tags: <> - publish: true - release: true - context: - - oplabs-gcr-release - requires: - - hold - - docker-build: - name: proxyd-docker-release - filters: - tags: - only: /^proxyd\/v.*/ - branches: - ignore: /.*/ - docker_name: proxyd - docker_tags: <> - publish: true - release: true - context: - - oplabs-gcr-release - requires: - - hold - - docker-build: - name: chain-mon-docker-release - filters: - tags: - only: /^chain-mon\/v.*/ - branches: - ignore: /.*/ - docker_name: chain-mon - docker_tags: <>,latest - publish: true - release: true - resource_class: xlarge - context: - - oplabs-gcr-release - requires: - - hold - - docker-build: - name: ci-builder-docker-release - filters: - tags: - only: /^ci-builder\/v.*/ - branches: - ignore: /.*/ - docker_name: ci-builder - docker_tags: <>,latest - publish: true - release: true - resource_class: xlarge - context: - - oplabs-gcr - requires: - - hold - - docker-build: - name: ci-builder-rust-docker-release - filters: - tags: - only: /^ci-builder-rust\/v.*/ - branches: - ignore: /.*/ - docker_name: ci-builder-rust - docker_tags: <>,latest - publish: true - release: true - resource_class: xlarge - context: - - oplabs-gcr - requires: - - hold - - scheduled-todo-issues: - when: - equal: [ build_four_hours, <> ] - jobs: - - todo-issues: - name: todo-issue-checks - context: - - slack - - scheduled-fpp: - when: - equal: [ build_four_hours, <> ] - jobs: - - fpp-verify: - context: - - slack - - oplabs-fpp-nodes - - develop-fault-proofs: - when: - and: - - or: - - equal: [ "develop", <> ] - - equal: [ true, <> ] - - not: - equal: [ scheduled_pipeline, << pipeline.trigger_source >> ] - jobs: - - go-mod-download - - cannon-prestate: - requires: - - go-mod-download - - pnpm-monorepo: - name: pnpm-monorepo - requires: - - go-mod-download - - go-e2e-test: - name: op-e2e-cannon-tests - module: op-e2e - target: test-cannon - parallelism: 4 - notify: true - mentions: "@proofs-squad" - requires: - - pnpm-monorepo - - cannon-prestate - context: - - slack - - develop-kontrol-tests: - when: - and: - - or: - - equal: [ "develop", <> ] - - equal: [ true, <> ] - - not: - equal: [ scheduled_pipeline, << pipeline.trigger_source >> ] - jobs: - - kontrol-tests: - context: - - slack - - scheduled-docker-publish: - when: - equal: [ build_hourly, <> ] - jobs: - - docker-build: - name: op-node-docker-publish - docker_name: op-node - docker_tags: <>,<> - platforms: "linux/amd64,linux/arm64" - publish: true - context: - - oplabs-gcr - - slack - - docker-build: - name: op-batcher-docker-publish - docker_name: op-batcher - docker_tags: <>,<> - platforms: "linux/amd64,linux/arm64" - publish: true - context: - - oplabs-gcr - - slack - - docker-build: - name: op-program-docker-publish - docker_name: op-program - docker_tags: <>,<> - platforms: "linux/amd64,linux/arm64" - publish: true - context: - - oplabs-gcr - - slack - - docker-build: - name: op-proposer-docker-publish - docker_name: op-proposer - docker_tags: <>,<> - platforms: "linux/amd64,linux/arm64" - publish: true - context: - - oplabs-gcr - - slack - - docker-build: - name: op-challenger-docker-publish - docker_name: op-challenger - docker_tags: <>,<> - platforms: "linux/amd64,linux/arm64" - publish: true - context: - - oplabs-gcr - - slack - - docker-build: - name: op-dispute-mon-docker-publish - docker_name: op-dispute-mon - docker_tags: <>,<> - platforms: "linux/amd64,linux/arm64" - publish: true - context: - - oplabs-gcr - - slack - - docker-build: - name: op-conductor-docker-publish - docker_name: op-conductor - docker_tags: <>,<> - platforms: "linux/amd64,linux/arm64" - publish: true - context: - - oplabs-gcr - - slack - - docker-build: - name: op-heartbeat-docker-publish - docker_name: op-heartbeat - docker_tags: <>,<> - platforms: "linux/amd64,linux/arm64" - publish: true - context: - - oplabs-gcr - - slack - - docker-build: - name: chain-mon-docker-publish - docker_name: chain-mon - docker_tags: <>,<> - resource_class: xlarge - publish: true - context: - - oplabs-gcr - - slack - - docker-build: - name: contracts-bedrock-docker-publish - docker_name: contracts-bedrock - docker_tags: <>,<> - resource_class: xlarge - requires: [ 'chain-mon-docker-publish' ] # use the cached base image - publish: true - context: - - oplabs-gcr - - slack - - scheduled-preimage-reproducibility: - when: - or: - - equal: [build_daily, <> ] - # Trigger on manual triggers if explicitly requested - - equal: [ true, << pipeline.parameters.reproducibility_dispatch >> ] - jobs: - - preimage-reproducibility: - matrix: - parameters: - version: ["0.1.0", "0.2.0", "0.3.0", "1.0.0", "1.1.0"] - context: - slack + # - shellcheck/check: + # name: shell-check + # # We don't need the `exclude` key as the orb detects the `.shellcheckrc` + # dir: . + # ignore-dirs: + # ./packages/contracts-bedrock/lib diff --git a/.github/dependabot.yml b/.github/dependabot.yml index e2c739a570d56..ce096cfc17fcb 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -7,7 +7,7 @@ updates: day: "tuesday" time: "14:30" timezone: "America/New_York" - open-pull-requests-limit: 10 + open-pull-requests-limit: 0 commit-message: prefix: "dependabot(docker): " labels: @@ -20,7 +20,7 @@ updates: day: "tuesday" time: "14:30" timezone: "America/New_York" - open-pull-requests-limit: 10 + open-pull-requests-limit: 0 commit-message: prefix: "dependabot(actions): " labels: @@ -33,7 +33,7 @@ updates: day: "tuesday" time: "14:30" timezone: "America/New_York" - open-pull-requests-limit: 10 + open-pull-requests-limit: 0 versioning-strategy: "auto" commit-message: prefix: "dependabot(npm): " @@ -47,7 +47,7 @@ updates: day: "tuesday" time: "14:30" timezone: "America/New_York" - open-pull-requests-limit: 10 + open-pull-requests-limit: 0 commit-message: prefix: "dependabot(gomod): " labels: diff --git a/.github/workflows/contracts-celo.yaml b/.github/workflows/contracts-celo.yaml new file mode 100644 index 0000000000000..4db01c9406a1d --- /dev/null +++ b/.github/workflows/contracts-celo.yaml @@ -0,0 +1,99 @@ +name: Alfajores-Holesky Deploy Celo4 L1 Contracts +on: + workflow_dispatch: + inputs: + deploy_contracts: + required: false + type: boolean + default: true + contracts_tag: + required: false + type: string + default: 'celo4' + deployment_context: + required: false + type: string + default: 'test-celo4' + l2_chain_id: + required: false + default: '42069' + +jobs: + deploy-contracts: + runs-on: ubuntu-latest + permissions: # Must change the job token permissions to use Akeyless JWT auth + id-token: write + contents: read + if: ${{ ! startsWith(github.triggering_actor, 'akeyless') }} + env: + DEPLOY_CONTRACTS: ${{ github.event_name == 'push' && 'true' || inputs.deploy_contracts }} + CONTRACTS_TAG: ${{ github.event_name == 'push' && 'op-contracts/v1.3.0' || inputs.contracts_tag }} + DEPLOYMENT_CONTEXT: ${{ github.event_name == 'push' && 'test' || inputs.deployment_context }} + L2_CHAIN_ID: ${{ github.event_name == 'push' && '42069' || inputs.l2_chain_id }} + L1_CHAIN_ID: '17000' # Holesky + L1_RPC_URL: 'https://ethereum-holesky-rpc.publicnode.com' + GS_ADMIN_ADDRESS: '0xb2397dF29AFB4B4661559436180019bEb7912985' + GS_BATCHER_ADDRESS: '0x7fDBe8F4D22ab511340667d7Ce5675568d09eBB4' + GS_PROPOSER_ADDRESS: '0xdCf30236Fa0aBE2ca0BEc2eE0a2F40b16A144DB3' + GS_SEQUENCER_ADDRESS: '0x3e2Df8efB6fA1d6E6021572a99BB67BA9ab2C59D' + steps: + + - name: "Get GitHub Token from Akeyless" + id: get_auth_token + uses: + docker://us-west1-docker.pkg.dev/devopsre/akeyless-public/akeyless-action:latest + with: + api-url: https://api.gateway.akeyless.celo-networks-dev.org + access-id: p-kf9vjzruht6l + dynamic-secrets: '{"/dynamic-secrets/keys/github/optimism/contents=write,pull_requests=write":"PAT"}' + + # "/static-secrets/devops-circle/alfajores/op-testnet-alfajores/HOLESKY_QUICKNODE_URL":"L1_RPC_URL", + - name: Akeyless get secrets + uses: docker://us-west1-docker.pkg.dev/devopsre/akeyless-public/akeyless-action:latest + with: + api-url: https://api.gateway.akeyless.celo-networks-dev.org + access-id: p-kf9vjzruht6l + static-secrets: '{ + "/static-secrets/devops-circle/alfajores/op-testnet-alfajores/GS_ADMIN_PRIVATE_KEY":"GS_ADMIN_PRIVATE_KEY" + }' + + - name: "Checkout" + uses: actions/checkout@v4 + with: + token: ${{ env.PAT }} + submodules: recursive + fetch-depth: 0 + + - name: Setup + uses: ./.github/actions/setup + + - name: Generate config JSON + run: | + cd packages/contracts-bedrock + ./scripts/getting-started/config-vars-celo.sh + + - name: Deploy L1 contracts + if: ${{ env.DEPLOY_CONTRACTS != 'false' }} + run: | + export IMPL_SALT=$(openssl rand -hex 32) + cd packages/contracts-bedrock + echo "Broadcasting ..." + forge script scripts/Deploy.s.sol:Deploy --private-key $GS_ADMIN_PRIVATE_KEY --broadcast --rpc-url $L1_RPC_URL --legacy + + - name: Generate genesis files + run: | + mkdir -p l2-config-files/$DEPLOYMENT_CONTEXT + cd op-node + go run cmd/main.go genesis l2 \ + --deploy-config ../packages/contracts-bedrock/deploy-config/$DEPLOYMENT_CONTEXT.json \ + --l1-deployments ../packages/contracts-bedrock/deployments/$DEPLOYMENT_CONTEXT/.deploy \ + --outfile.l2 ../l2-config-files/$DEPLOYMENT_CONTEXT/genesis-$(date +%s).json \ + --outfile.rollup ../l2-config-files/$DEPLOYMENT_CONTEXT/rollup-$(date +%s).json \ + --l1-rpc $L1_RPC_URL + + - name: "Commit genesis files" + uses: stefanzweifel/git-auto-commit-action@v5 + with: + commit_message: '[Automatic] - Commit genesis files' + branch: alvarof2/contracts + file_pattern: 'l2-config-files packages/contracts-bedrock/**' diff --git a/.github/workflows/contracts-op-stack.yaml b/.github/workflows/contracts-op-stack.yaml new file mode 100644 index 0000000000000..7c7efb2409c08 --- /dev/null +++ b/.github/workflows/contracts-op-stack.yaml @@ -0,0 +1,117 @@ +name: Alfajores-Holesky Deploy OP-Stack L1 Contracts +on: + workflow_dispatch: + inputs: + deploy_contracts: + required: false + type: boolean + default: true + contracts_tag: + required: false + type: string + default: 'op-contracts/v1.3.0' + deployment_context: + required: false + type: string + default: 'test-alvaro' + l2_chain_id: + required: false + default: '42069' + +jobs: + deploy-contracts: + runs-on: ubuntu-latest + permissions: # Must change the job token permissions to use Akeyless JWT auth + id-token: write + contents: read + if: ${{ ! startsWith(github.triggering_actor, 'akeyless') }} + env: + DEPLOY_CONTRACTS: ${{ github.event_name == 'push' && 'true' || inputs.deploy_contracts }} + CONTRACTS_TAG: ${{ github.event_name == 'push' && 'op-contracts/v1.3.0' || inputs.contracts_tag }} + DEPLOYMENT_CONTEXT: ${{ github.event_name == 'push' && 'test' || inputs.deployment_context }} + L2_CHAIN_ID: ${{ github.event_name == 'push' && '42069' || inputs.l2_chain_id }} + L1_CHAIN_ID: '17000' # Holesky + L1_RPC_URL: 'https://ethereum-holesky-rpc.publicnode.com' + GS_ADMIN_ADDRESS: '0xb2397dF29AFB4B4661559436180019bEb7912985' + GS_BATCHER_ADDRESS: '0x7fDBe8F4D22ab511340667d7Ce5675568d09eBB4' + GS_PROPOSER_ADDRESS: '0xdCf30236Fa0aBE2ca0BEc2eE0a2F40b16A144DB3' + GS_SEQUENCER_ADDRESS: '0x3e2Df8efB6fA1d6E6021572a99BB67BA9ab2C59D' + steps: + + - name: "Get GitHub Token from Akeyless" + id: get_auth_token + uses: + docker://us-west1-docker.pkg.dev/devopsre/akeyless-public/akeyless-action:latest + with: + api-url: https://api.gateway.akeyless.celo-networks-dev.org + access-id: p-kf9vjzruht6l + dynamic-secrets: '{"/dynamic-secrets/keys/github/optimism/contents=write,pull_requests=write":"PAT"}' + + # "/static-secrets/devops-circle/alfajores/op-testnet-alfajores/HOLESKY_QUICKNODE_URL":"L1_RPC_URL", + - name: Akeyless get secrets + uses: docker://us-west1-docker.pkg.dev/devopsre/akeyless-public/akeyless-action:latest + with: + api-url: https://api.gateway.akeyless.celo-networks-dev.org + access-id: p-kf9vjzruht6l + static-secrets: '{ + "/static-secrets/devops-circle/alfajores/op-testnet-alfajores/GS_ADMIN_PRIVATE_KEY":"GS_ADMIN_PRIVATE_KEY" + }' + + - name: "Checkout" + uses: actions/checkout@v4 + with: + token: ${{ env.PAT }} + submodules: recursive + fetch-depth: 0 + + - name: "Checkout OP Repo" + uses: actions/checkout@v4 + with: + repository: 'ethereum-optimism/optimism' + ref: '${{ env.CONTRACTS_TAG }}' + path: ethereum-optimism + submodules: recursive + fetch-depth: 0 + + - name: Setup + uses: ./.github/actions/setup + + - name: Generate config JSON + run: | + cd packages/contracts-bedrock + ./scripts/getting-started/config-vars-op-stack.sh + cp deploy-config/$DEPLOYMENT_CONTEXT.json /home/runner/work/optimism/optimism/ethereum-optimism/packages/contracts-bedrock/deploy-config/$DEPLOYMENT_CONTEXT.json + + - name: Deploy L1 contracts + if: ${{ env.DEPLOY_CONTRACTS != 'false' }} + run: | + export IMPL_SALT=$(openssl rand -hex 32) + cd ethereum-optimism/packages/contracts-bedrock + echo "Broadcasting ..." + forge script scripts/Deploy.s.sol:Deploy --private-key $GS_ADMIN_PRIVATE_KEY --broadcast --rpc-url $L1_RPC_URL --legacy + mkdir -p /home/runner/work/optimism/optimism/packages/contracts-bedrock/deployments/$DEPLOYMENT_CONTEXT + cp deployments/$DEPLOYMENT_CONTEXT/.deploy /home/runner/work/optimism/optimism/packages/contracts-bedrock/deployments/$DEPLOYMENT_CONTEXT/.deploy + + - name: Copy old .deploy file if contracts not deployed + if: ${{ env.DEPLOY_CONTRACTS == 'false' }} + run: | + mkdir -p ethereum-optimism/packages/contracts-bedrock/deployments/$DEPLOYMENT_CONTEXT + cp packages/contracts-bedrock/deployments/$DEPLOYMENT_CONTEXT/.deploy ethereum-optimism/packages/contracts-bedrock/deployments/$DEPLOYMENT_CONTEXT/.deploy + + - name: Generate genesis files + run: | + mkdir -p l2-config-files/$DEPLOYMENT_CONTEXT + cd ethereum-optimism/op-node + go run cmd/main.go genesis l2 \ + --deploy-config ../packages/contracts-bedrock/deploy-config/$DEPLOYMENT_CONTEXT.json \ + --l1-deployments ../packages/contracts-bedrock/deployments/$DEPLOYMENT_CONTEXT/.deploy \ + --outfile.l2 ../../l2-config-files/$DEPLOYMENT_CONTEXT/genesis-$(date +%s).json \ + --outfile.rollup ../../l2-config-files/$DEPLOYMENT_CONTEXT/rollup-$(date +%s).json \ + --l1-rpc $L1_RPC_URL + + - name: "Commit genesis files" + uses: stefanzweifel/git-auto-commit-action@v5 + with: + commit_message: '[Automatic] - Commit genesis files' + branch: alvarof2/contracts + file_pattern: 'l2-config-files packages/contracts-bedrock/**' diff --git a/.github/workflows/docker-build-scan.yaml b/.github/workflows/docker-build-scan.yaml new file mode 100644 index 0000000000000..631cdb37b0c87 --- /dev/null +++ b/.github/workflows/docker-build-scan.yaml @@ -0,0 +1,50 @@ +name: Docker Build Scan +on: + pull_request: + branches: + - "master" + - "celo*" + workflow_dispatch: + +jobs: + detect-files-changed: + runs-on: ubuntu-latest + outputs: + files-changed: ${{ steps.detect-files-changed.outputs.all_changed_files }} + steps: + - uses: actions/checkout@v4 + - name: Detect files changed + id: detect-files-changed + uses: tj-actions/changed-files@v44 + with: + separator: ',' + + build-cel2-migration-tool: + runs-on: ubuntu-latest + needs: detect-files-changed + if: | + contains(needs.detect-files-changed.outputs.files-changed, 'op-chain-ops/cmd/celo-dbmigrate') || + contains(needs.detect-files-changed.outputs.files-changed, 'op-chain-ops/cmd/celo-migrate') || + contains(needs.detect-files-changed.outputs.files-changed, 'op-chain-ops/Dockerfile') + permissions: + contents: read + id-token: write + security-events: write + steps: + - uses: actions/checkout@v4 + - name: Login at GCP Artifact Registry + uses: celo-org/reusable-workflows/.github/actions/auth-gcp-artifact-registry@v2.0 + with: + workload-id-provider: 'projects/1094498259535/locations/global/workloadIdentityPools/gh-optimism/providers/github-by-repos' + service-account: 'celo-optimism-gh@devopsre.iam.gserviceaccount.com' + docker-gcp-registries: us-west1-docker.pkg.dev + - name: Build and push container + uses: celo-org/reusable-workflows/.github/actions/build-container@v2.0 + with: + platforms: linux/amd64 + registry: us-west1-docker.pkg.dev/devopsre/dev-images/cel2-migration-tool + tags: ${{ github.sha }} + context: ./ + dockerfile: ./op-chain-ops/Dockerfile + push: true + trivy: false diff --git a/.github/workflows/docker-op-ufm-build-push.yaml b/.github/workflows/docker-op-ufm-build-push.yaml new file mode 100644 index 0000000000000..e4a0ab33b1033 --- /dev/null +++ b/.github/workflows/docker-op-ufm-build-push.yaml @@ -0,0 +1,41 @@ +--- +name: Build op-ufm container and push to cLabs registry +on: + push: + branches: + - cel4 + paths: + # Run if any of the following files are changed + - 'op-ufm/**' + workflow_dispatch: + +jobs: + build: + runs-on: ['self-hosted', 'org', '8-cpu'] + permissions: # Required for workload identity auth and push the trivy results to GitHub + contents: read + id-token: write + security-events: write + steps: + + - name: Checkout + uses: actions/checkout@v4 + + - name: Authenticate to Google Cloud + uses: celo-org/reusable-workflows/.github/actions/auth-gcp-artifact-registry@main + with: + workload-id-provider: projects/1094498259535/locations/global/workloadIdentityPools/gh-optimism/providers/github-by-repos + service-account: celo-optimism-gh@devopsre.iam.gserviceaccount.com + access-token-lifetime: "60m" + docker-gcp-registries: us-west1-docker.pkg.dev + + - name: Build, push and scan the container + uses: celo-org/reusable-workflows/.github/actions/build-container@main + with: + platforms: linux/amd64 + registry: us-west1-docker.pkg.dev/devopsre/dev-images/op-ufm + tags: test + context: . + dockerfile: op-ufm/Dockerfile + push: true + trivy: false diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml new file mode 100644 index 0000000000000..7e26fa340725e --- /dev/null +++ b/.github/workflows/pages.yml @@ -0,0 +1,33 @@ +name: Build and publish forkdiff github-pages +permissions: + contents: write +on: + push: + branches: + - celo[0-9]+ +jobs: + deploy: + concurrency: ci-${{ github.ref }} + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 1000 # make sure to fetch the old commit we diff against + + - name: Build forkdiff + uses: "docker://protolambda/forkdiff:0.1.0" + with: + args: -repo=/github/workspace -fork=/github/workspace/fork.yaml -out=/github/workspace/index.html + + - name: Build pages + run: | + mkdir -p tmp/pages + mv index.html tmp/pages/index.html + touch tmp/pages/.nojekyll + + - name: Deploy + uses: JamesIves/github-pages-deploy-action@v4 + with: + folder: tmp/pages + clean: true diff --git a/.gitignore b/.gitignore index 54c16a1f67c3f..16640801b3436 100644 --- a/.gitignore +++ b/.gitignore @@ -47,3 +47,6 @@ __pycache__ # Ignore echidna artifacts crytic-export + +# vscode +.vscode/ diff --git a/cel2-testnet/.envrc b/cel2-testnet/.envrc new file mode 100644 index 0000000000000..e5db1f8ee5e01 --- /dev/null +++ b/cel2-testnet/.envrc @@ -0,0 +1,26 @@ +export DEPLOYMENT_CONTEXT=cel2-testnet +export IMPL_SALT=$(openssl rand -hex 32) + +export L1_RPC=https://rpc-sepolia.rockx.com/ +export L1_RPC_KIND=any + +# The following keys shoud not be visible in a public repo and have therefore +# been moved to a separate file which is not added to git. +[ -f .env.secret ] && source .env.secret +# export ADMIN_PRIVKEY=... +# export PROPOSER_PRIVKEY=... +# export BATCHER_PRIVKEY=... +# export SEQUENCER_PRIVKEY=... +# export L1_RPC=https://eth-sepolia.g.alchemy.com/v2/... +# export L1_RPC_KIND=alchemy + +export ADMIN_ADDR=0x240B9A578bD7E82F4a078FB9609e99d6E3e0e5e3 +export PROPOSER_ADDR=0x6F0fF8717D1a5C1c0519Dd4Fc78915DfdA47A54F +export BATCHER_ADDR=0x7C308BC6B7C11443c8BD5557B4D40c25a4650310 +export SEQUENCER_ADDR=0x4f90ca6e2dD0FbBceC02F86dF69f0d6633cd2533 + +export L1_BRIDGE=0x0000000000000000000000000000000000000000 +export L2OO_ADDRESS=0x797131CD2bBcE80Ac7D1A4Fea63d2fc42cbEDd40 +export SEQUENCER_BATCH_INBOX_ADDRESS=0xff00000000000000000000000000000000042070 + +export MONITORING_SLACK_CHANNEL=C04PCPVQJLE diff --git a/cel2-testnet/README.md b/cel2-testnet/README.md new file mode 100644 index 0000000000000..f9eda33968d9a --- /dev/null +++ b/cel2-testnet/README.md @@ -0,0 +1,31 @@ +# CEL2 Testnet Setup + +Use [direnv](https://direnv.net/) or source `.envrc` to load all required environment variables. [Get the private keys via AKeyless](https://ui.gateway.akeyless.celo-networks-dev.org/items?id=215198970&name=%2Fstatic-secrets%2Fblockchain-circle%2FCel2+testnet+%60.env.secrets%60file+with+wallet+private+keys) and save file in this directory as `.env.secret`. + +## Initial Setup + +These steps should only be done once when creating a new testnet. The scripts in `initial_setup` are used to create the files in `generated`. Steps: + +* Edit .envrc and cel2-testnet.json as necessary. +* Run `initial_setup/deploy_contracts.sh`. +* Run `initial_setup/mk_l2config.sh`. + +If there have been many changes to upstream Optimism that make the old cel2-testnet.json obsolete, you can start from scratch by running `initial_setup/recreate_config.sh`. + +## Running the Testnet + +This assumes that you run the full testnet setup including the sequencer on our GCP instance. If you want to run a second node, you will have to turn off the sequencer and might have to do further adjustments. + +* Copy the `cel2-testnet` directory to the GCP instance `gcloud compute scp --zone "us-west1-b" --project "blockchaintestsglobaltestnet" --recurse ../cel2-testnet/ l2-celo-dev:/home/cel2` +* Build the docker images with `build_images.sh`. +* Upload the images to the remote docker instance with `upload-images.sh`. +* SSH into the remote host and start the docker-compose setup. `gcloud compute ssh --zone "us-west1-b" "l2-celo-dev" --project "blockchaintestsglobaltestnet" -- 'cd /home/cel2/cel2-testnet && ./run_docker.sh'` + +## Monitoring + +The `monitoring.sh` script will check the testnet health and submit messages via Slack if problems are detected. I recommend running it once per day. To do that via cron, execute +``` +sudo bash -c 'echo -e "#!/bin/bash\n/home/cel2/cel2-testnet/monitoring.sh >>/var/log/cel2_monitoring 2>&1" \ + > /etc/cron.daily/cel2_monitoring' +sudo chmod u+x /etc/cron.daily/cel2_monitoring +``` diff --git a/cel2-testnet/balances.sh b/cel2-testnet/balances.sh new file mode 100755 index 0000000000000..9e9f40e6b0b90 --- /dev/null +++ b/cel2-testnet/balances.sh @@ -0,0 +1,28 @@ +#!/bin/bash +# +# Check L1 account balances and return with exit code 1 if at least one balance +# is below min_balance. +set -eo pipefail + +SCRIPT_DIR=$(readlink -f $(dirname $0)) +cd $SCRIPT_DIR +source .envrc + +min_balance=4.0 +exit_code=0 + +# SEQUENCER does not need any balance +for wallet in ADMIN PROPOSER BATCHER +do + varname=${wallet}_ADDR + printf "%-12s" ${wallet}: + balance=$(cast balance -r $L1_RPC --ether ${!varname}) + echo $balance + if [ $wallet != ADMIN ] && (( $(echo "$balance < $min_balance" | bc -l) )) + then + echo $wallet BALANCE LOW, send funds to ${!varname}! + exit_code=1 + fi +done + +exit $exit_code diff --git a/cel2-testnet/build_images.sh b/cel2-testnet/build_images.sh new file mode 100755 index 0000000000000..2cb78459bb21c --- /dev/null +++ b/cel2-testnet/build_images.sh @@ -0,0 +1,6 @@ +#!/bin/bash +set -eo pipefail + +cd docker +export PATH=$(go1.19 env GOROOT)/bin:$PATH +DOCKER_DEFAULT_PLATFORM=linux/amd64 docker-compose build --progress plain diff --git a/cel2-testnet/cel2-testnet.json b/cel2-testnet/cel2-testnet.json new file mode 100644 index 0000000000000..8fdbfd3f007e5 --- /dev/null +++ b/cel2-testnet/cel2-testnet.json @@ -0,0 +1,79 @@ +{ + "l1StartingBlockTag": "0xd60599eb30a1a88808d2b3719e996e16f0fc2ad8b5a44237a3bf86a3e9d73bfa", + + "l1ChainID": 11155111, + "l2ChainID": 22242220, + "l2BlockTime": 5, + "l1BlockTime": 12, + + "maxSequencerDrift": 600, + "sequencerWindowSize": 3600, + "channelTimeout": 300, + + "p2pSequencerAddress": "$SEQUENCER_ADDR", + "batchInboxAddress": "0xff00000000000000000000000000000000042069", + "batchSenderAddress": "$BATCHER_ADDR", + + "l2OutputOracleSubmissionInterval": 120, + "l2OutputOracleStartingBlockNumber": 0, + "l2OutputOracleStartingTimestamp": 1708342368, + + "l2OutputOracleProposer": "$PROPOSER_ADDR", + "l2OutputOracleChallenger": "$ADMIN_ADDR", + + "finalizationPeriodSeconds": 12, + + "proxyAdminOwner": "$ADMIN_ADDR", + "baseFeeVaultRecipient": "$ADMIN_ADDR", + "l1FeeVaultRecipient": "$ADMIN_ADDR", + "sequencerFeeVaultRecipient": "$ADMIN_ADDR", + "finalSystemOwner": "$ADMIN_ADDR", + "superchainConfigGuardian": "$ADMIN_ADDR", + + "baseFeeVaultMinimumWithdrawalAmount": "0x8ac7230489e80000", + "l1FeeVaultMinimumWithdrawalAmount": "0x8ac7230489e80000", + "sequencerFeeVaultMinimumWithdrawalAmount": "0x8ac7230489e80000", + "baseFeeVaultWithdrawalNetwork": 0, + "l1FeeVaultWithdrawalNetwork": 0, + "sequencerFeeVaultWithdrawalNetwork": 0, + + "gasPriceOracleOverhead": 2100, + "gasPriceOracleScalar": 1000000, + + "enableGovernance": true, + "governanceTokenSymbol": "OP", + "governanceTokenName": "Optimism", + "governanceTokenOwner": "$ADMIN_ADDR", + + "l2GenesisBlockGasLimit": "0x1c9c380", + "l2GenesisBlockBaseFeePerGas": "0x3b9aca00", + "l2GenesisRegolithTimeOffset": "0x0", + + "eip1559Denominator": 50, + "eip1559DenominatorCanyon": 250, + "eip1559Elasticity": 6, + + "l2GenesisDeltaTimeOffset": null, + "l2GenesisCanyonTimeOffset": "0x0", + + "systemConfigStartBlock": 0, + + "requiredProtocolVersion": "0x0000000000000000000000000000000000000000000000000000000000000000", + "recommendedProtocolVersion": "0x0000000000000000000000000000000000000000000000000000000000000000", + + "faultGameAbsolutePrestate": "0x03c7ae758795765c6664a5d39bf63841c71ff191e9189522bad8ebff5d4eca98", + "faultGameMaxDepth": 44, + "faultGameMaxDuration": 1200, + "faultGameGenesisBlock": 0, + "faultGameGenesisOutputRoot": "0x0000000000000000000000000000000000000000000000000000000000000000", + "faultGameSplitDepth": 14, + + "preimageOracleMinProposalSize": 1800000, + "preimageOracleChallengePeriod": 86400, + "preimageOracleCancunActivationTimestamp": 0, + + "proofMaturityDelaySeconds": 12, + "disputeGameFinalityDelaySeconds": 6, + "respectedGameType": 0, + "useFaultProofs": false +} diff --git a/cel2-testnet/docker/docker-compose.yml b/cel2-testnet/docker/docker-compose.yml new file mode 100644 index 0000000000000..a93b5802b13da --- /dev/null +++ b/cel2-testnet/docker/docker-compose.yml @@ -0,0 +1,144 @@ +version: '3.4' + +# The volumes below mount the configs generated by the script into each +# service. + +volumes: + l1_data: + l2_data: + op_log: + + +services: + + l2: + image: celo-testnet-ops-l2 + build: + context: ${PWD}/../../ops-bedrock/ + dockerfile: ${PWD}/../../ops-bedrock/Dockerfile.l2 + ports: + - "9545:8545" + - "8060:6060" + volumes: + - "l2_data:/db" + - "${PWD}/../generated/genesis-l2.json:/genesis.json" + - "${PWD}/../generated/jwt-secret.txt:/config/jwt-secret.txt" + entrypoint: # pass the L2 specific flags by overriding the entry-point and adding extra arguments + - "/bin/sh" + - "/entrypoint.sh" + - "--authrpc.jwtsecret=/config/jwt-secret.txt" + + op-node: + depends_on: + - l2 + image: celo-testnet-ops-op-node + build: + context: ${PWD}/../.. + dockerfile: ${PWD}/../../op-node/Dockerfile + command: > + op-node + --l1=${L1_RPC} + --l1.rpckind=alchemy + --l2=http://l2:8551 + --l2.jwt-secret=/config/jwt-secret.txt + --sequencer.enabled + --sequencer.l1-confs=0 + --verifier.l1-confs=1 + --p2p.sequencer.key=${SEQUENCER_PRIVKEY} + --rollup.config=/rollup.json + --rpc.addr=0.0.0.0 + --rpc.port=8545 + --p2p.listen.ip=0.0.0.0 + --p2p.listen.tcp=9003 + --p2p.listen.udp=9003 + --p2p.scoring.peers=light + --p2p.ban.peers=true + --snapshotlog.file=/op_log/snapshot.log + --p2p.priv.path=/config/p2p-node-key.txt + --metrics.enabled + --metrics.addr=0.0.0.0 + --metrics.port=7300 + --pprof.enabled + --rpc.enable-admin + ports: + - "7545:8545" + - "9003:9003" + - "7300:7300" + - "6060:6060" + volumes: + # - "${PWD}/p2p-node-key.txt:/config/p2p-node-key.txt" # Will be generated on start + - "${PWD}/../generated/jwt-secret.txt:/config/jwt-secret.txt" + - "${PWD}/../generated/rollup.json:/rollup.json" + - op_log:/op_log + + op-proposer: + depends_on: + - l2 + - op-node + image: celo-testnet-ops-op-proposer + build: + context: ${PWD}/../.. + dockerfile: ${PWD}/../../op-proposer/Dockerfile + ports: + - "6062:6060" + - "7302:7300" + environment: + OP_PROPOSER_L1_ETH_RPC: ${L1_RPC} + OP_PROPOSER_ROLLUP_RPC: http://op-node:8545 + OP_PROPOSER_POLL_INTERVAL: 1s + OP_PROPOSER_NUM_CONFIRMATIONS: 1 + OP_PROPOSER_PRIVATE_KEY: "${PROPOSER_PRIVKEY}" + OP_PROPOSER_L2OO_ADDRESS: "${L2OO_ADDRESS}" + OP_PROPOSER_PPROF_ENABLED: "true" + OP_PROPOSER_METRICS_ENABLED: "true" + OP_PROPOSER_ALLOW_NON_FINALIZED: "true" + + op-batcher: + depends_on: + - l2 + - op-node + image: celo-testnet-ops-op-batcher + build: + context: ${PWD}/../.. + dockerfile: ${PWD}/../../op-batcher/Dockerfile + ports: + - "6061:6060" + - "7301:7300" + - "6545:8545" + environment: + OP_BATCHER_L1_ETH_RPC: ${L1_RPC} + OP_BATCHER_L2_ETH_RPC: http://l2:8545 + OP_BATCHER_ROLLUP_RPC: http://op-node:8545 + OP_BATCHER_MAX_CHANNEL_DURATION: 1 + OP_BATCHER_SUB_SAFETY_MARGIN: 4 # SWS is 15, ChannelTimeout is 40 + OP_BATCHER_POLL_INTERVAL: 1s + OP_BATCHER_NUM_CONFIRMATIONS: 1 + OP_BATCHER_PRIVATE_KEY: "${BATCHER_PRIVKEY}" + OP_BATCHER_PPROF_ENABLED: "true" + OP_BATCHER_METRICS_ENABLED: "true" + OP_BATCHER_RPC_ENABLE_ADMIN: "true" + + # artifact-server: + # depends_on: + # - l1 + # image: nginx:1.25-alpine + # ports: + # - "8080:80" + # volumes: + # - "${PWD}/../.testnet/:/usr/share/nginx/html/:ro" + # security_opt: + # - "no-new-privileges:true" + + # stateviz: + # build: + # context: ${PWD}/../.. + # dockerfile: ${PWD}/../../ops-bedrock/Dockerfile.stateviz + # command: + # - stateviz + # - -addr=0.0.0.0:8080 + # - -snapshot=/op_log/snapshot.log + # - -refresh=10s + # ports: + # - "9090:8080" + # volumes: + # - op_log:/op_log:ro diff --git a/cel2-testnet/initial_setup/deploy_contracts.sh b/cel2-testnet/initial_setup/deploy_contracts.sh new file mode 100755 index 0000000000000..146ad4df6f8a0 --- /dev/null +++ b/cel2-testnet/initial_setup/deploy_contracts.sh @@ -0,0 +1,15 @@ +#!/bin/bash +set -euo pipefail + +# Following the OP-stack tutorial, steps +# https://stack.optimism.io/docs/build/getting-started/#configure-your-network +# https://stack.optimism.io/docs/build/getting-started/#deploy-the-l1-contracts + +TESTNET_DIR=$(readlink -f $(dirname $0))/.. +cd $TESTNET_DIR +<$DEPLOYMENT_CONTEXT.json envsubst > ../packages/contracts-bedrock/deploy-config/$DEPLOYMENT_CONTEXT.json +cd ../packages/contracts-bedrock + +forge script scripts/Deploy.s.sol:Deploy --private-key $ADMIN_PRIVKEY --broadcast --rpc-url $L1_RPC && \ +forge script scripts/Deploy.s.sol:Deploy --sig 'sync()' --private-key $ADMIN_PRIVKEY --broadcast --rpc-url $L1_RPC \ +| tee deploy.log diff --git a/cel2-testnet/initial_setup/mk_l2config.sh b/cel2-testnet/initial_setup/mk_l2config.sh new file mode 100755 index 0000000000000..c1c969dbee2da --- /dev/null +++ b/cel2-testnet/initial_setup/mk_l2config.sh @@ -0,0 +1,17 @@ +#!/bin/bash +set -euo pipefail + +# Following the OP-stack tutorial, steps +# https://stack.optimism.io/docs/build/getting-started/#generate-the-l2-config-files + +export PATH=$(go1.19 env GOROOT)/bin:$PATH + +TESTNET_DIR=$(readlink -f $(dirname $0))/.. + +cd $TESTNET_DIR/../op-node +go run cmd/main.go genesis l2 \ + --deploy-config ../packages/contracts-bedrock/deploy-config/$DEPLOYMENT_CONTEXT.json \ + --deployment-dir ../packages/contracts-bedrock/deployments/$DEPLOYMENT_CONTEXT/ \ + --outfile.l2 $TESTNET_DIR/generated/genesis-l2.json \ + --outfile.rollup $TESTNET_DIR/generated/rollup.json \ + --l1-rpc $L1_RPC diff --git a/cel2-testnet/initial_setup/recreate_config.sh b/cel2-testnet/initial_setup/recreate_config.sh new file mode 100755 index 0000000000000..4168fcf4d3144 --- /dev/null +++ b/cel2-testnet/initial_setup/recreate_config.sh @@ -0,0 +1,12 @@ +#!/bin/bash +set -euo pipefail +set -x + +TESTNET_DIR=$(readlink -f $(dirname $0))/.. +cd "$TESTNET_DIR/.." + +( + cd packages/contracts-bedrock/ && + GS_ADMIN_ADDRESS='$ADMIN_ADDR' GS_BATCHER_ADDRESS='$BATCHER_ADDR' GS_PROPOSER_ADDRESS='$PROPOSER_ADDR' GS_SEQUENCER_ADDRESS='$SEQUENCER_ADDR' L1_RPC_URL=$L1_RPC scripts/getting-started/config.sh +) +cp packages/contracts-bedrock/deploy-config/getting-started.json cel2-testnet/cel2-testnet.json diff --git a/cel2-testnet/monitoring.sh b/cel2-testnet/monitoring.sh new file mode 100755 index 0000000000000..8f8f2817feccf --- /dev/null +++ b/cel2-testnet/monitoring.sh @@ -0,0 +1,37 @@ +#!/bin/bash +set -euo pipefail + +function send_message { + local msg="$1" + curl https://slack.com/api/chat.postMessage \ + -X POST -H 'Content-type: application/json' \ + -H "Authorization: Bearer $MONITORING_SLACK_SECRET" \ + --data '{"channel": "'"$MONITORING_SLACK_CHANNEL"'", "text": "'"$msg"'"}' +} + +SCRIPT_DIR=$(readlink -f $(dirname $0)) +cd $SCRIPT_DIR +source .envrc + + +### Balance check ### +balance_text=$($SCRIPT_DIR/balances.sh) \ + || send_message "*Cel2 Sepolia wallet balances*\n\`\`\`$balance_text\`\`\`" + + +### Alchemy API check ### +ALCHEMY_HTTP_CODE=$( + curl $L1_RPC -sS -o /dev/null -w "%{http_code}" \ + -X POST \ + -H "Content-Type: application/json" \ + --data '{"jsonrpc":"2.0","method":"web3_clientVersion","params":[],"id":1}') + +# 403 means out of quota, see https://docs.alchemy.com/reference/error-reference +if [ $ALCHEMY_HTTP_CODE = 403 ] +then + send_message "*WARNING*: The cel2 testnet is out of alchemy quota, leading to a stall of the network." +fi + + +### End notice to verify monitoring works when no warnings are generated ### +echo Monitoring finished at $(date) diff --git a/cel2-testnet/run_docker.sh b/cel2-testnet/run_docker.sh new file mode 100755 index 0000000000000..deea14aabe6ce --- /dev/null +++ b/cel2-testnet/run_docker.sh @@ -0,0 +1,13 @@ +#!/bin/bash +set -eo pipefail + +source .envrc + +# Create JWT if it does not exist, yet. +[ -f generated/jwt-secret.txt ] || openssl rand -hex 32 > generated/jwt-secret.txt + +PROJECT=cel2-testnet +cd docker +docker-compose -p $PROJECT up -d --no-build op-node +sleep 4 +docker-compose -p $PROJECT up -d --no-build op-proposer op-batcher diff --git a/cel2-testnet/upload-images.sh b/cel2-testnet/upload-images.sh new file mode 100755 index 0000000000000..8ad0e75c46dc6 --- /dev/null +++ b/cel2-testnet/upload-images.sh @@ -0,0 +1,9 @@ +#!/bin/bash +set -eo pipefail + +# Save all images into a tar file +mkdir -p tmp +docker save $(docker image ls | awk '/celo-testnet/ {print $1}') > tmp/celo-testnet-images.tgz +# Upload tar to gcp instance +gcloud compute scp --zone "us-west1-b" --project "blockchaintestsglobaltestnet" tmp/celo-testnet-images.tgz l2-celo-dev:~ +gcloud compute ssh --zone "us-west1-b" "l2-celo-dev" --project "blockchaintestsglobaltestnet" --command "docker -H unix:///run/docker.sock load OP optimism forkdiff" +footer: | + Fork-diff overview of changes made in [Celo's `optimism`](https://github.com/celo-org/optimism), + a fork of [Optimism's `optimism`](https://github.com/ethereum-optimism/optimism). + +base: + name: OP + url: https://github.com/ethereum-optimism/optimism + hash: 482633768be731462c7aa9b32b8eafb74289ad2e # tracks the last rebased commit +fork: + name: CELO + url: https://github.com/celo-org/optimism + ref: HEAD +def: + title: "Celo's optimism" + description: | + This is an overview of the changes in [Celo's `optimism` implementation](https://github.com/celo-org/optimism), + a fork of [Optimism's `optimism`](https://github.com/ethereum-optimism/optimism). + + Changes are currently separated by sub-package or component. Check out the [README](https://github.com/celo-org/optimism/blob/develop/README.md) + for more details about each of these components and packages. + + sub: + - title: "packages/*" + description: "" + sub: + - title: "common-ts" + description: "" + globs: + - "packages/common-ts/*" + - title: "contracts-bedrock" + description: "" + globs: + - "packages/contracts-bedrock/*" + - "packages/contracts-bedrock/*/*" + - "packages/contracts-bedrock/*/*/*" + - title: "core-utils" + description: "" + globs: + - "packages/core-utils/*" + - title: "chain-mon" + description: "" + globs: + - "packages/chain-mon/*" + - title: "sdk" + description: "" + globs: + - "packages/sdk/*" + - title: "op-bindings" + description: "" + globs: + - "op-bindings/*" + - "op-bindings/*/*" + - title: "op-batcher" + description: "" + globs: + - "op-batcher/*" + - title: "op-bootnode" + description: "" + globs: + - "op-bootnode/*" + - title: "op-chain-ops" + description: "" + globs: + - "op-chain-ops/*" + - "op-chain-ops/*/*" + - "op-chain-ops/*/*/*" + - title: "op-challenger" + description: "" + globs: + - "op-challenger/*" + - "op-challenger/*/*" + - "op-challenger/*/*/*" + - title: "op-e2e" + description: "" + globs: + - "op-e2e/*" + - "op-e2e/*/*" + - "op-e2e/*/*/*" + - "op-e2e/*/*/*/*" + + - title: "op-exporter" + description: "" + globs: + - "op-exporter/*" + - title: "op-heartbeat" + description: "" + globs: + - "op-heartbeat/*" + - title: "op-node" + description: "" + globs: + - "op-node/*" + - "op-node/*/*" + - "op-node/*/*/*" + - title: "op-program" + description: "" + globs: + - "op-program/*" + - title: "op-proposer" + description: "" + globs: + - "op-proposer/*" + - title: "op-service" + description: "" + globs: + - "op-service/*" + - title: "op-signer" + description: "" + globs: + - "op-signer/*" + - title: "op-wheel" + description: "" + globs: + - "op-wheel/*" + - title: "ops-bedrock" + description: "" + globs: + - "ops-bedrock/*" + - title: "proxyd" + description: "" + globs: + - "proxyd/*" + - title: "specs" + description: "" + globs: + - "specs/*" + - title: "indexer" + description: "" + globs: + - "indexer/*" + - "indexer/*/*" + - "indexer/*/*/*" + +# ignored globally, does not count towards line count +ignore: + - ".circleci/*" + - "*.sum" + - "go.mod" + - "fork.yaml" + - ".github/workflows/*" + - ".changeset/*" + - ".github/*" + - "CONTRIBUTING.md" + - "pnpm-lock.yaml" diff --git a/go.mod b/go.mod index b9a89cc086551..708213d2a2f24 100644 --- a/go.mod +++ b/go.mod @@ -226,7 +226,8 @@ require ( rsc.io/tmplfunc v0.0.3 // indirect ) -replace github.com/ethereum/go-ethereum => github.com/ethereum-optimism/op-geth v1.101315.2-rc.1 +// replace github.com/ethereum/go-ethereum => github.com/celo-org/op-geth v0.0.0-20240612131021-96a2a76ceaaa +replace github.com/ethereum/go-ethereum => github.com/celo-org/op-geth v0.0.0-20240612164035-ef9b9a19f53e //replace github.com/ethereum/go-ethereum v1.13.9 => ../op-geth diff --git a/go.sum b/go.sum index 0c66f61d48ae3..07804c7711ac2 100644 --- a/go.sum +++ b/go.sum @@ -77,6 +77,8 @@ github.com/btcsuite/snappy-go v1.0.0/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY= github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s= +github.com/celo-org/op-geth v0.0.0-20240612164035-ef9b9a19f53e h1:7KzqBAm9YmhrdtUG4dssU77za1ytBRuLIoqnzzJoBw8= +github.com/celo-org/op-geth v0.0.0-20240612164035-ef9b9a19f53e/go.mod h1:vObZmT4rKd8hjSblIktsJHtLX8SXbCoaIXEd42HMDB0= github.com/cespare/cp v0.1.0 h1:SE+dxFebS7Iik5LK0tsi1k9ZCxEaFX4AjQmoyA+1dJk= github.com/cespare/cp v0.1.0/go.mod h1:SOGHArjBr4JWaSDEVpWpo/hNg6RoKrls6Oh40hiwW+s= github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= @@ -170,8 +172,6 @@ github.com/elastic/gosigar v0.14.2 h1:Dg80n8cr90OZ7x+bAax/QjoW/XqTI11RmA79ZwIm9/ github.com/elastic/gosigar v0.14.2/go.mod h1:iXRIGg2tLnu7LBdpqzyQfGDEidKCfWcCMS0WKyPWoMs= github.com/ethereum-optimism/go-ethereum-hdwallet v0.1.3 h1:RWHKLhCrQThMfch+QJ1Z8veEq5ZO3DfIhZ7xgRP9WTc= github.com/ethereum-optimism/go-ethereum-hdwallet v0.1.3/go.mod h1:QziizLAiF0KqyLdNJYD7O5cpDlaFMNZzlxYNcWsJUxs= -github.com/ethereum-optimism/op-geth v1.101315.2-rc.1 h1:9sNukA27AqReNkBEaus2kf+DLNXgkksRj+NbJHYgqxc= -github.com/ethereum-optimism/op-geth v1.101315.2-rc.1/go.mod h1:vObZmT4rKd8hjSblIktsJHtLX8SXbCoaIXEd42HMDB0= github.com/ethereum-optimism/superchain-registry/superchain v0.0.0-20240610174713-583ceb57407d h1:lGvrOam2IOoszfxqrpXeIFIT58/e6N1VuOLVaai9GOg= github.com/ethereum-optimism/superchain-registry/superchain v0.0.0-20240610174713-583ceb57407d/go.mod h1:7xh2awFQqsiZxFrHKTgEd+InVfDRrkKVUIuK8SAFHp0= github.com/ethereum/c-kzg-4844 v0.4.0 h1:3MS1s4JtA868KpJxroZoepdV0ZKBp3u/O5HcZ7R3nlY= diff --git a/op-batcher/batcher/driver.go b/op-batcher/batcher/driver.go index 8d8e73bee2b4a..7585a94decad0 100644 --- a/op-batcher/batcher/driver.go +++ b/op-batcher/batcher/driver.go @@ -515,7 +515,7 @@ func (l *BatchSubmitter) sendTransaction(ctx context.Context, txdata txData, que candidate = l.calldataTxCandidate(data) } - intrinsicGas, err := core.IntrinsicGas(candidate.TxData, nil, false, true, true, false) + intrinsicGas, err := core.IntrinsicGas(candidate.TxData, nil, false, true, true, false, nil) if err != nil { // we log instead of return an error here because txmgr can do its own gas estimation l.Log.Error("Failed to calculate intrinsic gas", "err", err) diff --git a/op-chain-ops/Dockerfile b/op-chain-ops/Dockerfile new file mode 100644 index 0000000000000..7ab849471454d --- /dev/null +++ b/op-chain-ops/Dockerfile @@ -0,0 +1,30 @@ +FROM golang:1.21.1-alpine3.18 as builder + +RUN apk --no-cache add make + +COPY ./go.mod /app/go.mod +COPY ./go.sum /app/go.sum + +WORKDIR /app + +RUN go mod download + +COPY ./op-bindings /app/op-bindings +COPY ./op-service /app/op-service +COPY ./op-node /app/op-node +COPY ./op-chain-ops /app/op-chain-ops +WORKDIR /app/op-chain-ops +RUN make celo-dbmigrate celo-migrate + +FROM alpine:3.18 +RUN apk --no-cache add ca-certificates bash rsync + +# RUN addgroup -S app && adduser -S app -G app +# USER app +WORKDIR /app + +COPY --from=builder /app/op-chain-ops/bin/celo-dbmigrate /app +COPY --from=builder /app/op-chain-ops/bin/celo-migrate /app +ENV PATH="/app:${PATH}" + +ENTRYPOINT ["/app/celo-dbmigrate"] diff --git a/op-chain-ops/Makefile b/op-chain-ops/Makefile index 1808807c73781..eeda9b0b597d9 100644 --- a/op-chain-ops/Makefile +++ b/op-chain-ops/Makefile @@ -15,6 +15,12 @@ receipt-reference-builder: op-upgrade: go build -o ./bin/op-upgrade ./cmd/op-upgrade/main.go +celo-dbmigrate: + go build -o ./bin/celo-dbmigrate ./cmd/celo-dbmigrate/*.go + +celo-migrate: + go build -o ./bin/celo-migrate ./cmd/celo-migrate/*.go + test: go test ./... diff --git a/op-chain-ops/cmd/celo-dbmigrate/ancients.go b/op-chain-ops/cmd/celo-dbmigrate/ancients.go new file mode 100644 index 0000000000000..10f3ade5dfd17 --- /dev/null +++ b/op-chain-ops/cmd/celo-dbmigrate/ancients.go @@ -0,0 +1,191 @@ +package main + +import ( + "context" + "fmt" + "path/filepath" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/log" + "golang.org/x/sync/errgroup" +) + +// RLPBlockRange is a range of blocks in RLP format +type RLPBlockRange struct { + start uint64 + hashes [][]byte + headers [][]byte + bodies [][]byte + receipts [][]byte + tds [][]byte +} + +func migrateAncientsDb(oldDBPath, newDBPath string, batchSize uint64) (uint64, error) { + oldFreezer, err := rawdb.NewChainFreezer(filepath.Join(oldDBPath, "ancient"), "", false) // TODO can't be readonly because we need the .meta files to be created + if err != nil { + return 0, fmt.Errorf("failed to open old freezer: %v", err) + } + defer oldFreezer.Close() + + newFreezer, err := rawdb.NewChainFreezer(filepath.Join(newDBPath, "ancient"), "", false) + if err != nil { + return 0, fmt.Errorf("failed to open new freezer: %v", err) + } + defer newFreezer.Close() + + numAncientsOld, err := oldFreezer.Ancients() + if err != nil { + return 0, fmt.Errorf("failed to get number of ancients in old freezer: %v", err) + } + + numAncientsNew, err := newFreezer.Ancients() + if err != nil { + return 0, fmt.Errorf("failed to get number of ancients in new freezer: %v", err) + } + + log.Info("Migration Started", "process", "ancients migration", "startBlock", numAncientsNew, "endBlock", numAncientsOld, "count", numAncientsOld-numAncientsNew+1) + g, ctx := errgroup.WithContext(context.Background()) + readChan := make(chan RLPBlockRange, 10) + transformChan := make(chan RLPBlockRange, 10) + + log.Info("Migrating data", "start", numAncientsNew, "end", numAncientsOld, "step", batchSize) + + g.Go(func() error { + return readAncientBlocks(ctx, oldFreezer, numAncientsNew, numAncientsOld, batchSize, readChan) + }) + g.Go(func() error { return transformBlocks(ctx, readChan, transformChan) }) + g.Go(func() error { return writeAncientBlocks(ctx, newFreezer, transformChan) }) + + if err = g.Wait(); err != nil { + return 0, fmt.Errorf("failed to migrate ancients: %v", err) + } + + numAncientsNew, err = newFreezer.Ancients() + if err != nil { + return 0, fmt.Errorf("failed to get number of ancients in new freezer: %v", err) + } + + log.Info("Migration End", "process", "ancients migration", "totalBlocks", numAncientsNew) + return numAncientsNew, nil +} + +func readAncientBlocks(ctx context.Context, freezer *rawdb.Freezer, startBlock, endBlock, batchSize uint64, out chan<- RLPBlockRange) error { + defer close(out) + + for i := startBlock; i < endBlock; i += batchSize { + select { + case <-ctx.Done(): + return ctx.Err() + default: + count := min(batchSize, endBlock-i+1) + start := i + + blockRange := RLPBlockRange{ + start: start, + hashes: make([][]byte, count), + headers: make([][]byte, count), + bodies: make([][]byte, count), + receipts: make([][]byte, count), + tds: make([][]byte, count), + } + var err error + + blockRange.hashes, err = freezer.AncientRange(rawdb.ChainFreezerHashTable, start, count, 0) + if err != nil { + return fmt.Errorf("failed to read hashes from old freezer: %v", err) + } + blockRange.headers, err = freezer.AncientRange(rawdb.ChainFreezerHeaderTable, start, count, 0) + if err != nil { + return fmt.Errorf("failed to read headers from old freezer: %v", err) + } + blockRange.bodies, err = freezer.AncientRange(rawdb.ChainFreezerBodiesTable, start, count, 0) + if err != nil { + return fmt.Errorf("failed to read bodies from old freezer: %v", err) + } + blockRange.receipts, err = freezer.AncientRange(rawdb.ChainFreezerReceiptTable, start, count, 0) + if err != nil { + return fmt.Errorf("failed to read receipts from old freezer: %v", err) + } + blockRange.tds, err = freezer.AncientRange(rawdb.ChainFreezerDifficultyTable, start, count, 0) + if err != nil { + return fmt.Errorf("failed to read tds from old freezer: %v", err) + } + + out <- blockRange + } + } + return nil +} + +func transformBlocks(ctx context.Context, in <-chan RLPBlockRange, out chan<- RLPBlockRange) error { + // Transform blocks from the in channel and send them to the out channel + defer close(out) + for blockRange := range in { + select { + case <-ctx.Done(): + return ctx.Err() + default: + for i := range blockRange.hashes { + blockNumber := blockRange.start + uint64(i) + + newHeader, err := transformHeader(blockRange.headers[i]) + if err != nil { + return fmt.Errorf("can't transform header: %v", err) + } + newBody, err := transformBlockBody(blockRange.bodies[i]) + if err != nil { + return fmt.Errorf("can't transform body: %v", err) + } + + if yes, newHash := hasSameHash(newHeader, blockRange.hashes[i]); !yes { + log.Error("Hash mismatch", "block", blockNumber, "oldHash", common.BytesToHash(blockRange.hashes[i]), "newHash", newHash) + return fmt.Errorf("hash mismatch at block %d", blockNumber) + } + + blockRange.headers[i] = newHeader + blockRange.bodies[i] = newBody + } + out <- blockRange + } + } + return nil +} + +func writeAncientBlocks(ctx context.Context, freezer *rawdb.Freezer, in <-chan RLPBlockRange) error { + // Write blocks from the in channel to the newDb + for blockRange := range in { + select { + case <-ctx.Done(): + return ctx.Err() + default: + _, err := freezer.ModifyAncients(func(aWriter ethdb.AncientWriteOp) error { + for i := range blockRange.hashes { + blockNumber := blockRange.start + uint64(i) + if err := aWriter.AppendRaw(rawdb.ChainFreezerHashTable, blockNumber, blockRange.hashes[i]); err != nil { + return fmt.Errorf("can't write hash to Freezer: %v", err) + } + if err := aWriter.AppendRaw(rawdb.ChainFreezerHeaderTable, blockNumber, blockRange.headers[i]); err != nil { + return fmt.Errorf("can't write header to Freezer: %v", err) + } + if err := aWriter.AppendRaw(rawdb.ChainFreezerBodiesTable, blockNumber, blockRange.bodies[i]); err != nil { + return fmt.Errorf("can't write body to Freezer: %v", err) + } + if err := aWriter.AppendRaw(rawdb.ChainFreezerReceiptTable, blockNumber, blockRange.receipts[i]); err != nil { + return fmt.Errorf("can't write receipts to Freezer: %v", err) + } + if err := aWriter.AppendRaw(rawdb.ChainFreezerDifficultyTable, blockNumber, blockRange.tds[i]); err != nil { + return fmt.Errorf("can't write td to Freezer: %v", err) + } + } + return nil + }) + if err != nil { + return fmt.Errorf("failed to write block range: %v", err) + } + log.Info("Wrote ancient blocks", "start", blockRange.start, "end", blockRange.start+uint64(len(blockRange.hashes)-1), "count", len(blockRange.hashes)) + } + } + return nil +} diff --git a/op-chain-ops/cmd/celo-dbmigrate/db.go b/op-chain-ops/cmd/celo-dbmigrate/db.go new file mode 100644 index 0000000000000..37675c07ca232 --- /dev/null +++ b/op-chain-ops/cmd/celo-dbmigrate/db.go @@ -0,0 +1,52 @@ +package main + +import ( + "encoding/binary" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/ethdb" +) + +const ( + DB_CACHE = 1024 // size of the cache in MB + DB_HANDLES = 60 // number of handles + LAST_MIGRATED_BLOCK_KEY = "celoLastMigratedBlock" +) + +var ( + headerPrefix = []byte("h") // headerPrefix + num (uint64 big endian) + hash -> header +) + +// encodeBlockNumber encodes a block number as big endian uint64 +func encodeBlockNumber(number uint64) []byte { + enc := make([]byte, 8) + binary.BigEndian.PutUint64(enc, number) + return enc +} + +// headerKey = headerPrefix + num (uint64 big endian) + hash +func headerKey(number uint64, hash common.Hash) []byte { + return append(append(headerPrefix, encodeBlockNumber(number)...), hash.Bytes()...) +} + +// readLastMigratedBlock returns the last migration number. +func readLastMigratedBlock(db ethdb.KeyValueReader) uint64 { + data, err := db.Get([]byte(LAST_MIGRATED_BLOCK_KEY)) + if err != nil { + return 0 + } + number := binary.BigEndian.Uint64(data) + return number +} + +// writeLastMigratedBlock stores the last migration number. +func writeLastMigratedBlock(db ethdb.KeyValueWriter, number uint64) error { + enc := make([]byte, 8) + binary.BigEndian.PutUint64(enc, number) + return db.Put([]byte(LAST_MIGRATED_BLOCK_KEY), enc) +} + +// deleteLastMigratedBlock removes the last migration number. +func deleteLastMigratedBlock(db ethdb.KeyValueWriter) error { + return db.Delete([]byte(LAST_MIGRATED_BLOCK_KEY)) +} diff --git a/op-chain-ops/cmd/celo-dbmigrate/main.go b/op-chain-ops/cmd/celo-dbmigrate/main.go new file mode 100644 index 0000000000000..6998ef5e4fedd --- /dev/null +++ b/op-chain-ops/cmd/celo-dbmigrate/main.go @@ -0,0 +1,187 @@ +package main + +import ( + "flag" + "fmt" + "os" + "os/exec" + "path/filepath" + "runtime/debug" + + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/log" + "github.com/mattn/go-isatty" +) + +// How to run: +// go run ./op-chain-ops/cmd/celo-dbmigrate -oldDB /path/to/oldDB -newDB /path/to/newDB [-batchSize 1000] [-verbosity 3] [-memoryLimit 7500] [-clear-all] [-clear-nonAncients] +// +// This script will migrate block data from the old database to the new database +// You can set the log level using the -verbosity flag +// The number of ancient records to migrate in one batch can be set using the -batchSize flag +// The default batch size is 1000 +// You can set a memory limit in MB using the -memoryLimit flag. Defaults to 7500 MB. Make sure to set a limit that is less than your machine's available memory. +// Use -clear-all to start with a fresh new database +// Use -clear-nonAncients to keep migrated ancients, but not non-ancients + +func main() { + oldDBPath := flag.String("oldDB", "", "Path to the old database chaindata directory (read-only)") + newDBPath := flag.String("newDB", "", "Path to the new database") + batchSize := flag.Uint64("batchSize", 10000, "Number of records to migrate in one batch") + verbosity := flag.Uint64("verbosity", 3, "Log level (0:crit, 1:err, 2:warn, 3:info, 4:debug, 5:trace)") + memoryLimit := flag.Int64("memoryLimit", 7500, "Memory limit in MB") + + clearAll := flag.Bool("clear-all", false, "Use this to start with a fresh new database") + clearNonAncients := flag.Bool("clear-nonAncients", false, "Use to keep migrated ancients, but not non-ancients") + flag.Parse() + + log.Root().SetHandler(log.LvlFilterHandler(log.Lvl(*verbosity), log.StreamHandler(os.Stderr, log.TerminalFormat(isatty.IsTerminal(os.Stderr.Fd()))))) + + debug.SetMemoryLimit(*memoryLimit * 1 << 20) // Set memory limit, converting from MB to bytes + + var err error + + // check that `rsync` command is available + if _, err := exec.LookPath("rsync"); err != nil { + log.Info("Please install `rsync` to use this script") + return + } + + if *oldDBPath == "" || *newDBPath == "" { + log.Info("Please provide both oldDB and newDB flags") + flag.Usage() + return + } + + if *clearAll { + if err := os.RemoveAll(*newDBPath); err != nil { + log.Crit("Failed to remove new database", "err", err) + } + } + if *clearNonAncients { + if err := cleanupNonAncientDb(*newDBPath); err != nil { + log.Crit("Failed to cleanup non-ancient database", "err", err) + } + } + + if err := createEmptyNewDb(*newDBPath); err != nil { + log.Crit("Failed to create new database", "err", err) + } + + var numAncientsNew uint64 + if numAncientsNew, err = migrateAncientsDb(*oldDBPath, *newDBPath, *batchSize); err != nil { + log.Crit("Failed to migrate ancients database", "err", err) + } + + var numNonAncients uint64 + if numNonAncients, err = migrateNonAncientsDb(*oldDBPath, *newDBPath, numAncientsNew-1, *batchSize); err != nil { + log.Crit("Failed to migrate non-ancients database", "err", err) + } + + log.Info("Migration Completed", "migratedAncients", numAncientsNew, "migratedNonAncients", numNonAncients) +} + +func migrateNonAncientsDb(oldDbPath, newDbPath string, lastAncientBlock, batchSize uint64) (uint64, error) { + // First copy files from old database to new database + log.Info("Copy files from old database", "process", "db migration") + cmd := exec.Command("rsync", "-v", "-a", "--exclude=ancient", oldDbPath+"/", newDbPath) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + if err := cmd.Run(); err != nil { + return 0, fmt.Errorf("failed to copy old database to new database: %v", err) + } + + // Open the new database without access to AncientsDb + newDB, err := rawdb.NewLevelDBDatabase(newDbPath, DB_CACHE, DB_HANDLES, "", false) + if err != nil { + return 0, fmt.Errorf("failed to open new database: %v", err) + } + defer newDB.Close() + + // get the last block number + hash := rawdb.ReadHeadHeaderHash(newDB) + lastBlock := *rawdb.ReadHeaderNumber(newDB, hash) + lastMigratedBlock := readLastMigratedBlock(newDB) + + // if migration was interrupted, start from the last migrated block + fromBlock := max(lastAncientBlock, lastMigratedBlock) + 1 + + log.Info("Migration started", "process", "db migration", "startBlock", fromBlock, "endBlock", lastBlock, "count", lastBlock-fromBlock) + + for i := fromBlock; i <= lastBlock; i += batchSize { + numbersHash := rawdb.ReadAllHashesInRange(newDB, i, i+batchSize-1) + + log.Info("Processing Range", "process", "db migration", "from", i, "to(inclusve)", i+batchSize-1, "count", len(numbersHash)) + for _, numberHash := range numbersHash { + // read header and body + header := rawdb.ReadHeaderRLP(newDB, numberHash.Hash, numberHash.Number) + body := rawdb.ReadBodyRLP(newDB, numberHash.Hash, numberHash.Number) + + // transform header and body + newHeader, err := transformHeader(header) + if err != nil { + return 0, fmt.Errorf("failed to transform header: block %d - %x: %w", numberHash.Number, numberHash.Hash, err) + } + newBody, err := transformBlockBody(body) + if err != nil { + return 0, fmt.Errorf("failed to transform body: block %d - %x: %w", numberHash.Number, numberHash.Hash, err) + } + + if yes, newHash := hasSameHash(newHeader, numberHash.Hash[:]); !yes { + log.Error("Hash mismatch", "block", numberHash.Number, "oldHash", numberHash.Hash, "newHash", newHash) + return 0, fmt.Errorf("hash mismatch at block %d - %x", numberHash.Number, numberHash.Hash) + } + + // write header and body + batch := newDB.NewBatch() + rawdb.WriteBodyRLP(batch, numberHash.Hash, numberHash.Number, newBody) + _ = batch.Put(headerKey(numberHash.Number, numberHash.Hash), newHeader) + _ = writeLastMigratedBlock(batch, numberHash.Number) + if err := batch.Write(); err != nil { + return 0, fmt.Errorf("failed to write header and body: block %d - %x: %w", numberHash.Number, numberHash.Hash, err) + } + } + } + + toBeRemoved := rawdb.ReadAllHashesInRange(newDB, 1, lastAncientBlock) + log.Info("Removing frozen blocks", "process", "db migration", "count", len(toBeRemoved)) + batch := newDB.NewBatch() + for _, numberHash := range toBeRemoved { + rawdb.DeleteBlockWithoutNumber(batch, numberHash.Hash, numberHash.Number) + rawdb.DeleteCanonicalHash(batch, numberHash.Number) + } + if err := batch.Write(); err != nil { + return 0, fmt.Errorf("failed to delete frozen blocks: %w", err) + } + + // if migration finished, remove the last migration number + if err := deleteLastMigratedBlock(newDB); err != nil { + return 0, fmt.Errorf("failed to delete last migration number: %v", err) + } + log.Info("Migration ended", "process", "db migration", "migratedBlocks", lastBlock-fromBlock+1, "removedBlocks", len(toBeRemoved)) + + return lastBlock - fromBlock + 1, nil +} + +func createEmptyNewDb(newDBPath string) error { + if err := os.MkdirAll(newDBPath, 0755); err != nil { + return fmt.Errorf("failed to create new database directory: %v", err) + } + return nil +} + +func cleanupNonAncientDb(dir string) error { + files, err := os.ReadDir(dir) + if err != nil { + return fmt.Errorf("failed to read directory: %v", err) + } + for _, file := range files { + if file.Name() != "ancient" { + err := os.RemoveAll(filepath.Join(dir, file.Name())) + if err != nil { + return fmt.Errorf("failed to remove file: %v", err) + } + } + } + return nil +} diff --git a/op-chain-ops/cmd/celo-dbmigrate/transform.go b/op-chain-ops/cmd/celo-dbmigrate/transform.go new file mode 100644 index 0000000000000..650c64b72230a --- /dev/null +++ b/op-chain-ops/cmd/celo-dbmigrate/transform.go @@ -0,0 +1,104 @@ +package main + +import ( + "bytes" + "errors" + "fmt" + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/rlp" +) + +var ( + IstanbulExtraVanity = 32 // Fixed number of extra-data bytes reserved for validator vanity +) + +// IstanbulAggregatedSeal is the aggregated seal for Istanbul blocks +type IstanbulAggregatedSeal struct { + // Bitmap is a bitmap having an active bit for each validator that signed this block + Bitmap *big.Int + // Signature is an aggregated BLS signature resulting from signatures by each validator that signed this block + Signature []byte + // Round is the round in which the signature was created. + Round *big.Int +} + +// IstanbulExtra is the extra-data for Istanbul blocks +type IstanbulExtra struct { + // AddedValidators are the validators that have been added in the block + AddedValidators []common.Address + // AddedValidatorsPublicKeys are the BLS public keys for the validators added in the block + AddedValidatorsPublicKeys [][96]byte + // RemovedValidators is a bitmap having an active bit for each removed validator in the block + RemovedValidators *big.Int + // Seal is an ECDSA signature by the proposer + Seal []byte + // AggregatedSeal contains the aggregated BLS signature created via IBFT consensus. + AggregatedSeal IstanbulAggregatedSeal + // ParentAggregatedSeal contains and aggregated BLS signature for the previous block. + ParentAggregatedSeal IstanbulAggregatedSeal +} + +// transformHeader removes the aggregated seal from the header +func transformHeader(header []byte) ([]byte, error) { + newHeader := new(types.Header) + err := rlp.DecodeBytes(header, &newHeader) + if err != nil { + return nil, err + } + + if len(newHeader.Extra) < IstanbulExtraVanity { + return nil, errors.New("invalid istanbul header extra-data") + } + + istanbulExtra := IstanbulExtra{} + err = rlp.DecodeBytes(newHeader.Extra[IstanbulExtraVanity:], &istanbulExtra) + if err != nil { + return nil, err + } + + istanbulExtra.AggregatedSeal = IstanbulAggregatedSeal{} + + payload, err := rlp.EncodeToBytes(&istanbulExtra) + if err != nil { + return nil, err + } + + newHeader.Extra = append(newHeader.Extra[:IstanbulExtraVanity], payload...) + + return rlp.EncodeToBytes(newHeader) +} + +func hasSameHash(newHeader, oldHash []byte) (bool, common.Hash) { + newHash := crypto.Keccak256Hash(newHeader) + return bytes.Equal(oldHash, newHash.Bytes()), newHash +} + +// transformBlockBody migrates the block body from the old format to the new format (works with []byte input output) +func transformBlockBody(oldBodyData []byte) ([]byte, error) { + // decode body into celo-blockchain Body structure + // remove epochSnarkData and randomness data + var celoBody struct { + Transactions types.Transactions + Randomness rlp.RawValue + EpochSnarkData rlp.RawValue + } + if err := rlp.DecodeBytes(oldBodyData, &celoBody); err != nil { + return nil, fmt.Errorf("failed to RLP decode body: %w", err) + } + + // transform into op-geth types.Body structure + newBody := types.Body{ + Transactions: celoBody.Transactions, + Uncles: []*types.Header{}, + } + newBodyData, err := rlp.EncodeToBytes(newBody) + if err != nil { + return nil, fmt.Errorf("failed to RLP encode body: %w", err) + } + + return newBodyData, nil +} diff --git a/op-chain-ops/cmd/celo-migrate/main.go b/op-chain-ops/cmd/celo-migrate/main.go new file mode 100644 index 0000000000000..aa810bd4c0bec --- /dev/null +++ b/op-chain-ops/cmd/celo-migrate/main.go @@ -0,0 +1,419 @@ +package main + +import ( + "bytes" + "context" + "errors" + "fmt" + "math/big" + "os" + "path/filepath" + "time" + + "github.com/ethereum-optimism/optimism/op-chain-ops/genesis" + "github.com/ethereum-optimism/optimism/op-service/jsonutil" + oplog "github.com/ethereum-optimism/optimism/op-service/log" + "github.com/ethereum-optimism/optimism/op-service/predeploys" + "github.com/holiman/uint256" + "github.com/mattn/go-isatty" + + "github.com/urfave/cli/v2" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/ethclient" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/rpc" + "github.com/ethereum/go-ethereum/trie" + "github.com/ethereum/go-ethereum/triedb" +) + +var ( + deployConfigFlag = &cli.PathFlag{ + Name: "deploy-config", + Usage: "Path to the JSON file that was used for the bedrock contracts deployment. A test example can be found here 'op-chain-ops/genesis/testdata/test-deploy-config-full.json' and documentation for the fields is at https://docs.optimism.io/builders/chain-operators/management/configuration", + Required: true, + } + l1DeploymentsFlag = &cli.PathFlag{ + Name: "l1-deployments", + Usage: "Path to L1 deployments JSON file, the output of running the bedrock contracts deployment for the given 'deploy-config'", + Required: true, + } + l1RPCFlag = &cli.StringFlag{ + Name: "l1-rpc", + Usage: "RPC URL for a node of the L1 defined in the 'deploy-config'", + Required: true, + } + l2AllocsFlag = &cli.PathFlag{ + Name: "l2-allocs", + Usage: "Path to L2 genesis allocs file", + Required: true, + } + outfileRollupConfigFlag = &cli.PathFlag{ + Name: "outfile.rollup-config", + Usage: "Path to write the rollup config JSON file, to be provided to op-node with the 'rollup.config' flag", + Required: true, + } + dbPathFlag = &cli.StringFlag{ + Name: "db-path", + Usage: "Path to the Celo database, not including the `celo/chaindata` part", + Required: true, + } + dryRunFlag = &cli.BoolFlag{ + Name: "dry-run", + Usage: "Dry run the upgrade by not committing the database", + } + + flags = []cli.Flag{ + deployConfigFlag, + l1DeploymentsFlag, + l1RPCFlag, + l2AllocsFlag, + outfileRollupConfigFlag, + dbPathFlag, + dryRunFlag, + } + + // TODO: read those form the deploy config + // TODO(pl): select values + EIP1559Denominator = uint64(50) + EIP1559DenominatorCanyon = uint64(250) + EIP1559Elasticity = uint64(10) + + OutFilePerm = os.FileMode(0o440) +) + +func main() { + color := isatty.IsTerminal(os.Stderr.Fd()) + handler := log.NewTerminalHandler(os.Stderr, color) + oplog.SetGlobalLogHandler(handler) + + app := &cli.App{ + Name: "migrate", + Usage: "Migrate Celo state to a CeL2 DB", + Flags: flags, + Action: func(ctx *cli.Context) error { + deployConfig := ctx.Path("deploy-config") + l1Deployments := ctx.Path("l1-deployments") + l1RPC := ctx.String("l1-rpc") + l2AllocsPath := ctx.Path("l2-allocs") + outfileRollupConfig := ctx.Path("outfile.rollup-config") + dbPath := ctx.String("db-path") + dryRun := ctx.Bool("dry-run") + + // Read deployment configuration + log.Info("Deploy config", "path", deployConfig) + config, err := genesis.NewDeployConfig(deployConfig) + if err != nil { + return err + } + + if config.DeployCeloContracts { + return errors.New("DeployCeloContracts is not supported in migration") + } + if config.FundDevAccounts { + return errors.New("FundDevAccounts is not supported in migration") + } + + // Try reading the L1 deployment information + deployments, err := genesis.NewL1Deployments(l1Deployments) + if err != nil { + return fmt.Errorf("cannot read L1 deployments at %s: %w", l1Deployments, err) + } + config.SetDeployments(deployments) + + // Get latest block information from L1 + var l1StartBlock *types.Block + client, err := ethclient.Dial(l1RPC) + if err != nil { + return fmt.Errorf("cannot dial %s: %w", l1RPC, err) + } + + if config.L1StartingBlockTag == nil { + l1StartBlock, err = client.BlockByNumber(context.Background(), nil) + if err != nil { + return fmt.Errorf("cannot fetch latest block: %w", err) + } + tag := rpc.BlockNumberOrHashWithHash(l1StartBlock.Hash(), true) + config.L1StartingBlockTag = (*genesis.MarshalableRPCBlockNumberOrHash)(&tag) + } else if config.L1StartingBlockTag.BlockHash != nil { + l1StartBlock, err = client.BlockByHash(context.Background(), *config.L1StartingBlockTag.BlockHash) + if err != nil { + return fmt.Errorf("cannot fetch block by hash: %w", err) + } + } else if config.L1StartingBlockTag.BlockNumber != nil { + l1StartBlock, err = client.BlockByNumber(context.Background(), big.NewInt(config.L1StartingBlockTag.BlockNumber.Int64())) + if err != nil { + return fmt.Errorf("cannot fetch block by number: %w", err) + } + } + + // Ensure that there is a starting L1 block + if l1StartBlock == nil { + return fmt.Errorf("no starting L1 block") + } + + // Sanity check the config. Do this after filling in the L1StartingBlockTag + // if it is not defined. + if err := config.Check(); err != nil { + return err + } + + log.Info("Using L1 Start Block", "number", l1StartBlock.Number(), "hash", l1StartBlock.Hash().Hex()) + + // Build the L2 genesis block + l2Allocs, err := genesis.LoadForgeAllocs(l2AllocsPath) + if err != nil { + return err + } + + l2Genesis, err := genesis.BuildL2Genesis(config, l2Allocs, l1StartBlock) + if err != nil { + return fmt.Errorf("error creating l2 genesis: %w", err) + } + + // Write changes to state to actual state database + cel2Header, err := ApplyMigrationChangesToDB(l2Genesis, dbPath, !dryRun) + if err != nil { + return err + } + log.Info("Updated Cel2 state") + + rollupConfig, err := config.RollupConfig(l1StartBlock, cel2Header.Hash(), cel2Header.Number.Uint64()) + if err != nil { + return err + } + if err := rollupConfig.Check(); err != nil { + return fmt.Errorf("generated rollup config does not pass validation: %w", err) + } + + log.Info("Writing rollup config", "file", outfileRollupConfig) + if err := jsonutil.WriteJSON(outfileRollupConfig, rollupConfig, OutFilePerm); err != nil { + return err + } + + return nil + }, + } + + if err := app.Run(os.Args); err != nil { + log.Crit("error in migration", "err", err) + } + log.Info("Finished migration successfully!") +} + +func ApplyMigrationChangesToDB(genesis *core.Genesis, dbPath string, commit bool) (*types.Header, error) { + log.Info("Opening Celo database", "dbPath", dbPath) + ldb, err := openCeloDb(dbPath) + if err != nil { + return nil, fmt.Errorf("cannot open DB: %w", err) + } + log.Info("Loaded Celo L1 DB", "db", ldb) + + // Grab the hash of the tip of the legacy chain. + hash := rawdb.ReadHeadHeaderHash(ldb) + log.Info("Reading chain tip from database", "hash", hash) + + // Grab the header number. + num := rawdb.ReadHeaderNumber(ldb, hash) + if num == nil { + return nil, fmt.Errorf("cannot find header number for %s", hash) + } + log.Info("Reading chain tip num from database", "number", num) + + // Grab the full header. + header := rawdb.ReadHeader(ldb, hash, *num) + log.Info("Read header from database", "header", header) + + // We need to update the chain config to set the correct hardforks. + genesisHash := rawdb.ReadCanonicalHash(ldb, 0) + cfg := rawdb.ReadChainConfig(ldb, genesisHash) + if cfg == nil { + log.Crit("chain config not found") + } + log.Info("Read chain config from database", "config", cfg) + + // Set up the backing store. + // TODO(pl): Do we need the preimages setting here? + underlyingDB := state.NewDatabaseWithConfig(ldb, &triedb.Config{Preimages: true}) + + // Open up the state database. + db, err := state.New(header.Root, underlyingDB, nil) + if err != nil { + return nil, fmt.Errorf("cannot open StateDB: %w", err) + } + + // So far we applied changes in the memory VM and collected changes in the genesis struct + // Now we iterate through all accounts that have been written there and set them inside the statedb. + // This will change the state root + // Another property is that the total balance changes must be 0 + accountCounter := 0 + overwriteCounter := 0 + for k, v := range genesis.Alloc { + accountCounter++ + if db.Exist(k) { + equal := bytes.Equal(db.GetCode(k), v.Code) + + log.Warn("Operating on existing state", "account", k, "equalCode", equal) + overwriteCounter++ + } + // TODO(pl): decide what to do with existing accounts. + db.CreateAccount(k) + + // CreateAccount above copied the balance, check if we change it + if db.GetBalance(k).Cmp(uint256.MustFromBig(v.Balance)) != 0 { + // TODO(pl): make this a hard error once the migration has been tested more + log.Warn("Moving account changed native balance", "address", k, "oldBalance", db.GetBalance(k), "newBalance", v.Balance) + } + + db.SetNonce(k, v.Nonce) + db.SetBalance(k, uint256.MustFromBig(v.Balance)) + db.SetCode(k, v.Code) + db.SetStorage(k, v.Storage) + + log.Info("Moved account", "address", k) + } + log.Info("Migrated OP contracts into state DB", "copiedAccounts", accountCounter, "overwrittenAccounts", overwriteCounter) + + migrationBlock := new(big.Int).Add(header.Number, common.Big1) + + // We're done messing around with the database, so we can now commit the changes to the DB. + // Note that this doesn't actually write the changes to disk. + log.Info("Committing state DB") + newRoot, err := db.Commit(migrationBlock.Uint64(), true) + if err != nil { + return nil, err + } + + // Create the header for the Bedrock transition block. + cel2Header := &types.Header{ + ParentHash: header.Hash(), + UncleHash: types.EmptyUncleHash, + Coinbase: predeploys.SequencerFeeVaultAddr, + Root: newRoot, + TxHash: types.EmptyTxsHash, + ReceiptHash: types.EmptyReceiptsHash, + Bloom: types.Bloom{}, + Difficulty: new(big.Int).Set(common.Big0), + Number: migrationBlock, + GasLimit: header.GasLimit, + GasUsed: 0, + Time: uint64(time.Now().Unix()), // TODO(pl): Needed to avoid L1-L2 time mismatches + Extra: []byte("CeL2 migration"), + MixDigest: common.Hash{}, + Nonce: types.BlockNonce{}, + BaseFee: new(big.Int).Set(header.BaseFee), + } + log.Info("Build Cel2 migration header", "header", cel2Header) + + // Create the Bedrock transition block from the header. Note that there are no transactions, + // uncle blocks, or receipts in the Bedrock transition block. + cel2Block := types.NewBlock(cel2Header, nil, nil, nil, trie.NewStackTrie(nil)) + + // We did it! + log.Info( + "Built Cel2 migration block", + "hash", cel2Block.Hash(), + "root", cel2Block.Root(), + "number", cel2Block.NumberU64(), + "gas-used", cel2Block.GasUsed(), + "gas-limit", cel2Block.GasLimit(), + ) + + // If we're not actually writing this to disk, then we're done. + if !commit { + log.Info("Dry run complete") + return nil, nil + } + + // Otherwise we need to write the changes to disk. First we commit the state changes. + log.Info("Committing trie DB") + if err := db.Database().TrieDB().Commit(newRoot, true); err != nil { + return nil, err + } + + // Next we write the Cel2 genesis block to the database. + rawdb.WriteTd(ldb, cel2Block.Hash(), cel2Block.NumberU64(), cel2Block.Difficulty()) + rawdb.WriteBlock(ldb, cel2Block) + rawdb.WriteReceipts(ldb, cel2Block.Hash(), cel2Block.NumberU64(), nil) + rawdb.WriteCanonicalHash(ldb, cel2Block.Hash(), cel2Block.NumberU64()) + rawdb.WriteHeadBlockHash(ldb, cel2Block.Hash()) + rawdb.WriteHeadFastBlockHash(ldb, cel2Block.Hash()) + rawdb.WriteHeadHeaderHash(ldb, cel2Block.Hash()) + + // Mark the first CeL2 block as finalized + rawdb.WriteFinalizedBlockHash(ldb, cel2Block.Hash()) + + // Set the standard options. + cfg.LondonBlock = cel2Block.Number() + cfg.BerlinBlock = cel2Block.Number() + cfg.ArrowGlacierBlock = cel2Block.Number() + cfg.GrayGlacierBlock = cel2Block.Number() + cfg.MergeNetsplitBlock = cel2Block.Number() + cfg.TerminalTotalDifficulty = big.NewInt(0) + cfg.TerminalTotalDifficultyPassed = true + cfg.ShanghaiTime = &cel2Header.Time + cfg.CancunTime = &cel2Header.Time + + // Set the Optimism options. + cfg.BedrockBlock = cel2Block.Number() + // Enable Regolith from the start of Bedrock + cfg.RegolithTime = new(uint64) // what are those? do we need those? + cfg.Optimism = ¶ms.OptimismConfig{ + EIP1559Denominator: EIP1559Denominator, + EIP1559DenominatorCanyon: EIP1559DenominatorCanyon, + EIP1559Elasticity: EIP1559Elasticity, + } + cfg.CanyonTime = &cel2Header.Time + cfg.EcotoneTime = &cel2Header.Time + cfg.Cel2Time = &cel2Header.Time + + // Write the chain config to disk. + // TODO(pl): Why do we need to write this with the genesis hash, not `cel2Block.Hash()`?` + rawdb.WriteChainConfig(ldb, genesisHash, cfg) + log.Info("Wrote updated chain config", "config", cfg) + + // We're done! + log.Info( + "Wrote CeL2 migration block", + "height", cel2Header.Number, + "root", cel2Header.Root.String(), + "hash", cel2Header.Hash().String(), + "timestamp", cel2Header.Time, + ) + + // Close the database handle + if err := ldb.Close(); err != nil { + return nil, err + } + + return cel2Header, nil +} + +// Opens a Celo database, stored in the `celo` subfolder +func openCeloDb(path string) (ethdb.Database, error) { + if _, err := os.Stat(path); errors.Is(err, os.ErrNotExist) { + return nil, err + } + + chaindataPath := filepath.Join(path, "celo", "chaindata") + ancientPath := filepath.Join(chaindataPath, "ancient") + ldb, err := rawdb.Open(rawdb.OpenOptions{ + Type: "leveldb", + Directory: chaindataPath, + AncientsDirectory: ancientPath, + Namespace: "", + Cache: 1024, + Handles: 60, + ReadOnly: false, + }) + if err != nil { + return nil, err + } + return ldb, nil +} diff --git a/op-chain-ops/cmd/check-derivation/main.go b/op-chain-ops/cmd/check-derivation/main.go index 499b128f8767f..88a13f6efce7f 100644 --- a/op-chain-ops/cmd/check-derivation/main.go +++ b/op-chain-ops/cmd/check-derivation/main.go @@ -225,7 +225,7 @@ func getRandomSignedTransaction(ctx context.Context, ethClient *ethclient.Client var txData types.TxData switch txType { case types.LegacyTxType: - gasLimit, err := core.IntrinsicGas(data, nil, false, true, true, false) + gasLimit, err := core.IntrinsicGas(data, nil, false, true, true, false, nil) if err != nil { return nil, fmt.Errorf("failed to get intrinsicGas: %w", err) } @@ -242,7 +242,7 @@ func getRandomSignedTransaction(ctx context.Context, ethClient *ethclient.Client Address: randomAddress, StorageKeys: []common.Hash{common.HexToHash("0x1234")}, }} - gasLimit, err := core.IntrinsicGas(data, accessList, false, true, true, false) + gasLimit, err := core.IntrinsicGas(data, accessList, false, true, true, false, nil) if err != nil { return nil, fmt.Errorf("failed to get intrinsicGas: %w", err) } @@ -257,7 +257,7 @@ func getRandomSignedTransaction(ctx context.Context, ethClient *ethclient.Client Data: data, } case types.DynamicFeeTxType: - gasLimit, err := core.IntrinsicGas(data, nil, false, true, true, false) + gasLimit, err := core.IntrinsicGas(data, nil, false, true, true, false, nil) if err != nil { return nil, fmt.Errorf("failed to get intrinsicGas: %w", err) } diff --git a/op-chain-ops/genesis/config.go b/op-chain-ops/genesis/config.go index a2661b057ecb0..2d82b679af2aa 100644 --- a/op-chain-ops/genesis/config.go +++ b/op-chain-ops/genesis/config.go @@ -292,6 +292,9 @@ type DeployConfig struct { // UseInterop is a flag that indicates if the system is using interop UseInterop bool `json:"useInterop,omitempty"` + + // DeployCeloContracts indicates whether to deploy Celo contracts. + DeployCeloContracts bool `json:"deployCeloContracts"` } // Copy will deeply copy the DeployConfig. This does a JSON roundtrip to copy @@ -660,6 +663,7 @@ func (d *DeployConfig) RollupConfig(l1StartBlock *types.Block, l2GenesisBlockHas FjordTime: d.FjordTime(l1StartBlock.Time()), InteropTime: d.InteropTime(l1StartBlock.Time()), PlasmaConfig: plasma, + Cel2Time: d.RegolithTime(l1StartBlock.Time()), }, nil } diff --git a/op-chain-ops/genesis/genesis.go b/op-chain-ops/genesis/genesis.go index bbfb15c346496..93953a06089cd 100644 --- a/op-chain-ops/genesis/genesis.go +++ b/op-chain-ops/genesis/genesis.go @@ -68,6 +68,7 @@ func NewL2Genesis(config *DeployConfig, block *types.Block) (*core.Genesis, erro EcotoneTime: config.EcotoneTime(block.Time()), FjordTime: config.FjordTime(block.Time()), InteropTime: config.InteropTime(block.Time()), + Cel2Time: config.RegolithTime(block.Time()), Optimism: ¶ms.OptimismConfig{ EIP1559Denominator: eip1559Denom, EIP1559Elasticity: eip1559Elasticity, diff --git a/op-chain-ops/genesis/testdata/test-deploy-config-full.json b/op-chain-ops/genesis/testdata/test-deploy-config-full.json index c0aefac625ff5..09415e40bfd16 100644 --- a/op-chain-ops/genesis/testdata/test-deploy-config-full.json +++ b/op-chain-ops/genesis/testdata/test-deploy-config-full.json @@ -92,5 +92,6 @@ "daChallengeProxy": "0x0000000000000000000000000000000000000000", "daChallengeWindow": 0, "daResolveWindow": 0, - "daResolverRefundPercentage": 0 + "daResolverRefundPercentage": 0, + "deployCeloContracts": false } diff --git a/op-e2e/actions/l2_batcher.go b/op-e2e/actions/l2_batcher.go index 310a6cade9516..fd249e58fe6ab 100644 --- a/op-e2e/actions/l2_batcher.go +++ b/op-e2e/actions/l2_batcher.go @@ -277,7 +277,7 @@ func (s *L2Batcher) ActL2BatchSubmit(t Testing, txOpts ...func(tx *types.Dynamic opt(rawTx) } - gas, err := core.IntrinsicGas(rawTx.Data, nil, false, true, true, false) + gas, err := core.IntrinsicGas(rawTx.Data, nil, false, true, true, false, nil) require.NoError(t, err, "need to compute intrinsic gas") rawTx.Gas = gas txData = rawTx @@ -468,7 +468,7 @@ func (s *L2Batcher) ActL2BatchSubmitGarbage(t Testing, kind GarbageKind) { GasFeeCap: gasFeeCap, Data: outputFrame, } - gas, err := core.IntrinsicGas(rawTx.Data, nil, false, true, true, false) + gas, err := core.IntrinsicGas(rawTx.Data, nil, false, true, true, false, nil) require.NoError(t, err, "need to compute intrinsic gas") rawTx.Gas = gas diff --git a/op-e2e/actions/l2_batcher_test.go b/op-e2e/actions/l2_batcher_test.go index 3a137ce992af6..0717850765387 100644 --- a/op-e2e/actions/l2_batcher_test.go +++ b/op-e2e/actions/l2_batcher_test.go @@ -496,7 +496,7 @@ func BigL2Txs(gt *testing.T, deltaTimeOffset *hexutil.Uint64) { data := make([]byte, 120_000) // very large L2 txs, as large as the tx-pool will accept _, err := rng.Read(data[:]) // fill with random bytes, to make compression ineffective require.NoError(t, err) - gas, err := core.IntrinsicGas(data, nil, false, true, true, false) + gas, err := core.IntrinsicGas(data, nil, false, true, true, false, nil) require.NoError(t, err) if gas > engine.engineApi.RemainingBlockGas() { break diff --git a/op-e2e/actions/span_batch_test.go b/op-e2e/actions/span_batch_test.go index 6ccb76a461bd6..39dd3f817a7ae 100644 --- a/op-e2e/actions/span_batch_test.go +++ b/op-e2e/actions/span_batch_test.go @@ -524,7 +524,7 @@ func TestSpanBatchLowThroughputChain(gt *testing.T) { data := make([]byte, rand.Intn(100)) _, err := crand.Read(data[:]) // fill with random bytes require.NoError(t, err) - gas, err := core.IntrinsicGas(data, nil, false, true, true, false) + gas, err := core.IntrinsicGas(data, nil, false, true, true, false, nil) require.NoError(t, err) baseFee := seqEngine.l2Chain.CurrentBlock().BaseFee nonce, err := cl.PendingNonceAt(t.Ctx(), addrs[userIdx]) @@ -663,7 +663,7 @@ func TestBatchEquivalence(gt *testing.T) { data := make([]byte, rand.Intn(100)) _, err := crand.Read(data[:]) // fill with random bytes require.NoError(t, err) - gas, err := core.IntrinsicGas(data, nil, false, true, true, false) + gas, err := core.IntrinsicGas(data, nil, false, true, true, false, nil) require.NoError(t, err) baseFee := seqEngine.l2Chain.CurrentBlock().BaseFee nonce, err := seqEngCl.PendingNonceAt(t.Ctx(), addrs[userIdx]) diff --git a/op-e2e/actions/sync_test.go b/op-e2e/actions/sync_test.go index e7521bdd8c94b..19e9f3098439b 100644 --- a/op-e2e/actions/sync_test.go +++ b/op-e2e/actions/sync_test.go @@ -896,7 +896,7 @@ func TestInvalidPayloadInSpanBatch(gt *testing.T) { aliceNonce, err := seqEng.EthClient().PendingNonceAt(t.Ctx(), dp.Addresses.Alice) require.NoError(t, err) data := make([]byte, rand.Intn(100)) - gas, err := core.IntrinsicGas(data, nil, false, true, true, false) + gas, err := core.IntrinsicGas(data, nil, false, true, true, false, nil) require.NoError(t, err) baseFee := seqEng.l2Chain.CurrentBlock().BaseFee tx := types.MustSignNewTx(dp.Secrets.Alice, signer, &types.DynamicFeeTx{ diff --git a/op-e2e/brotli_batcher_test.go b/op-e2e/brotli_batcher_test.go index 97211c471ba05..1ebb27efb5ad7 100644 --- a/op-e2e/brotli_batcher_test.go +++ b/op-e2e/brotli_batcher_test.go @@ -85,7 +85,7 @@ func TestBrotliBatcherFjord(t *testing.T) { opts.Value = big.NewInt(1_000_000_000) opts.Nonce = 1 // Already have deposit opts.ToAddr = &common.Address{0xff, 0xff} - opts.Gas, err = core.IntrinsicGas(opts.Data, nil, false, true, true, false) + opts.Gas, err = core.IntrinsicGas(opts.Data, nil, false, true, true, false, nil) require.NoError(t, err) opts.VerifyOnClients(l2Verif) }) diff --git a/op-e2e/celo/run_all_tests.sh b/op-e2e/celo/run_all_tests.sh new file mode 100755 index 0000000000000..272dea9753685 --- /dev/null +++ b/op-e2e/celo/run_all_tests.sh @@ -0,0 +1,56 @@ +#!/bin/bash +set -eo pipefail + +SCRIPT_DIR=$(readlink -f "$(dirname "$0")") +TEST_GLOB=$1 +cd "$SCRIPT_DIR" || exit 1 +source "$SCRIPT_DIR/shared.sh" + +## Start geth +cd "$SCRIPT_DIR/../.." || exit 1 +trap 'cd "$SCRIPT_DIR/../.." && make devnet-down' EXIT # kill bg job at exit +make devnet-up + +# Wait for geth to be ready +for _ in {1..10} +do + if cast block &> /dev/null + then + break + fi + sleep 0.2 +done + +## Run tests +echo Geth ready, start tests +failures=0 +tests=0 +cd "$SCRIPT_DIR" || exit 1 +for f in test_*"$TEST_GLOB"* +do + echo -e "\nRun $f" + if "./$f" + then + tput setaf 2 || true + echo "PASS $f" + else + tput setaf 1 || true + echo "FAIL $f ❌" + ((failures++)) || true + fi + tput sgr0 || true + ((tests++)) || true +done + +## Final summary +echo +if [[ $failures -eq 0 ]] +then + tput setaf 2 || true + echo All tests succeeded! +else + tput setaf 1 || true + echo $failures/$tests failed. +fi +tput sgr0 || true +exit $failures diff --git a/op-e2e/celo/shared.sh b/op-e2e/celo/shared.sh new file mode 100644 index 0000000000000..913d77a5c79cc --- /dev/null +++ b/op-e2e/celo/shared.sh @@ -0,0 +1,9 @@ +#!/bin/bash +#shellcheck disable=SC2034 # unused vars make sense in a shared file + +export ETH_RPC_URL=http://127.0.0.1:9545 + +ACC_PRIVKEY=ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 +ACC_ADDR=$(cast wallet address $ACC_PRIVKEY) +REGISTRY_ADDR=0x000000000000000000000000000000000000ce10 +TOKEN_ADDR=0x471ece3750da237f93b8e339c536989b8978a438 diff --git a/op-e2e/celo/test_token_duality.sh b/op-e2e/celo/test_token_duality.sh new file mode 100755 index 0000000000000..355afef8c7ca7 --- /dev/null +++ b/op-e2e/celo/test_token_duality.sh @@ -0,0 +1,12 @@ +#!/bin/bash +#shellcheck disable=SC2086 +set -eo pipefail + +source shared.sh + +# Send token and check balance +balance_before=$(cast balance 0x000000000000000000000000000000000000dEaD) +cast send --private-key $ACC_PRIVKEY $TOKEN_ADDR 'transfer(address to, uint256 value) returns (bool)' 0x000000000000000000000000000000000000dEaD 100 +balance_after=$(cast balance 0x000000000000000000000000000000000000dEaD) +echo "Balance change: $balance_before -> $balance_after" +[[ $((balance_before + 100)) -eq $balance_after ]] || (echo "Balance did not change as expected"; exit 1) diff --git a/op-e2e/eip4844_test.go b/op-e2e/eip4844_test.go index 5b5cc1d5308ed..c6f481ab67fe7 100644 --- a/op-e2e/eip4844_test.go +++ b/op-e2e/eip4844_test.go @@ -102,7 +102,7 @@ func testSystem4844E2E(t *testing.T, multiBlob bool) { opts.ToAddr = &common.Address{0xff, 0xff} // put some random data in the tx to make it fill up 6 blobs (multi-blob case) opts.Data = testutils.RandomData(rand.New(rand.NewSource(420)), 400) - opts.Gas, err = core.IntrinsicGas(opts.Data, nil, false, true, true, false) + opts.Gas, err = core.IntrinsicGas(opts.Data, nil, false, true, true, false, nil) require.NoError(t, err) opts.VerifyOnClients(l2Verif) }) diff --git a/op-e2e/setup.go b/op-e2e/setup.go index 2b255879180c1..78120858c516c 100644 --- a/op-e2e/setup.go +++ b/op-e2e/setup.go @@ -539,6 +539,7 @@ func (cfg SystemConfig) Start(t *testing.T, _opts ...SystemConfigOption) (*Syste EcotoneTime: cfg.DeployConfig.EcotoneTime(uint64(cfg.DeployConfig.L1GenesisBlockTimestamp)), FjordTime: cfg.DeployConfig.FjordTime(uint64(cfg.DeployConfig.L1GenesisBlockTimestamp)), InteropTime: cfg.DeployConfig.InteropTime(uint64(cfg.DeployConfig.L1GenesisBlockTimestamp)), + Cel2Time: cfg.DeployConfig.RegolithTime(uint64(cfg.DeployConfig.L1GenesisBlockTimestamp)), ProtocolVersionsAddress: cfg.L1Deployments.ProtocolVersionsProxy, } } diff --git a/op-e2e/system_test.go b/op-e2e/system_test.go index 4dcea8bd96c36..b1b2b857f98ee 100644 --- a/op-e2e/system_test.go +++ b/op-e2e/system_test.go @@ -1280,8 +1280,16 @@ func testFees(t *testing.T, cfg SystemConfig) { require.Equal(t, decimals.Uint64(), uint64(6), "wrong gpo decimals") + // Celo changes the base fee recipient + var baseFeeRecipient common.Address + if sys.RollupConfig.Cel2Time == nil { + baseFeeRecipient = predeploys.BaseFeeVaultAddr + } else { + baseFeeRecipient = predeploys.FeeHandlerAddr + } + // BaseFee Recipient - baseFeeRecipientStartBalance, err := l2Seq.BalanceAt(context.Background(), predeploys.BaseFeeVaultAddr, big.NewInt(rpc.EarliestBlockNumber.Int64())) + baseFeeRecipientStartBalance, err := l2Seq.BalanceAt(context.Background(), baseFeeRecipient, big.NewInt(rpc.EarliestBlockNumber.Int64())) require.Nil(t, err) // L1Fee Recipient @@ -1324,7 +1332,7 @@ func testFees(t *testing.T, cfg SystemConfig) { endBalance, err := l2Seq.BalanceAt(context.Background(), fromAddr, header.Number) require.Nil(t, err) - baseFeeRecipientEndBalance, err := l2Seq.BalanceAt(context.Background(), predeploys.BaseFeeVaultAddr, header.Number) + baseFeeRecipientEndBalance, err := l2Seq.BalanceAt(context.Background(), baseFeeRecipient, header.Number) require.Nil(t, err) l1Header, err := l1.HeaderByNumber(context.Background(), nil) diff --git a/op-node/rollup/types.go b/op-node/rollup/types.go index 816181687567a..8a42d37ebeb98 100644 --- a/op-node/rollup/types.go +++ b/op-node/rollup/types.go @@ -92,6 +92,7 @@ type Config struct { // "Regolith" is the loose deposited rock that sits on top of Bedrock. // Active if RegolithTime != nil && L2 block timestamp >= *RegolithTime, inactive otherwise. RegolithTime *uint64 `json:"regolith_time,omitempty"` + Cel2Time *uint64 `json:"cel2_time,omitempty"` // CanyonTime sets the activation time of the Canyon network upgrade. // Active if CanyonTime != nil && L2 block timestamp >= *CanyonTime, inactive otherwise. @@ -599,6 +600,7 @@ func (c *Config) LogDescription(log log.Logger, l2Chains map[string]string) { "fjord_time", fmtForkTimeOrUnset(c.FjordTime), "interop_time", fmtForkTimeOrUnset(c.InteropTime), "plasma_mode", c.PlasmaConfig != nil, + "cel2_time", fmtForkTimeOrUnset(c.Cel2Time), ) } diff --git a/op-service/predeploys/addresses.go b/op-service/predeploys/addresses.go index 3602bb5448d3f..c55dcfbebcac4 100644 --- a/op-service/predeploys/addresses.go +++ b/op-service/predeploys/addresses.go @@ -38,6 +38,17 @@ const ( EntryPoint_v060 = "0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789" SenderCreator_v070 = "0xEFC2c1444eBCC4Db75e7613d20C6a62fF67A167C" EntryPoint_v070 = "0x0000000071727De22E5E9d8BAf0edAc6f37da032" + + // Celo + CeloRegistry = "0x000000000000000000000000000000000000ce10" + GoldToken = "0x471ece3750da237f93b8e339c536989b8978a438" + FeeHandler = "0xcd437749e43a154c07f3553504c68fbfd56b8778" + FeeCurrencyWhitelist = "0xbb024e9cdcb2f9e34d893630d19611b8a5381b3c" + MentoFeeHandlerSeller = "0x4efa274b7e33476c961065000d58ee09f7921a74" + UniswapFeeHandlerSeller = "0xd3aee28548dbb65df03981f0dc0713bfcbd10a97" + SortedOracles = "0xefb84935239dacdecf7c5ba76d8de40b077b7b33" + AddressSortedLinkedListWithMedian = "0xED477A99035d0c1e11369F1D7A4e587893cc002B" + FeeCurrency = "0x4200000000000000000000000000000000001022" ) var ( @@ -76,6 +87,19 @@ var ( Predeploys = make(map[string]*Predeploy) PredeploysByAddress = make(map[common.Address]*Predeploy) + + // Celo + CeloRegistryAddr = common.HexToAddress(CeloRegistry) + GoldTokenAddr = common.HexToAddress(GoldToken) + FeeHandlerAddr = common.HexToAddress(FeeHandler) + FeeCurrencyWhitelistAddr = common.HexToAddress(FeeCurrencyWhitelist) + MentoFeeHandlerSellerAddr = common.HexToAddress(MentoFeeHandlerSeller) + UniswapFeeHandlerSellerAddr = common.HexToAddress(UniswapFeeHandlerSeller) + SortedOraclesAddr = common.HexToAddress(SortedOracles) + AddressSortedLinkedListWithMedianAddr = common.HexToAddress(AddressSortedLinkedListWithMedian) + FeeCurrencyAddr = common.HexToAddress(FeeCurrency) + + CeloPredeploys = make(map[string]*Predeploy) ) func init() { @@ -157,6 +181,20 @@ func init() { ProxyDisabled: true, } + // Celo + CeloPredeploys["CeloRegistry"] = &Predeploy{Address: CeloRegistryAddr} + CeloPredeploys["GoldToken"] = &Predeploy{Address: GoldTokenAddr} + CeloPredeploys["FeeHandler"] = &Predeploy{Address: FeeHandlerAddr} + CeloPredeploys["FeeCurrencyWhitelist"] = &Predeploy{Address: FeeCurrencyWhitelistAddr} + CeloPredeploys["MentoFeeHandlerSeller"] = &Predeploy{Address: MentoFeeHandlerSellerAddr} + CeloPredeploys["UniswapFeeHandlerSeller"] = &Predeploy{Address: UniswapFeeHandlerSellerAddr} + CeloPredeploys["SortedOracles"] = &Predeploy{Address: SortedOraclesAddr} + CeloPredeploys["AddressSortedLinkedListWithMedian"] = &Predeploy{Address: AddressSortedLinkedListWithMedianAddr} + CeloPredeploys["FeeCurrency"] = &Predeploy{Address: FeeCurrencyAddr} + for key, predeploy := range CeloPredeploys { + Predeploys[key] = predeploy + } + for _, predeploy := range Predeploys { PredeploysByAddress[predeploy.Address] = predeploy } diff --git a/ops-bedrock/Dockerfile.l2 b/ops-bedrock/Dockerfile.l2 index 976ab27498598..a933c477e5cbf 100644 --- a/ops-bedrock/Dockerfile.l2 +++ b/ops-bedrock/Dockerfile.l2 @@ -1,4 +1,4 @@ -FROM us-docker.pkg.dev/oplabs-tools-artifacts/images/op-geth:optimism +FROM --platform=linux/amd64 us-west1-docker.pkg.dev/blockchaintestsglobaltestnet/dev-images/op-geth@sha256:c8c823b13ad01d1a528a61094c1d6fda7308013c692787692c06045d8bec9f8a RUN apk add --no-cache jq diff --git a/packages/contracts-bedrock/lib/clones-with-immutable-args b/packages/contracts-bedrock/lib/clones-with-immutable-args new file mode 160000 index 0000000000000..105efee1b9127 --- /dev/null +++ b/packages/contracts-bedrock/lib/clones-with-immutable-args @@ -0,0 +1 @@ +Subproject commit 105efee1b9127ed7f6fedf139e1fc796ce8791f2 diff --git a/packages/contracts-bedrock/package.json b/packages/contracts-bedrock/package.json index 13d1b79d93348..16ea8e0204813 100644 --- a/packages/contracts-bedrock/package.json +++ b/packages/contracts-bedrock/package.json @@ -12,6 +12,7 @@ "scripts": { "prebuild": "./scripts/checks/check-foundry-install.sh", "build": "forge build", + "build:linkedLibraries": "forge build --libraries src/celo/common/linkedlists/AddressSortedLinkedListWithMedian.sol:AddressSortedLinkedListWithMedian:0xED477A99035d0c1e11369F1D7A4e587893cc002B", "build:go-ffi": "(cd scripts/go-ffi && go build)", "autogen:invariant-docs": "npx tsx scripts/autogen/generate-invariant-docs.ts", "test": "pnpm build:go-ffi && forge test", diff --git a/packages/contracts-bedrock/scripts/DeployConfig.s.sol b/packages/contracts-bedrock/scripts/DeployConfig.s.sol index 25869e97f0807..5faf1b94b3bdd 100644 --- a/packages/contracts-bedrock/scripts/DeployConfig.s.sol +++ b/packages/contracts-bedrock/scripts/DeployConfig.s.sol @@ -91,6 +91,8 @@ contract DeployConfig is Script { bool public useInterop; + bool public deployCeloContracts; + function read(string memory _path) public { console.log("DeployConfig: reading file %s", _path); try vm.readFile(_path) returns (string memory data) { @@ -174,6 +176,9 @@ contract DeployConfig is Script { customGasTokenAddress = _readOr(_json, "$.customGasTokenAddress", address(0)); useInterop = _readOr(_json, "$.useInterop", false); + + // Celo specific config + deployCeloContracts = _readOr(_json, "$.deployCeloContracts", false); } function fork() public view returns (Fork fork_) { @@ -236,6 +241,11 @@ contract DeployConfig is Script { fundDevAccounts = _fundDevAccounts; } + /// @notice Allow the `deployCeloContracts` 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; diff --git a/packages/contracts-bedrock/scripts/L2Genesis.s.sol b/packages/contracts-bedrock/scripts/L2Genesis.s.sol index 44607c5346508..23b02e1118bbc 100644 --- a/packages/contracts-bedrock/scripts/L2Genesis.s.sol +++ b/packages/contracts-bedrock/scripts/L2Genesis.s.sol @@ -26,6 +26,19 @@ import { L1StandardBridge } from "src/L1/L1StandardBridge.sol"; import { FeeVault } from "src/universal/FeeVault.sol"; import { EIP1967Helper } from "test/mocks/EIP1967Helper.sol"; import { Process } from "scripts/libraries/Process.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 { FeeCurrencyWhitelist } from "src/celo/FeeCurrencyWhitelist.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 { AddressSortedLinkedListWithMedian } from "src/celo/common/linkedlists/AddressSortedLinkedListWithMedian.sol"; +import { StableTokenV2 } from "src/celo/StableTokenV2.sol"; interface IInitializable { function initialize(address _addr) external; @@ -134,10 +147,15 @@ 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(); } @@ -568,4 +586,190 @@ contract L2Genesis is Deployer { vm.deal(devAccounts[i], DEV_ACCOUNT_FUND_AMT); } } + + ///@notice Sets all proxies and implementations for Celo contracts + function setCeloPredeploys() internal { + console.log("Deploying Celo contracts"); + + setCeloRegistry(); + setCeloGoldToken(); + setCeloFeeHandler(); + setCeloFeeCurrencyWhitelist(); + setCeloMentoFeeHandlerSeller(); + setCeloUniswapFeeHandlerSeller(); + // setCeloSortedOracles(); + // setCeloAddressSortedLinkedListWithMedian(); + setCeloFeeCurrency(); + setFeeCurrencyDirectory(); + + address[] memory initialBalanceAddresses = new address[](1); + initialBalanceAddresses[0] = devAccounts[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 setCeloFeeCurrencyWhitelist() internal { + FeeCurrencyWhitelist kontract = new FeeCurrencyWhitelist({ test: false }); + + address precompile = CeloPredeploys.FEE_CURRENCY_WHITELIST; + 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)); + } + + // 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, deployer); + vm.stopPrank(); + vm.startPrank(deployer); + + 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(deployer); + } } diff --git a/packages/contracts-bedrock/scripts/getting-started/config-vars-celo.sh b/packages/contracts-bedrock/scripts/getting-started/config-vars-celo.sh new file mode 100755 index 0000000000000..52520eb0d07bd --- /dev/null +++ b/packages/contracts-bedrock/scripts/getting-started/config-vars-celo.sh @@ -0,0 +1,112 @@ +#!/usr/bin/env bash + +# This script is used to generate the getting-started.json configuration file +# used in the Getting Started quickstart guide on the docs site. Avoids the +# need to have the getting-started.json committed to the repo since it's an +# invalid JSON file when not filled in, which is annoying. + +reqenv() { + if [ -z "${!1}" ]; then + echo "Error: environment variable '$1' is undefined" + exit 1 + fi +} + +# Check required environment variables +reqenv "GS_ADMIN_ADDRESS" +reqenv "GS_BATCHER_ADDRESS" +reqenv "GS_PROPOSER_ADDRESS" +reqenv "GS_SEQUENCER_ADDRESS" +reqenv "L1_RPC_URL" + +# Get the finalized block timestamp and hash +block=$(cast block finalized --rpc-url "$L1_RPC_URL") +timestamp=$(echo "$block" | awk '/timestamp/ { print $2 }') +blockhash=$(echo "$block" | awk '/hash/ { print $2 }') + +# Generate the config file +config=$(cat << EOL +{ + "l1StartingBlockTag": "$blockhash", + + "l1ChainID": $L1_CHAIN_ID, + "l2ChainID": $L2_CHAIN_ID, + "l2BlockTime": 2, + "l1BlockTime": 12, + + "maxSequencerDrift": 600, + "sequencerWindowSize": 3600, + "channelTimeout": 300, + + "p2pSequencerAddress": "$GS_SEQUENCER_ADDRESS", + "batchInboxAddress": "0xff00000000000000000000000000000000042069", + "batchSenderAddress": "$GS_BATCHER_ADDRESS", + + "l2OutputOracleSubmissionInterval": 120, + "l2OutputOracleStartingBlockNumber": 0, + "l2OutputOracleStartingTimestamp": $timestamp, + + "l2OutputOracleProposer": "$GS_PROPOSER_ADDRESS", + "l2OutputOracleChallenger": "$GS_ADMIN_ADDRESS", + + "finalizationPeriodSeconds": 12, + + "proxyAdminOwner": "$GS_ADMIN_ADDRESS", + "baseFeeVaultRecipient": "$GS_ADMIN_ADDRESS", + "l1FeeVaultRecipient": "$GS_ADMIN_ADDRESS", + "sequencerFeeVaultRecipient": "$GS_ADMIN_ADDRESS", + "finalSystemOwner": "$GS_ADMIN_ADDRESS", + "superchainConfigGuardian": "$GS_ADMIN_ADDRESS", + + "baseFeeVaultMinimumWithdrawalAmount": "0x8ac7230489e80000", + "l1FeeVaultMinimumWithdrawalAmount": "0x8ac7230489e80000", + "sequencerFeeVaultMinimumWithdrawalAmount": "0x8ac7230489e80000", + "baseFeeVaultWithdrawalNetwork": 0, + "l1FeeVaultWithdrawalNetwork": 0, + "sequencerFeeVaultWithdrawalNetwork": 0, + + "gasPriceOracleOverhead": 2100, + "gasPriceOracleScalar": 1000000, + + "enableGovernance": true, + "governanceTokenSymbol": "OP", + "governanceTokenName": "Optimism", + "governanceTokenOwner": "$GS_ADMIN_ADDRESS", + + "l2GenesisBlockGasLimit": "0x1c9c380", + "l2GenesisBlockBaseFeePerGas": "0x3b9aca00", + "l2GenesisRegolithTimeOffset": "0x0", + + "eip1559Denominator": 50, + "eip1559DenominatorCanyon": 250, + "eip1559Elasticity": 6, + + "l2GenesisDeltaTimeOffset": null, + "l2GenesisCanyonTimeOffset": "0x0", + + "systemConfigStartBlock": 0, + + "requiredProtocolVersion": "0x0000000000000000000000000000000000000000000000000000000000000000", + "recommendedProtocolVersion": "0x0000000000000000000000000000000000000000000000000000000000000000", + + "faultGameAbsolutePrestate": "0x03c7ae758795765c6664a5d39bf63841c71ff191e9189522bad8ebff5d4eca98", + "faultGameMaxDepth": 44, + "faultGameMaxDuration": 1200, + "faultGameGenesisBlock": 0, + "faultGameGenesisOutputRoot": "0x0000000000000000000000000000000000000000000000000000000000000000", + "faultGameSplitDepth": 14, + + "preimageOracleMinProposalSize": 1800000, + "preimageOracleChallengePeriod": 86400, + + "fundDevAccounts": false, + "useFaultProofs": false, + "proofMaturityDelaySeconds": 604800, + "disputeGameFinalityDelaySeconds": 302400, + "respectedGameType": 0 +} +EOL +) + +# Write the config file +echo "$config" > deploy-config/$DEPLOYMENT_CONTEXT.json diff --git a/packages/contracts-bedrock/scripts/getting-started/config-vars-op-stack.sh b/packages/contracts-bedrock/scripts/getting-started/config-vars-op-stack.sh new file mode 100755 index 0000000000000..6bd06486a139a --- /dev/null +++ b/packages/contracts-bedrock/scripts/getting-started/config-vars-op-stack.sh @@ -0,0 +1,106 @@ +#!/usr/bin/env bash + +# This script is used to generate the getting-started.json configuration file +# used in the Getting Started quickstart guide on the docs site. Avoids the +# need to have the getting-started.json committed to the repo since it's an +# invalid JSON file when not filled in, which is annoying. + +reqenv() { + if [ -z "${!1}" ]; then + echo "Error: environment variable '$1' is undefined" + exit 1 + fi +} + +# Check required environment variables +reqenv "GS_ADMIN_ADDRESS" +reqenv "GS_BATCHER_ADDRESS" +reqenv "GS_PROPOSER_ADDRESS" +reqenv "GS_SEQUENCER_ADDRESS" +reqenv "L1_RPC_URL" + +# Get the finalized block timestamp and hash +block=$(cast block finalized --rpc-url "$L1_RPC_URL") +timestamp=$(echo "$block" | awk '/timestamp/ { print $2 }') +blockhash=$(echo "$block" | awk '/hash/ { print $2 }') + +# Generate the config file +config=$(cat << EOL +{ + "l1StartingBlockTag": "$blockhash", + + "l1ChainID": $L1_CHAIN_ID, + "l2ChainID": $L2_CHAIN_ID, + "l2BlockTime": 2, + "l1BlockTime": 12, + + "maxSequencerDrift": 600, + "sequencerWindowSize": 3600, + "channelTimeout": 300, + + "p2pSequencerAddress": "$GS_SEQUENCER_ADDRESS", + "batchInboxAddress": "0xff00000000000000000000000000000000042069", + "batchSenderAddress": "$GS_BATCHER_ADDRESS", + + "l2OutputOracleSubmissionInterval": 120, + "l2OutputOracleStartingBlockNumber": 0, + "l2OutputOracleStartingTimestamp": $timestamp, + + "l2OutputOracleProposer": "$GS_PROPOSER_ADDRESS", + "l2OutputOracleChallenger": "$GS_ADMIN_ADDRESS", + + "finalizationPeriodSeconds": 12, + + "proxyAdminOwner": "$GS_ADMIN_ADDRESS", + "baseFeeVaultRecipient": "$GS_ADMIN_ADDRESS", + "l1FeeVaultRecipient": "$GS_ADMIN_ADDRESS", + "sequencerFeeVaultRecipient": "$GS_ADMIN_ADDRESS", + "finalSystemOwner": "$GS_ADMIN_ADDRESS", + "superchainConfigGuardian": "$GS_ADMIN_ADDRESS", + + "baseFeeVaultMinimumWithdrawalAmount": "0x8ac7230489e80000", + "l1FeeVaultMinimumWithdrawalAmount": "0x8ac7230489e80000", + "sequencerFeeVaultMinimumWithdrawalAmount": "0x8ac7230489e80000", + "baseFeeVaultWithdrawalNetwork": 0, + "l1FeeVaultWithdrawalNetwork": 0, + "sequencerFeeVaultWithdrawalNetwork": 0, + + "gasPriceOracleOverhead": 2100, + "gasPriceOracleScalar": 1000000, + + "enableGovernance": true, + "governanceTokenSymbol": "OP", + "governanceTokenName": "Optimism", + "governanceTokenOwner": "$GS_ADMIN_ADDRESS", + + "l2GenesisBlockGasLimit": "0x1c9c380", + "l2GenesisBlockBaseFeePerGas": "0x3b9aca00", + "l2GenesisRegolithTimeOffset": "0x0", + + "eip1559Denominator": 50, + "eip1559DenominatorCanyon": 250, + "eip1559Elasticity": 6, + + "l2GenesisDeltaTimeOffset": null, + "l2GenesisCanyonTimeOffset": "0x0", + + "systemConfigStartBlock": 0, + + "requiredProtocolVersion": "0x0000000000000000000000000000000000000000000000000000000000000000", + "recommendedProtocolVersion": "0x0000000000000000000000000000000000000000000000000000000000000000", + + "faultGameAbsolutePrestate": "0x03c7ae758795765c6664a5d39bf63841c71ff191e9189522bad8ebff5d4eca98", + "faultGameMaxDepth": 44, + "faultGameMaxDuration": 1200, + "faultGameGenesisBlock": 0, + "faultGameGenesisOutputRoot": "0x0000000000000000000000000000000000000000000000000000000000000000", + "faultGameSplitDepth": 14, + + "preimageOracleMinProposalSize": 1800000, + "preimageOracleChallengePeriod": 86400 +} +EOL +) + +# Write the config file +echo "$config" > deploy-config/$DEPLOYMENT_CONTEXT.json diff --git a/packages/contracts-bedrock/semver-lock.json b/packages/contracts-bedrock/semver-lock.json index a98510e2dfcd5..3b1926edf5a5f 100644 --- a/packages/contracts-bedrock/semver-lock.json +++ b/packages/contracts-bedrock/semver-lock.json @@ -176,11 +176,11 @@ "sourceCodeHash": "0x323f707d4cebc38f59f9241098a1d7e5e790ffcaf1719065edabf4cb794ac745" }, "src/universal/OptimismMintableERC20.sol": { - "initCodeHash": "0x7c6e1cf86cf8622d8beceafa3610ff88eceb3b0fafff0491bfa26a7b876c4d9a", - "sourceCodeHash": "0x52737b23e99bf79dd2c23196b3298e80aa41f740efc6adc7916e696833eb546a" + "initCodeHash": "0x9b18a1ae827de2c28d3b4f92d9fc718889f23f37fd973cf07ea31b93b8f71d87", + "sourceCodeHash": "0x9d5be3fd300151aae4722eb2f76c3ab0256adad0f4b8b2dec80bbeb2cd142ca1" }, "src/universal/OptimismMintableERC20Factory.sol": { - "initCodeHash": "0xf6f522681e7ae940cb778db68004f122b25194296a65bba7ad1d792bd593c4a6", + "initCodeHash": "0xf433cfb2b9a65b29c1dd9b6a724bddd3c7bb64e49273492d5bd52e7cb424c4e2", "sourceCodeHash": "0x9b8c73ea139f13028008eedef53a6b07576cd6b08979574e6dde3192656e9268" }, "src/universal/OptimismMintableERC721.sol": { 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/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/FeeCurrencyWhitelist.json b/packages/contracts-bedrock/snapshots/abi/FeeCurrencyWhitelist.json new file mode 100644 index 0000000000000..47fa034f515f4 --- /dev/null +++ b/packages/contracts-bedrock/snapshots/abi/FeeCurrencyWhitelist.json @@ -0,0 +1,202 @@ +[ + { + "inputs": [ + { + "internalType": "bool", + "name": "test", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "tokenAddress", + "type": "address" + } + ], + "name": "addToken", + "outputs": [], + "stateMutability": "nonpayable", + "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": "getWhitelist", + "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": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "tokenAddress", + "type": "address" + }, + { + "internalType": "uint256", + "name": "index", + "type": "uint256" + } + ], + "name": "removeToken", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "whitelist", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "FeeCurrencyWhitelistRemoved", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "FeeCurrencyWhitelisted", + "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" + } +] \ 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/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..de195ff0c5c7f --- /dev/null +++ b/packages/contracts-bedrock/snapshots/abi/MockSortedOracles.json @@ -0,0 +1,225 @@ +[ + { + "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": "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 3c6f5e9ab3480..8ced7535ac1de 100644 --- a/packages/contracts-bedrock/snapshots/abi/OptimismMintableERC20.json +++ b/packages/contracts-bedrock/snapshots/abi/OptimismMintableERC20.json @@ -154,6 +154,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/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/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/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/FeeCurrencyWhitelist.json b/packages/contracts-bedrock/snapshots/storageLayout/FeeCurrencyWhitelist.json new file mode 100644 index 0000000000000..fed27094a71bd --- /dev/null +++ b/packages/contracts-bedrock/snapshots/storageLayout/FeeCurrencyWhitelist.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": "whitelist", + "offset": 0, + "slot": "1", + "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/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/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/celo/CalledByVm.sol b/packages/contracts-bedrock/src/celo/CalledByVm.sol new file mode 100644 index 0000000000000..c3f6efe12072e --- /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.15; + +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..b52b0de6509a1 --- /dev/null +++ b/packages/contracts-bedrock/src/celo/CeloPredeploys.sol @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import { console2 as console } from "forge-std/console2.sol"; + +/// @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 FEE_CURRENCY_WHITELIST = 0xBB024E9cdCB2f9E34d893630D19611B8A5381b3c; + 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 BRIDGED_ETH = 0x4200000000000000000000000000000000001023; + address internal constant FEE_CURRENCY_DIRECTORY = 0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB; + 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 == FEE_CURRENCY_WHITELIST) return "FeeCurrencyWhitelist"; + 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 == BRIDGED_ETH) return "BridgedEth"; + 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..7da4cfb35ddfe --- /dev/null +++ b/packages/contracts-bedrock/src/celo/CeloRegistry.sol @@ -0,0 +1,95 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.15; + +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/FeeCurrency.sol b/packages/contracts-bedrock/src/celo/FeeCurrency.sol new file mode 100644 index 0000000000000..59516e3d9e485 --- /dev/null +++ b/packages/contracts-bedrock/src/celo/FeeCurrency.sol @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.15; + +import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; + +abstract contract FeeCurrency 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) public 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 + ) + public + 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/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/FeeCurrencyWhitelist.sol b/packages/contracts-bedrock/src/celo/FeeCurrencyWhitelist.sol new file mode 100644 index 0000000000000..d52d4a155ea65 --- /dev/null +++ b/packages/contracts-bedrock/src/celo/FeeCurrencyWhitelist.sol @@ -0,0 +1,77 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.15; + +import "../../lib/openzeppelin-contracts/contracts/access/Ownable.sol"; + +import "./interfaces/IFeeCurrencyWhitelist.sol"; + +import "./common/Initializable.sol"; + +import "./common/interfaces/ICeloVersionedContract.sol"; + +/** + * @title Holds a whitelist of the ERC20+ tokens that can be used to pay for gas + * Not including the native Celo token + */ +contract FeeCurrencyWhitelist is IFeeCurrencyWhitelist, Ownable, Initializable, ICeloVersionedContract { + // Array of all the tokens enabled + address[] public whitelist; + + event FeeCurrencyWhitelisted(address token); + + event FeeCurrencyWhitelistRemoved(address token); + + /** + * @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 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, 1, 0); + } + + /** + * @notice Removes a Mento token as enabled fee token. Tokens added with addToken should be + * removed with this function. + * @param tokenAddress The address of the token to remove. + * @param index The index of the token in the whitelist array. + */ + function removeToken(address tokenAddress, uint256 index) public onlyOwner { + require(whitelist[index] == tokenAddress, "Index does not match"); + uint256 length = whitelist.length; + whitelist[index] = whitelist[length - 1]; + whitelist.pop(); + emit FeeCurrencyWhitelistRemoved(tokenAddress); + } + + /** + * @dev Add a token to the whitelist + * @param tokenAddress The address of the token to add. + */ + function addToken(address tokenAddress) external onlyOwner { + whitelist.push(tokenAddress); + emit FeeCurrencyWhitelisted(tokenAddress); + } + + /** + * @return a list of all tokens enabled as gas fee currency. + */ + function getWhitelist() external view returns (address[] memory) { + return whitelist; + } +} diff --git a/packages/contracts-bedrock/src/celo/FeeHandler.sol b/packages/contracts-bedrock/src/celo/FeeHandler.sol new file mode 100644 index 0000000000000..00a1b0bde4fcb --- /dev/null +++ b/packages/contracts-bedrock/src/celo/FeeHandler.sol @@ -0,0 +1,543 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.15; + +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..4d22125af4d64 --- /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.15; + +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..e7236678670a7 --- /dev/null +++ b/packages/contracts-bedrock/src/celo/GoldToken.sol @@ -0,0 +1,272 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.15; + +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..7929728eef4ed --- /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.15; + +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..e5a9ff455f391 --- /dev/null +++ b/packages/contracts-bedrock/src/celo/MentoFeeHandlerSeller.sol @@ -0,0 +1,85 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.15; + +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..68632df65abc9 --- /dev/null +++ b/packages/contracts-bedrock/src/celo/StableTokenV2.sol @@ -0,0 +1,336 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.15; + +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..54ce14eaf37cf --- /dev/null +++ b/packages/contracts-bedrock/src/celo/UniswapFeeHandlerSeller.sol @@ -0,0 +1,193 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.15; + +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..0764125d65c19 --- /dev/null +++ b/packages/contracts-bedrock/src/celo/UsingRegistry.sol @@ -0,0 +1,126 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.15; + +import "../../lib/openzeppelin-contracts/contracts/access/Ownable.sol"; +import "../../lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; + +import "./interfaces/IAccounts.sol"; +import "./interfaces/IFeeCurrencyWhitelist.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 FEE_CURRENCY_WHITELIST_REGISTRY_ID = keccak256(abi.encodePacked("FeeCurrencyWhitelist")); + 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 getFeeCurrencyWhitelistRegistry() internal view returns (IFeeCurrencyWhitelist) { + return IFeeCurrencyWhitelist(registry.getAddressForOrDie(FEE_CURRENCY_WHITELIST_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..613da18562198 --- /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.15; + +/** + * @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..7541ea6fa5717 --- /dev/null +++ b/packages/contracts-bedrock/src/celo/common/Freezable.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.15; + +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..92baac5494d3b --- /dev/null +++ b/packages/contracts-bedrock/src/celo/common/Initializable.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.15; + +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..5bf2033f31726 --- /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.15; + +/** + * @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..37b1538c2a121 --- /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.15; + +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..b707a446a685a --- /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.15; + +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..c3a9df0ee324a --- /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.15; + +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..38ae7359e0e06 --- /dev/null +++ b/packages/contracts-bedrock/src/celo/common/linkedlists/AddressSortedLinkedList.sol @@ -0,0 +1,267 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.15; + +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..2ddf56612244e --- /dev/null +++ b/packages/contracts-bedrock/src/celo/common/linkedlists/AddressSortedLinkedListWithMedian.sol @@ -0,0 +1,160 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.15; + +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..d04e8b7e027cb --- /dev/null +++ b/packages/contracts-bedrock/src/celo/common/linkedlists/LinkedList.sol @@ -0,0 +1,162 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.15; + +/** + * @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..9703cf565523d --- /dev/null +++ b/packages/contracts-bedrock/src/celo/common/linkedlists/SortedLinkedList.sol @@ -0,0 +1,218 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.15; + +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..458ef55422077 --- /dev/null +++ b/packages/contracts-bedrock/src/celo/common/linkedlists/SortedLinkedListWithMedian.sol @@ -0,0 +1,253 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.15; + +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..f099ce364a270 --- /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.15; + +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..883844ea8f219 --- /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.15; + +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..38002d58914c7 --- /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.15; + +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..e211ce7399e37 --- /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.15; + +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..8a10e91fc8129 --- /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.15; + +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..5c1a1d7a8f484 --- /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.15; + +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..87c145a4a1bb9 --- /dev/null +++ b/packages/contracts-bedrock/src/celo/identity/interfaces/IEscrow.sol @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.15; + +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..c0586eb9e44dc --- /dev/null +++ b/packages/contracts-bedrock/src/celo/identity/interfaces/IFederatedAttestations.sol @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.15; + +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..ca188432c0dda --- /dev/null +++ b/packages/contracts-bedrock/src/celo/identity/interfaces/IOdisPayments.sol @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.15; + +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..65cf3082d685c --- /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.15; + +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..734dcddeb941d --- /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.15; + +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..95e586da3954f --- /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.15; + +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 copy.sol b/packages/contracts-bedrock/src/celo/interfaces/ICeloToken copy.sol new file mode 100644 index 0000000000000..5bf2033f31726 --- /dev/null +++ b/packages/contracts-bedrock/src/celo/interfaces/ICeloToken copy.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.15; + +/** + * @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/ICeloToken.sol b/packages/contracts-bedrock/src/celo/interfaces/ICeloToken.sol new file mode 100644 index 0000000000000..5bf2033f31726 --- /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.15; + +/** + * @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..37b1538c2a121 --- /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.15; + +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/IFeeCurrencyWhitelist.sol b/packages/contracts-bedrock/src/celo/interfaces/IFeeCurrencyWhitelist.sol new file mode 100644 index 0000000000000..e2c6b60ed6375 --- /dev/null +++ b/packages/contracts-bedrock/src/celo/interfaces/IFeeCurrencyWhitelist.sol @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity ^0.8.15; + +interface IFeeCurrencyWhitelist { + function addToken(address) external; + function getWhitelist() external view returns (address[] memory); +} 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..a629b3325a5ba --- /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.15; + +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..5c7f392814b61 --- /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.15; + +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..5828bee3c7467 --- /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.15; + +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..b3ae66a92756c --- /dev/null +++ b/packages/contracts-bedrock/src/celo/interfaces/IOracle.sol @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.5.13 <0.9.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..b13febff81fc8 --- /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.5.17 <9.0.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..b309071d9f0ad --- /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.15; + +/** + * @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..4e15e8a8750d4 --- /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.15; + +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..14f77c10549a1 --- /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.15; + +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..c0b681dfb8aee --- /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.15; + +/** + * @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..d2209dac5d2c8 --- /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.15; + +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..26430da7a3bea --- /dev/null +++ b/packages/contracts-bedrock/src/celo/stability/interfaces/IBreakerBox.sol @@ -0,0 +1,140 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.5.13 <0.9.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..b3ae66a92756c --- /dev/null +++ b/packages/contracts-bedrock/src/celo/stability/interfaces/IOracle.sol @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.5.13 <0.9.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..ecea4210cd40e --- /dev/null +++ b/packages/contracts-bedrock/src/celo/stability/interfaces/ISortedOracles.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.15; + +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..329f057f0649a --- /dev/null +++ b/packages/contracts-bedrock/src/celo/testing/MockSortedOracles.sol @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.15; + +/** + * @title A mock SortedOracles for testing. + */ +contract MockSortedOracles { + 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 medianRate(address token) external 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..14c6495920a1f --- /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.15; + +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..f1755edb137d0 --- /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.15; + +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/universal/OptimismMintableERC20.sol b/packages/contracts-bedrock/src/universal/OptimismMintableERC20.sol index 43ddd65424f89..915dcefc1761d 100644 --- a/packages/contracts-bedrock/src/universal/OptimismMintableERC20.sol +++ b/packages/contracts-bedrock/src/universal/OptimismMintableERC20.sol @@ -5,6 +5,7 @@ import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import { IERC165 } from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; import { ILegacyMintableERC20, IOptimismMintableERC20 } from "src/universal/IOptimismMintableERC20.sol"; import { ISemver } from "src/universal/ISemver.sol"; +import { FeeCurrency } from "src/celo/FeeCurrency.sol"; /// @title OptimismMintableERC20 /// @notice OptimismMintableERC20 is a standard extension of the base ERC20 token contract designed @@ -12,7 +13,7 @@ import { ISemver } from "src/universal/ISemver.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 IOptimismMintableERC20, ILegacyMintableERC20, ERC20, ISemver { +contract OptimismMintableERC20 is IOptimismMintableERC20, ILegacyMintableERC20, ERC20, ISemver, FeeCurrency { /// @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/L2Genesis.t.sol b/packages/contracts-bedrock/test/L2Genesis.t.sol index f851f62d634d8..01451960d5bff 100644 --- a/packages/contracts-bedrock/test/L2Genesis.t.sol +++ b/packages/contracts-bedrock/test/L2Genesis.t.sol @@ -181,6 +181,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); @@ -190,6 +191,7 @@ contract L2GenesisTest is Test { expected += 256; // precompiles expected += 12; // preinstalls expected += 1; // 4788 deployer account + expected += 16; // Celo contracts // 16 prefunded dev accounts are excluded assertEq(expected, getJSONKeyCount(_path), "key count check");