diff --git a/.github/scripts/update_move_toml.sh b/.github/scripts/update_move_toml.sh new file mode 100644 index 000000000..b0b11205f --- /dev/null +++ b/.github/scripts/update_move_toml.sh @@ -0,0 +1,50 @@ +#!/bin/bash + +MOVE_TOML_PATH="protocol-units/bridge/move-modules/Move.toml" + +# Initialize Aptos and capture output +INIT_OUTPUT=$(aptos init 2>&1) +echo "$INIT_OUTPUT" + +# Extract the account address from the initialization output +ADDRESS=$(echo "$INIT_OUTPUT" | grep -oE '0x[a-f0-9]{64}' | head -1) +if [[ -z "$ADDRESS" ]]; then + echo "Error: Failed to extract the Aptos account address." + exit 1 +fi + +# Generate a random seed +RANDOM_SEED=$(shuf -i 0-1000000 -n 1) +echo "Seed: $RANDOM_SEED" + +# Derive the resource account address using the random seed +RESOURCE_OUTPUT=$(aptos account derive-resource-account-address --address "$ADDRESS" --seed "" --seed-encoding hex 2>&1) +echo "Resource address derivation output: $RESOURCE_OUTPUT" + +# Extract the resource address directly +RESOURCE_ADDRESS=$(echo "$RESOURCE_OUTPUT" | grep -oE '[a-f0-9]{64}') + +if [[ -z "$RESOURCE_ADDRESS" ]]; then + echo "Error: Failed to extract the resource account address." + exit 1 +fi + +# Prepend the 0x to the resource address +RESOURCE_ADDRESS="0x$RESOURCE_ADDRESS" + +echo "Extracted address: $ADDRESS" +echo "Derived resource address: $RESOURCE_ADDRESS" + +# Update the Move.toml file with the addresses +sed -i "s/^resource_addr = \".*\"/resource_addr = \"$RESOURCE_ADDRESS\"/" "$MOVE_TOML_PATH" +sed -i "s/^atomic_bridge = \".*\"/atomic_bridge = \"$RESOURCE_ADDRESS\"/" "$MOVE_TOML_PATH" +sed -i "s/^moveth = \".*\"/moveth = \"$RESOURCE_ADDRESS\"/" "$MOVE_TOML_PATH" +sed -i "s/^master_minter = \".*\"/master_minter = \"$RESOURCE_ADDRESS\"/" "$MOVE_TOML_PATH" +sed -i "s/^minter = \".*\"/minter = \"$RESOURCE_ADDRESS\"/" "$MOVE_TOML_PATH" +sed -i "s/^admin = \".*\"/admin = \"$RESOURCE_ADDRESS\"/" "$MOVE_TOML_PATH" +sed -i "s/^origin_addr = \".*\"/origin_addr = \"$ADDRESS\"/" "$MOVE_TOML_PATH" + +echo "Move.toml updated with ADDRESS: $ADDRESS and RESOURCE_ADDRESS: $RESOURCE_ADDRESS" + +echo "Contents of $MOVE_TOML_PATH:" +cat "$MOVE_TOML_PATH" \ No newline at end of file diff --git a/.github/workflows/build-push-containers-select.yml b/.github/workflows/build-push-containers-select.yml index 58aa5d07e..bb65d7a10 100644 --- a/.github/workflows/build-push-containers-select.yml +++ b/.github/workflows/build-push-containers-select.yml @@ -8,7 +8,7 @@ on: - synchronize push: branches: - - main + - '**' jobs: @@ -485,6 +485,64 @@ jobs: - name: Build and Push Docker image movement run: | ./scripts/movement/manifest suzuka-client-e2e-simple-interaction + + suzuka-indexer-build: + permissions: + contents: read + packages: write + strategy: + matrix: + architecture: [x86_64, arm64] + + runs-on: ${{ matrix.architecture == 'x86_64' && 'buildjet-8vcpu-ubuntu-2204' || 'buildjet-8vcpu-ubuntu-2204-arm' }} + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Login to Docker Hub + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Login to Docker Hub to Avoid Rate Limiting + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKER_HUB_USERNAME }} + password: ${{ secrets.DOCKER_HUB_TOKEN }} + + - name: Build and Push Docker image movement + run: | + ./scripts/movement/build-push-image suzuka-indexer + + suzuka-indexer-manifest: + permissions: + contents: read + packages: write + needs: suzuka-indexer-build + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Login to Docker Hub + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Login to Docker Hub to Avoid Rate Limiting + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKER_HUB_USERNAME }} + password: ${{ secrets.DOCKER_HUB_TOKEN }} + + - name: Build and Push Docker image movement + run: | + ./scripts/movement/manifest suzuka-indexer container-checks: if: github.event.label.name == 'cicd:suzuka-containers' || github.ref == 'refs/heads/main' diff --git a/.github/workflows/checks-select.yml b/.github/workflows/checks-select.yml index 3b863d272..18fbc9d5b 100755 --- a/.github/workflows/checks-select.yml +++ b/.github/workflows/checks-select.yml @@ -8,10 +8,11 @@ on: - synchronize push: branches: - - main + - '**' jobs: - cargo-check: + + build: strategy: matrix: include: @@ -31,8 +32,44 @@ jobs: - name: Install Nix uses: DeterminateSystems/nix-installer-action@main + - uses: cachix/cachix-action@v15 + with: + name: movementlabs + authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}' + - name: Run Cargo Check in nix environment - run: nix develop --command bash -c "cargo check --all-targets" + run: | + nix develop --command bash -c "cargo check --all-targets" + + unit-tests: + strategy: + matrix: + include: + - os: ubuntu-22.04 + arch: x86_64 + runs-on: buildjet-8vcpu-ubuntu-2204 + - os: macos-13-latest + arch: arm64 + runs-on: macos-13-xlarge + + runs-on: ${{ matrix.runs-on }} + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Install Nix + uses: DeterminateSystems/nix-installer-action@main + + - name: Run unit tests in nix environment + run: | + nix develop --command bash < +aws ssm start-session \ + --target "${INSTANCE_ID}" \ + --region us-east-1 \ + --document-name AWS-StartPortForwardingSession \ + --parameters '{"portNumber":["30734"],"localPortNumber":["30734"]}' +``` + +test +```bash +# brew install grpcurl +grpcurl -plaintext localhost:30734 list aptos.indexer.v1.RawData +``` + +2. Make sure that all other containers are stop +```bash +docker ps +``` + +3. Clean previous runs and create required `config.json` by the indexer in the +proper location. + +In another terminal +```bash + +GIT_ROOT=$(git rev-parse --show-toplevel) +DOT_MOVEMENT_PATH="${HOME}/.movement" +INDEXER_JSON_CONF_SRC="${GIT_ROOT}"/docker/compose/suzuka-indexer/indexer-config.json +INDEXER_JSON_CONF_DST="${DOT_MOVEMENT_PATH}"/config.json +docker rm -f $(docker ps -aq) \ + ; docker volume rm $(docker volume ls -q) \ + ; rm -rf "${DOT_MOVEMENT_PATH}"/* \ + && cp "${INDEXER_JSON_CONF_SRC}" "${INDEXER_JSON_CONF_DST}" \ + && docker compose \ + --env-file docker/compose/suzuka-indexer/.remote-suzuka-node.env \ + -f docker/compose/suzuka-indexer/docker-compose.indexer.yml \ + up +``` + +logs +```bash +docker compose \ + --env-file docker/compose/suzuka-indexer/.remote-suzuka-node.env \ + -f docker/compose/suzuka-indexer/docker-compose.indexer.yml \ + logs suzuka-indexer +``` + +attach to suzuka-indexer container +```bash +docker compose \ + --env-file docker/compose/suzuka-indexer/.remote-suzuka-node.env \ + -f docker/compose/suzuka-indexer/docker-compose.indexer.yml\ + exec -it suzuka-indexer /bin/sh +``` + +check if indexer can reach remote rpc +```bash +docker compose \ + --env-file docker/compose/suzuka-indexer/.remote-suzuka-node.env \ + -f docker/compose/suzuka-indexer/docker-compose.indexer.yml\ + exec -it suzuka-indexer nc -vz host.docker.internal 30734 +``` + +check size of local DB on disk +```bash +docker run --rm -v suzuka-indexer_postgres_data:/volume alpine sh -c "du -sh /volume" +``` \ No newline at end of file diff --git a/docker/compose/suzuka-indexer/docker-compose.hasura.yml b/docker/compose/suzuka-indexer/docker-compose.hasura.yml new file mode 100644 index 000000000..4ff5c7a7a --- /dev/null +++ b/docker/compose/suzuka-indexer/docker-compose.hasura.yml @@ -0,0 +1,47 @@ +services: + + graphql-engine: + container_name: graphql-engine + image: hasura/graphql-engine:v2.40.0 + ports: + - "8085:8085" + restart: always + environment: + HASURA_GRAPHQL_SERVER_PORT: 8085 + ## postgres database to store Hasura metadata + HASURA_GRAPHQL_METADATA_DATABASE_URL: postgresql://postgres:password@${POSTGRES_DB_HOST}:5432/postgres + HASURA_GRAPHQL_DATABASE_URL: postgresql://postgres:password@postgres:5432/${POSTGRES_DB_HOST} + ## this env var can be used to add the above postgres database to Hasura as a data source. this can be removed/updated based on your needs + PG_DATABASE_URL: postgres://postgres:postgres:password@${POSTGRES_DB_HOST}:5432/postgres + ## enable the console served by server + HASURA_GRAPHQL_ENABLE_CONSOLE: "true" # set to "false" to disable console + ## enable debugging mode. It is recommended to disable this in production + HASURA_GRAPHQL_DEV_MODE: "true" + HASURA_GRAPHQL_ENABLED_LOG_TYPES: startup, http-log, webhook-log, websocket-log, query-log + ## uncomment next line to run console offline (i.e load console assets from server instead of CDN) + # HASURA_GRAPHQL_CONSOLE_ASSETS_DIR: /srv/console-assets + ## uncomment next line to set an admin secret + # HASURA_GRAPHQL_ADMIN_SECRET: myadminsecretkey + HASURA_GRAPHQL_METADATA_DEFAULTS: '{"backend_configs":{"dataconnector":{"athena":{"uri":"http://data-connector-agent:8081/api/v1/athena"},"mariadb":{"uri":"http://data-connector-agent:8081/api/v1/mariadb"},"mysql8":{"uri":"http://data-connector-agent:8081/api/v1/mysql"},"oracle":{"uri":"http://data-connector-agent:8081/api/v1/oracle"},"snowflake":{"uri":"http://data-connector-agent:8081/api/v1/snowflake"}}}}' + depends_on: + data-connector-agent: + condition: service_healthy + + data-connector-agent: + image: hasura/graphql-data-connector:v2.40.0 + restart: always + ports: + - 8081:8081 + environment: + QUARKUS_LOG_LEVEL: ERROR # FATAL, ERROR, WARN, INFO, DEBUG, TRACE + ## https://quarkus.io/guides/opentelemetry#configuration-reference + QUARKUS_OPENTELEMETRY_ENABLED: "false" + ## QUARKUS_OPENTELEMETRY_TRACER_EXPORTER_OTLP_ENDPOINT: http://jaeger:4317 + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:8081/api/v1/athena/health"] + interval: 5s + timeout: 10s + retries: 8 + start_period: 8s +volumes: + db_data: diff --git a/docker/compose/suzuka-indexer/docker-compose.indexer.yml b/docker/compose/suzuka-indexer/docker-compose.indexer.yml new file mode 100644 index 000000000..7938e9759 --- /dev/null +++ b/docker/compose/suzuka-indexer/docker-compose.indexer.yml @@ -0,0 +1,87 @@ +services: + + postgres: + image: postgres:15 + user: postgres + command: postgres -c shared_buffers=256MB -c max_connections=1000 -c unix_socket_directories='/tmp' + container_name: postgres + environment: + - POSTGRES_USER=postgres + - POSTGRES_PASSWORD=password + - POSTGRES_DB=postgres + - POSTGRES_DB_HOST=${POSTGRES_DB_HOST} + ports: + - "5432:5432" + volumes: + - postgres_data:/var/lib/postgresql/data # Persistent volume for PostgreSQL data + restart: on-failure:3 + healthcheck: + test: ["CMD-SHELL", "pg_isready"] + interval: 1s + timeout: 5s + retries: 10 + + suzuka-indexer: + image: ghcr.io/movementlabsxyz/suzuka-indexer:${CONTAINER_REV} + # entrypoint: '/bin/sh -c "tail -f /dev/null"' + container_name: suzuka-indexer + environment: + - DOT_MOVEMENT_PATH=/.movement + - MAPTOS_INDEXER_GRPC_LISTEN_HOSTNAME=${MAPTOS_INDEXER_GRPC_LISTEN_HOSTNAME} + - INDEXER_PROCESSOR_POSTGRES_CONNECTION_STRING=${INDEXER_PROCESSOR_POSTGRES_CONNECTION_STRING} + volumes: + - ${DOT_MOVEMENT_PATH}:/.movement + restart: always + depends_on: + - postgres + + graphql-engine: + image: hasura/graphql-engine:v2.40.0 + ports: + - "8085:8085" + restart: always + environment: + HASURA_GRAPHQL_SERVER_PORT: 8085 + ## postgres database to store Hasura metadata + HASURA_GRAPHQL_METADATA_DATABASE_URL: postgresql://postgres:password@${POSTGRES_DB_HOST}:5432/postgres + HASURA_GRAPHQL_DATABASE_URL: postgresql://postgres:password@${POSTGRES_DB_HOST}:5432/postgres + ## this env var can be used to add the above postgres database to Hasura as a data source. this can be removed/updated based on your needs + PG_DATABASE_URL: postgres://postgres:postgres:password@${POSTGRES_DB_HOST}:5432/postgres + ## enable the console served by server + HASURA_GRAPHQL_ENABLE_CONSOLE: "true" # set to "false" to disable console + ## enable debugging mode. It is recommended to disable this in production + HASURA_GRAPHQL_DEV_MODE: "true" + HASURA_GRAPHQL_ENABLED_LOG_TYPES: startup, http-log, webhook-log, websocket-log, query-log + ## uncomment next line to run console offline (i.e load console assets from server instead of CDN) + # HASURA_GRAPHQL_CONSOLE_ASSETS_DIR: /srv/console-assets + ## uncomment next line to set an admin secret + # HASURA_GRAPHQL_ADMIN_SECRET: myadminsecretkey + HASURA_GRAPHQL_METADATA_DEFAULTS: '{"backend_configs":{"dataconnector":{"athena":{"uri":"http://data-connector-agent:8081/api/v1/athena"},"mariadb":{"uri":"http://data-connector-agent:8081/api/v1/mariadb"},"mysql8":{"uri":"http://data-connector-agent:8081/api/v1/mysql"},"oracle":{"uri":"http://data-connector-agent:8081/api/v1/oracle"},"snowflake":{"uri":"http://data-connector-agent:8081/api/v1/snowflake"}}}}' + depends_on: + data-connector-agent: + condition: service_healthy + + data-connector-agent: + image: hasura/graphql-data-connector:v2.40.0 + restart: always + ports: + - 8081:8081 + environment: + QUARKUS_LOG_LEVEL: ERROR # FATAL, ERROR, WARN, INFO, DEBUG, TRACE + ## https://quarkus.io/guides/opentelemetry#configuration-reference + QUARKUS_OPENTELEMETRY_ENABLED: "false" + ## QUARKUS_OPENTELEMETRY_TRACER_EXPORTER_OTLP_ENDPOINT: http://jaeger:4317 + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:8081/api/v1/athena/health"] + interval: 5s + timeout: 10s + retries: 5 + start_period: 5s + depends_on: + - suzuka-indexer + +volumes: + postgres_data: + driver: local + + diff --git a/docker/compose/suzuka-indexer/docker-compose.local-development.indexer.yml b/docker/compose/suzuka-indexer/docker-compose.local-development.indexer.yml new file mode 100644 index 000000000..28a412b86 --- /dev/null +++ b/docker/compose/suzuka-indexer/docker-compose.local-development.indexer.yml @@ -0,0 +1,85 @@ +services: + + postgres: + image: postgres:15 + user: postgres + command: postgres -c shared_buffers=256MB -c max_connections=1000 -c unix_socket_directories='/tmp' + container_name: postgres + environment: + - POSTGRES_USER=postgres + - POSTGRES_PASSWORD=password + - POSTGRES_DB=postgres + - POSTGRES_DB_HOST=${POSTGRES_DB_HOST} + ports: + - "5432:5432" + restart: on-failure:3 + healthcheck: + test: ["CMD-SHELL", "pg_isready"] + interval: 1s + timeout: 5s + retries: 10 + + suzuka-indexer: + image: ghcr.io/movementlabsxyz/suzuka-indexer:${CONTAINER_REV} + # entrypoint: '/bin/sh -c "tail -f /dev/null"' + container_name: suzuka-indexer + environment: + - DOT_MOVEMENT_PATH=/.movement + - MAPTOS_INDEXER_GRPC_LISTEN_HOSTNAME=${MAPTOS_INDEXER_GRPC_LISTEN_HOSTNAME} + - INDEXER_PROCESSOR_POSTGRES_CONNECTION_STRING=${INDEXER_PROCESSOR_POSTGRES_CONNECTION_STRING} + volumes: + - ${DOT_MOVEMENT_PATH}:/.movement + restart: on-failure:5 + depends_on: + - postgres + - suzuka-full-node + graphql-engine: + image: hasura/graphql-engine:v2.40.0 + ports: + - "8085:8085" + restart: always + environment: + HASURA_GRAPHQL_SERVER_PORT: 8085 + ## postgres database to store Hasura metadata + HASURA_GRAPHQL_METADATA_DATABASE_URL: postgresql://postgres:password@${POSTGRES_DB_HOST}:5432/postgres + HASURA_GRAPHQL_DATABASE_URL: postgresql://postgres:password@${POSTGRES_DB_HOST}:5432/postgres + ## this env var can be used to add the above postgres database to Hasura as a data source. this can be removed/updated based on your needs + PG_DATABASE_URL: postgres://postgres:postgres:password@${POSTGRES_DB_HOST}:5432/postgres + ## enable the console served by server + HASURA_GRAPHQL_ENABLE_CONSOLE: "true" # set to "false" to disable console + ## enable debugging mode. It is recommended to disable this in production + HASURA_GRAPHQL_DEV_MODE: "true" + HASURA_GRAPHQL_ENABLED_LOG_TYPES: startup, http-log, webhook-log, websocket-log, query-log + ## uncomment next line to run console offline (i.e load console assets from server instead of CDN) + # HASURA_GRAPHQL_CONSOLE_ASSETS_DIR: /srv/console-assets + ## uncomment next line to set an admin secret + # HASURA_GRAPHQL_ADMIN_SECRET: myadminsecretkey + HASURA_GRAPHQL_METADATA_DEFAULTS: '{"backend_configs":{"dataconnector":{"athena":{"uri":"http://data-connector-agent:8081/api/v1/athena"},"mariadb":{"uri":"http://data-connector-agent:8081/api/v1/mariadb"},"mysql8":{"uri":"http://data-connector-agent:8081/api/v1/mysql"},"oracle":{"uri":"http://data-connector-agent:8081/api/v1/oracle"},"snowflake":{"uri":"http://data-connector-agent:8081/api/v1/snowflake"}}}}' + depends_on: + data-connector-agent: + condition: service_healthy + + data-connector-agent: + image: hasura/graphql-data-connector:v2.40.0 + restart: always + ports: + - 8081:8081 + environment: + QUARKUS_LOG_LEVEL: ERROR # FATAL, ERROR, WARN, INFO, DEBUG, TRACE + ## https://quarkus.io/guides/opentelemetry#configuration-reference + QUARKUS_OPENTELEMETRY_ENABLED: "false" + ## QUARKUS_OPENTELEMETRY_TRACER_EXPORTER_OTLP_ENDPOINT: http://jaeger:4317 + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:8081/api/v1/athena/health"] + interval: 5s + timeout: 10s + retries: 5 + start_period: 5s + depends_on: + - suzuka-indexer + +volumes: + dot-movement: + db_data: + + diff --git a/docker/compose/suzuka-indexer/indexer-config.json b/docker/compose/suzuka-indexer/indexer-config.json new file mode 100644 index 000000000..ddbb0236d --- /dev/null +++ b/docker/compose/suzuka-indexer/indexer-config.json @@ -0,0 +1,27 @@ +{ + "maptos_config": { + "chain": { + "maptos_chain_id": 27 + }, + "maptos_config": { + "indexer": { + "maptos_indexer_grpc_listen_hostname": "host.docker.internal", + "maptos_indexer_grpc_listen_port": 30734, + "maptos_indexer_grpc_inactivity_timeout": 60, + "maptos_indexer_grpc_inactivity_ping_interval": 10 + }, + "indexer_processor": { + "postgres_connection_string": "postgres://postgres:password@postgres:5432", + "indexer_processor_auth_token": "auth_token" + }, + "client": { + "maptos_rest_connection_hostname": "host.docker.internal", + "maptos_rest_connection_port": 30731, + "maptos_faucet_rest_connection_hostname": "host.docker.internal", + "maptos_faucet_rest_connection_port": 30732, + "maptos_indexer_grpc_connection_hostname": "host.docker.internal", + "maptos_indexer_grpc_connection_port": 30734 + } + } + } +} \ No newline at end of file diff --git a/nix/cargo-semver.nix b/docs/testing/README.md similarity index 100% rename from nix/cargo-semver.nix rename to docs/testing/README.md diff --git a/docs/testing/pipeline/README.md b/docs/testing/pipeline/README.md new file mode 100644 index 000000000..345f2ee65 --- /dev/null +++ b/docs/testing/pipeline/README.md @@ -0,0 +1,22 @@ +# Pipeline +We intend to rely on our CI/CD pipeline to validate software correctness. + +This primarily runs in GitHub Actions and uses a tag-based solution to minimize machine use. PRs opt into running certain workflows by tagging the PR. The workflow will run on the tag event and so PRs must be retagged. + +This places an extra onus on CODEOWNERS to ensure that PRs are properly tagged and the needed checks are run. + +Generally, before merging to main, the PR should have run all the tests. However, beforehand, the PR may opt into running only the tests that are relevant to the changes. + +To help guide the relevant code-owners, we maintain the following graph of dependencies (in DOT syntax). An edge from A to B indicates that B should not pass if A fails, i.e., you can assume the checks in B will not be satisfied if you have not yet passed the checks in A. + +```dot +digraph TestDependencies { + "cargo check (workspace)" -> "cargo test (workspace)" + "cargo test (workspace)" -> "cicd:suzuka-full-node" + "cicd:suzuka-full-node" -> "cicd:suzuka-containers" + "cargo test (workspace)" -> "cicd:bridge" + "cicd:mcr" -> "cicd:suzuka-full-node" +} +``` + +To render this graph, you can visit [WebGraphViz](http://www.webgraphviz.com/) and paste the above program into the text box. diff --git a/flake.lock b/flake.lock index 78612dbb8..955ef6c69 100644 --- a/flake.lock +++ b/flake.lock @@ -7,11 +7,11 @@ ] }, "locked": { - "lastModified": 1717025063, - "narHash": "sha256-dIubLa56W9sNNz0e8jGxrX3CAkPXsq7snuFA/Ie6dn8=", + "lastModified": 1721842668, + "narHash": "sha256-k3oiD2z2AAwBFLa4+xfU+7G5fisRXfkvrMTCJrjZzXo=", "owner": "ipetkov", "repo": "crane", - "rev": "480dff0be03dac0e51a8dfc26e882b0d123a450e", + "rev": "529c1a0b1f29f0d78fa3086b8f6a134c71ef3aaf", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index 5c2d0bdef..9e3cd5883 100644 --- a/flake.nix +++ b/flake.nix @@ -9,182 +9,119 @@ }; - outputs = { - self, - nixpkgs, - rust-overlay, - flake-utils, - foundry, - crane, - ... - }: - flake-utils.lib.eachSystem ["aarch64-darwin" "x86_64-darwin" "x86_64-linux" "aarch64-linux"] ( - - system: let - overrides = (builtins.fromTOML (builtins.readFile ./rust-toolchain.toml)); - - overlays = [ - (import rust-overlay) - foundry.overlay - ]; - + outputs = { nixpkgs, rust-overlay, flake-utils, foundry, crane, ... }: + flake-utils.lib.eachDefaultSystem (system: + let pkgs = import nixpkgs { - inherit system overlays; + inherit system; + overlays = [ (import rust-overlay) foundry.overlay ]; }; - craneLib = crane.mkLib pkgs; - - frameworks = pkgs.darwin.apple_sdk.frameworks; - - buildDependencies = with pkgs; [ - llvmPackages.bintools - openssl - openssl.dev - libiconv - pkg-config - libclang.lib - libz - clang - pkg-config - protobuf - rustPlatform.bindgenHook - lld - mold - coreutils - gcc - rust - postgresql - ]; - - sysDependencies = with pkgs; [] - ++ lib.optionals stdenv.isDarwin [ - frameworks.Security - frameworks.CoreServices - frameworks.SystemConfiguration - frameworks.AppKit - ] ++ lib.optionals stdenv.isLinux [ - udev - systemd - snappy - bzip2 - ]; - - testDependencies = with pkgs; [ - python311 - poetry - just - foundry-bin - process-compose - celestia-node - celestia-app - monza-aptos - jq - docker - solc - grpcurl - grpcui - ]; - - # Specific version of toolchain - rust = pkgs.rust-bin.fromRustupToolchainFile ./rust-toolchain.toml; - - rustPlatform = pkgs.makeRustPlatform { - cargo = rust; - rustc = rust; + toolchain = p: (p.rust-bin.fromRustupToolchainFile ./rust-toolchain.toml).override { + extensions = [ "rustfmt" ]; }; + craneLib = (crane.mkLib pkgs).overrideToolchain(toolchain); - # Needs to be removed soon and replaced with aptos-faucet-service - monza-aptos = pkgs.stdenv.mkDerivation { - pname = "monza-aptos"; - version = "branch-monza"; - - src = pkgs.fetchFromGitHub { - owner = "movementlabsxyz"; - repo = "aptos-core"; - rev = "06443b81f6b8b8742c4aa47eba9e315b5e6502ff"; - sha256 = "sha256-iIYGbIh9yPtC6c22+KDi/LgDbxLEMhk4JJMGvweMJ1Q="; - }; - - installPhase = '' - cp -r . $out - ''; + monza-aptos = pkgs.fetchgit { + url = "https://github.com/movementlabsxyz/aptos-core"; + rev = "06443b81f6b8b8742c4aa47eba9e315b5e6502ff"; + hash = "sha256-W42uBu2A1ctlI07eWEGxtf0T8NgH03SPJkYSBly4zZ4="; + }; - meta = with pkgs.lib; { - description = "Aptos core repository on the monza branch"; - homepage = "https://github.com/movementlabsxyz/aptos-core"; - license = licenses.asl20; - }; + aptos-faucet-service-common-args = { + pname = "aptos-faucet-service"; + version = "2.0.1"; + + src = monza-aptos; + strictDeps = true; + + nativeBuildInputs = with pkgs; [ + pkg-config + ]; + buildInputs = with pkgs; [ + openssl + systemd + rocksdb + rustPlatform.bindgenHook + ]; }; - # Remember, remove this thing above - - # celestia-node - celestia-node = import ./nix/celestia-node.nix { inherit pkgs; }; - - # celestia-app - celestia-app = import ./nix/celestia-app.nix { inherit pkgs; }; - - # aptos-faucet-service - aptos-faucet-service = import ./nix/aptos-faucet-service.nix { - inherit pkgs; - commonArgs = { - src = pkgs.fetchFromGitHub { - owner = "movementlabsxyz"; - repo = "aptos-core"; - rev = "06443b81f6b8b8742c4aa47eba9e315b5e6502ff"; - sha256 = "sha256-iIYGbIh9yPtC6c22+KDi/LgDbxLEMhk4JJMGvweMJ1Q="; - }; - strictDeps = true; - - buildInputs = with pkgs; [] ++buildDependencies ++ sysDependencies; - nativeBuildInputs = with pkgs; [] ++buildDependencies ++sysDependencies; + aptos-faucet-service-deps = craneLib.buildDepsOnly aptos-faucet-service-common-args; + aptos-faucet-service = craneLib.buildPackage (aptos-faucet-service-common-args // { + cargoArtifacts = aptos-faucet-service-deps; + cargoExtraArgs = "-p aptos-faucet-service"; + }); + + celestia-app = pkgs.buildGoModule { + pname = "celestia-app"; + version = "1.8.0"; + + src = pkgs.fetchgit { + url = "https://github.com/celestiaorg/celestia-app"; + rev = "e75a1fdc8f2386d9f389cb596c88ca7cc19563af"; + hash = "sha256-EE9r1sybbm4Hyh57/nC8utMx/uFdMsIdPecxBtDqAbk="; }; - inherit craneLib; - }; - - in - with pkgs; { - packages.aptos-faucet-service = aptos-faucet-service; + vendorHash = "sha256-2vU1liAm0us7Nk1eawgMvarhq77+IUS0VE61FuvQbuQ="; + subPackages = [ "cmd/celestia-appd" ]; + }; - packages.celestia-node = celestia-node; + celestia-node = pkgs.buildGoModule { + pname = "celestia-node"; + version = "0.13.3"; - packages.celestia-app = celestia-app; - - # Used for workaround for failing vendor dep builds in nix - devShells.docker-build = mkShell { - buildInputs = [] ++buildDependencies ++sysDependencies; - nativeBuildInputs = [] ++buildDependencies ++sysDependencies; - OPENSSL_DEV=pkgs.openssl.dev; - PKG_CONFIG_PATH = "${pkgs.openssl.dev}/lib/pkgconfig"; - SNAPPY = if stdenv.isLinux then pkgs.snappy else null; - shellHook = '' - #!/usr/bin/env bash - echo "rust-build shell" - ''; + src = pkgs.fetchgit { + url = "https://github.com/celestiaorg/celestia-node"; + rev = "05238b3e087eb9ecd3b9684cd0125f2400f6f0c7"; + hash = "sha256-bmFcJrC4ocbCw1pew2HKEdLj6+1D/0VuWtdoTs1S2sU="; }; - # Development Shell - devShells.default = mkShell { - - ROCKSDB=pkgs.rocksdb; - - # for linux set SNAPPY variable - SNAPPY = if stdenv.isLinux then pkgs.snappy else null; - + vendorHash = "sha256-8RC/9KiFOsEJDpt7d8WtzRLn0HzYrZ1LIHo6lOKSQxU="; + subPackages = [ "cmd/celestia" "cmd/cel-key" ]; + }; + + in { + packages = { + inherit aptos-faucet-service celestia-app celestia-node; + }; + devShells = rec { + default = docker-build; + docker-build = pkgs.mkShell { + ROCKSDB = pkgs.rocksdb; + SNAPPY = if pkgs.stdenv.isLinux then pkgs.snappy else null; OPENSSL_DEV = pkgs.openssl.dev; - PKG_CONFIG_PATH = "${pkgs.openssl.dev}/lib/pkgconfig"; MONZA_APTOS_PATH = monza-aptos; - - buildInputs = [] ++buildDependencies ++sysDependencies ++testDependencies; - nativeBuildInputs = [] ++buildDependencies ++sysDependencies; + + buildInputs = with pkgs; [ + # rust toolchain + (toolchain pkgs) + + # build dependencies + llvmPackages.bintools openssl openssl.dev libiconv pkg-config + libclang.lib libz clang pkg-config protobuf rustPlatform.bindgenHook + lld mold coreutils postgresql + + # test dependencies + python311 poetry just foundry-bin process-compose jq docker solc + grpcurl grpcui + + monza-aptos + celestia-app celestia-node + ] ++ lib.optionals stdenv.isDarwin (with pkgs.darwin.apple_sdk.frameworks; [ + Security CoreServices SystemConfiguration AppKit + ]) ++ lib.optionals stdenv.isLinux (with pkgs; [ + udev systemd snappy bzip2 elfutils.dev + ]); + + LD_LIBRARY_PATH = "${pkgs.stdenv.cc.cc.lib}/lib/"; shellHook = '' - #!/bin/bash -e + #!/usr/bin/env ${pkgs.bash} - // # Movement Swap Core - DOT_MOVEMENT_PATH=$(pwd)/.movement + DOT_MOVEMENT_PATH=$(pwd).movement mkdir -p $DOT_MOVEMENT_PATH + # export PKG_CONFIG_PATH=$PKG_CONFIG_PATH_FOR_TARGET + echo "Monza Aptos path: $MONZA_APTOS_PATH" cat <<'EOF' _ _ __ _ _ ____ _ _ ____ __ _ ____ @@ -196,8 +133,7 @@ echo "Develop with Move Anywhere" ''; }; - } + }; + } ); } - - diff --git a/justfile b/justfile index 190b8f860..04a5c6d98 100644 --- a/justfile +++ b/justfile @@ -10,7 +10,5 @@ mcr-client RUNTIME FEATURES *ARGS: ./scripts/movement/run mcr-client {{ RUNTIME }} {{ FEATURES }} {{ ARGS }} build-push-container IMAGE: ./scripts/movement/build-push-image {{ IMAGE }} -mcr RUNTIME FEATURES *ARGS: - ./scripts/movement/run mcr {{ RUNTIME }} {{ FEATURES }} {{ ARGS }} container-test: ./scripts/tests/container-test diff --git a/networks/suzuka/indexer/Cargo.toml b/networks/suzuka/indexer/Cargo.toml new file mode 100644 index 000000000..8e64e8ba3 --- /dev/null +++ b/networks/suzuka/indexer/Cargo.toml @@ -0,0 +1,32 @@ +[package] +name = "suzuka-indexer-service" +version = { workspace = true } +edition = { workspace = true } +license = { workspace = true } +authors = { workspace = true } +repository = { workspace = true } +homepage = { workspace = true } +publish = { workspace = true } +rust-version = { workspace = true } + +[[bin]] +name = "load_metadata" +path = "bin/load_metadata.rs" + +[dependencies] +anyhow = { workspace = true } +tokio = { workspace = true } +dot-movement = { workspace = true } +num_cpus = { workspace = true } +processor = { workspace = true } +server-framework = { workspace = true } +tracing = { workspace = true } +tracing-subscriber = { workspace = true } +maptos-execution-util = { workspace = true } +clap = { workspace = true } +reqwest = { workspace = true, features = ["json"] } +serde_json = { workspace = true } +tempfile = { workspace = true } + +[lints] +workspace = true diff --git a/networks/suzuka/indexer/bin/load_metadata.rs b/networks/suzuka/indexer/bin/load_metadata.rs new file mode 100644 index 000000000..c74681b2a --- /dev/null +++ b/networks/suzuka/indexer/bin/load_metadata.rs @@ -0,0 +1,104 @@ +use anyhow::{anyhow, Context, Result}; +use reqwest::Url; +use tracing::info; + +const HASURA_METADATA: &str = include_str!("../hasura_metadata.json"); + +#[tokio::main] +async fn main() -> Result<(), anyhow::Error> { + let indexer_api_url = + std::env::var("INDEXER_API_URL").unwrap_or("http://127.0.0.1:8085".to_string()); + + // Replace the postgres connection definition in the metadata file + // with the provided in the env var INDEXER_V2_POSTGRES_URL + let postgres_host = std::env::var("POSTGRES_DB_HOST").unwrap_or("postgres".to_string()); + let postgres_url = format!("postgres://postgres:password@{postgres_host}:5432/postgres"); + let metadata_file = HASURA_METADATA.replace("INDEXER_V2_POSTGRES_URL", &postgres_url); + + post_metadata(indexer_api_url.parse()?, &metadata_file) + .await + .context("Failed to apply Hasura metadata for Indexer API")?; + + Ok(()) +} + +/// This submits a POST request to apply metadata to a Hasura API. +async fn post_metadata(url: Url, metadata_content: &str) -> Result<()> { + // Parse the metadata content as JSON. + let metadata_json: serde_json::Value = serde_json::from_str(metadata_content)?; + + // Make the request. + info!("Submitting request to apply Hasura metadata"); + let response = + make_hasura_metadata_request(url, "replace_metadata", Some(metadata_json)).await?; + info!("Received response for applying Hasura metadata: {:?}", response); + + // Confirm that the metadata was applied successfully and there is no inconsistency + // between the schema and the underlying DB schema. + if let Some(obj) = response.as_object() { + if let Some(is_consistent_val) = obj.get("is_consistent") { + if is_consistent_val.as_bool() == Some(true) { + return Ok(()); + } + } + } + + Err(anyhow!( + "Something went wrong applying the Hasura metadata, perhaps it is not consistent with the DB. Response: {:#?}", + response + )) +} + +/// This confirms that the metadata has been applied. We use this in the health +/// checker. +pub async fn confirm_metadata_applied(url: Url) -> Result<()> { + // Make the request. + info!("Confirming Hasura metadata applied..."); + let response = make_hasura_metadata_request(url, "export_metadata", None).await?; + info!("Received response for confirming Hasura metadata applied: {:?}", response); + + // If the sources field is set it means the metadata was applied successfully. + if let Some(obj) = response.as_object() { + if let Some(sources) = obj.get("sources") { + if let Some(sources) = sources.as_array() { + if !sources.is_empty() { + return Ok(()); + } + } + } + } + + Err(anyhow!("The Hasura metadata has not been applied yet. Response: {:#?}", response)) +} + +/// The /v1/metadata endpoint supports a few different operations based on the `type` +/// field in the request body. All requests have a similar format, with these `type` +/// and `args` fields. +async fn make_hasura_metadata_request( + mut url: Url, + typ: &str, + args: Option, +) -> Result { + let client = reqwest::Client::new(); + + // Update the query path. + url.set_path("/v1/metadata"); + + // Construct the payload. + let mut payload = serde_json::Map::new(); + payload.insert("type".to_string(), serde_json::Value::String(typ.to_string())); + + // If args is provided, use that. Otherwise use an empty object. We have to set it + // no matter what because the API expects the args key to be set. + let args = match args { + Some(args) => args, + None => serde_json::Value::Object(serde_json::Map::new()), + }; + payload.insert("args".to_string(), args); + + // Send the POST request. + let response = client.post(url).json(&payload).send().await?; + + // Return the response as a JSON value. + response.json().await.context("Failed to parse response as JSON") +} diff --git a/networks/suzuka/indexer/hasura_metadata.json b/networks/suzuka/indexer/hasura_metadata.json new file mode 100644 index 000000000..3e233a3d0 --- /dev/null +++ b/networks/suzuka/indexer/hasura_metadata.json @@ -0,0 +1,2239 @@ +{ + "resource_version": 319, + "metadata": { + "version": 3, + "sources": [ + { + "name": "indexer-v2", + "kind": "postgres", + "tables": [ + { + "table": { + "name": "parsed_asset_uris", + "schema": "nft_metadata_crawler" + }, + "select_permissions": [ + { + "role": "anonymous", + "permission": { + "columns": [ + "animation_optimizer_retry_count", + "asset_uri", + "cdn_animation_uri", + "cdn_image_uri", + "cdn_json_uri", + "image_optimizer_retry_count", + "json_parser_retry_count", + "raw_animation_uri", + "raw_image_uri" + ], + "filter": {}, + "limit": 100 + } + } + ] + }, + { + "table": { + "name": "account_transactions", + "schema": "public" + }, + "object_relationships": [ + { + "name": "user_transaction", + "using": { + "manual_configuration": { + "column_mapping": { + "transaction_version": "version" + }, + "insertion_order": null, + "remote_table": { + "name": "user_transactions", + "schema": "public" + } + } + } + } + ], + "array_relationships": [ + { + "name": "coin_activities", + "using": { + "manual_configuration": { + "column_mapping": { + "transaction_version": "transaction_version" + }, + "insertion_order": null, + "remote_table": { + "name": "coin_activities", + "schema": "public" + } + } + } + }, + { + "name": "delegated_staking_activities", + "using": { + "manual_configuration": { + "column_mapping": { + "transaction_version": "transaction_version" + }, + "insertion_order": null, + "remote_table": { + "name": "delegated_staking_activities", + "schema": "public" + } + } + } + }, + { + "name": "fungible_asset_activities", + "using": { + "manual_configuration": { + "column_mapping": { + "transaction_version": "transaction_version" + }, + "insertion_order": null, + "remote_table": { + "name": "fungible_asset_activities", + "schema": "public" + } + } + } + }, + { + "name": "token_activities", + "using": { + "manual_configuration": { + "column_mapping": { + "transaction_version": "transaction_version" + }, + "insertion_order": null, + "remote_table": { + "name": "token_activities", + "schema": "public" + } + } + } + }, + { + "name": "token_activities_v2", + "using": { + "manual_configuration": { + "column_mapping": { + "transaction_version": "transaction_version" + }, + "insertion_order": null, + "remote_table": { + "name": "token_activities_v2", + "schema": "public" + } + } + } + } + ], + "select_permissions": [ + { + "role": "anonymous", + "permission": { + "columns": ["account_address", "transaction_version"], + "filter": {}, + "limit": 100, + "allow_aggregations": true + } + } + ] + }, + { + "table": { + "name": "address_events_summary", + "schema": "public" + }, + "object_relationships": [ + { + "name": "block_metadata", + "using": { + "manual_configuration": { + "column_mapping": { + "min_block_height": "block_height" + }, + "insertion_order": null, + "remote_table": { + "name": "block_metadata_transactions", + "schema": "public" + } + } + } + } + ], + "select_permissions": [ + { + "role": "anonymous", + "permission": { + "columns": [ + "min_block_height", + "num_distinct_versions", + "account_address" + ], + "filter": {}, + "limit": 100 + } + } + ] + }, + { + "table": { + "name": "address_version_from_events", + "schema": "public" + }, + "array_relationships": [ + { + "name": "coin_activities", + "using": { + "manual_configuration": { + "column_mapping": { + "transaction_version": "transaction_version" + }, + "insertion_order": null, + "remote_table": { + "name": "coin_activities", + "schema": "public" + } + } + } + }, + { + "name": "delegated_staking_activities", + "using": { + "manual_configuration": { + "column_mapping": { + "transaction_version": "transaction_version" + }, + "insertion_order": null, + "remote_table": { + "name": "delegated_staking_activities", + "schema": "public" + } + } + } + }, + { + "name": "token_activities", + "using": { + "manual_configuration": { + "column_mapping": { + "transaction_version": "transaction_version" + }, + "insertion_order": null, + "remote_table": { + "name": "token_activities", + "schema": "public" + } + } + } + }, + { + "name": "token_activities_v2", + "using": { + "manual_configuration": { + "column_mapping": { + "transaction_version": "transaction_version" + }, + "insertion_order": null, + "remote_table": { + "name": "token_activities_v2", + "schema": "public" + } + } + } + } + ], + "select_permissions": [ + { + "role": "anonymous", + "permission": { + "columns": ["account_address", "transaction_version"], + "filter": {}, + "limit": 100, + "allow_aggregations": true + } + } + ] + }, + { + "table": { + "name": "address_version_from_move_resources", + "schema": "public" + }, + "array_relationships": [ + { + "name": "coin_activities", + "using": { + "manual_configuration": { + "column_mapping": { + "transaction_version": "transaction_version" + }, + "insertion_order": null, + "remote_table": { + "name": "coin_activities", + "schema": "public" + } + } + } + }, + { + "name": "delegated_staking_activities", + "using": { + "manual_configuration": { + "column_mapping": { + "transaction_version": "transaction_version" + }, + "insertion_order": null, + "remote_table": { + "name": "delegated_staking_activities", + "schema": "public" + } + } + } + }, + { + "name": "token_activities", + "using": { + "manual_configuration": { + "column_mapping": { + "transaction_version": "transaction_version" + }, + "insertion_order": null, + "remote_table": { + "name": "token_activities", + "schema": "public" + } + } + } + }, + { + "name": "token_activities_v2", + "using": { + "manual_configuration": { + "column_mapping": { + "transaction_version": "transaction_version" + }, + "insertion_order": null, + "remote_table": { + "name": "token_activities_v2", + "schema": "public" + } + } + } + } + ], + "select_permissions": [ + { + "role": "anonymous", + "permission": { + "columns": ["address", "transaction_version"], + "filter": {}, + "limit": 100, + "allow_aggregations": true + } + } + ] + }, + { + "table": { + "name": "block_metadata_transactions", + "schema": "public" + }, + "select_permissions": [ + { + "role": "anonymous", + "permission": { + "columns": [ + "block_height", + "epoch", + "failed_proposer_indices", + "id", + "previous_block_votes_bitvec", + "proposer", + "round", + "timestamp", + "version" + ], + "filter": {}, + "limit": 100 + } + } + ] + }, + { + "table": { + "name": "coin_activities", + "schema": "public" + }, + "object_relationships": [ + { + "name": "coin_info", + "using": { + "manual_configuration": { + "column_mapping": { + "coin_type": "coin_type" + }, + "insertion_order": null, + "remote_table": { + "name": "coin_infos", + "schema": "public" + } + } + } + } + ], + "array_relationships": [ + { + "name": "aptos_names", + "using": { + "manual_configuration": { + "column_mapping": { + "owner_address": "registered_address" + }, + "insertion_order": null, + "remote_table": { + "name": "current_aptos_names", + "schema": "public" + } + } + } + } + ], + "select_permissions": [ + { + "role": "anonymous", + "permission": { + "columns": [ + "activity_type", + "amount", + "block_height", + "coin_type", + "entry_function_id_str", + "event_account_address", + "event_creation_number", + "event_index", + "event_sequence_number", + "is_gas_fee", + "is_transaction_success", + "owner_address", + "storage_refund_amount", + "transaction_timestamp", + "transaction_version" + ], + "filter": {}, + "limit": 100, + "allow_aggregations": true + } + } + ] + }, + { + "table": { + "name": "coin_balances", + "schema": "public" + }, + "select_permissions": [ + { + "role": "anonymous", + "permission": { + "columns": [ + "amount", + "coin_type", + "coin_type_hash", + "owner_address", + "transaction_timestamp", + "transaction_version" + ], + "filter": {}, + "limit": 100 + } + } + ] + }, + { + "table": { + "name": "coin_infos", + "schema": "public" + }, + "select_permissions": [ + { + "role": "anonymous", + "permission": { + "columns": [ + "coin_type", + "coin_type_hash", + "creator_address", + "decimals", + "name", + "supply_aggregator_table_handle", + "supply_aggregator_table_key", + "symbol", + "transaction_created_timestamp", + "transaction_version_created" + ], + "filter": {}, + "limit": 100 + } + } + ] + }, + { + "table": { + "name": "coin_supply", + "schema": "public" + }, + "select_permissions": [ + { + "role": "anonymous", + "permission": { + "columns": [ + "coin_type", + "coin_type_hash", + "supply", + "transaction_epoch", + "transaction_timestamp", + "transaction_version" + ], + "filter": {}, + "limit": 100 + } + } + ] + }, + { + "table": { + "name": "collection_datas", + "schema": "public" + }, + "select_permissions": [ + { + "role": "anonymous", + "permission": { + "columns": [ + "collection_data_id_hash", + "collection_name", + "creator_address", + "description", + "description_mutable", + "maximum", + "maximum_mutable", + "metadata_uri", + "supply", + "table_handle", + "transaction_timestamp", + "transaction_version", + "uri_mutable" + ], + "filter": {}, + "limit": 100 + } + } + ] + }, + { + "table": { + "name": "current_ans_lookup", + "schema": "public" + }, + "array_relationships": [ + { + "name": "all_token_ownerships", + "using": { + "manual_configuration": { + "column_mapping": { + "token_name": "name" + }, + "insertion_order": null, + "remote_table": { + "name": "current_token_ownerships", + "schema": "public" + } + } + } + } + ], + "select_permissions": [ + { + "role": "anonymous", + "permission": { + "columns": [ + "domain", + "expiration_timestamp", + "is_deleted", + "last_transaction_version", + "registered_address", + "subdomain", + "token_name" + ], + "filter": {}, + "limit": 100 + } + } + ] + }, + { + "table": { + "name": "current_ans_lookup_v2", + "schema": "public" + }, + "select_permissions": [ + { + "role": "anonymous", + "permission": { + "columns": [ + "domain", + "expiration_timestamp", + "is_deleted", + "last_transaction_version", + "registered_address", + "subdomain", + "token_name", + "token_standard" + ], + "filter": {}, + "limit": 100 + }, + "comment": "" + } + ] + }, + { + "table": { + "name": "current_aptos_names", + "schema": "public" + }, + "object_relationships": [ + { + "name": "is_domain_owner", + "using": { + "manual_configuration": { + "column_mapping": { + "domain_with_suffix": "token_name", + "owner_address": "owner_address" + }, + "insertion_order": null, + "remote_table": { + "name": "current_aptos_names", + "schema": "public" + } + } + } + } + ], + "select_permissions": [ + { + "role": "anonymous", + "permission": { + "columns": [ + "domain", + "domain_expiration_timestamp", + "domain_with_suffix", + "expiration_timestamp", + "is_active", + "is_primary", + "last_transaction_version", + "owner_address", + "registered_address", + "subdomain", + "subdomain_expiration_policy", + "token_name", + "token_standard" + ], + "filter": {}, + "limit": 100, + "allow_aggregations": true + }, + "comment": "" + } + ] + }, + { + "table": { + "name": "current_coin_balances", + "schema": "public" + }, + "object_relationships": [ + { + "name": "coin_info", + "using": { + "manual_configuration": { + "column_mapping": { + "coin_type_hash": "coin_type_hash" + }, + "insertion_order": null, + "remote_table": { + "name": "coin_infos", + "schema": "public" + } + } + } + } + ], + "select_permissions": [ + { + "role": "anonymous", + "permission": { + "columns": [ + "amount", + "coin_type", + "coin_type_hash", + "last_transaction_timestamp", + "last_transaction_version", + "owner_address" + ], + "filter": {}, + "limit": 100 + } + } + ] + }, + { + "table": { + "name": "current_collection_datas", + "schema": "public" + }, + "select_permissions": [ + { + "role": "anonymous", + "permission": { + "columns": [ + "collection_data_id_hash", + "collection_name", + "creator_address", + "description", + "description_mutable", + "last_transaction_timestamp", + "last_transaction_version", + "maximum", + "maximum_mutable", + "metadata_uri", + "supply", + "table_handle", + "uri_mutable" + ], + "filter": {}, + "limit": 100 + } + } + ] + }, + { + "table": { + "name": "current_collection_ownership_v2_view", + "schema": "public" + }, + "object_relationships": [ + { + "name": "current_collection", + "using": { + "manual_configuration": { + "column_mapping": { + "collection_id": "collection_id" + }, + "insertion_order": null, + "remote_table": { + "name": "current_collections_v2", + "schema": "public" + } + } + } + } + ], + "select_permissions": [ + { + "role": "anonymous", + "permission": { + "columns": [ + "distinct_tokens", + "last_transaction_version", + "collection_id", + "collection_name", + "creator_address", + "owner_address", + "collection_uri", + "single_token_uri" + ], + "filter": {}, + "limit": 100, + "allow_aggregations": true + } + } + ] + }, + { + "table": { + "name": "current_collections_v2", + "schema": "public" + }, + "object_relationships": [ + { + "name": "cdn_asset_uris", + "using": { + "manual_configuration": { + "column_mapping": { + "uri": "asset_uri" + }, + "insertion_order": null, + "remote_table": { + "name": "parsed_asset_uris", + "schema": "nft_metadata_crawler" + } + } + } + } + ], + "select_permissions": [ + { + "role": "anonymous", + "permission": { + "columns": [ + "collection_id", + "collection_name", + "creator_address", + "current_supply", + "description", + "last_transaction_timestamp", + "last_transaction_version", + "max_supply", + "mutable_description", + "mutable_uri", + "table_handle_v1", + "token_standard", + "total_minted_v2", + "uri" + ], + "filter": {}, + "limit": 100 + } + } + ] + }, + { + "table": { + "name": "current_delegated_staking_pool_balances", + "schema": "public" + }, + "select_permissions": [ + { + "role": "anonymous", + "permission": { + "columns": [ + "active_table_handle", + "inactive_table_handle", + "last_transaction_version", + "operator_commission_percentage", + "staking_pool_address", + "total_coins", + "total_shares" + ], + "filter": {}, + "limit": 100 + } + } + ] + }, + { + "table": { + "name": "current_delegated_voter", + "schema": "public" + }, + "select_permissions": [ + { + "role": "anonymous", + "permission": { + "columns": [ + "delegation_pool_address", + "delegator_address", + "last_transaction_timestamp", + "last_transaction_version", + "pending_voter", + "table_handle", + "voter" + ], + "filter": {}, + "limit": 100 + }, + "comment": "" + } + ] + }, + { + "table": { + "name": "current_delegator_balances", + "schema": "public" + }, + "object_relationships": [ + { + "name": "current_pool_balance", + "using": { + "manual_configuration": { + "column_mapping": { + "pool_address": "staking_pool_address" + }, + "insertion_order": null, + "remote_table": { + "name": "current_delegated_staking_pool_balances", + "schema": "public" + } + } + } + }, + { + "name": "staking_pool_metadata", + "using": { + "manual_configuration": { + "column_mapping": { + "pool_address": "staking_pool_address" + }, + "insertion_order": null, + "remote_table": { + "name": "current_staking_pool_voter", + "schema": "public" + } + } + } + } + ], + "select_permissions": [ + { + "role": "anonymous", + "permission": { + "columns": [ + "delegator_address", + "last_transaction_version", + "parent_table_handle", + "pool_address", + "pool_type", + "shares", + "table_handle" + ], + "filter": {}, + "limit": 100 + } + } + ] + }, + { + "table": { + "name": "current_fungible_asset_balances", + "schema": "public" + }, + "object_relationships": [ + { + "name": "metadata", + "using": { + "manual_configuration": { + "column_mapping": { + "asset_type": "asset_type" + }, + "insertion_order": null, + "remote_table": { + "name": "fungible_asset_metadata", + "schema": "public" + } + } + } + } + ], + "select_permissions": [ + { + "role": "anonymous", + "permission": { + "columns": [ + "amount", + "asset_type", + "is_frozen", + "is_primary", + "last_transaction_timestamp", + "last_transaction_version", + "owner_address", + "storage_id", + "token_standard" + ], + "filter": {}, + "limit": 100, + "allow_aggregations": true + } + } + ] + }, + { + "table": { + "name": "current_objects", + "schema": "public" + }, + "select_permissions": [ + { + "role": "anonymous", + "permission": { + "columns": [ + "allow_ungated_transfer", + "is_deleted", + "last_guid_creation_num", + "last_transaction_version", + "object_address", + "owner_address", + "state_key_hash" + ], + "filter": {}, + "limit": 100 + } + } + ] + }, + { + "table": { + "name": "current_staking_pool_voter", + "schema": "public" + }, + "array_relationships": [ + { + "name": "operator_aptos_name", + "using": { + "manual_configuration": { + "column_mapping": { + "operator_address": "registered_address" + }, + "insertion_order": null, + "remote_table": { + "name": "current_aptos_names", + "schema": "public" + } + } + } + } + ], + "select_permissions": [ + { + "role": "anonymous", + "permission": { + "columns": [ + "last_transaction_version", + "operator_address", + "staking_pool_address", + "voter_address" + ], + "filter": {}, + "limit": 100 + } + } + ] + }, + { + "table": { + "name": "current_table_items", + "schema": "public" + }, + "select_permissions": [ + { + "role": "anonymous", + "permission": { + "columns": [ + "decoded_key", + "decoded_value", + "is_deleted", + "key", + "key_hash", + "last_transaction_version", + "table_handle" + ], + "filter": {}, + "limit": 100 + } + } + ] + }, + { + "table": { + "name": "current_token_datas", + "schema": "public" + }, + "object_relationships": [ + { + "name": "current_collection_data", + "using": { + "manual_configuration": { + "column_mapping": { + "collection_data_id_hash": "collection_data_id_hash" + }, + "insertion_order": null, + "remote_table": { + "name": "current_collection_datas", + "schema": "public" + } + } + } + } + ], + "select_permissions": [ + { + "role": "anonymous", + "permission": { + "columns": [ + "collection_data_id_hash", + "collection_name", + "creator_address", + "default_properties", + "description", + "description_mutable", + "largest_property_version", + "last_transaction_timestamp", + "last_transaction_version", + "maximum", + "maximum_mutable", + "metadata_uri", + "name", + "payee_address", + "properties_mutable", + "royalty_mutable", + "royalty_points_denominator", + "royalty_points_numerator", + "supply", + "token_data_id_hash", + "uri_mutable" + ], + "filter": {}, + "limit": 100 + } + } + ] + }, + { + "table": { + "name": "current_token_datas_v2", + "schema": "public" + }, + "object_relationships": [ + { + "name": "aptos_name", + "using": { + "manual_configuration": { + "column_mapping": { + "token_name": "token_name" + }, + "insertion_order": null, + "remote_table": { + "name": "current_aptos_names", + "schema": "public" + } + } + } + }, + { + "name": "cdn_asset_uris", + "using": { + "manual_configuration": { + "column_mapping": { + "token_uri": "asset_uri" + }, + "insertion_order": null, + "remote_table": { + "name": "parsed_asset_uris", + "schema": "nft_metadata_crawler" + } + } + } + }, + { + "name": "current_collection", + "using": { + "manual_configuration": { + "column_mapping": { + "collection_id": "collection_id" + }, + "insertion_order": null, + "remote_table": { + "name": "current_collections_v2", + "schema": "public" + } + } + } + } + ], + "array_relationships": [ + { + "name": "current_token_ownerships", + "using": { + "manual_configuration": { + "column_mapping": { + "token_data_id": "token_data_id" + }, + "insertion_order": null, + "remote_table": { + "name": "current_token_ownerships_v2", + "schema": "public" + } + } + } + } + ], + "select_permissions": [ + { + "role": "anonymous", + "permission": { + "columns": [ + "collection_id", + "decimals", + "description", + "is_deleted_v2", + "is_fungible_v2", + "largest_property_version_v1", + "last_transaction_timestamp", + "last_transaction_version", + "maximum", + "supply", + "token_data_id", + "token_name", + "token_properties", + "token_standard", + "token_uri" + ], + "filter": {}, + "limit": 100 + } + } + ] + }, + { + "table": { + "name": "current_token_ownerships", + "schema": "public" + }, + "object_relationships": [ + { + "name": "aptos_name", + "using": { + "manual_configuration": { + "column_mapping": { + "name": "token_name" + }, + "insertion_order": null, + "remote_table": { + "name": "current_aptos_names", + "schema": "public" + } + } + } + }, + { + "name": "current_collection_data", + "using": { + "manual_configuration": { + "column_mapping": { + "collection_data_id_hash": "collection_data_id_hash" + }, + "insertion_order": null, + "remote_table": { + "name": "current_collection_datas", + "schema": "public" + } + } + } + }, + { + "name": "current_token_data", + "using": { + "manual_configuration": { + "column_mapping": { + "token_data_id_hash": "token_data_id_hash" + }, + "insertion_order": null, + "remote_table": { + "name": "current_token_datas", + "schema": "public" + } + } + } + } + ], + "select_permissions": [ + { + "role": "anonymous", + "permission": { + "columns": [ + "amount", + "collection_data_id_hash", + "collection_name", + "creator_address", + "last_transaction_timestamp", + "last_transaction_version", + "name", + "owner_address", + "property_version", + "table_type", + "token_data_id_hash", + "token_properties" + ], + "filter": {}, + "limit": 100, + "allow_aggregations": true + } + } + ] + }, + { + "table": { + "name": "current_token_ownerships_v2", + "schema": "public" + }, + "object_relationships": [ + { + "name": "current_token_data", + "using": { + "manual_configuration": { + "column_mapping": { + "token_data_id": "token_data_id" + }, + "insertion_order": null, + "remote_table": { + "name": "current_token_datas_v2", + "schema": "public" + } + } + } + } + ], + "array_relationships": [ + { + "name": "composed_nfts", + "using": { + "manual_configuration": { + "column_mapping": { + "token_data_id": "owner_address" + }, + "insertion_order": null, + "remote_table": { + "name": "current_token_ownerships_v2", + "schema": "public" + } + } + } + } + ], + "select_permissions": [ + { + "role": "anonymous", + "permission": { + "columns": [ + "amount", + "is_fungible_v2", + "is_soulbound_v2", + "last_transaction_timestamp", + "last_transaction_version", + "non_transferrable_by_owner", + "owner_address", + "property_version_v1", + "storage_id", + "table_type_v1", + "token_data_id", + "token_properties_mutated_v1", + "token_standard" + ], + "filter": {}, + "limit": 100, + "allow_aggregations": true + } + } + ] + }, + { + "table": { + "name": "current_token_pending_claims", + "schema": "public" + }, + "object_relationships": [ + { + "name": "current_collection_data", + "using": { + "manual_configuration": { + "column_mapping": { + "collection_data_id_hash": "collection_data_id_hash" + }, + "insertion_order": null, + "remote_table": { + "name": "current_collection_datas", + "schema": "public" + } + } + } + }, + { + "name": "current_collection_v2", + "using": { + "manual_configuration": { + "column_mapping": { + "collection_id": "collection_id" + }, + "insertion_order": null, + "remote_table": { + "name": "current_collections_v2", + "schema": "public" + } + } + } + }, + { + "name": "current_token_data", + "using": { + "manual_configuration": { + "column_mapping": { + "token_data_id_hash": "token_data_id_hash" + }, + "insertion_order": null, + "remote_table": { + "name": "current_token_datas", + "schema": "public" + } + } + } + }, + { + "name": "current_token_data_v2", + "using": { + "manual_configuration": { + "column_mapping": { + "token_data_id": "token_data_id" + }, + "insertion_order": null, + "remote_table": { + "name": "current_token_datas_v2", + "schema": "public" + } + } + } + }, + { + "name": "token", + "using": { + "manual_configuration": { + "column_mapping": { + "last_transaction_version": "transaction_version", + "property_version": "property_version", + "token_data_id_hash": "token_data_id_hash" + }, + "insertion_order": null, + "remote_table": { + "name": "tokens", + "schema": "public" + } + } + } + } + ], + "select_permissions": [ + { + "role": "anonymous", + "permission": { + "columns": [ + "amount", + "collection_data_id_hash", + "collection_id", + "collection_name", + "creator_address", + "from_address", + "last_transaction_timestamp", + "last_transaction_version", + "name", + "property_version", + "table_handle", + "to_address", + "token_data_id", + "token_data_id_hash" + ], + "filter": {}, + "limit": 100 + } + } + ] + }, + { + "table": { + "name": "delegated_staking_activities", + "schema": "public" + }, + "select_permissions": [ + { + "role": "anonymous", + "permission": { + "columns": [ + "amount", + "delegator_address", + "event_index", + "event_type", + "pool_address", + "transaction_version" + ], + "filter": {}, + "limit": 100 + } + } + ] + }, + { + "table": { + "name": "delegated_staking_pool_balances", + "schema": "public" + }, + "select_permissions": [ + { + "role": "anonymous", + "permission": { + "columns": [ + "active_table_handle", + "inactive_table_handle", + "operator_commission_percentage", + "staking_pool_address", + "total_coins", + "total_shares", + "transaction_version" + ], + "filter": {}, + "limit": 100, + "allow_aggregations": true + }, + "comment": "" + } + ] + }, + { + "table": { + "name": "delegated_staking_pools", + "schema": "public" + }, + "object_relationships": [ + { + "name": "current_staking_pool", + "using": { + "manual_configuration": { + "column_mapping": { + "staking_pool_address": "staking_pool_address" + }, + "insertion_order": null, + "remote_table": { + "name": "current_staking_pool_voter", + "schema": "public" + } + } + } + } + ], + "select_permissions": [ + { + "role": "anonymous", + "permission": { + "columns": [ + "first_transaction_version", + "staking_pool_address" + ], + "filter": {}, + "limit": 100 + } + } + ] + }, + { + "table": { + "name": "delegator_distinct_pool", + "schema": "public" + }, + "object_relationships": [ + { + "name": "current_pool_balance", + "using": { + "manual_configuration": { + "column_mapping": { + "pool_address": "staking_pool_address" + }, + "insertion_order": null, + "remote_table": { + "name": "current_delegated_staking_pool_balances", + "schema": "public" + } + } + } + }, + { + "name": "staking_pool_metadata", + "using": { + "manual_configuration": { + "column_mapping": { + "pool_address": "staking_pool_address" + }, + "insertion_order": null, + "remote_table": { + "name": "current_staking_pool_voter", + "schema": "public" + } + } + } + } + ], + "select_permissions": [ + { + "role": "anonymous", + "permission": { + "columns": ["delegator_address", "pool_address"], + "filter": {}, + "limit": 100, + "allow_aggregations": true + } + } + ] + }, + { + "table": { + "name": "events", + "schema": "public" + }, + "select_permissions": [ + { + "role": "anonymous", + "permission": { + "columns": [ + "account_address", + "creation_number", + "data", + "event_index", + "sequence_number", + "transaction_block_height", + "transaction_version", + "type", + "indexed_type" + ], + "filter": {}, + "limit": 100 + } + } + ] + }, + { + "table": { + "name": "fungible_asset_activities", + "schema": "public" + }, + "object_relationships": [ + { + "name": "metadata", + "using": { + "manual_configuration": { + "column_mapping": { + "asset_type": "asset_type" + }, + "insertion_order": null, + "remote_table": { + "name": "fungible_asset_metadata", + "schema": "public" + } + } + } + } + ], + "array_relationships": [ + { + "name": "owner_aptos_names", + "using": { + "manual_configuration": { + "column_mapping": { + "owner_address": "registered_address" + }, + "insertion_order": null, + "remote_table": { + "name": "current_aptos_names", + "schema": "public" + } + } + } + } + ], + "select_permissions": [ + { + "role": "anonymous", + "permission": { + "columns": [ + "amount", + "asset_type", + "block_height", + "entry_function_id_str", + "event_index", + "gas_fee_payer_address", + "is_frozen", + "is_gas_fee", + "is_transaction_success", + "owner_address", + "storage_id", + "storage_refund_amount", + "token_standard", + "transaction_timestamp", + "transaction_version", + "type" + ], + "filter": {}, + "limit": 100 + } + } + ] + }, + { + "table": { + "name": "fungible_asset_metadata", + "schema": "public" + }, + "select_permissions": [ + { + "role": "anonymous", + "permission": { + "columns": [ + "asset_type", + "creator_address", + "decimals", + "icon_uri", + "last_transaction_timestamp", + "last_transaction_version", + "maximum_v2", + "name", + "project_uri", + "supply_aggregator_table_handle_v1", + "supply_aggregator_table_key_v1", + "supply_v2", + "symbol", + "token_standard" + ], + "filter": {}, + "limit": 100 + } + } + ] + }, + { + "table": { + "name": "indexer_status", + "schema": "public" + }, + "select_permissions": [ + { + "role": "anonymous", + "permission": { + "columns": ["db", "is_indexer_up"], + "filter": {}, + "limit": 100 + } + } + ] + }, + { + "table": { + "name": "ledger_infos", + "schema": "public" + }, + "select_permissions": [ + { + "role": "anonymous", + "permission": { + "columns": ["chain_id"], + "filter": {} + } + } + ] + }, + { + "table": { + "name": "move_resources", + "schema": "public" + }, + "select_permissions": [ + { + "role": "anonymous", + "permission": { + "columns": ["address", "transaction_version"], + "filter": {}, + "limit": 100, + "allow_aggregations": true + } + } + ] + }, + { + "table": { + "name": "num_active_delegator_per_pool", + "schema": "public" + }, + "select_permissions": [ + { + "role": "anonymous", + "permission": { + "columns": ["num_active_delegator", "pool_address"], + "filter": {}, + "limit": 100 + } + } + ] + }, + { + "table": { + "name": "processor_status", + "schema": "public" + }, + "select_permissions": [ + { + "role": "anonymous", + "permission": { + "columns": [ + "last_success_version", + "last_transaction_timestamp", + "last_updated", + "processor" + ], + "filter": {}, + "limit": 100 + } + } + ] + }, + { + "table": { + "name": "proposal_votes", + "schema": "public" + }, + "select_permissions": [ + { + "role": "anonymous", + "permission": { + "columns": [ + "num_votes", + "proposal_id", + "should_pass", + "staking_pool_address", + "transaction_timestamp", + "transaction_version", + "voter_address" + ], + "filter": {}, + "limit": 100, + "allow_aggregations": true + } + } + ] + }, + { + "table": { + "name": "signatures", + "schema": "public" + }, + "select_permissions": [ + { + "role": "anonymous", + "permission": { + "columns": [ + "is_sender_primary", + "multi_agent_index", + "multi_sig_index", + "public_key", + "public_key_indices", + "signature", + "signer", + "threshold", + "transaction_block_height", + "transaction_version", + "type" + ], + "filter": {}, + "limit": 100 + }, + "comment": "" + } + ] + }, + { + "table": { + "name": "table_items", + "schema": "public" + }, + "select_permissions": [ + { + "role": "anonymous", + "permission": { + "columns": [ + "decoded_key", + "decoded_value", + "key", + "table_handle", + "transaction_version", + "write_set_change_index" + ], + "filter": {}, + "limit": 100 + } + } + ] + }, + { + "table": { + "name": "table_metadatas", + "schema": "public" + }, + "select_permissions": [ + { + "role": "anonymous", + "permission": { + "columns": ["handle", "key_type", "value_type"], + "filter": {}, + "limit": 100 + } + } + ] + }, + { + "table": { + "name": "token_activities", + "schema": "public" + }, + "object_relationships": [ + { + "name": "current_token_data", + "using": { + "manual_configuration": { + "column_mapping": { + "token_data_id_hash": "token_data_id_hash" + }, + "insertion_order": null, + "remote_table": { + "name": "current_token_datas", + "schema": "public" + } + } + } + } + ], + "array_relationships": [ + { + "name": "aptos_names_owner", + "using": { + "manual_configuration": { + "column_mapping": { + "event_account_address": "registered_address" + }, + "insertion_order": null, + "remote_table": { + "name": "current_aptos_names", + "schema": "public" + } + } + } + }, + { + "name": "aptos_names_to", + "using": { + "manual_configuration": { + "column_mapping": { + "to_address": "registered_address" + }, + "insertion_order": null, + "remote_table": { + "name": "current_aptos_names", + "schema": "public" + } + } + } + } + ], + "select_permissions": [ + { + "role": "anonymous", + "permission": { + "columns": [ + "coin_amount", + "coin_type", + "collection_data_id_hash", + "collection_name", + "creator_address", + "event_account_address", + "event_creation_number", + "event_index", + "event_sequence_number", + "from_address", + "name", + "property_version", + "to_address", + "token_amount", + "token_data_id_hash", + "transaction_timestamp", + "transaction_version", + "transfer_type" + ], + "filter": {}, + "limit": 100, + "allow_aggregations": true + } + } + ] + }, + { + "table": { + "name": "token_activities_v2", + "schema": "public" + }, + "object_relationships": [ + { + "name": "current_token_data", + "using": { + "manual_configuration": { + "column_mapping": { + "token_data_id": "token_data_id" + }, + "insertion_order": null, + "remote_table": { + "name": "current_token_datas_v2", + "schema": "public" + } + } + } + } + ], + "array_relationships": [ + { + "name": "aptos_names_from", + "using": { + "manual_configuration": { + "column_mapping": { + "from_address": "registered_address" + }, + "insertion_order": null, + "remote_table": { + "name": "current_aptos_names", + "schema": "public" + } + } + } + }, + { + "name": "aptos_names_to", + "using": { + "manual_configuration": { + "column_mapping": { + "to_address": "registered_address" + }, + "insertion_order": null, + "remote_table": { + "name": "current_aptos_names", + "schema": "public" + } + } + } + } + ], + "select_permissions": [ + { + "role": "anonymous", + "permission": { + "columns": [ + "after_value", + "before_value", + "entry_function_id_str", + "event_account_address", + "event_index", + "from_address", + "is_fungible_v2", + "property_version_v1", + "to_address", + "token_amount", + "token_data_id", + "token_standard", + "transaction_timestamp", + "transaction_version", + "type" + ], + "filter": {}, + "limit": 100, + "allow_aggregations": true + } + } + ] + }, + { + "table": { + "name": "token_datas", + "schema": "public" + }, + "select_permissions": [ + { + "role": "anonymous", + "permission": { + "columns": [ + "collection_data_id_hash", + "collection_name", + "creator_address", + "default_properties", + "description", + "description_mutable", + "largest_property_version", + "maximum", + "maximum_mutable", + "metadata_uri", + "name", + "payee_address", + "properties_mutable", + "royalty_mutable", + "royalty_points_denominator", + "royalty_points_numerator", + "supply", + "token_data_id_hash", + "transaction_timestamp", + "transaction_version", + "uri_mutable" + ], + "filter": {}, + "limit": 100 + } + } + ] + }, + { + "table": { + "name": "token_ownerships", + "schema": "public" + }, + "select_permissions": [ + { + "role": "anonymous", + "permission": { + "columns": [ + "amount", + "collection_data_id_hash", + "collection_name", + "creator_address", + "name", + "owner_address", + "property_version", + "table_handle", + "table_type", + "token_data_id_hash", + "transaction_timestamp", + "transaction_version" + ], + "filter": {}, + "limit": 100 + } + } + ] + }, + { + "table": { + "name": "tokens", + "schema": "public" + }, + "select_permissions": [ + { + "role": "anonymous", + "permission": { + "columns": [ + "collection_data_id_hash", + "collection_name", + "creator_address", + "name", + "property_version", + "token_data_id_hash", + "token_properties", + "transaction_timestamp", + "transaction_version" + ], + "filter": {}, + "limit": 100 + } + } + ] + }, + { + "table": { + "name": "user_transactions", + "schema": "public" + }, + "select_permissions": [ + { + "role": "anonymous", + "permission": { + "columns": [ + "block_height", + "entry_function_id_str", + "epoch", + "expiration_timestamp_secs", + "gas_unit_price", + "max_gas_amount", + "parent_signature_type", + "sender", + "sequence_number", + "timestamp", + "version" + ], + "filter": {}, + "limit": 100 + } + } + ] + } + ], + "configuration": { + "connection_info": { + "database_url": "INDEXER_V2_POSTGRES_URL", + "isolation_level": "read-committed", + "use_prepared_statements": false + } + } + } + ], + "query_collections": [ + { + "name": "allowed-queries", + "definition": { + "queries": [ + { + "name": "Latest Processor Status", + "query": "query LatestProcessorStatus {\n processor_status {\n processor\n last_updated\n last_success_version\n last_transaction_timestamp\n }\n}" + } + ] + } + } + ], + "allowlist": [ + { + "collection": "allowed-queries", + "scope": { + "global": true + } + } + ], + "rest_endpoints": [ + { + "comment": "", + "definition": { + "query": { + "collection_name": "allowed-queries", + "query_name": "Latest Processor Status" + } + }, + "methods": ["GET"], + "name": "Latest Processor Status", + "url": "get_latest_processor_status" + } + ], + "api_limits": { + "depth_limit": { + "global": 5, + "per_role": {} + }, + "disabled": false, + "time_limit": { + "global": 10, + "per_role": {} + } + }, + "metrics_config": { + "analyze_query_variables": true, + "analyze_response_body": true + } + } +} diff --git a/networks/suzuka/indexer/src/main.rs b/networks/suzuka/indexer/src/main.rs new file mode 100644 index 000000000..46516eb62 --- /dev/null +++ b/networks/suzuka/indexer/src/main.rs @@ -0,0 +1,183 @@ +use processor::IndexerGrpcProcessorConfig; +use server_framework::RunnableConfig; +use std::io::Write; +use tokio::task::JoinSet; +use tokio::time::Duration; + +const RUNTIME_WORKER_MULTIPLIER: usize = 2; + +fn main() -> Result<(), anyhow::Error> { + use tracing_subscriber::EnvFilter; + + tracing_subscriber::fmt() + .with_env_filter( + EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info")), + ) + .init(); + + let dot_movement = dot_movement::DotMovement::try_from_env()?; + let maptos_config = dot_movement + .try_get_or_create_config_from_json::()?; + + let default_indexer_config = build_processor_conf("default_processor", &maptos_config)?; + let usertx_indexer_config = build_processor_conf("user_transaction_processor", &maptos_config)?; + let accounttx_indexer_config = + build_processor_conf("account_transactions_processor", &maptos_config)?; + let coin_indexer_config = build_processor_conf("coin_processor", &maptos_config)?; + let event_indexer_config = build_processor_conf("events_processor", &maptos_config)?; + let fungible_indexer_config = build_processor_conf("fungible_asset_processor", &maptos_config)?; + let txmeta_indexer_config = + build_processor_conf("transaction_metadata_processor", &maptos_config)?; + + let num_cpus = num_cpus::get(); + let worker_threads = (num_cpus * RUNTIME_WORKER_MULTIPLIER).max(16); + println!( + "[Processor] Starting processor tokio runtime: num_cpus={}, worker_threads={}", + num_cpus, worker_threads + ); + + let mut builder = tokio::runtime::Builder::new_multi_thread(); + let ret: Result<(), anyhow::Error> = builder + .disable_lifo_slot() + .enable_all() + .worker_threads(worker_threads) + .build() + .unwrap() + .block_on({ + async move { + // Test the Grpc connection. + // The gRpc connection can fail because the Suzuka-node is started but the port is still not open. + // If the connection fail wait and retry. + test_grpc_connection(&maptos_config).await?; + + let mut set = JoinSet::new(); + set.spawn(async move { default_indexer_config.run().await }); + //wait all the migration is done. + tokio::time::sleep(Duration::from_secs(12)).await; + set.spawn(async move { usertx_indexer_config.run().await }); + set.spawn(async move { accounttx_indexer_config.run().await }); + set.spawn(async move { coin_indexer_config.run().await }); + set.spawn(async move { event_indexer_config.run().await }); + set.spawn(async move { fungible_indexer_config.run().await }); + set.spawn(async move { txmeta_indexer_config.run().await }); + + while let Some(res) = set.join_next().await { + if let Err(err) = res { + tracing::error!("An Error occurs during indexer execution: {err}"); + // If a processor break to avoid data inconsistency between processor + break; + } + } + set.shutdown().await; + Err(anyhow::anyhow!("At least One indexer processor failed. Exit")) + } + }); + if let Err(err) = ret { + tracing::error!("Indexer execution failed: {err}"); + std::process::exit(1); + } else { + std::process::exit(1); + } +} + +fn build_processor_conf( + processor_name: &str, + maptos_config: &maptos_execution_util::config::Config, +) -> Result { + let indexer_grpc_data_service_address = build_grpc_url(maptos_config); + + let default_sleep_time_between_request: u64 = std::env::var("SLEEP_TIME_BETWEEN_REQUEST_MS") + .map(|t| t.parse().unwrap_or(10)) + .unwrap_or(10); + //create config file + let indexer_config_content = format!( + "processor_config: + type: {} +postgres_connection_string: {}/postgres +indexer_grpc_data_service_address: {} +indexer_grpc_http2_ping_interval_in_secs: {} +indexer_grpc_http2_ping_timeout_in_secs: {} +auth_token: \"{}\" +default_sleep_time_between_request: {}", + processor_name, + maptos_config.indexer_processor.postgres_connection_string, + indexer_grpc_data_service_address, + maptos_config.indexer.maptos_indexer_grpc_inactivity_timeout, + maptos_config.indexer.maptos_indexer_grpc_inactivity_ping_interval, + maptos_config.indexer_processor.indexer_processor_auth_token, + default_sleep_time_between_request, + ); + + //let indexer_config_path = dot_movement.get_path().join("indexer_config.yaml"); + let mut output_file = tempfile::NamedTempFile::new()?; + // let mut output_file = std::fs::File::create(&indexer_config_path).map_err(|err| { + // anyhow::anyhow!("Indexer temps config file :{indexer_config_path:?} can't be created because of err:{err}") + // })?; + write!(output_file, "{}", indexer_config_content)?; + + let indexer_config = + server_framework::load::(&output_file.path().to_path_buf())?; + Ok(indexer_config) +} + +use reqwest::Client as HttpClient; + +async fn test_grpc_connection( + maptos_config: &maptos_execution_util::config::Config, +) -> Result<(), anyhow::Error> { + let indexer_grpc_data_service_address = build_grpc_url(maptos_config); + + let client = HttpClient::builder() + .http2_prior_knowledge() // Enforce HTTP/2 for gRpc + .timeout(Duration::from_secs(10)) + .build()?; + + let mut retry = 0; + while retry < 5 { + let response = client + .get(&indexer_grpc_data_service_address) + .header("Content-Type", "application/grpc") + .send() + .await; + + match response { + Ok(resp) => { + let status = resp.status(); + let body = resp.text().await?; + println!("Received response: {} {:?}", status, body); + if status.is_success() { + break; + } else { + tracing::info!("GRpc server return a bad status: {:?}. Retrying...", status); + tokio::time::sleep(Duration::from_secs(1)).await; // Wait before retrying + } + } + Err(err) => { + tracing::info!("Failed to connect to the gRp server: {:?}. Retrying...", err); + tokio::time::sleep(Duration::from_secs(1)).await; // Wait before retrying + } + } + retry += 1; + } + + if retry == 5 { + Err(anyhow::anyhow!( + "Faild to connect to the Grpc server : {indexer_grpc_data_service_address}" + )) + } else { + Ok(()) + } +} + +fn build_grpc_url(maptos_config: &maptos_execution_util::config::Config) -> String { + let indexer_grpc_data_service_address = format!( + "http://{}:{}", + maptos_config.indexer.maptos_indexer_grpc_listen_hostname, + maptos_config.indexer.maptos_indexer_grpc_listen_port + ); + tracing::info!( + "Connecting to indexer gRPC server at: {}", + indexer_grpc_data_service_address.clone() + ); + indexer_grpc_data_service_address +} diff --git a/networks/suzuka/setup/src/main.rs b/networks/suzuka/setup/src/main.rs index 06c1961a2..78d76b88a 100644 --- a/networks/suzuka/setup/src/main.rs +++ b/networks/suzuka/setup/src/main.rs @@ -39,7 +39,7 @@ async fn main() -> Result<(), anyhow::Error> { // get the config file let dot_movement = dot_movement::DotMovement::try_from_env()?; - let mut config_file = dot_movement.try_get_or_create_config_file().await?; + let config_file = dot_movement.try_get_or_create_config_file().await?; // get a matching godfig object let godfig: Godfig = Godfig::new(ConfigFile::new(config_file), vec![]); diff --git a/networks/suzuka/suzuka-full-node/Cargo.toml b/networks/suzuka/suzuka-full-node/Cargo.toml index 51360797e..27ce49fa2 100644 --- a/networks/suzuka/suzuka-full-node/Cargo.toml +++ b/networks/suzuka/suzuka-full-node/Cargo.toml @@ -17,9 +17,9 @@ m1-da-light-node-client = { workspace = true } m1-da-light-node-util = { workspace = true } mcr-settlement-client = { workspace = true, features = ["mock"] } mcr-settlement-manager = { workspace = true } -async-channel = { workspace = true } serde_json = { workspace = true } anyhow = { workspace = true } +futures = { workspace = true } tokio = { workspace = true } tokio-stream = { workspace = true } sha2 = { workspace = true } diff --git a/networks/suzuka/suzuka-full-node/src/da_db.rs b/networks/suzuka/suzuka-full-node/src/da_db.rs new file mode 100644 index 000000000..38c057406 --- /dev/null +++ b/networks/suzuka/suzuka-full-node/src/da_db.rs @@ -0,0 +1,100 @@ +use rocksdb::{ColumnFamilyDescriptor, Options, DB}; + +use std::path::Path; +use std::sync::Arc; + +mod column_families { + pub const EXECUTED_BLOCKS: &str = "executed_blocks"; + pub const SYNCED_HEIGHT: &str = "synced_height"; +} +use column_families::*; + +/// Simple data store for locally recorded DA events. +/// +/// An async access API is provided to avoid blocking async tasks. +/// The methods must be executed in the context of a Tokio runtime. +#[derive(Clone, Debug)] +pub struct DaDB { + inner: Arc, +} + +impl DaDB { + pub fn open(path: impl AsRef) -> anyhow::Result { + let mut options = Options::default(); + options.create_if_missing(true); + options.create_missing_column_families(true); + + let synced_height = ColumnFamilyDescriptor::new(SYNCED_HEIGHT, Options::default()); + let executed_blocks = ColumnFamilyDescriptor::new(EXECUTED_BLOCKS, Options::default()); + + let db = DB::open_cf_descriptors(&options, path, vec![synced_height, executed_blocks]) + .map_err(|e| anyhow::anyhow!("Failed to open DA DB: {:?}", e))?; + Ok(Self { inner: Arc::new(db) }) + } + + pub async fn add_executed_block(&self, id: String) -> Result<(), anyhow::Error> { + let da_db = self.inner.clone(); + tokio::task::spawn_blocking(move || { + let cf = da_db + .cf_handle(EXECUTED_BLOCKS) + .ok_or(anyhow::anyhow!("No executed_blocks column family"))?; + da_db + .put_cf(&cf, id.clone(), id) + .map_err(|e| anyhow::anyhow!("Failed to add executed block: {:?}", e)) + }) + .await??; + Ok(()) + } + + pub async fn has_executed_block(&self, id: String) -> Result { + let da_db = self.inner.clone(); + let id = tokio::task::spawn_blocking(move || { + let cf = da_db + .cf_handle(EXECUTED_BLOCKS) + .ok_or(anyhow::anyhow!("No executed_blocks column family"))?; + da_db + .get_cf(&cf, id) + .map_err(|e| anyhow::anyhow!("Failed to get executed block: {:?}", e)) + }) + .await??; + Ok(id.is_some()) + } + + pub async fn set_synced_height(&self, height: u64) -> Result<(), anyhow::Error> { + // This is heavy for this purpose, but progressively the contents of the DA DB will be used for more things + let da_db = self.inner.clone(); + tokio::task::spawn_blocking(move || { + let cf = da_db + .cf_handle(SYNCED_HEIGHT) + .ok_or(anyhow::anyhow!("No synced_height column family"))?; + let height = serde_json::to_string(&height) + .map_err(|e| anyhow::anyhow!("Failed to serialize synced height: {:?}", e))?; + da_db + .put_cf(&cf, "synced_height", height) + .map_err(|e| anyhow::anyhow!("Failed to set synced height: {:?}", e)) + }) + .await??; + Ok(()) + } + + pub async fn get_synced_height(&self) -> Result { + // This is heavy for this purpose, but progressively the contents of the DA DB will be used for more things + let da_db = self.inner.clone(); + let height = tokio::task::spawn_blocking(move || { + let cf = da_db + .cf_handle(SYNCED_HEIGHT) + .ok_or(anyhow::anyhow!("No synced_height column family"))?; + let height = da_db + .get_cf(&cf, "synced_height") + .map_err(|e| anyhow::anyhow!("Failed to get synced height: {:?}", e))?; + let height = match height { + Some(height) => serde_json::from_slice(&height) + .map_err(|e| anyhow::anyhow!("Failed to deserialize synced height: {:?}", e))?, + None => 0, + }; + Ok::(height) + }) + .await??; + Ok(height) + } +} diff --git a/networks/suzuka/suzuka-full-node/src/lib.rs b/networks/suzuka/suzuka-full-node/src/lib.rs index c9201ea39..0b3d4740a 100644 --- a/networks/suzuka/suzuka-full-node/src/lib.rs +++ b/networks/suzuka/suzuka-full-node/src/lib.rs @@ -1,27 +1,7 @@ +mod da_db; pub mod manager; pub mod partial; +mod tasks; #[cfg(test)] pub mod tests; - -pub trait SuzukaFullNode { - /// Runs the services until crash or shutdown. - async fn run_services(&self) -> Result<(), anyhow::Error>; - - /// Runs the background tasks until crash or shutdown. - async fn run_background_tasks(&self) -> Result<(), anyhow::Error>; - - /// Runs the executor until crash or shutdown. - async fn run_executor(&self) -> Result<(), anyhow::Error>; - - /// Runs the maptos rest api service until crash or shutdown. - async fn run_movement_rest(&self) -> Result<(), anyhow::Error>; - - /// Runs the full node until crash or shutdown. - async fn run(&self) -> Result<(), anyhow::Error> { - // run services and executor concurrently - tokio::try_join!(self.run_background_tasks(), self.run_services(), self.run_executor())?; - - Ok(()) - } -} diff --git a/networks/suzuka/suzuka-full-node/src/main.rs b/networks/suzuka/suzuka-full-node/src/main.rs index d29f19359..eea463a88 100644 --- a/networks/suzuka/suzuka-full-node/src/main.rs +++ b/networks/suzuka/suzuka-full-node/src/main.rs @@ -1,5 +1,4 @@ -use maptos_dof_execution::v1::Executor; -use suzuka_full_node::{manager::Manager, partial::SuzukaPartialNode}; +use suzuka_full_node::manager::Manager; use std::env; use std::process::ExitCode; @@ -16,7 +15,7 @@ async fn main() -> Result { let dot_movement = dot_movement::DotMovement::try_from_env()?; let config_file = dot_movement.try_get_or_create_config_file().await?; - let manager = Manager::>::new(config_file).await?; + let manager = Manager::new(config_file).await?; manager.try_run().await?; Ok(ExitCode::SUCCESS) diff --git a/networks/suzuka/suzuka-full-node/src/manager.rs b/networks/suzuka/suzuka-full-node/src/manager.rs index fcce5c547..ec2d58529 100644 --- a/networks/suzuka/suzuka-full-node/src/manager.rs +++ b/networks/suzuka/suzuka-full-node/src/manager.rs @@ -1,26 +1,20 @@ use super::partial::SuzukaPartialNode; -use crate::SuzukaFullNode; use anyhow::Context; use godfig::{backend::config_file::ConfigFile, Godfig}; -use maptos_dof_execution::v1::Executor; use suzuka_config::Config; use tokio::signal::unix::signal; use tokio::signal::unix::SignalKind; #[derive(Clone)] -pub struct Manager -where - Dof: SuzukaFullNode, -{ +pub struct Manager { godfig: Godfig, - _marker: std::marker::PhantomData, } // Implements a very simple manager using a marker strategy pattern. -impl Manager> { +impl Manager { pub async fn new(file: tokio::fs::File) -> Result { let godfig = Godfig::new(ConfigFile::new(file), vec![]); - Ok(Self { godfig, _marker: std::marker::PhantomData }) + Ok(Self { godfig }) } pub async fn try_run(&self) -> Result<(), anyhow::Error> { @@ -49,22 +43,17 @@ impl Manager> { let config = self.godfig.try_wait_for_ready().await?; - let (executor, background_task) = SuzukaPartialNode::try_from_config(config) + let node = SuzukaPartialNode::try_from_config(config) .await .context("Failed to create the executor")?; - let background_join_handle = tokio::spawn(background_task); - - let executor_join_handle = tokio::spawn(async move { executor.run().await }); + let join_handle = tokio::spawn(node.run()); // Use tokio::select! to wait for either the handle or a cancellation signal tokio::select! { _ = stop_rx.changed() =>(), // manage Suzuka node execution return. - res = background_join_handle => { - res??; - }, - res = executor_join_handle => { + res = join_handle => { res??; }, }; diff --git a/networks/suzuka/suzuka-full-node/src/partial.rs b/networks/suzuka/suzuka-full-node/src/partial.rs index 316a80cb2..31c6bd50c 100644 --- a/networks/suzuka/suzuka-full-node/src/partial.rs +++ b/networks/suzuka/suzuka-full-node/src/partial.rs @@ -1,487 +1,78 @@ -use crate::SuzukaFullNode; -use m1_da_light_node_client::{ - blob_response, BatchWriteRequest, BlobWrite, LightNodeServiceClient, - StreamReadFromHeightRequest, -}; -use maptos_dof_execution::{ - v1::Executor, DynOptFinExecutor, ExecutableBlock, ExecutableTransactions, HashValue, - SignatureVerifiedTransaction, SignedTransaction, Transaction, -}; -use mcr_settlement_client::{McrSettlementClient, McrSettlementClientOperations}; +use crate::{da_db::DaDB, tasks}; +use m1_da_light_node_client::LightNodeServiceClient; +use maptos_dof_execution::MakeOptFinServices; +use maptos_dof_execution::{v1::Executor, DynOptFinExecutor}; +use mcr_settlement_client::McrSettlementClient; use mcr_settlement_manager::CommitmentEventStream; -use mcr_settlement_manager::{McrSettlementManager, McrSettlementManagerOperations}; +use mcr_settlement_manager::McrSettlementManager; use movement_rest::MovementRest; -use movement_types::{Block, BlockCommitment, BlockCommitmentEvent}; +use suzuka_config::Config; use anyhow::Context; -use async_channel::{Receiver, Sender}; -use core::sync::atomic::AtomicU64; -use rocksdb::{ColumnFamilyDescriptor, Options, DB}; -use std::future::Future; -use std::sync::Arc; -use std::time::Duration; -use tokio_stream::StreamExt; -use tracing::{debug, error, info, info_span, warn, Instrument}; +use tokio::sync::mpsc; +use tokio::try_join; +use tracing::debug; + pub struct SuzukaPartialNode { executor: T, - transaction_sender: Sender, - pub transaction_receiver: Receiver, light_node_client: LightNodeServiceClient, settlement_manager: McrSettlementManager, + commitment_events: Option, movement_rest: MovementRest, - pub config: suzuka_config::Config, - da_db: Arc, + config: Config, + da_db: DaDB, } -const LOGGING_UID: AtomicU64 = AtomicU64::new(0); - impl SuzukaPartialNode where - T: DynOptFinExecutor + Clone + Send + Sync, -{ - pub fn new( - executor: T, - light_node_client: LightNodeServiceClient, - settlement_client: C, - movement_rest: MovementRest, - config: &suzuka_config::Config, - da_db: DB, - ) -> (Self, impl Future> + Send) - where - C: McrSettlementClientOperations + Send + 'static, - { - let (settlement_manager, commitment_events) = - McrSettlementManager::new(settlement_client, &config.mcr); - let (transaction_sender, transaction_receiver) = async_channel::unbounded(); - let bg_executor = executor.clone(); - ( - Self { - executor, - transaction_sender, - transaction_receiver, - light_node_client, - settlement_manager, - movement_rest, - config: config.clone(), - da_db: Arc::new(da_db), - }, - read_commitment_events(commitment_events, bg_executor), - ) - } - - fn bind_transaction_channel(&mut self) { - self.executor.set_tx_channel(self.transaction_sender.clone()); - } - - pub fn bound( - executor: T, - light_node_client: LightNodeServiceClient, - settlement_client: C, - movement_rest: MovementRest, - config: &suzuka_config::Config, - da_db: DB, - ) -> Result<(Self, impl Future> + Send), anyhow::Error> - where - C: McrSettlementClientOperations + Send + 'static, - { - let (mut node, background_task) = - Self::new(executor, light_node_client, settlement_client, movement_rest, config, da_db); - node.bind_transaction_channel(); - Ok((node, background_task)) - } - - async fn next_transaction_batch_write(&self) -> Result<(), anyhow::Error> { - // limit the total time batching transactions - let start = std::time::Instant::now(); - let (_, half_building_time) = self - .config - .m1_da_light_node - .m1_da_light_node_config - .try_block_building_parameters()?; - - let mut transactions = Vec::new(); - - let batch_id = LOGGING_UID.fetch_add(1, std::sync::atomic::Ordering::SeqCst); - loop { - let remaining = match half_building_time.checked_sub(start.elapsed().as_millis() as u64) - { - Some(remaining) => remaining, - None => { - // we have exceeded the half building time - break; - } - }; - - match tokio::time::timeout( - Duration::from_millis(remaining), - self.transaction_receiver.recv(), - ) - .await - { - Ok(transaction) => match transaction { - Ok(transaction) => { - info!( - target : "movement_timing", - batch_id = %batch_id, - tx_hash = %transaction.committed_hash(), - sender = %transaction.sender(), - sequence_number = transaction.sequence_number(), - "received transaction", - ); - let serialized_aptos_transaction = serde_json::to_vec(&transaction)?; - let movement_transaction = movement_types::Transaction::new( - serialized_aptos_transaction, - transaction.sequence_number(), - ); - let serialized_transaction = serde_json::to_vec(&movement_transaction)?; - transactions.push(BlobWrite { data: serialized_transaction }); - } - Err(_) => { - break; - } - }, - Err(_) => { - break; - } - } - } - - if transactions.len() > 0 { - info!( - target: "movement_timing", - batch_id = %batch_id, - transaction_count = transactions.len(), - "built_batch_write" - ); - let batch_write = BatchWriteRequest { blobs: transactions }; - let mut light_node_client = self.light_node_client.clone(); - tokio::task::spawn(async move { - light_node_client.batch_write(batch_write).await?; - Ok::<(), anyhow::Error>(()) - }); - } - - Ok(()) - } - - async fn write_transactions_to_da(&self) -> Result<(), anyhow::Error> { - loop { - self.next_transaction_batch_write().await?; - } - } - - // receive transactions from the transaction channel and send them to be executed - // ! This assumes the m1 da light node is running sequencer mode - pub async fn read_blocks_from_da(&self) -> Result<(), anyhow::Error> { - let mut stream = { - let mut light_node_client = self.light_node_client.clone(); - light_node_client - .stream_read_from_height(StreamReadFromHeightRequest { - height: self.get_synced_height().await?, - }) - .await? - } - .into_inner(); - - while let Some(blob) = stream.next().await { - debug!("Got blob: {:?}", blob); - - // get the block - let (block_bytes, block_timestamp, block_id, da_height) = match blob? - .blob - .ok_or(anyhow::anyhow!("No blob in response"))? - .blob_type - .ok_or(anyhow::anyhow!("No blob type in response"))? - { - blob_response::BlobType::SequencedBlobBlock(blob) => { - (blob.data, blob.timestamp, blob.blob_id, blob.height) - } - _ => { - anyhow::bail!("Invalid blob type in response") - } - }; - - // check if the block has already been executed - if self.has_executed_block(block_id.clone()).await? { - warn!("Block already executed: {:#?}. It will be skipped", block_id); - continue; - } - - // the da height must be greater than 1 - if da_height < 2 { - anyhow::bail!("Invalid DA height: {:?}", da_height); - } - - // decompress the block bytes - let block = tokio::task::spawn_blocking(move || { - let decompressed_block_bytes = zstd::decode_all(&block_bytes[..])?; - let block: Block = bcs::from_bytes(&decompressed_block_bytes)?; - Ok::(block) - }) - .await??; - - // get the transactions - let transaction_count = block.transactions.len(); - let span = info_span!(target: "movement_timing", "execute_block", id = %block_id); - let commitment = - self.execute_block_with_retries(block, block_timestamp).instrument(span).await?; - self.executor.decrement_transactions_in_flight(transaction_count as u64); - - // mark the da_height - 1 as synced - // we can't mark this height as synced because we must allow for the possibility of multiple blocks at the same height according to the m1 da specifications (which currently is built on celestia which itself allows more than one block at the same height) - self.set_synced_height(da_height - 1).await?; - - // set the block as executed - self.add_executed_block(block_id.to_string()).await?; - - // todo: this needs defaults - if self.config.mcr.should_settle() { - info!("Posting block commitment via settlement manager"); - match self.settlement_manager.post_block_commitment(commitment).await { - Ok(_) => {} - Err(e) => { - error!("Failed to post block commitment: {:?}", e); - } - } - } else { - info!("Skipping settlement"); - } - } - - Ok(()) - } - - /// Retries executing a block several times. - /// This can be valid behavior if the block timestamps are too tightly clustered for the full node execution. - /// However, this has to be deterministic, otherwise nodes will not be able to agree on the block commitment. - /// - /// This protocol has a bit of a cascading effect, whereby increasing the timestamp of a block will mean that the next block has a greater likelihood of also needing to have its timestamp increased and with a greater number of retries. This will generally reset so long as the retry increment and count do not increases the timestamp beyond the block building time. - async fn execute_block_with_retries( - &self, - block: Block, - mut block_timestamp: u64, - ) -> anyhow::Result { - for _ in 0..self.config.execution_extension.block_retry_count { - // we have to clone here because the block is supposed to be consumed by the executor - match self.execute_block(block.clone(), block_timestamp).await { - Ok(commitment) => return Ok(commitment), - Err(e) => { - error!("Failed to execute block: {:?}. Retrying", e); - block_timestamp += - self.config.execution_extension.block_retry_increment_microseconds; // increase the timestamp by 5 ms (5000 microseconds) - } - } - } - - anyhow::bail!("Failed to execute block after 5 retries") - } - - async fn execute_block( - &self, - block: Block, - block_timestamp: u64, - ) -> anyhow::Result { - let block_id = block.id(); - let block_hash = HashValue::from_slice(block.id())?; - - // get the transactions - let mut block_transactions = Vec::new(); - let block_metadata = self - .executor - .build_block_metadata(HashValue::sha3_256_of(block_id.0.as_slice()), block_timestamp) - .await?; - let block_metadata_transaction = - SignatureVerifiedTransaction::Valid(Transaction::BlockMetadata(block_metadata)); - block_transactions.push(block_metadata_transaction); - - for transaction in block.transactions { - let signed_transaction: SignedTransaction = serde_json::from_slice(&transaction.data)?; - let signature_verified_transaction = SignatureVerifiedTransaction::Valid( - Transaction::UserTransaction(signed_transaction), - ); - block_transactions.push(signature_verified_transaction); - } - - // form the executable transactions vec - let block = ExecutableTransactions::Unsharded(block_transactions); - - // form the executable block and execute it - let executable_block = ExecutableBlock::new(block_hash, block); - let block_id = executable_block.block_id; - let commitment = self.executor.execute_block_opt(executable_block).await?; - - info!("Executed block: {}", block_id); - - Ok(commitment) - } -} - -pub async fn read_commitment_events( - mut stream: CommitmentEventStream, - executor: T, -) -> anyhow::Result<()> -where - T: DynOptFinExecutor + Send + Sync, -{ - while let Some(res) = stream.next().await { - let event = match res { - Ok(event) => event, - Err(e) => { - error!("Failed to get commitment event: {:?}", e); - continue; - } - }; - match event { - BlockCommitmentEvent::Accepted(commitment) => { - debug!("Commitment accepted: {:?}", commitment); - executor.set_finalized_block_height(commitment.height).context(format!( - "failed to set finalized block height {}", - commitment.height - ))?; - } - BlockCommitmentEvent::Rejected { height, reason } => { - debug!("Commitment rejected: {:?} {:?}", height, reason); - let current_head_height = executor.get_block_head_height().await?; - if height > current_head_height { - // Nothing to revert - } else { - executor - .revert_block_head_to(height - 1) - .context(format!("failed to revert to block height {}", height - 1))?; - } - } - } - } - - Ok(()) -} - -impl SuzukaFullNode for SuzukaPartialNode -where - T: DynOptFinExecutor + Clone + Send + Sync, + T: DynOptFinExecutor + Send + 'static, { - /// Runs the services until crash or shutdown. - async fn run_services(&self) -> Result<(), anyhow::Error> { - self.executor.run_service().await?; - - Ok(()) - } - - /// Runs the background tasks until crash or shutdown. - async fn run_background_tasks(&self) -> Result<(), anyhow::Error> { - self.executor.run_background_tasks().await?; - - Ok(()) - } - // ! Currently this only implements opt. /// Runs the executor until crash or shutdown. - async fn run_executor(&self) -> Result<(), anyhow::Error> { - // ! todo: this is a temporary solution to rollover the genesis block, really this (a) needs to be read from the DA and (b) requires modifications to Aptos Core. - self.executor.rollover_genesis_block().await?; - // wait for both tasks to finish - tokio::try_join!(self.write_transactions_to_da(), self.read_blocks_from_da())?; - - Ok(()) - } - - /// Runs the maptos rest api service until crash or shutdown. - async fn run_movement_rest(&self) -> Result<(), anyhow::Error> { - self.movement_rest.run_service().await?; - Ok(()) - } -} - -impl SuzukaPartialNode { - pub async fn create_or_get_da_db(config: &suzuka_config::Config) -> Result { - let path = config.da_db.da_db_path.clone(); - - let mut options = Options::default(); - options.create_if_missing(true); - options.create_missing_column_families(true); - - let synced_height = ColumnFamilyDescriptor::new("synced_height", Options::default()); - let executed_blocks = ColumnFamilyDescriptor::new("executed_blocks", Options::default()); - - let db = DB::open_cf_descriptors(&options, path, vec![synced_height, executed_blocks]) - .map_err(|e| anyhow::anyhow!("Failed to open DA DB: {:?}", e))?; - - Ok(db) - } - - pub async fn set_synced_height(&self, height: u64) -> Result<(), anyhow::Error> { - // This is heavy for this purpose, but progressively the contents of the DA DB will be used for more things - let da_db = self.da_db.clone(); - tokio::task::spawn_blocking(move || { - let cf = da_db - .cf_handle("synced_height") - .ok_or(anyhow::anyhow!("No synced_height column family"))?; - let height = serde_json::to_string(&height) - .map_err(|e| anyhow::anyhow!("Failed to serialize synced height: {:?}", e))?; - da_db - .put_cf(&cf, "synced_height", height) - .map_err(|e| anyhow::anyhow!("Failed to set synced height: {:?}", e)) - }) - .await??; - Ok(()) - } - - pub async fn get_synced_height(&self) -> Result { - // This is heavy for this purpose, but progressively the contents of the DA DB will be used for more things - let da_db = self.da_db.clone(); - let height = tokio::task::spawn_blocking(move || { - let cf = da_db - .cf_handle("synced_height") - .ok_or(anyhow::anyhow!("No synced_height column family"))?; - let height = da_db - .get_cf(&cf, "synced_height") - .map_err(|e| anyhow::anyhow!("Failed to get synced height: {:?}", e))?; - let height = match height { - Some(height) => serde_json::from_slice(&height) - .map_err(|e| anyhow::anyhow!("Failed to deserialize synced height: {:?}", e))?, - None => 0, - }; - Ok::(height) - }) - .await??; - Ok(height) - } - - pub async fn add_executed_block(&self, id: String) -> Result<(), anyhow::Error> { - let da_db = self.da_db.clone(); - tokio::task::spawn_blocking(move || { - let cf = da_db - .cf_handle("executed_blocks") - .ok_or(anyhow::anyhow!("No executed_blocks column family"))?; - da_db - .put_cf(&cf, id.clone(), id) - .map_err(|e| anyhow::anyhow!("Failed to add executed block: {:?}", e)) - }) - .await??; - Ok(()) - } + pub async fn run(self) -> Result<(), anyhow::Error> { + let (transaction_sender, transaction_receiver) = mpsc::channel(16); + let (context, exec_background) = self + .executor + .background(transaction_sender, &self.config.execution_config.maptos_config)?; + let services = context.services(); + let mut movement_rest = self.movement_rest; + movement_rest.set_context(services.opt_api_context()); + let exec_settle_task = tasks::execute_settle::Task::new( + self.executor, + self.settlement_manager, + self.da_db, + self.light_node_client.clone(), + self.commitment_events, + self.config.execution_extension.clone(), + ); + let transaction_ingress_task = tasks::transaction_ingress::Task::new( + transaction_receiver, + self.light_node_client, + // FIXME: why are the struct member names so tautological? + self.config.m1_da_light_node.m1_da_light_node_config, + ); - pub async fn has_executed_block(&self, id: String) -> Result { - let da_db = self.da_db.clone(); - let id = tokio::task::spawn_blocking(move || { - let cf = da_db - .cf_handle("executed_blocks") - .ok_or(anyhow::anyhow!("No executed_blocks column family"))?; - da_db - .get_cf(&cf, id) - .map_err(|e| anyhow::anyhow!("Failed to get executed block: {:?}", e)) - }) - .await??; - Ok(id.is_some()) + let ( + execution_and_settlement_result, + transaction_ingress_result, + background_task_result, + services_result, + ) = try_join!( + tokio::spawn(async move { exec_settle_task.run().await }), + tokio::spawn(async move { transaction_ingress_task.run().await }), + tokio::spawn(exec_background), + tokio::spawn(services.run()), + // tokio::spawn(async move { movement_rest.run_service().await }), + )?; + execution_and_settlement_result + .and(transaction_ingress_result) + .and(background_task_result) + .and(services_result) } } impl SuzukaPartialNode { - pub async fn try_from_config( - config: suzuka_config::Config, - ) -> Result<(Self, impl Future> + Send), anyhow::Error> { - let (tx, _) = async_channel::unbounded(); - + pub async fn try_from_config(config: Config) -> Result { // todo: extract into getter let light_node_connection_hostname = config .m1_da_light_node @@ -506,26 +97,34 @@ impl SuzukaPartialNode { .context("Failed to connect to light node")?; debug!("Creating the executor"); - let executor = Executor::try_from_config(tx, config.execution_config.maptos_config.clone()) + let executor = Executor::try_from_config(&config.execution_config.maptos_config) .context("Failed to create the inner executor")?; debug!("Creating the settlement client"); - let settlement_client = McrSettlementClient::build_with_config(config.mcr.clone()) + let settlement_client = McrSettlementClient::build_with_config(&config.mcr) .await .context("Failed to build MCR settlement client with config")?; + let (settlement_manager, commitment_events) = + McrSettlementManager::new(settlement_client, &config.mcr); + let commitment_events = + if config.mcr.should_settle() { Some(commitment_events) } else { None }; debug!("Creating the movement rest service"); - let movement_rest = MovementRest::try_from_env(Some(executor.executor.context.clone())) - .context("Failed to create MovementRest")?; + let movement_rest = + MovementRest::try_from_env().context("Failed to create MovementRest")?; debug!("Creating the DA DB"); - let da_db = Self::create_or_get_da_db(&config) - .await - .context("Failed to create or get DA DB")?; - - Self::bound(executor, light_node_client, settlement_client, movement_rest, &config, da_db) - .context( - "Failed to bind the executor, light node client, settlement client, and movement rest", - ) + let da_db = + DaDB::open(&config.da_db.da_db_path).context("Failed to create or get DA DB")?; + + Ok(Self { + executor, + light_node_client, + settlement_manager, + commitment_events, + movement_rest, + config, + da_db, + }) } } diff --git a/networks/suzuka/suzuka-full-node/src/tasks/execute_settle.rs b/networks/suzuka/suzuka-full-node/src/tasks/execute_settle.rs new file mode 100644 index 000000000..6a72785f5 --- /dev/null +++ b/networks/suzuka/suzuka-full-node/src/tasks/execute_settle.rs @@ -0,0 +1,268 @@ +//! Task module to execute blocks from the DA and process settlement. + +use crate::da_db::DaDB; + +use m1_da_light_node_client::{ + blob_response, LightNodeServiceClient, StreamReadFromHeightRequest, + StreamReadFromHeightResponse, +}; +use maptos_dof_execution::{ + DynOptFinExecutor, ExecutableBlock, ExecutableTransactions, HashValue, + SignatureVerifiedTransaction, SignedTransaction, Transaction, +}; +use mcr_settlement_manager::{CommitmentEventStream, McrSettlementManagerOperations}; +use movement_types::block::{Block, BlockCommitment, BlockCommitmentEvent}; + +use anyhow::Context; +use futures::{future::Either, stream}; +use suzuka_config::execution_extension; +use tokio::select; +use tokio_stream::{Stream, StreamExt}; +use tracing::{debug, error, info, info_span, warn, Instrument}; + +pub struct Task { + executor: E, + settlement_manager: S, + da_db: DaDB, + da_light_node_client: LightNodeServiceClient, + // Stream receiving commitment events, conditionally enabled + commitment_events: + Either::Item>>, + execution_extension: execution_extension::Config, +} + +impl Task { + pub(crate) fn new( + executor: E, + settlement_manager: S, + da_db: DaDB, + da_light_node_client: LightNodeServiceClient, + commitment_events: Option, + execution_extension: execution_extension::Config, + ) -> Self { + let commitment_events = match commitment_events { + Some(stream) => Either::Left(stream), + None => Either::Right(stream::pending()), + }; + Task { + executor, + settlement_manager, + da_db, + da_light_node_client, + commitment_events, + execution_extension, + } + } + + fn settlement_enabled(&self) -> bool { + matches!(&self.commitment_events, Either::Left(_)) + } +} + +impl Task +where + E: DynOptFinExecutor, + S: McrSettlementManagerOperations, +{ + pub async fn run(mut self) -> anyhow::Result<()> { + // TODO: this is a temporary solution to rollover the genesis block, really this + // (a) needs to be read from the DA and + // (b) requires modifications to Aptos Core. + self.executor.rollover_genesis_block().await?; + + let mut blocks_from_da = self + .da_light_node_client + .stream_read_from_height(StreamReadFromHeightRequest { + height: self.da_db.get_synced_height().await?, + }) + .await? + .into_inner(); + + loop { + select! { + Some(res) = blocks_from_da.next() => { + let response = res.context("failed to get next block from DA")?; + self.process_block_from_da(response).await?; + } + Some(res) = self.commitment_events.next() => { + let event = res.context("failed to get commitment event")?; + self.process_commitment_event(event).await?; + } + else => break, + } + } + Ok(()) + } + + async fn process_block_from_da( + &mut self, + response: StreamReadFromHeightResponse, + ) -> anyhow::Result<()> { + // get the block + let (block_bytes, block_timestamp, block_id, da_height) = match response + .blob + .ok_or(anyhow::anyhow!("No blob in response"))? + .blob_type + .ok_or(anyhow::anyhow!("No blob type in response"))? + { + blob_response::BlobType::SequencedBlobBlock(blob) => { + (blob.data, blob.timestamp, blob.blob_id, blob.height) + } + _ => { + anyhow::bail!("Invalid blob type in response") + } + }; + + // check if the block has already been executed + if self.da_db.has_executed_block(block_id.clone()).await? { + warn!("Block already executed: {:#?}. It will be skipped", block_id); + return Ok(()); + } + + // the da height must be greater than 1 + if da_height < 2 { + anyhow::bail!("Invalid DA height: {:?}", da_height); + } + + // decompress the block bytes + let block = tokio::task::spawn_blocking(move || { + let decompressed_block_bytes = zstd::decode_all(&block_bytes[..])?; + let block: Block = bcs::from_bytes(&decompressed_block_bytes)?; + Ok::(block) + }) + .await??; + + // get the transactions + let transactions_count = block.transactions().len(); + let span = info_span!(target: "movement_timing", "execute_block", id = %block_id); + let commitment = + self.execute_block_with_retries(block, block_timestamp).instrument(span).await?; + + // decrement the number of transactions in flight on the executor + self.executor.decrement_transactions_in_flight(transactions_count as u64); + + // mark the da_height - 1 as synced + // we can't mark this height as synced because we must allow for the possibility of multiple blocks at the same height according to the m1 da specifications (which currently is built on celestia which itself allows more than one block at the same height) + self.da_db.set_synced_height(da_height - 1).await?; + + // set the block as executed + self.da_db.add_executed_block(block_id.to_string()).await?; + + // todo: this needs defaults + if self.settlement_enabled() { + info!("Posting block commitment via settlement manager"); + match self.settlement_manager.post_block_commitment(commitment).await { + Ok(_) => {} + Err(e) => { + error!("Failed to post block commitment: {:?}", e); + } + } + } else { + info!(block_id = %block_id, "Skipping settlement"); + } + + Ok(()) + } +} + +impl Task +where + E: DynOptFinExecutor, +{ + /// Retries executing a block several times. + /// This can be valid behavior if the block timestamps are too tightly clustered for the full node execution. + /// However, this has to be deterministic, otherwise nodes will not be able to agree on the block commitment. + async fn execute_block_with_retries( + &mut self, + block: Block, + mut block_timestamp: u64, + ) -> anyhow::Result { + for _ in 0..self.execution_extension.block_retry_count { + // we have to clone here because the block is supposed to be consumed by the executor + match self.execute_block(block.clone(), block_timestamp).await { + Ok(commitment) => return Ok(commitment), + Err(e) => { + warn!("Failed to execute block: {:?}. Retrying", e); + block_timestamp += self.execution_extension.block_retry_increment_microseconds; // increase the timestamp by 5 ms (5000 microseconds) + } + } + } + + anyhow::bail!("Failed to execute block after 5 retries") + } + + async fn execute_block( + &mut self, + block: Block, + block_timestamp: u64, + ) -> anyhow::Result { + let block_id = block.id(); + let block_hash = HashValue::from_slice(block.id())?; + + // get the transactions + let mut block_transactions = Vec::new(); + let block_metadata = self.executor.build_block_metadata( + HashValue::sha3_256_of(block_id.as_bytes().as_slice()), + block_timestamp, + )?; + let block_metadata_transaction = + SignatureVerifiedTransaction::Valid(Transaction::BlockMetadata(block_metadata)); + block_transactions.push(block_metadata_transaction); + + for transaction in block.transactions() { + let signed_transaction: SignedTransaction = serde_json::from_slice(transaction.data())?; + + // check if the transaction has already been executed to prevent replays + if self + .executor + .has_executed_transaction_opt(signed_transaction.committed_hash())? + { + continue; + } + + let signature_verified_transaction = SignatureVerifiedTransaction::Valid( + Transaction::UserTransaction(signed_transaction), + ); + block_transactions.push(signature_verified_transaction); + } + + // form the executable transactions vec + let block = ExecutableTransactions::Unsharded(block_transactions); + + // form the executable block and execute it + let executable_block = ExecutableBlock::new(block_hash, block); + let block_id = executable_block.block_id; + let commitment = self.executor.execute_block_opt(executable_block).await?; + + info!("Executed block: {}", block_id); + + Ok(commitment) + } + + async fn process_commitment_event( + &mut self, + event: BlockCommitmentEvent, + ) -> anyhow::Result<()> { + match event { + BlockCommitmentEvent::Accepted(commitment) => { + debug!("Commitment accepted: {:?}", commitment); + self.executor + .set_finalized_block_height(commitment.height()) + .context("failed to set finalized block height") + } + BlockCommitmentEvent::Rejected { height, reason } => { + debug!("Commitment rejected: {:?} {:?}", height, reason); + let current_head_height = self.executor.get_block_head_height()?; + if height > current_head_height { + // Nothing to revert + Ok(()) + } else { + self.executor + .revert_block_head_to(height - 1) + .await + .context(format!("failed to revert to block height {}", height - 1)) + } + } + } + } +} diff --git a/networks/suzuka/suzuka-full-node/src/tasks/mod.rs b/networks/suzuka/suzuka-full-node/src/tasks/mod.rs new file mode 100644 index 000000000..fbd32b3ca --- /dev/null +++ b/networks/suzuka/suzuka-full-node/src/tasks/mod.rs @@ -0,0 +1,4 @@ +//! Modules to separate full node processing into actor-like tasks. + +pub mod execute_settle; +pub mod transaction_ingress; diff --git a/networks/suzuka/suzuka-full-node/src/tasks/transaction_ingress.rs b/networks/suzuka/suzuka-full-node/src/tasks/transaction_ingress.rs new file mode 100644 index 000000000..a02108f53 --- /dev/null +++ b/networks/suzuka/suzuka-full-node/src/tasks/transaction_ingress.rs @@ -0,0 +1,113 @@ +//! Task to process incoming transactions and write to DA + +use m1_da_light_node_client::{BatchWriteRequest, BlobWrite, LightNodeServiceClient}; +use m1_da_light_node_util::config::Config as LightNodeConfig; +use maptos_dof_execution::SignedTransaction; + +use tokio::sync::mpsc; +use tracing::{info, warn}; + +use std::ops::ControlFlow; +use std::sync::atomic::AtomicU64; +use std::time::{Duration, Instant}; + +const LOGGING_UID: AtomicU64 = AtomicU64::new(0); + +pub struct Task { + transaction_receiver: mpsc::Receiver, + da_light_node_client: LightNodeServiceClient, + da_light_node_config: LightNodeConfig, +} + +impl Task { + pub(crate) fn new( + transaction_receiver: mpsc::Receiver, + da_light_node_client: LightNodeServiceClient, + da_light_node_config: LightNodeConfig, + ) -> Self { + Task { transaction_receiver, da_light_node_client, da_light_node_config } + } + + pub async fn run(mut self) -> anyhow::Result<()> { + while let ControlFlow::Continue(()) = self.spawn_write_next_transaction_batch().await? {} + Ok(()) + } + + /// Constructs a batch of transactions then spawns the write request to the DA in the background. + async fn spawn_write_next_transaction_batch( + &mut self, + ) -> Result, anyhow::Error> { + use ControlFlow::{Break, Continue}; + + // limit the total time batching transactions + let start = Instant::now(); + let (_, half_building_time) = self.da_light_node_config.try_block_building_parameters()?; + + let mut transactions = Vec::new(); + + let batch_id = LOGGING_UID.fetch_add(1, std::sync::atomic::Ordering::SeqCst); + loop { + let remaining = match half_building_time.checked_sub(start.elapsed().as_millis() as u64) + { + Some(remaining) => remaining, + None => { + // we have exceeded the half building time + break; + } + }; + + match tokio::time::timeout( + Duration::from_millis(remaining), + self.transaction_receiver.recv(), + ) + .await + { + Ok(transaction) => match transaction { + Some(transaction) => { + info!( + target : "movement_timing", + batch_id = %batch_id, + tx_hash = %transaction.committed_hash(), + sender = %transaction.sender(), + sequence_number = transaction.sequence_number(), + "received transaction", + ); + let serialized_aptos_transaction = serde_json::to_vec(&transaction)?; + let movement_transaction = movement_types::transaction::Transaction::new( + serialized_aptos_transaction, + transaction.sequence_number(), + ); + let serialized_transaction = serde_json::to_vec(&movement_transaction)?; + transactions.push(BlobWrite { data: serialized_transaction }); + } + None => { + // The transaction stream is closed, terminate the task. + return Ok(Break(())); + } + }, + Err(_) => { + break; + } + } + } + + if transactions.len() > 0 { + info!( + target: "movement_timing", + batch_id = %batch_id, + transaction_count = transactions.len(), + "built_batch_write" + ); + let batch_write = BatchWriteRequest { blobs: transactions }; + // spawn the actual batch write request in the background + let mut da_light_node_client = self.da_light_node_client.clone(); + tokio::spawn(async move { + if let Err(e) = da_light_node_client.batch_write(batch_write).await { + warn!("failed to write batch to DA: {:?}", e); + } + }); + } + + Ok(Continue(())) + } +} diff --git a/nix/aptos-faucet-service.nix b/nix/aptos-faucet-service.nix deleted file mode 100755 index cd87b31f2..000000000 --- a/nix/aptos-faucet-service.nix +++ /dev/null @@ -1,20 +0,0 @@ -{ pkgs, commonArgs, craneLib }: - -craneLib.buildPackage (commonArgs // { - doCheck = false; - cargoArtifacts = craneLib.buildDepsOnly commonArgs; - - buildPhase = '' - export PATH=$PATH:${pkgs.rustfmt}/bin - export OPENSSL_DEV=${pkgs.openssl.dev} - export PKG_CONFIG_PATH=${pkgs.openssl.dev}/lib/pkgconfig${pkgs.lib.optionalString pkgs.stdenv.isLinux ":${pkgs.lib.getDev pkgs.systemd}/lib/pkgconfig"} - export SNAPPY=${if pkgs.stdenv.isLinux then pkgs.snappy else ""} - export PATH=$PATH:${pkgs.rustfmt}/bin - cargo build --release --package aptos-faucet-service - ''; - - installPhase = '' - mkdir -p $out/bin - cp target/release/aptos-faucet-service $out/bin/ - ''; -}) diff --git a/nix/celestia-app.nix b/nix/celestia-app.nix deleted file mode 100644 index 951c5d54c..000000000 --- a/nix/celestia-app.nix +++ /dev/null @@ -1,25 +0,0 @@ -{ pkgs, }: - -pkgs.buildGoModule rec { - pname = "celestia-app"; - version = "v1.8.0"; - - src = pkgs.fetchFromGitHub { - owner = "celestiaorg"; - repo = "celestia-app"; - rev = "e75a1fdc8f2386d9f389cb596c88ca7cc19563af"; - sha256 = "sha256-EE9r1sybbm4Hyh57/nC8utMx/uFdMsIdPecxBtDqAbk="; - }; - - vendorHash = "sha256-2vU1liAm0us7Nk1eawgMvarhq77+IUS0VE61FuvQbuQ="; - - # Specify the subpackage to build - subPackages = [ "cmd/celestia-appd" ]; - - meta = with pkgs.lib; { - description = "Celestia App is PoS full node implementation for the Celestia blockchain."; - homepage = "https://github.com/celestiaorg/celestia-app"; - license = licenses.mit; - maintainers = with maintainers; [ maintainers.example ]; - }; -} \ No newline at end of file diff --git a/nix/celestia-node.nix b/nix/celestia-node.nix deleted file mode 100644 index 30b4493fa..000000000 --- a/nix/celestia-node.nix +++ /dev/null @@ -1,25 +0,0 @@ -{ pkgs, }: - -pkgs.buildGoModule rec { - pname = "celestia-node"; - version = "0.13.3"; - - src = pkgs.fetchFromGitHub { - owner = "celestiaorg"; - repo = "celestia-node"; - rev = "05238b3e087eb9ecd3b9684cd0125f2400f6f0c7"; - sha256 = "sha256-bmFcJrC4ocbCw1pew2HKEdLj6+1D/0VuWtdoTs1S2sU="; # Replace with the actual sha256 - }; - - vendorHash = "sha256-8RC/9KiFOsEJDpt7d8WtzRLn0HzYrZ1LIHo6lOKSQxU="; # Replace with the correct vendor hash - - # Specify the subpackage to build - subPackages = [ "cmd/celestia" "cmd/cel-key" ]; - - meta = with pkgs.lib; { - description = "Build specific Go subpackage in Nix"; - homepage = "https://github.com/celestiaorg/celestia-node"; - license = licenses.mit; - maintainers = with maintainers; [ maintainers.example ]; - }; -} diff --git a/out/WETH9.sol/WETH9.json b/out/WETH9.sol/WETH9.json new file mode 100644 index 000000000..e048d08ec --- /dev/null +++ b/out/WETH9.sol/WETH9.json @@ -0,0 +1 @@ +{"abi":[{"type":"receive","stateMutability":"payable"},{"type":"function","name":"allowance","inputs":[{"name":"","type":"address","internalType":"address"},{"name":"","type":"address","internalType":"address"}],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"type":"function","name":"approve","inputs":[{"name":"guy","type":"address","internalType":"address"},{"name":"wad","type":"uint256","internalType":"uint256"}],"outputs":[{"name":"","type":"bool","internalType":"bool"}],"stateMutability":"nonpayable"},{"type":"function","name":"balanceOf","inputs":[{"name":"","type":"address","internalType":"address"}],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"type":"function","name":"decimals","inputs":[],"outputs":[{"name":"","type":"uint8","internalType":"uint8"}],"stateMutability":"view"},{"type":"function","name":"deposit","inputs":[],"outputs":[],"stateMutability":"payable"},{"type":"function","name":"name","inputs":[],"outputs":[{"name":"","type":"string","internalType":"string"}],"stateMutability":"view"},{"type":"function","name":"symbol","inputs":[],"outputs":[{"name":"","type":"string","internalType":"string"}],"stateMutability":"view"},{"type":"function","name":"totalSupply","inputs":[],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"type":"function","name":"transfer","inputs":[{"name":"dst","type":"address","internalType":"address"},{"name":"wad","type":"uint256","internalType":"uint256"}],"outputs":[{"name":"","type":"bool","internalType":"bool"}],"stateMutability":"nonpayable"},{"type":"function","name":"transferFrom","inputs":[{"name":"src","type":"address","internalType":"address"},{"name":"dst","type":"address","internalType":"address"},{"name":"wad","type":"uint256","internalType":"uint256"}],"outputs":[{"name":"","type":"bool","internalType":"bool"}],"stateMutability":"nonpayable"},{"type":"function","name":"withdraw","inputs":[{"name":"wad","type":"uint256","internalType":"uint256"}],"outputs":[],"stateMutability":"nonpayable"},{"type":"event","name":"Approval","inputs":[{"name":"src","type":"address","indexed":true,"internalType":"address"},{"name":"guy","type":"address","indexed":true,"internalType":"address"},{"name":"wad","type":"uint256","indexed":false,"internalType":"uint256"}],"anonymous":false},{"type":"event","name":"Deposit","inputs":[{"name":"dst","type":"address","indexed":true,"internalType":"address"},{"name":"wad","type":"uint256","indexed":false,"internalType":"uint256"}],"anonymous":false},{"type":"event","name":"Transfer","inputs":[{"name":"src","type":"address","indexed":true,"internalType":"address"},{"name":"dst","type":"address","indexed":true,"internalType":"address"},{"name":"wad","type":"uint256","indexed":false,"internalType":"uint256"}],"anonymous":false},{"type":"event","name":"Withdrawal","inputs":[{"name":"src","type":"address","indexed":true,"internalType":"address"},{"name":"wad","type":"uint256","indexed":false,"internalType":"uint256"}],"anonymous":false}],"bytecode":{"object":"0x60c0604052600d60809081526c2bb930b83832b21022ba3432b960991b60a05260009061002c9082610114565b506040805180820190915260048152630ae8aa8960e31b60208201526001906100559082610114565b506002805460ff1916601217905534801561006f57600080fd5b506101d2565b634e487b7160e01b600052604160045260246000fd5b600181811c9082168061009f57607f821691505b6020821081036100bf57634e487b7160e01b600052602260045260246000fd5b50919050565b601f82111561010f57806000526020600020601f840160051c810160208510156100ec5750805b601f840160051c820191505b8181101561010c57600081556001016100f8565b50505b505050565b81516001600160401b0381111561012d5761012d610075565b6101418161013b845461008b565b846100c5565b6020601f821160018114610175576000831561015d5750848201515b600019600385901b1c1916600184901b17845561010c565b600084815260208120601f198516915b828110156101a55787850151825560209485019460019092019101610185565b50848210156101c35786840151600019600387901b60f8161c191681555b50505050600190811b01905550565b610870806101e16000396000f3fe6080604052600436106100a05760003560e01c8063313ce56711610064578063313ce5671461016c57806370a082311461019857806395d89b41146101c5578063a9059cbb146101da578063d0e30db0146101fa578063dd62ed3e1461020257600080fd5b806306fdde03146100b4578063095ea7b3146100df57806318160ddd1461010f57806323b872dd1461012c5780632e1a7d4d1461014c57600080fd5b366100af576100ad61023a565b005b600080fd5b3480156100c057600080fd5b506100c9610295565b6040516100d6919061068c565b60405180910390f35b3480156100eb57600080fd5b506100ff6100fa3660046106f6565b610323565b60405190151581526020016100d6565b34801561011b57600080fd5b50475b6040519081526020016100d6565b34801561013857600080fd5b506100ff610147366004610720565b610390565b34801561015857600080fd5b506100ad61016736600461075d565b61058b565b34801561017857600080fd5b506002546101869060ff1681565b60405160ff90911681526020016100d6565b3480156101a457600080fd5b5061011e6101b3366004610776565b60036020526000908152604090205481565b3480156101d157600080fd5b506100c961066b565b3480156101e657600080fd5b506100ff6101f53660046106f6565b610678565b6100ad61023a565b34801561020e57600080fd5b5061011e61021d366004610791565b600460209081526000928352604080842090915290825290205481565b33600090815260036020526040812080543492906102599084906107da565b909155505060405134815233907fe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c9060200160405180910390a2565b600080546102a2906107ed565b80601f01602080910402602001604051908101604052809291908181526020018280546102ce906107ed565b801561031b5780601f106102f05761010080835404028352916020019161031b565b820191906000526020600020905b8154815290600101906020018083116102fe57829003601f168201915b505050505081565b3360008181526004602090815260408083206001600160a01b038716808552925280832085905551919290917f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9259061037e9086815260200190565b60405180910390a35060015b92915050565b6001600160a01b0383166000908152600360205260408120548211156103f45760405162461bcd60e51b8152602060048201526014602482015273496e73756666696369656e742062616c616e636560601b60448201526064015b60405180910390fd5b6001600160a01b038416331480159061043257506001600160a01b038416600090815260046020908152604080832033845290915290205460001914155b156104d8576001600160a01b038416600090815260046020908152604080832033845290915290205482111561049f5760405162461bcd60e51b8152602060048201526012602482015271105b1b1bddd85b98d948195e18d95959195960721b60448201526064016103eb565b6001600160a01b0384166000908152600460209081526040808320338452909152812080548492906104d2908490610827565b90915550505b6001600160a01b03841660009081526003602052604081208054849290610500908490610827565b90915550506001600160a01b0383166000908152600360205260408120805484929061052d9084906107da565b92505081905550826001600160a01b0316846001600160a01b03167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef8460405161057991815260200190565b60405180910390a35060019392505050565b336000908152600360205260409020548111156105e15760405162461bcd60e51b8152602060048201526014602482015273496e73756666696369656e742062616c616e636560601b60448201526064016103eb565b3360009081526003602052604081208054839290610600908490610827565b9091555050604051339082156108fc029083906000818181858888f19350505050158015610632573d6000803e3d6000fd5b5060405181815233907f7fcf532c15f0a6db0bd6d0e038bea71d30d808c7d98cb3bf7268a95bf5081b659060200160405180910390a250565b600180546102a2906107ed565b6000610685338484610390565b9392505050565b602081526000825180602084015260005b818110156106ba576020818601810151604086840101520161069d565b506000604082850101526040601f19601f83011684010191505092915050565b80356001600160a01b03811681146106f157600080fd5b919050565b6000806040838503121561070957600080fd5b610712836106da565b946020939093013593505050565b60008060006060848603121561073557600080fd5b61073e846106da565b925061074c602085016106da565b929592945050506040919091013590565b60006020828403121561076f57600080fd5b5035919050565b60006020828403121561078857600080fd5b610685826106da565b600080604083850312156107a457600080fd5b6107ad836106da565b91506107bb602084016106da565b90509250929050565b634e487b7160e01b600052601160045260246000fd5b8082018082111561038a5761038a6107c4565b600181811c9082168061080157607f821691505b60208210810361082157634e487b7160e01b600052602260045260246000fd5b50919050565b8181038181111561038a5761038a6107c456fea264697066735822122083e6d1dfb18c7a4953c9ec852d6b89cc446773e4ee001c4108c7be8fee0805e964736f6c634300081a0033","sourceMap":"476:40:0:-:0;455:1895;476:40;;455:1895;476:40;;;-1:-1:-1;;;476:40:0;;-1:-1:-1;;476:40:0;;-1:-1:-1;476:40:0;:::i;:::-;-1:-1:-1;522:31:0;;;;;;;;;;;;-1:-1:-1;;;522:31:0;;;;;;;;;;:::i;:::-;-1:-1:-1;559:27:0;;;-1:-1:-1;;559:27:0;584:2;559:27;;;455:1895;;;;;;;;;;;;14:127:1;75:10;70:3;66:20;63:1;56:31;106:4;103:1;96:15;130:4;127:1;120:15;146:380;225:1;221:12;;;;268;;;289:61;;343:4;335:6;331:17;321:27;;289:61;396:2;388:6;385:14;365:18;362:38;359:161;;442:10;437:3;433:20;430:1;423:31;477:4;474:1;467:15;505:4;502:1;495:15;359:161;;146:380;;;:::o;657:518::-;759:2;754:3;751:11;748:421;;;795:5;792:1;785:16;839:4;836:1;826:18;909:2;897:10;893:19;890:1;886:27;880:4;876:38;945:4;933:10;930:20;927:47;;;-1:-1:-1;968:4:1;927:47;1023:2;1018:3;1014:12;1011:1;1007:20;1001:4;997:31;987:41;;1078:81;1096:2;1089:5;1086:13;1078:81;;;1155:1;1141:16;;1122:1;1111:13;1078:81;;;1082:3;;748:421;657:518;;;:::o;1351:1299::-;1471:10;;-1:-1:-1;;;;;1493:30:1;;1490:56;;;1526:18;;:::i;:::-;1555:97;1645:6;1605:38;1637:4;1631:11;1605:38;:::i;:::-;1599:4;1555:97;:::i;:::-;1701:4;1732:2;1721:14;;1749:1;1744:649;;;;2437:1;2454:6;2451:89;;;-1:-1:-1;2506:19:1;;;2500:26;2451:89;-1:-1:-1;;1308:1:1;1304:11;;;1300:24;1296:29;1286:40;1332:1;1328:11;;;1283:57;2553:81;;1714:930;;1744:649;604:1;597:14;;;641:4;628:18;;-1:-1:-1;;1780:20:1;;;1898:222;1912:7;1909:1;1906:14;1898:222;;;1994:19;;;1988:26;1973:42;;2101:4;2086:20;;;;2054:1;2042:14;;;;1928:12;1898:222;;;1902:3;2148:6;2139:7;2136:19;2133:201;;;2209:19;;;2203:26;-1:-1:-1;;2292:1:1;2288:14;;;2304:3;2284:24;2280:37;2276:42;2261:58;2246:74;;2133:201;-1:-1:-1;;;;2380:1:1;2364:14;;;2360:22;2347:36;;-1:-1:-1;1351:1299:1:o;:::-;455:1895:0;;;;;;","linkReferences":{}},"deployedBytecode":{"object":"0x6080604052600436106100a05760003560e01c8063313ce56711610064578063313ce5671461016c57806370a082311461019857806395d89b41146101c5578063a9059cbb146101da578063d0e30db0146101fa578063dd62ed3e1461020257600080fd5b806306fdde03146100b4578063095ea7b3146100df57806318160ddd1461010f57806323b872dd1461012c5780632e1a7d4d1461014c57600080fd5b366100af576100ad61023a565b005b600080fd5b3480156100c057600080fd5b506100c9610295565b6040516100d6919061068c565b60405180910390f35b3480156100eb57600080fd5b506100ff6100fa3660046106f6565b610323565b60405190151581526020016100d6565b34801561011b57600080fd5b50475b6040519081526020016100d6565b34801561013857600080fd5b506100ff610147366004610720565b610390565b34801561015857600080fd5b506100ad61016736600461075d565b61058b565b34801561017857600080fd5b506002546101869060ff1681565b60405160ff90911681526020016100d6565b3480156101a457600080fd5b5061011e6101b3366004610776565b60036020526000908152604090205481565b3480156101d157600080fd5b506100c961066b565b3480156101e657600080fd5b506100ff6101f53660046106f6565b610678565b6100ad61023a565b34801561020e57600080fd5b5061011e61021d366004610791565b600460209081526000928352604080842090915290825290205481565b33600090815260036020526040812080543492906102599084906107da565b909155505060405134815233907fe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c9060200160405180910390a2565b600080546102a2906107ed565b80601f01602080910402602001604051908101604052809291908181526020018280546102ce906107ed565b801561031b5780601f106102f05761010080835404028352916020019161031b565b820191906000526020600020905b8154815290600101906020018083116102fe57829003601f168201915b505050505081565b3360008181526004602090815260408083206001600160a01b038716808552925280832085905551919290917f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9259061037e9086815260200190565b60405180910390a35060015b92915050565b6001600160a01b0383166000908152600360205260408120548211156103f45760405162461bcd60e51b8152602060048201526014602482015273496e73756666696369656e742062616c616e636560601b60448201526064015b60405180910390fd5b6001600160a01b038416331480159061043257506001600160a01b038416600090815260046020908152604080832033845290915290205460001914155b156104d8576001600160a01b038416600090815260046020908152604080832033845290915290205482111561049f5760405162461bcd60e51b8152602060048201526012602482015271105b1b1bddd85b98d948195e18d95959195960721b60448201526064016103eb565b6001600160a01b0384166000908152600460209081526040808320338452909152812080548492906104d2908490610827565b90915550505b6001600160a01b03841660009081526003602052604081208054849290610500908490610827565b90915550506001600160a01b0383166000908152600360205260408120805484929061052d9084906107da565b92505081905550826001600160a01b0316846001600160a01b03167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef8460405161057991815260200190565b60405180910390a35060019392505050565b336000908152600360205260409020548111156105e15760405162461bcd60e51b8152602060048201526014602482015273496e73756666696369656e742062616c616e636560601b60448201526064016103eb565b3360009081526003602052604081208054839290610600908490610827565b9091555050604051339082156108fc029083906000818181858888f19350505050158015610632573d6000803e3d6000fd5b5060405181815233907f7fcf532c15f0a6db0bd6d0e038bea71d30d808c7d98cb3bf7268a95bf5081b659060200160405180910390a250565b600180546102a2906107ed565b6000610685338484610390565b9392505050565b602081526000825180602084015260005b818110156106ba576020818601810151604086840101520161069d565b506000604082850101526040601f19601f83011684010191505092915050565b80356001600160a01b03811681146106f157600080fd5b919050565b6000806040838503121561070957600080fd5b610712836106da565b946020939093013593505050565b60008060006060848603121561073557600080fd5b61073e846106da565b925061074c602085016106da565b929592945050506040919091013590565b60006020828403121561076f57600080fd5b5035919050565b60006020828403121561078857600080fd5b610685826106da565b600080604083850312156107a457600080fd5b6107ad836106da565b91506107bb602084016106da565b90509250929050565b634e487b7160e01b600052601160045260246000fd5b8082018082111561038a5761038a6107c4565b600181811c9082168061080157607f821691505b60208210810361082157634e487b7160e01b600052602260045260246000fd5b50919050565b8181038181111561038a5761038a6107c456fea264697066735822122083e6d1dfb18c7a4953c9ec852d6b89cc446773e4ee001c4108c7be8fee0805e964736f6c634300081a0033","sourceMap":"455:1895:0:-:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;1025:9;:7;:9::i;:::-;455:1895;;;;;476:40;;;;;;;;;;;;;:::i;:::-;;;;;;;:::i;:::-;;;;;;;;1526:177;;;;;;;;;;-1:-1:-1;1526:177:0;;;;;:::i;:::-;;:::i;:::-;;;1194:14:1;;1187:22;1169:41;;1157:2;1142:18;1526:177:0;1029:187:1;1425:95:0;;;;;;;;;;-1:-1:-1;1492:21:0;1425:95;;;1367:25:1;;;1355:2;1340:18;1425:95:0;1221:177:1;1836:512:0;;;;;;;;;;-1:-1:-1;1836:512:0;;;;;:::i;:::-;;:::i;1183:236::-;;;;;;;;;;-1:-1:-1;1183:236:0;;;;;:::i;:::-;;:::i;559:27::-;;;;;;;;;;-1:-1:-1;559:27:0;;;;;;;;;;;2185:4:1;2173:17;;;2155:36;;2143:2;2128:18;559:27:0;2013:184:1;845:65:0;;;;;;;;;;-1:-1:-1;845:65:0;;;;;:::i;:::-;;;;;;;;;;;;;;522:31;;;;;;;;;;;;;:::i;1709:121::-;;;;;;;;;;-1:-1:-1;1709:121:0;;;;;:::i;:::-;;:::i;1047:130::-;;;:::i;916:65::-;;;;;;;;;;-1:-1:-1;916:65:0;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;1047:130;1101:10;1091:21;;;;:9;:21;;;;;:34;;1116:9;;1091:21;:34;;1116:9;;1091:34;:::i;:::-;;;;-1:-1:-1;;1140:30:0;;1160:9;1367:25:1;;1148:10:0;;1140:30;;1355:2:1;1340:18;1140:30:0;;;;;;;1047:130::o;476:40::-;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::o;1526:177::-;1608:10;1582:4;1598:21;;;:9;:21;;;;;;;;-1:-1:-1;;;;;1598:26:0;;;;;;;;;;:32;;;1645:30;1582:4;;1598:26;;1645:30;;;;1627:3;1367:25:1;;1355:2;1340:18;;1221:177;1645:30:0;;;;;;;;-1:-1:-1;1692:4:0;1526:177;;;;;:::o;1836:512::-;-1:-1:-1;;;;;1954:14:0;;1926:4;1954:14;;;:9;:14;;;;;;:21;-1:-1:-1;1954:21:0;1946:54;;;;-1:-1:-1;;;1946:54:0;;3507:2:1;1946:54:0;;;3489:21:1;3546:2;3526:18;;;3519:30;-1:-1:-1;;;3565:18:1;;;3558:50;3625:18;;1946:54:0;;;;;;;;;-1:-1:-1;;;;;2015:17:0;;2022:10;2015:17;;;;:65;;-1:-1:-1;;;;;;2036:14:0;;;;;;:9;:14;;;;;;;;2051:10;2036:26;;;;;;;;-1:-1:-1;;2036:44:0;;2015:65;2011:207;;;-1:-1:-1;;;;;2104:14:0;;;;;;:9;:14;;;;;;;;2119:10;2104:26;;;;;;;;:33;-1:-1:-1;2104:33:0;2096:64;;;;-1:-1:-1;;;2096:64:0;;3856:2:1;2096:64:0;;;3838:21:1;3895:2;3875:18;;;3868:30;-1:-1:-1;;;3914:18:1;;;3907:48;3972:18;;2096:64:0;3654:342:1;2096:64:0;-1:-1:-1;;;;;2174:14:0;;;;;;:9;:14;;;;;;;;2189:10;2174:26;;;;;;;:33;;2204:3;;2174:14;:33;;2204:3;;2174:33;:::i;:::-;;;;-1:-1:-1;;2011:207:0;-1:-1:-1;;;;;2228:14:0;;;;;;:9;:14;;;;;:21;;2246:3;;2228:14;:21;;2246:3;;2228:21;:::i;:::-;;;;-1:-1:-1;;;;;;;2259:14:0;;;;;;:9;:14;;;;;:21;;2277:3;;2259:14;:21;;2277:3;;2259:21;:::i;:::-;;;;;;;;2310:3;-1:-1:-1;;;;;2296:23:0;2305:3;-1:-1:-1;;;;;2296:23:0;;2315:3;2296:23;;;;1367:25:1;;1355:2;1340:18;;1221:177;2296:23:0;;;;;;;;-1:-1:-1;2337:4:0;1836:512;;;;;:::o;1183:236::-;1246:10;1236:21;;;;:9;:21;;;;;;:28;-1:-1:-1;1236:28:0;1228:61;;;;-1:-1:-1;;;1228:61:0;;3507:2:1;1228:61:0;;;3489:21:1;3546:2;3526:18;;;3519:30;-1:-1:-1;;;3565:18:1;;;3558:50;3625:18;;1228:61:0;3305:344:1;1228:61:0;1309:10;1299:21;;;;:9;:21;;;;;:28;;1324:3;;1299:21;:28;;1324:3;;1299:28;:::i;:::-;;;;-1:-1:-1;;1337:33:0;;1345:10;;1337:33;;;;;1366:3;;1337:33;;;;1366:3;1345:10;1337:33;;;;;;;;;;;;;;;;;;;;-1:-1:-1;1385:27:0;;1367:25:1;;;1396:10:0;;1385:27;;1355:2:1;1340:18;1385:27:0;;;;;;;1183:236;:::o;522:31::-;;;;;;;:::i;1709:121::-;1766:4;1789:34;1802:10;1814:3;1819;1789:12;:34::i;:::-;1782:41;1709:121;-1:-1:-1;;;1709:121:0:o;14:527:1:-;163:2;152:9;145:21;126:4;195:6;189:13;238:6;233:2;222:9;218:18;211:34;263:1;273:140;287:6;284:1;281:13;273:140;;;398:2;382:14;;;378:23;;372:30;367:2;348:17;;;344:26;337:66;302:10;273:140;;;277:3;462:1;457:2;448:6;437:9;433:22;429:31;422:42;532:2;525;521:7;516:2;508:6;504:15;500:29;489:9;485:45;481:54;473:62;;;14:527;;;;:::o;546:173::-;614:20;;-1:-1:-1;;;;;663:31:1;;653:42;;643:70;;709:1;706;699:12;643:70;546:173;;;:::o;724:300::-;792:6;800;853:2;841:9;832:7;828:23;824:32;821:52;;;869:1;866;859:12;821:52;892:29;911:9;892:29;:::i;:::-;882:39;990:2;975:18;;;;962:32;;-1:-1:-1;;;724:300:1:o;1403:374::-;1480:6;1488;1496;1549:2;1537:9;1528:7;1524:23;1520:32;1517:52;;;1565:1;1562;1555:12;1517:52;1588:29;1607:9;1588:29;:::i;:::-;1578:39;;1636:38;1670:2;1659:9;1655:18;1636:38;:::i;:::-;1403:374;;1626:48;;-1:-1:-1;;;1743:2:1;1728:18;;;;1715:32;;1403:374::o;1782:226::-;1841:6;1894:2;1882:9;1873:7;1869:23;1865:32;1862:52;;;1910:1;1907;1900:12;1862:52;-1:-1:-1;1955:23:1;;1782:226;-1:-1:-1;1782:226:1:o;2202:186::-;2261:6;2314:2;2302:9;2293:7;2289:23;2285:32;2282:52;;;2330:1;2327;2320:12;2282:52;2353:29;2372:9;2353:29;:::i;2393:260::-;2461:6;2469;2522:2;2510:9;2501:7;2497:23;2493:32;2490:52;;;2538:1;2535;2528:12;2490:52;2561:29;2580:9;2561:29;:::i;:::-;2551:39;;2609:38;2643:2;2632:9;2628:18;2609:38;:::i;:::-;2599:48;;2393:260;;;;;:::o;2658:127::-;2719:10;2714:3;2710:20;2707:1;2700:31;2750:4;2747:1;2740:15;2774:4;2771:1;2764:15;2790:125;2855:9;;;2876:10;;;2873:36;;;2889:18;;:::i;2920:380::-;2999:1;2995:12;;;;3042;;;3063:61;;3117:4;3109:6;3105:17;3095:27;;3063:61;3170:2;3162:6;3159:14;3139:18;3136:38;3133:161;;3216:10;3211:3;3207:20;3204:1;3197:31;3251:4;3248:1;3241:15;3279:4;3276:1;3269:15;3133:161;;2920:380;;;:::o;4001:128::-;4068:9;;;4089:11;;;4086:37;;;4103:18;;:::i","linkReferences":{}},"methodIdentifiers":{"allowance(address,address)":"dd62ed3e","approve(address,uint256)":"095ea7b3","balanceOf(address)":"70a08231","decimals()":"313ce567","deposit()":"d0e30db0","name()":"06fdde03","symbol()":"95d89b41","totalSupply()":"18160ddd","transfer(address,uint256)":"a9059cbb","transferFrom(address,address,uint256)":"23b872dd","withdraw(uint256)":"2e1a7d4d"},"rawMetadata":"{\"compiler\":{\"version\":\"0.8.26+commit.8a97fa7a\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"src\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"guy\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"wad\",\"type\":\"uint256\"}],\"name\":\"Approval\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"dst\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"wad\",\"type\":\"uint256\"}],\"name\":\"Deposit\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"src\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"dst\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"wad\",\"type\":\"uint256\"}],\"name\":\"Transfer\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"src\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"wad\",\"type\":\"uint256\"}],\"name\":\"Withdrawal\",\"type\":\"event\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"name\":\"allowance\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"guy\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"wad\",\"type\":\"uint256\"}],\"name\":\"approve\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"name\":\"balanceOf\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"decimals\",\"outputs\":[{\"internalType\":\"uint8\",\"name\":\"\",\"type\":\"uint8\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"deposit\",\"outputs\":[],\"stateMutability\":\"payable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"name\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"symbol\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"totalSupply\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"dst\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"wad\",\"type\":\"uint256\"}],\"name\":\"transfer\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"src\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"dst\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"wad\",\"type\":\"uint256\"}],\"name\":\"transferFrom\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"wad\",\"type\":\"uint256\"}],\"name\":\"withdraw\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"stateMutability\":\"payable\",\"type\":\"receive\"}],\"devdoc\":{\"kind\":\"dev\",\"methods\":{},\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{},\"version\":1}},\"settings\":{\"compilationTarget\":{\"protocol-units/bridge/contracts/src/WETH9.sol\":\"WETH9\"},\"evmVersion\":\"paris\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"ipfs\"},\"optimizer\":{\"enabled\":true,\"runs\":200},\"remappings\":[]},\"sources\":{\"protocol-units/bridge/contracts/src/WETH9.sol\":{\"keccak256\":\"0xc63e3968b0d4de42af1d597897b2e60c1f29b6874e9129ab15127a7f57c0c6fa\",\"urls\":[\"bzz-raw://cb5f5b9a20236814999614a1fc5704ed61dc9d55ecc0084728df060c4955f853\",\"dweb:/ipfs/QmV5btizG9qVXs559eZsE1mEQhDz911UnyBrL4mAUeyiQU\"]}},\"version\":1}","metadata":{"compiler":{"version":"0.8.26+commit.8a97fa7a"},"language":"Solidity","output":{"abi":[{"inputs":[{"internalType":"address","name":"src","type":"address","indexed":true},{"internalType":"address","name":"guy","type":"address","indexed":true},{"internalType":"uint256","name":"wad","type":"uint256","indexed":false}],"type":"event","name":"Approval","anonymous":false},{"inputs":[{"internalType":"address","name":"dst","type":"address","indexed":true},{"internalType":"uint256","name":"wad","type":"uint256","indexed":false}],"type":"event","name":"Deposit","anonymous":false},{"inputs":[{"internalType":"address","name":"src","type":"address","indexed":true},{"internalType":"address","name":"dst","type":"address","indexed":true},{"internalType":"uint256","name":"wad","type":"uint256","indexed":false}],"type":"event","name":"Transfer","anonymous":false},{"inputs":[{"internalType":"address","name":"src","type":"address","indexed":true},{"internalType":"uint256","name":"wad","type":"uint256","indexed":false}],"type":"event","name":"Withdrawal","anonymous":false},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function","name":"allowance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}]},{"inputs":[{"internalType":"address","name":"guy","type":"address"},{"internalType":"uint256","name":"wad","type":"uint256"}],"stateMutability":"nonpayable","type":"function","name":"approve","outputs":[{"internalType":"bool","name":"","type":"bool"}]},{"inputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function","name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}]},{"inputs":[],"stateMutability":"view","type":"function","name":"decimals","outputs":[{"internalType":"uint8","name":"","type":"uint8"}]},{"inputs":[],"stateMutability":"payable","type":"function","name":"deposit"},{"inputs":[],"stateMutability":"view","type":"function","name":"name","outputs":[{"internalType":"string","name":"","type":"string"}]},{"inputs":[],"stateMutability":"view","type":"function","name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}]},{"inputs":[],"stateMutability":"view","type":"function","name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}]},{"inputs":[{"internalType":"address","name":"dst","type":"address"},{"internalType":"uint256","name":"wad","type":"uint256"}],"stateMutability":"nonpayable","type":"function","name":"transfer","outputs":[{"internalType":"bool","name":"","type":"bool"}]},{"inputs":[{"internalType":"address","name":"src","type":"address"},{"internalType":"address","name":"dst","type":"address"},{"internalType":"uint256","name":"wad","type":"uint256"}],"stateMutability":"nonpayable","type":"function","name":"transferFrom","outputs":[{"internalType":"bool","name":"","type":"bool"}]},{"inputs":[{"internalType":"uint256","name":"wad","type":"uint256"}],"stateMutability":"nonpayable","type":"function","name":"withdraw"},{"inputs":[],"stateMutability":"payable","type":"receive"}],"devdoc":{"kind":"dev","methods":{},"version":1},"userdoc":{"kind":"user","methods":{},"version":1}},"settings":{"remappings":[],"optimizer":{"enabled":true,"runs":200},"metadata":{"bytecodeHash":"ipfs"},"compilationTarget":{"protocol-units/bridge/contracts/src/WETH9.sol":"WETH9"},"evmVersion":"paris","libraries":{}},"sources":{"protocol-units/bridge/contracts/src/WETH9.sol":{"keccak256":"0xc63e3968b0d4de42af1d597897b2e60c1f29b6874e9129ab15127a7f57c0c6fa","urls":["bzz-raw://cb5f5b9a20236814999614a1fc5704ed61dc9d55ecc0084728df060c4955f853","dweb:/ipfs/QmV5btizG9qVXs559eZsE1mEQhDz911UnyBrL4mAUeyiQU"],"license":null}},"version":1},"id":0} \ No newline at end of file diff --git a/process-compose/mcr-client/process-compose.test.yml b/process-compose/mcr-client/process-compose.test.yml index d4837afb1..8994b5f5a 100644 --- a/process-compose/mcr-client/process-compose.test.yml +++ b/process-compose/mcr-client/process-compose.test.yml @@ -7,7 +7,7 @@ processes: mcr-client-tests: command: | set -e - cargo test -p mcr-settlement-client --features="e2e" test_client_settlement -- --nocapture + cargo test -p mcr-settlement-client --features="e2e" -- --nocapture depends_on: eth: condition: process_healthy diff --git a/process-compose/mcr/process-compose.local.yml b/process-compose/mcr/process-compose.local.yml deleted file mode 100644 index 6b0340d50..000000000 --- a/process-compose/mcr/process-compose.local.yml +++ /dev/null @@ -1,20 +0,0 @@ -version: "3" - -environment: - -processes: - - eth: - command: | - ./scripts/mcr/run-anvil-for-mcr - readiness_probe: - initial_delay_seconds: 3 - exec: - command: echo "true" - - deploy-mcr: - command: | - . ./scripts/mcr/deploy-mcr-to-anvil - depends_on: - eth: - condition: process_healthy \ No newline at end of file diff --git a/process-compose/mcr/process-compose.test.yml b/process-compose/mcr/process-compose.test.yml deleted file mode 100644 index e06bfa763..000000000 --- a/process-compose/mcr/process-compose.test.yml +++ /dev/null @@ -1,19 +0,0 @@ -version: "3" - -environment: - -processes: - - mcr-client-tests: - command: | - set -e - cargo test -p mcr-settlement-client - depends_on: - eth: - condition: process_healthy - deploy-mcr: - condition: process_completed_successfully - availability: - # NOTE: `restart: exit_on_failure` is not needed since - # exit_on_end implies it. - exit_on_end: true \ No newline at end of file diff --git a/process-compose/mcr/process-compose.yml b/process-compose/mcr/process-compose.yml deleted file mode 100644 index 333bcc112..000000000 --- a/process-compose/mcr/process-compose.yml +++ /dev/null @@ -1,19 +0,0 @@ -version: "3" - -environment: - -processes: - - eth: - command: | - exit 1 - readiness_probe: - exec: - command: echo "true" - - deploy-mcr: - command: | - exit 1 - depends_on: - eth: - condition: process_healthy \ No newline at end of file diff --git a/process-compose/suzuka-full-node/process-compose.hasura.yml b/process-compose/suzuka-full-node/process-compose.hasura.yml new file mode 100644 index 000000000..61f02ba0f --- /dev/null +++ b/process-compose/suzuka-full-node/process-compose.hasura.yml @@ -0,0 +1,19 @@ +version: "3" + +processes: + + hasura: + environment: + - "POSTGRES_DB_HOST=${POSTGRES_HOST_IP}" + + command: | + docker compose -f docker/compose/suzuka-indexer/docker-compose.hasura.yml up --force-recreate + env: + RUST_LOG: info + depends_on: + indexer: + condition: process_healthy + readiness_probe: + initial_delay_seconds: 30 + exec: + command: curl http://localhost:8085/console/ diff --git a/process-compose/suzuka-full-node/process-compose.indexer-test.yml b/process-compose/suzuka-full-node/process-compose.indexer-test.yml new file mode 100644 index 000000000..e716b6ae0 --- /dev/null +++ b/process-compose/suzuka-full-node/process-compose.indexer-test.yml @@ -0,0 +1,19 @@ +version: "3" + +environment: + +processes: + + indexer-test: + environment: + - "POSTGRES_DB_HOST=${POSTGRES_HOST_IP}" + command: | + cargo run -p suzuka-indexer-service --bin load_metadata + ./scripts/services/indexer/test_indexer + depends_on: + indexer: + condition: process_healthy + hasura: + condition: process_healthy + availability: + exit_on_end: true diff --git a/process-compose/suzuka-full-node/process-compose.indexer.yml b/process-compose/suzuka-full-node/process-compose.indexer.yml new file mode 100644 index 000000000..70e9e2fde --- /dev/null +++ b/process-compose/suzuka-full-node/process-compose.indexer.yml @@ -0,0 +1,38 @@ +version: "3" + +processes: + + build_indexer: + command: | + ./scripts/services/indexer/build + availability: + restart: exit_on_failure + depends_on: + build: + condition: process_completed_successfully + + postgres: + command: | + ./scripts/postgres/start-dev + + readiness_probe: + initial_delay_seconds: 5 + exec: + command: echo "true" + + indexer: + command: | + suzuka-indexer-service + env: + RUST_LOG: info + readiness_probe: + initial_delay_seconds: 5 + exec: + command: echo "true" + depends_on: + build_indexer: + condition: process_completed_successfully + postgres: + condition: process_healthy + suzuka-full-node: + condition: process_healthy diff --git a/process-compose/suzuka-full-node/process-compose.setup.yml b/process-compose/suzuka-full-node/process-compose.setup.yml index c5a4aa7ea..7aa531629 100644 --- a/process-compose/suzuka-full-node/process-compose.setup.yml +++ b/process-compose/suzuka-full-node/process-compose.setup.yml @@ -4,13 +4,13 @@ environment: processes: - setup: + suzuka-setup: command: | suzuka-full-node-setup depends_on: build: condition: process_completed_successfully readiness_probe: - initial_delay_seconds: 3 + initial_delay_seconds: 10 exec: command: echo "true" \ No newline at end of file diff --git a/process-compose/suzuka-full-node/process-compose.yml b/process-compose/suzuka-full-node/process-compose.yml index 342bccb9c..cfb8a1365 100644 --- a/process-compose/suzuka-full-node/process-compose.yml +++ b/process-compose/suzuka-full-node/process-compose.yml @@ -40,15 +40,6 @@ processes: initial_delay_seconds: 3 exec: command: grpcurl -plaintext 0.0.0.0:30730 list - - postgres: - command: | - ./scripts/postgres/start-dev - - readiness_probe: - initial_delay_seconds: 10 - exec: - command: echo "true" suzuka-full-node: command: | @@ -58,15 +49,12 @@ processes: depends_on: m1-da-light-node: condition: process_healthy - postgres: - condition: process_healthy readiness_probe: initial_delay_seconds: 10 exec: command: curl http://0.0.0.0:30731 - suzuka-faucet: - + suzuka-faucet: command : | suzuka-faucet-service run-simple depends_on: diff --git a/protocol-units/README.md b/protocol-units/README.md index 7515e5104..a5d62b5fe 100644 --- a/protocol-units/README.md +++ b/protocol-units/README.md @@ -1,5 +1,11 @@ # `protocol-units` We identify the following protocol unit categories: -1. [Data Availability](./data-availability/README.md): Protocol units concerned with enabling the secure submission and ordered retrieval of transaction data to and from a network. Light node clients and servers are members of this category. -2. [Mempool](./mempool/README.md): Protocol units concerned with the acceptance and ordering of transactions in a network prior to consensus. Mempool modules are members of this category. -3. [Sequencing](./sequencing/README.md): Protocol units concerned with consensus on the order of transactions in a network. Sequencer node implementations are members of this category. \ No newline at end of file +- [Data Availability](./da/m1/README.md): Protocol units concerned with enabling the secure submission and ordered retrieval of transaction data to and from a network. Light node clients and servers are members of this category. +- [Mempool](./mempool/README.md): Protocol units concerned with the acceptance and ordering of transactions in a network prior to consensus. Mempool modules are members of this category. +- [Sequencing](./sequencing/README.md): Protocol units concerned with consensus on the order of transactions in a network. Sequencer node implementations are members of this category. +- [Bridge](./bridge): Protocol units concerned with cross-blockchain bridging using atomic swaps. The atomic bridge consists of several packages and utilities to bridge from Ethereum to Movement, which can be extended to support any blockchains. +- [Cryptography](./cryptography): Protocol units concerned with cryptographic operations. Cryptography and data structure-related utilities are members of this category. +- [Execution](./execution): Protocol units concerned with execution. Block executors and related unities are members of this category. +- [Movement REST service](./movement-rest): Protocol units to support Movement's REST API. `movement-rest` provides additional Movement REST API endpoints. +- [Settlement](./settlement): Protocol units concerned with settlement. Movement's multi-commitment rollup and related settlement utilities are members of this category. +- [Storage](./storage): Protocol units concerned with storage. `jelly-move`, `move-access-log`, and `mpt-move` are members of this category. diff --git a/protocol-units/bridge/chains/ethereum/Cargo.toml b/protocol-units/bridge/chains/ethereum/Cargo.toml new file mode 100644 index 000000000..3cc8fa66f --- /dev/null +++ b/protocol-units/bridge/chains/ethereum/Cargo.toml @@ -0,0 +1,46 @@ +[package] +name = "ethereum-bridge" +description = "bridge components for the ethereum bridge" +authors = { workspace = true } +edition = { workspace = true } +homepage = { workspace = true } +license = { workspace = true } +repository = { workspace = true } + +version = { workspace = true } + +[lib] +path = "src/lib.rs" + +[dependencies] +async-trait = { workspace = true } +anyhow = { workspace = true } +futures = { workspace = true } +poem = { workspace = true } +hex = { workspace = true } +keccak-hash = { workspace = true } +tokio = { workspace = true } +tracing = { workspace = true } +tracing-subscriber = { workspace = true } +serde = { workspace = true } +rand_chacha = "0.2.2" +rand = { workspace = true } +thiserror = { workspace = true } + +alloy = { workspace = true, features = [ + "full", + "rpc", + "rpc-types", + "serde", + "rlp", + "contract", + "sol-types", +] } +alloy-rlp.workspace = true + +bridge-shared = { workspace = true } +serde_with.workspace = true +url = { workspace = true, features = ["serde"] } + +#To be removed after send_transaction refactor +mcr-settlement-client = { workspace = true } diff --git a/protocol-units/bridge/chains/ethereum/abis/AtomicBridgeCounterparty.json b/protocol-units/bridge/chains/ethereum/abis/AtomicBridgeCounterparty.json new file mode 100644 index 000000000..98131e05d --- /dev/null +++ b/protocol-units/bridge/chains/ethereum/abis/AtomicBridgeCounterparty.json @@ -0,0 +1 @@ +{"abi":[{"type":"function","name":"abortBridgeTransfer","inputs":[{"name":"bridgeTransferId","type":"bytes32","internalType":"bytes32"}],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"atomicBridgeInitiator","inputs":[],"outputs":[{"name":"","type":"address","internalType":"contract AtomicBridgeInitiator"}],"stateMutability":"view"},{"type":"function","name":"bridgeTransfers","inputs":[{"name":"","type":"bytes32","internalType":"bytes32"}],"outputs":[{"name":"originator","type":"bytes32","internalType":"bytes32"},{"name":"recipient","type":"address","internalType":"address"},{"name":"amount","type":"uint256","internalType":"uint256"},{"name":"hashLock","type":"bytes32","internalType":"bytes32"},{"name":"timeLock","type":"uint256","internalType":"uint256"},{"name":"state","type":"uint8","internalType":"enum AtomicBridgeCounterparty.MessageState"}],"stateMutability":"view"},{"type":"function","name":"completeBridgeTransfer","inputs":[{"name":"bridgeTransferId","type":"bytes32","internalType":"bytes32"},{"name":"preImage","type":"bytes32","internalType":"bytes32"}],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"counterpartyTimeLockDuration","inputs":[],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"type":"function","name":"initialize","inputs":[{"name":"_atomicBridgeInitiator","type":"address","internalType":"address"},{"name":"owner","type":"address","internalType":"address"},{"name":"_timeLockDuration","type":"uint256","internalType":"uint256"}],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"lockBridgeTransfer","inputs":[{"name":"originator","type":"bytes32","internalType":"bytes32"},{"name":"bridgeTransferId","type":"bytes32","internalType":"bytes32"},{"name":"hashLock","type":"bytes32","internalType":"bytes32"},{"name":"recipient","type":"address","internalType":"address"},{"name":"amount","type":"uint256","internalType":"uint256"}],"outputs":[{"name":"","type":"bool","internalType":"bool"}],"stateMutability":"nonpayable"},{"type":"function","name":"owner","inputs":[],"outputs":[{"name":"","type":"address","internalType":"address"}],"stateMutability":"view"},{"type":"function","name":"renounceOwnership","inputs":[],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"setAtomicBridgeInitiator","inputs":[{"name":"_atomicBridgeInitiator","type":"address","internalType":"address"}],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"setTimeLockDuration","inputs":[{"name":"_timeLockDuration","type":"uint256","internalType":"uint256"}],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"transferOwnership","inputs":[{"name":"newOwner","type":"address","internalType":"address"}],"outputs":[],"stateMutability":"nonpayable"},{"type":"event","name":"BridgeTransferAborted","inputs":[{"name":"bridgeTransferId","type":"bytes32","indexed":true,"internalType":"bytes32"}],"anonymous":false},{"type":"event","name":"BridgeTransferCompleted","inputs":[{"name":"bridgeTransferId","type":"bytes32","indexed":true,"internalType":"bytes32"},{"name":"pre_image","type":"bytes32","indexed":false,"internalType":"bytes32"}],"anonymous":false},{"type":"event","name":"BridgeTransferLocked","inputs":[{"name":"bridgeTransferId","type":"bytes32","indexed":true,"internalType":"bytes32"},{"name":"recipient","type":"address","indexed":true,"internalType":"address"},{"name":"amount","type":"uint256","indexed":false,"internalType":"uint256"},{"name":"hashLock","type":"bytes32","indexed":false,"internalType":"bytes32"},{"name":"timeLock","type":"uint256","indexed":false,"internalType":"uint256"}],"anonymous":false},{"type":"event","name":"Initialized","inputs":[{"name":"version","type":"uint64","indexed":false,"internalType":"uint64"}],"anonymous":false},{"type":"event","name":"OwnershipTransferred","inputs":[{"name":"previousOwner","type":"address","indexed":true,"internalType":"address"},{"name":"newOwner","type":"address","indexed":true,"internalType":"address"}],"anonymous":false},{"type":"error","name":"BridgeTransferHasBeenCompleted","inputs":[]},{"type":"error","name":"BridgeTransferInvalid","inputs":[]},{"type":"error","name":"BridgeTransferStateNotInitialized","inputs":[]},{"type":"error","name":"BridgeTransferStateNotPending","inputs":[]},{"type":"error","name":"InsufficientWethBalance","inputs":[]},{"type":"error","name":"InvalidInitialization","inputs":[]},{"type":"error","name":"InvalidSecret","inputs":[]},{"type":"error","name":"NotInitializing","inputs":[]},{"type":"error","name":"OwnableInvalidOwner","inputs":[{"name":"owner","type":"address","internalType":"address"}]},{"type":"error","name":"OwnableUnauthorizedAccount","inputs":[{"name":"account","type":"address","internalType":"address"}]},{"type":"error","name":"TimeLockExpired","inputs":[]},{"type":"error","name":"TimeLockNotExpired","inputs":[]},{"type":"error","name":"Unauthorized","inputs":[]},{"type":"error","name":"WETHTransferFailed","inputs":[]},{"type":"error","name":"ZeroAddress","inputs":[]},{"type":"error","name":"ZeroAmount","inputs":[]}],"bytecode":{"object":"0x6080604052348015600e575f80fd5b50610b3d8061001c5f395ff3fe608060405234801561000f575f80fd5b50600436106100b1575f3560e01c806396d17d491161006e57806396d17d49146101645780639f8f87991461017b578063c95b659f1461018e578063e0d9cbc4146101a1578063ee2c59f814610203578063f2fde38b14610215575f80fd5b80631794bb3c146100b557806327b3ea07146100ca57806371115eb2146100f2578063715018a6146101055780637ce087481461010d5780638da5cb5b14610120575b5f80fd5b6100c86100c336600461098b565b610228565b005b6100dd6100d83660046109c5565b61037e565b60405190151581526020015b60405180910390f35b6100c8610100366004610a08565b61056e565b6100c861057b565b6100c861011b366004610a1f565b61058e565b7f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c199300546001600160a01b03165b6040516001600160a01b0390911681526020016100e9565b61016d60025481565b6040519081526020016100e9565b6100c8610189366004610a3f565b6105de565b6100c861019c366004610a08565b610753565b6101f16101af366004610a08565b600160208190525f91825260409091208054918101546002820154600383015460048401546005909401546001600160a01b0390931693919290919060ff1686565b6040516100e996959493929190610a73565b5f5461014c906001600160a01b031681565b6100c8610223366004610a1f565b610801565b7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a008054600160401b810460ff16159067ffffffffffffffff165f8115801561026d5750825b90505f8267ffffffffffffffff1660011480156102895750303b155b905081158015610297575080155b156102b55760405163f92ee8a960e01b815260040160405180910390fd5b845467ffffffffffffffff1916600117855583156102df57845460ff60401b1916600160401b1785555b6001600160a01b0388166103065760405163d92e233d60e01b815260040160405180910390fd5b5f80546001600160a01b0319166001600160a01b038a1617905561032987610843565b6002869055831561037457845460ff60401b19168555604051600181527fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d29060200160405180910390a15b5050505050505050565b5f610387610854565b815f036103a757604051631f2a200560e01b815260040160405180910390fd5b5f546040805163258d975160e21b8152905184926001600160a01b0316916396365d449160048083019260209291908290030181865afa1580156103ed573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906104119190610acb565b1015610430576040516318fb0fc360e11b815260040160405180910390fd5b5f6002544261043f9190610ae2565b90506040518060c00160405280888152602001856001600160a01b031681526020018481526020018681526020018281526020015f600281111561048557610485610a5f565b90525f87815260016020818152604092839020845181559084015181830180546001600160a01b0319166001600160a01b0390921691909117905591830151600280840191909155606084015160038401556080840151600484015560a08401516005840180549193909260ff1990921691849081111561050857610508610a5f565b0217905550506002546040805186815260208101899052908101919091526001600160a01b038616915087907fa03230f5967404ba170c5be8445486911e87267000597e4ba9ed9d4b014fa4bd9060600160405180910390a35060019695505050505050565b610576610854565b600255565b610583610854565b61058c5f6108af565b565b610596610854565b6001600160a01b0381166105bd5760405163d92e233d60e01b815260040160405180910390fd5b5f80546001600160a01b0319166001600160a01b0392909216919091179055565b5f82815260016020526040812090600582015460ff16600281111561060557610605610a5f565b146106235760405163a92c04c760e01b815260040160405180910390fd5b5f8260405160200161063791815260200190565b604051602081830303815290604052805190602001209050816003015481146106735760405163abab6bd760e01b815260040160405180910390fd5b81600401544211156106985760405163179a39d160e01b815260040160405180910390fd5b60058201805460ff191660019081179091555f549083015460028401546040516324c44ed760e21b81526001600160a01b03928316600482015260248101919091529116906393113b5c906044015f604051808303815f87803b1580156106fd575f80fd5b505af115801561070f573d5f803e3d5ffd5b50505050837f05ddc886acde01b77731bfad1dcfb6abf529f05c28ea66556fe87429bb2789ea8460405161074591815260200190565b60405180910390a250505050565b61075b610854565b5f81815260016020526040812090600582015460ff16600281111561078257610782610a5f565b146107a05760405163a92c04c760e01b815260040160405180910390fd5b806004015442116107c45760405163191f4d1b60e31b815260040160405180910390fd5b60058101805460ff1916600217905560405182907f9b398e0a546c4aa218ac0b98f5e2196a7aff605b50c19eb795f9cb3b2f5d5b55905f90a25050565b610809610854565b6001600160a01b03811661083757604051631e4fbdf760e01b81525f60048201526024015b60405180910390fd5b610840816108af565b50565b61084b61091f565b61084081610968565b336108867f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c199300546001600160a01b031690565b6001600160a01b03161461058c5760405163118cdaa760e01b815233600482015260240161082e565b7f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c19930080546001600160a01b031981166001600160a01b03848116918217845560405192169182907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0905f90a3505050565b7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a0054600160401b900460ff1661058c57604051631afcd79f60e31b815260040160405180910390fd5b61080961091f565b80356001600160a01b0381168114610986575f80fd5b919050565b5f805f6060848603121561099d575f80fd5b6109a684610970565b92506109b460208501610970565b929592945050506040919091013590565b5f805f805f60a086880312156109d9575f80fd5b8535945060208601359350604086013592506109f760608701610970565b949793965091946080013592915050565b5f60208284031215610a18575f80fd5b5035919050565b5f60208284031215610a2f575f80fd5b610a3882610970565b9392505050565b5f8060408385031215610a50575f80fd5b50508035926020909101359150565b634e487b7160e01b5f52602160045260245ffd5b8681526001600160a01b038616602082015260408101859052606081018490526080810183905260c0810160038310610aba57634e487b7160e01b5f52602160045260245ffd5b8260a0830152979650505050505050565b5f60208284031215610adb575f80fd5b5051919050565b80820180821115610b0157634e487b7160e01b5f52601160045260245ffd5b9291505056fea2646970667358221220a7a1551674b9118bf9d820e82dbbe6c1fc43afda197e628ed2d3d9e6954c328a64736f6c634300081a0033","sourceMap":"303:3496:39:-:0;;;;;;;;;;;;;;;;;;;","linkReferences":{}},"deployedBytecode":{"object":"0x608060405234801561000f575f80fd5b50600436106100b1575f3560e01c806396d17d491161006e57806396d17d49146101645780639f8f87991461017b578063c95b659f1461018e578063e0d9cbc4146101a1578063ee2c59f814610203578063f2fde38b14610215575f80fd5b80631794bb3c146100b557806327b3ea07146100ca57806371115eb2146100f2578063715018a6146101055780637ce087481461010d5780638da5cb5b14610120575b5f80fd5b6100c86100c336600461098b565b610228565b005b6100dd6100d83660046109c5565b61037e565b60405190151581526020015b60405180910390f35b6100c8610100366004610a08565b61056e565b6100c861057b565b6100c861011b366004610a1f565b61058e565b7f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c199300546001600160a01b03165b6040516001600160a01b0390911681526020016100e9565b61016d60025481565b6040519081526020016100e9565b6100c8610189366004610a3f565b6105de565b6100c861019c366004610a08565b610753565b6101f16101af366004610a08565b600160208190525f91825260409091208054918101546002820154600383015460048401546005909401546001600160a01b0390931693919290919060ff1686565b6040516100e996959493929190610a73565b5f5461014c906001600160a01b031681565b6100c8610223366004610a1f565b610801565b7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a008054600160401b810460ff16159067ffffffffffffffff165f8115801561026d5750825b90505f8267ffffffffffffffff1660011480156102895750303b155b905081158015610297575080155b156102b55760405163f92ee8a960e01b815260040160405180910390fd5b845467ffffffffffffffff1916600117855583156102df57845460ff60401b1916600160401b1785555b6001600160a01b0388166103065760405163d92e233d60e01b815260040160405180910390fd5b5f80546001600160a01b0319166001600160a01b038a1617905561032987610843565b6002869055831561037457845460ff60401b19168555604051600181527fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d29060200160405180910390a15b5050505050505050565b5f610387610854565b815f036103a757604051631f2a200560e01b815260040160405180910390fd5b5f546040805163258d975160e21b8152905184926001600160a01b0316916396365d449160048083019260209291908290030181865afa1580156103ed573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906104119190610acb565b1015610430576040516318fb0fc360e11b815260040160405180910390fd5b5f6002544261043f9190610ae2565b90506040518060c00160405280888152602001856001600160a01b031681526020018481526020018681526020018281526020015f600281111561048557610485610a5f565b90525f87815260016020818152604092839020845181559084015181830180546001600160a01b0319166001600160a01b0390921691909117905591830151600280840191909155606084015160038401556080840151600484015560a08401516005840180549193909260ff1990921691849081111561050857610508610a5f565b0217905550506002546040805186815260208101899052908101919091526001600160a01b038616915087907fa03230f5967404ba170c5be8445486911e87267000597e4ba9ed9d4b014fa4bd9060600160405180910390a35060019695505050505050565b610576610854565b600255565b610583610854565b61058c5f6108af565b565b610596610854565b6001600160a01b0381166105bd5760405163d92e233d60e01b815260040160405180910390fd5b5f80546001600160a01b0319166001600160a01b0392909216919091179055565b5f82815260016020526040812090600582015460ff16600281111561060557610605610a5f565b146106235760405163a92c04c760e01b815260040160405180910390fd5b5f8260405160200161063791815260200190565b604051602081830303815290604052805190602001209050816003015481146106735760405163abab6bd760e01b815260040160405180910390fd5b81600401544211156106985760405163179a39d160e01b815260040160405180910390fd5b60058201805460ff191660019081179091555f549083015460028401546040516324c44ed760e21b81526001600160a01b03928316600482015260248101919091529116906393113b5c906044015f604051808303815f87803b1580156106fd575f80fd5b505af115801561070f573d5f803e3d5ffd5b50505050837f05ddc886acde01b77731bfad1dcfb6abf529f05c28ea66556fe87429bb2789ea8460405161074591815260200190565b60405180910390a250505050565b61075b610854565b5f81815260016020526040812090600582015460ff16600281111561078257610782610a5f565b146107a05760405163a92c04c760e01b815260040160405180910390fd5b806004015442116107c45760405163191f4d1b60e31b815260040160405180910390fd5b60058101805460ff1916600217905560405182907f9b398e0a546c4aa218ac0b98f5e2196a7aff605b50c19eb795f9cb3b2f5d5b55905f90a25050565b610809610854565b6001600160a01b03811661083757604051631e4fbdf760e01b81525f60048201526024015b60405180910390fd5b610840816108af565b50565b61084b61091f565b61084081610968565b336108867f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c199300546001600160a01b031690565b6001600160a01b03161461058c5760405163118cdaa760e01b815233600482015260240161082e565b7f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c19930080546001600160a01b031981166001600160a01b03848116918217845560405192169182907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0905f90a3505050565b7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a0054600160401b900460ff1661058c57604051631afcd79f60e31b815260040160405180910390fd5b61080961091f565b80356001600160a01b0381168114610986575f80fd5b919050565b5f805f6060848603121561099d575f80fd5b6109a684610970565b92506109b460208501610970565b929592945050506040919091013590565b5f805f805f60a086880312156109d9575f80fd5b8535945060208601359350604086013592506109f760608701610970565b949793965091946080013592915050565b5f60208284031215610a18575f80fd5b5035919050565b5f60208284031215610a2f575f80fd5b610a3882610970565b9392505050565b5f8060408385031215610a50575f80fd5b50508035926020909101359150565b634e487b7160e01b5f52602160045260245ffd5b8681526001600160a01b038616602082015260408101859052606081018490526080810183905260c0810160038310610aba57634e487b7160e01b5f52602160045260245ffd5b8260a0830152979650505050505050565b5f60208284031215610adb575f80fd5b5051919050565b80820180821115610b0157634e487b7160e01b5f52601160045260245ffd5b9291505056fea2646970667358221220a7a1551674b9118bf9d820e82dbbe6c1fc43afda197e628ed2d3d9e6954c328a64736f6c634300081a0033","sourceMap":"303:3496:39:-:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;948:412;;;;;;:::i;:::-;;:::i;:::-;;1761:912;;;;;;:::i;:::-;;:::i;:::-;;;1403:14:47;;1396:22;1378:41;;1366:2;1351:18;1761:912:39;;;;;;;;1615:140;;;;;;:::i;:::-;;:::i;3155:101:23:-;;;:::i;1366:243:39:-;;;;;;:::i;:::-;;:::i;2441:144:23:-;1313:22;2570:8;-1:-1:-1;;;;;2570:8:23;2441:144;;;-1:-1:-1;;;;;2016:32:47;;;1998:51;;1986:2;1971:18;2441:144:23;1852:203:47;898:43:39;;;;;;;;;2206:25:47;;;2194:2;2179:18;898:43:39;2060:177:47;2679:676:39;;;;;;:::i;:::-;;:::i;3361:436::-;;;;;;:::i;:::-;;:::i;788:64::-;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;;;;788:64:39;;;;;;;;;;;;;;;;;;;;;;;;;:::i;732:50::-;;;;;-1:-1:-1;;;;;732:50:39;;;3405:215:23;;;;;;:::i;:::-;;:::i;948:412:39:-;8870:21:24;4302:15;;-1:-1:-1;;;4302:15:24;;;;4301:16;;4348:14;;4158:30;4726:16;;:34;;;;;4746:14;4726:34;4706:54;;4770:17;4790:11;:16;;4805:1;4790:16;:50;;;;-1:-1:-1;4818:4:24;4810:25;:30;4790:50;4770:70;;4856:12;4855:13;:30;;;;;4873:12;4872:13;4855:30;4851:91;;;4908:23;;-1:-1:-1;;;4908:23:24;;;;;;;;;;;4851:91;4951:18;;-1:-1:-1;;4951:18:24;4968:1;4951:18;;;4979:67;;;;5013:22;;-1:-1:-1;;;;5013:22:24;-1:-1:-1;;;5013:22:24;;;4979:67;-1:-1:-1;;;;;1075:36:39;::::1;1071:62;;1120:13;;-1:-1:-1::0;;;1120:13:39::1;;;;;;;;;;;1071:62;1143:21;:69:::0;;-1:-1:-1;;;;;;1143:69:39::1;-1:-1:-1::0;;;;;1143:69:39;::::1;;::::0;;1222:21:::1;1237:5:::0;1222:14:::1;:21::i;:::-;1305:28;:48:::0;;;5066:101:24;;;;5100:23;;-1:-1:-1;;;;5100:23:24;;;5142:14;;-1:-1:-1;4083:50:47;;5142:14:24;;4071:2:47;4056:18;5142:14:24;;;;;;;5066:101;4092:1081;;;;;948:412:39;;;:::o;1761:912::-;1962:4;2334:13:23;:11;:13::i;:::-;1982:6:39::1;1992:1;1982:11:::0;1978:36:::1;;2002:12;;-1:-1:-1::0;;;2002:12:39::1;;;;;;;;;;;1978:36;2028:21;::::0;:35:::1;::::0;;-1:-1:-1;;;2028:35:39;;;;2066:6;;-1:-1:-1;;;;;2028:21:39::1;::::0;:33:::1;::::0;:35:::1;::::0;;::::1;::::0;::::1;::::0;;;;;;;;:21;:35:::1;;;;;;;;;::::0;::::1;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;:44;2024:82;;;2081:25;;-1:-1:-1::0;;;2081:25:39::1;;;;;;;;;;;2024:82;2184:16;2221:28;;2203:15;:46;;;;:::i;:::-;2184:65;;2296:236;;;;;;;;2378:10;2296:236;;;;2343:9;-1:-1:-1::0;;;;;2296:236:39::1;;;;;2410:6;2296:236;;;;2440:8;2296:236;;;;2472:8;2296:236;;;;2501:20;2296:236;;;;;;;;:::i;:::-;::::0;;2260:33:::1;::::0;;;:15:::1;:33;::::0;;;;;;;;:272;;;;;;::::1;::::0;;;::::1;::::0;;-1:-1:-1;;;;;;2260:272:39::1;-1:-1:-1::0;;;;;2260:272:39;;::::1;::::0;;;::::1;::::0;;;;::::1;::::0;::::1;::::0;;::::1;::::0;;;;::::1;::::0;::::1;::::0;::::1;::::0;::::1;::::0;::::1;::::0;::::1;::::0;::::1;::::0;::::1;::::0;::::1;::::0;::::1;::::0;::::1;::::0;::::1;::::0;;;;;;-1:-1:-1;;2260:272:39;;::::1;::::0;;;;::::1;;;;;;:::i;:::-;;;::::0;;-1:-1:-1;;2616:28:39::1;::::0;2548:97:::1;::::0;;4762:25:47;;;4818:2;4803:18;;4796:34;;;4846:18;;;4839:34;;;;-1:-1:-1;;;;;2548:97:39;::::1;::::0;-1:-1:-1;2569:16:39;;2548:97:::1;::::0;4750:2:47;4735:18;2548:97:39::1;;;;;;;-1:-1:-1::0;2662:4:39::1;::::0;1761:912;-1:-1:-1;;;;;;1761:912:39:o;1615:140::-;2334:13:23;:11;:13::i;:::-;1700:28:39::1;:48:::0;1615:140::o;3155:101:23:-;2334:13;:11;:13::i;:::-;3219:30:::1;3246:1;3219:18;:30::i;:::-;3155:101::o:0;1366:243:39:-;2334:13:23;:11;:13::i;:::-;-1:-1:-1;;;;;1465:36:39;::::1;1461:62;;1510:13;;-1:-1:-1::0;;;1510:13:39::1;;;;;;;;;;;1461:62;1533:21;:69:::0;;-1:-1:-1;;;;;;1533:69:39::1;-1:-1:-1::0;;;;;1533:69:39;;;::::1;::::0;;;::::1;::::0;;1366:243::o;2679:676::-;2774:37;2814:33;;;:15;:33;;;;;;2861:13;;;;;;:37;;;;;;;;:::i;:::-;;2857:81;;2907:31;;-1:-1:-1;;;2907:31:39;;;;;;;;;;;2857:81;2948:20;2998:8;2981:26;;;;;;5013:19:47;;5057:2;5048:12;;4884:182;2981:26:39;;;;;;;;;;;;;2971:37;;;;;;2948:60;;3038:7;:16;;;3022:12;:32;3018:60;;3063:15;;-1:-1:-1;;;3063:15:39;;;;;;;;;;;3018:60;3110:7;:16;;;3092:15;:34;3088:64;;;3135:17;;-1:-1:-1;;;3135:17:39;;;;;;;;;;;3088:64;3163:13;;;:38;;-1:-1:-1;;3163:38:39;3179:22;3163:38;;;;;;-1:-1:-1;3212:21:39;3247:17;;;;3266:14;;;;3212:69;;-1:-1:-1;;;3212:69:39;;-1:-1:-1;;;;;3247:17:39;;;3212:69;;;5245:51:47;5312:18;;;5305:34;;;;3212:21:39;;;:34;;5218:18:47;;3212:69:39;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;3321:16;3297:51;3339:8;3297:51;;;;2206:25:47;;2194:2;2179:18;;2060:177;3297:51:39;;;;;;;;2764:591;;2679:676;;:::o;3361:436::-;2334:13:23;:11;:13::i;:::-;3445:37:39::1;3485:33:::0;;;:15:::1;:33;::::0;;;;;3532:13:::1;::::0;::::1;::::0;::::1;;:37;::::0;::::1;;;;;;:::i;:::-;;3528:81;;3578:31;;-1:-1:-1::0;;;3578:31:39::1;;;;;;;;;;;3528:81;3642:7;:16;;;3623:15;:35;3619:68;;3667:20;;-1:-1:-1::0;;;3667:20:39::1;;;;;;;;;;;3619:68;3698:13;::::0;::::1;:37:::0;;-1:-1:-1;;3698:37:39::1;3714:21;3698:37;::::0;;3751:39:::1;::::0;3773:16;;3751:39:::1;::::0;-1:-1:-1;;3751:39:39::1;3435:362;3361:436:::0;:::o;3405:215:23:-;2334:13;:11;:13::i;:::-;-1:-1:-1;;;;;3489:22:23;::::1;3485:91;;3534:31;::::0;-1:-1:-1;;;3534:31:23;;3562:1:::1;3534:31;::::0;::::1;1998:51:47::0;1971:18;;3534:31:23::1;;;;;;;;3485:91;3585:28;3604:8;3585:18;:28::i;:::-;3405:215:::0;:::o;1847:127::-;6931:20:24;:18;:20::i;:::-;1929:38:23::1;1954:12;1929:24;:38::i;2658:162::-:0;966:10:25;2717:7:23;1313:22;2570:8;-1:-1:-1;;;;;2570:8:23;;2441:144;2717:7;-1:-1:-1;;;;;2717:23:23;;2713:101;;2763:40;;-1:-1:-1;;;2763:40:23;;966:10:25;2763:40:23;;;1998:51:47;1971:18;;2763:40:23;1852:203:47;3774:248:23;1313:22;3923:8;;-1:-1:-1;;;;;;3941:19:23;;-1:-1:-1;;;;;3941:19:23;;;;;;;;3975:40;;3923:8;;;;;3975:40;;3847:24;;3975:40;3837:185;;3774:248;:::o;7084:141:24:-;8870:21;8560:40;-1:-1:-1;;;8560:40:24;;;;7146:73;;7191:17;;-1:-1:-1;;;7191:17:24;;;;;;;;;;;1980:235:23;6931:20:24;:18;:20::i;14:173:47:-;82:20;;-1:-1:-1;;;;;131:31:47;;121:42;;111:70;;177:1;174;167:12;111:70;14:173;;;:::o;192:374::-;269:6;277;285;338:2;326:9;317:7;313:23;309:32;306:52;;;354:1;351;344:12;306:52;377:29;396:9;377:29;:::i;:::-;367:39;;425:38;459:2;448:9;444:18;425:38;:::i;:::-;192:374;;415:48;;-1:-1:-1;;;532:2:47;517:18;;;;504:32;;192:374::o;571:662::-;666:6;674;682;690;698;751:3;739:9;730:7;726:23;722:33;719:53;;;768:1;765;758:12;719:53;813:23;;;-1:-1:-1;933:2:47;918:18;;905:32;;-1:-1:-1;1036:2:47;1021:18;;1008:32;;-1:-1:-1;1085:38:47;1119:2;1104:18;;1085:38;:::i;:::-;571:662;;;;-1:-1:-1;571:662:47;;1196:3;1181:19;1168:33;;571:662;-1:-1:-1;;571:662:47:o;1430:226::-;1489:6;1542:2;1530:9;1521:7;1517:23;1513:32;1510:52;;;1558:1;1555;1548:12;1510:52;-1:-1:-1;1603:23:47;;1430:226;-1:-1:-1;1430:226:47:o;1661:186::-;1720:6;1773:2;1761:9;1752:7;1748:23;1744:32;1741:52;;;1789:1;1786;1779:12;1741:52;1812:29;1831:9;1812:29;:::i;:::-;1802:39;1661:186;-1:-1:-1;;;1661:186:47:o;2242:346::-;2310:6;2318;2371:2;2359:9;2350:7;2346:23;2342:32;2339:52;;;2387:1;2384;2377:12;2339:52;-1:-1:-1;;2432:23:47;;;2552:2;2537:18;;;2524:32;;-1:-1:-1;2242:346:47:o;2824:127::-;2885:10;2880:3;2876:20;2873:1;2866:31;2916:4;2913:1;2906:15;2940:4;2937:1;2930:15;2956:730;3259:25;;;-1:-1:-1;;;;;3320:32:47;;3315:2;3300:18;;3293:60;3384:2;3369:18;;3362:34;;;3427:2;3412:18;;3405:34;;;3470:3;3455:19;;3448:35;;;3246:3;3231:19;;3513:1;3502:13;;3492:144;;3558:10;3553:3;3549:20;3546:1;3539:31;3593:4;3590:1;3583:15;3621:4;3618:1;3611:15;3492:144;3673:6;3667:3;3656:9;3652:19;3645:35;2956:730;;;;;;;;;:::o;4144:184::-;4214:6;4267:2;4255:9;4246:7;4242:23;4238:32;4235:52;;;4283:1;4280;4273:12;4235:52;-1:-1:-1;4306:16:47;;4144:184;-1:-1:-1;4144:184:47:o;4333:222::-;4398:9;;;4419:10;;;4416:133;;;4471:10;4466:3;4462:20;4459:1;4452:31;4506:4;4503:1;4496:15;4534:4;4531:1;4524:15;4416:133;4333:222;;;;:::o","linkReferences":{}},"methodIdentifiers":{"abortBridgeTransfer(bytes32)":"c95b659f","atomicBridgeInitiator()":"ee2c59f8","bridgeTransfers(bytes32)":"e0d9cbc4","completeBridgeTransfer(bytes32,bytes32)":"9f8f8799","counterpartyTimeLockDuration()":"96d17d49","initialize(address,address,uint256)":"1794bb3c","lockBridgeTransfer(bytes32,bytes32,bytes32,address,uint256)":"27b3ea07","owner()":"8da5cb5b","renounceOwnership()":"715018a6","setAtomicBridgeInitiator(address)":"7ce08748","setTimeLockDuration(uint256)":"71115eb2","transferOwnership(address)":"f2fde38b"},"rawMetadata":"{\"compiler\":{\"version\":\"0.8.26+commit.8a97fa7a\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"inputs\":[],\"name\":\"BridgeTransferHasBeenCompleted\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"BridgeTransferInvalid\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"BridgeTransferStateNotInitialized\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"BridgeTransferStateNotPending\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InsufficientWethBalance\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidInitialization\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidSecret\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"NotInitializing\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"}],\"name\":\"OwnableInvalidOwner\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"}],\"name\":\"OwnableUnauthorizedAccount\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"TimeLockExpired\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"TimeLockNotExpired\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"Unauthorized\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"WETHTransferFailed\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"ZeroAddress\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"ZeroAmount\",\"type\":\"error\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"bridgeTransferId\",\"type\":\"bytes32\"}],\"name\":\"BridgeTransferAborted\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"bridgeTransferId\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"bytes32\",\"name\":\"pre_image\",\"type\":\"bytes32\"}],\"name\":\"BridgeTransferCompleted\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"bridgeTransferId\",\"type\":\"bytes32\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"bytes32\",\"name\":\"hashLock\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"timeLock\",\"type\":\"uint256\"}],\"name\":\"BridgeTransferLocked\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint64\",\"name\":\"version\",\"type\":\"uint64\"}],\"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\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"bridgeTransferId\",\"type\":\"bytes32\"}],\"name\":\"abortBridgeTransfer\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"atomicBridgeInitiator\",\"outputs\":[{\"internalType\":\"contract AtomicBridgeInitiator\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"name\":\"bridgeTransfers\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"originator\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"},{\"internalType\":\"bytes32\",\"name\":\"hashLock\",\"type\":\"bytes32\"},{\"internalType\":\"uint256\",\"name\":\"timeLock\",\"type\":\"uint256\"},{\"internalType\":\"enum AtomicBridgeCounterparty.MessageState\",\"name\":\"state\",\"type\":\"uint8\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"bridgeTransferId\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"preImage\",\"type\":\"bytes32\"}],\"name\":\"completeBridgeTransfer\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"counterpartyTimeLockDuration\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_atomicBridgeInitiator\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"_timeLockDuration\",\"type\":\"uint256\"}],\"name\":\"initialize\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"originator\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"bridgeTransferId\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"hashLock\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"lockBridgeTransfer\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"owner\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"renounceOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_atomicBridgeInitiator\",\"type\":\"address\"}],\"name\":\"setAtomicBridgeInitiator\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"_timeLockDuration\",\"type\":\"uint256\"}],\"name\":\"setTimeLockDuration\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"transferOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}],\"devdoc\":{\"errors\":{\"InvalidInitialization()\":[{\"details\":\"The contract is already initialized.\"}],\"NotInitializing()\":[{\"details\":\"The contract is not initializing.\"}],\"OwnableInvalidOwner(address)\":[{\"details\":\"The owner is not a valid owner account. (eg. `address(0)`)\"}],\"OwnableUnauthorizedAccount(address)\":[{\"details\":\"The caller account is not authorized to perform an operation.\"}]},\"events\":{\"Initialized(uint64)\":{\"details\":\"Triggered when the contract has been initialized or reinitialized.\"}},\"kind\":\"dev\",\"methods\":{\"abortBridgeTransfer(bytes32)\":{\"details\":\"Cancels the bridge transfer and refunds the initiator if the timelock has expired\",\"params\":{\"bridgeTransferId\":\"Unique identifier for the BridgeTransfer\"}},\"completeBridgeTransfer(bytes32,bytes32)\":{\"details\":\"Completes the bridge transfer and withdraws WETH to the recipient\",\"params\":{\"bridgeTransferId\":\"Unique identifier for the BridgeTransfer\",\"preImage\":\"The secret that unlocks the funds\"}},\"owner()\":{\"details\":\"Returns the address of the current owner.\"},\"renounceOwnership()\":{\"details\":\"Leaves the contract without owner. It will not be possible to call `onlyOwner` functions. Can only be called by the current owner. NOTE: Renouncing ownership will leave the contract without an owner, thereby disabling any functionality that is only available to the owner.\"},\"transferOwnership(address)\":{\"details\":\"Transfers ownership of the contract to a new account (`newOwner`). Can only be called by the current owner.\"}},\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{},\"version\":1}},\"settings\":{\"compilationTarget\":{\"src/AtomicBridgeCounterparty.sol\":\"AtomicBridgeCounterparty\"},\"evmVersion\":\"cancun\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"ipfs\"},\"optimizer\":{\"enabled\":true,\"runs\":200},\"remappings\":[\":@openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/\",\":@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/\",\":ds-test/=lib/openzeppelin-contracts/lib/forge-std/lib/ds-test/src/\",\":erc4626-tests/=lib/openzeppelin-contracts/lib/erc4626-tests/\",\":forge-std/=lib/forge-std/src/\",\":openzeppelin-contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/\",\":openzeppelin-contracts/=lib/openzeppelin-contracts/\"]},\"sources\":{\"lib/openzeppelin-contracts-upgradeable/contracts/access/OwnableUpgradeable.sol\":{\"keccak256\":\"0xc163fcf9bb10138631a9ba5564df1fa25db9adff73bd9ee868a8ae1858fe093a\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://9706d43a0124053d9880f6e31a59f31bc0a6a3dc1acd66ce0a16e1111658c5f6\",\"dweb:/ipfs/QmUFmfowzkRwGtDu36cXV9SPTBHJ3n7dG9xQiK5B28jTf2\"]},\"lib/openzeppelin-contracts-upgradeable/contracts/proxy/utils/Initializable.sol\":{\"keccak256\":\"0x631188737069917d2f909d29ce62c4d48611d326686ba6683e26b72a23bfac0b\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://7a61054ae84cd6c4d04c0c4450ba1d6de41e27e0a2c4f1bcdf58f796b401c609\",\"dweb:/ipfs/QmUvtdp7X1mRVyC3CsHrtPbgoqWaXHp3S1ZR24tpAQYJWM\"]},\"lib/openzeppelin-contracts-upgradeable/contracts/utils/ContextUpgradeable.sol\":{\"keccak256\":\"0xdbef5f0c787055227243a7318ef74c8a5a1108ca3a07f2b3a00ef67769e1e397\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://08e39f23d5b4692f9a40803e53a8156b72b4c1f9902a88cd65ba964db103dab9\",\"dweb:/ipfs/QmPKn6EYDgpga7KtpkA8wV2yJCYGMtc9K4LkJfhKX2RVSV\"]},\"lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol\":{\"keccak256\":\"0xee2337af2dc162a973b4be6d3f7c16f06298259e0af48c5470d2839bfa8a22f4\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://30c476b4b2f405c1bb3f0bae15b006d129c80f1bfd9d0f2038160a3bb9745009\",\"dweb:/ipfs/Qmb3VcuDufv6xbHeVgksC4tHpc5gKYVqBEwjEXW72XzSvN\"]},\"src/AtomicBridgeCounterparty.sol\":{\"keccak256\":\"0x1434defb73d03cf07db074faa16221579f36abbf768e6b04f523141dd568cbd6\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://463973fad78438ff422470db13d131c2bbd224544f1dd4c32a41b881d6dc88d3\",\"dweb:/ipfs/QmSYZLrvd19juf4tDoh4dLzv8G4FEA9aCMWnH3CdC7r5U2\"]},\"src/AtomicBridgeInitiator.sol\":{\"keccak256\":\"0xa331acc393bc52e014ca46b18b5b3127075c995509fb2b898415087ea3276c32\",\"urls\":[\"bzz-raw://e7f05a3dc5fddae0daf8617027f2704493d0214c95e80888a2f40ddbbc3c85ab\",\"dweb:/ipfs/Qmc9VjwVQ9aNVC5S7Vfsh1pmUbCDQBiXiqJ3JcbEfaZ5wd\"]},\"src/IAtomicBridgeCounterparty.sol\":{\"keccak256\":\"0xda85d70db6c806f926609d23f74720254568461d432ab101b0c8ddbedf5c7401\",\"urls\":[\"bzz-raw://7a0eff12549e8ed6c853648029ce05c05499dc65a291c0478ca0b0dc6ce91c05\",\"dweb:/ipfs/QmS5CNUa21V9GRyh2e3teLtT83KjhrVyqc3wV2WYtaKCs3\"]},\"src/IAtomicBridgeInitiator.sol\":{\"keccak256\":\"0x8397d0de470d4fe4f52ae03292085532b2d1681b5bd332992d303d3e630047be\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://4c8826a0ee54c30746ddb91e35a7ef9c0e33acb68c19a0424a05237b3b02753f\",\"dweb:/ipfs/QmPrPd8hpdmiXryYYSycChgrnHwsaTgHp4xJL3wLT3mRmz\"]},\"src/IWETH9.sol\":{\"keccak256\":\"0xff29528c0f48eaad5156c8d66ccd8f754037bba234434807db302e604db9f9a5\",\"urls\":[\"bzz-raw://a7322b029eb6cd2bbf204fb879947a91aad09bca79373583e5c5b7a2630a6c6b\",\"dweb:/ipfs/QmfLWmtiEhdTiD5kFNruHiMYs4dY5joGgJQ4HU2wCTHF6E\"]}},\"version\":1}","metadata":{"compiler":{"version":"0.8.26+commit.8a97fa7a"},"language":"Solidity","output":{"abi":[{"inputs":[],"type":"error","name":"BridgeTransferHasBeenCompleted"},{"inputs":[],"type":"error","name":"BridgeTransferInvalid"},{"inputs":[],"type":"error","name":"BridgeTransferStateNotInitialized"},{"inputs":[],"type":"error","name":"BridgeTransferStateNotPending"},{"inputs":[],"type":"error","name":"InsufficientWethBalance"},{"inputs":[],"type":"error","name":"InvalidInitialization"},{"inputs":[],"type":"error","name":"InvalidSecret"},{"inputs":[],"type":"error","name":"NotInitializing"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"type":"error","name":"OwnableInvalidOwner"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"type":"error","name":"OwnableUnauthorizedAccount"},{"inputs":[],"type":"error","name":"TimeLockExpired"},{"inputs":[],"type":"error","name":"TimeLockNotExpired"},{"inputs":[],"type":"error","name":"Unauthorized"},{"inputs":[],"type":"error","name":"WETHTransferFailed"},{"inputs":[],"type":"error","name":"ZeroAddress"},{"inputs":[],"type":"error","name":"ZeroAmount"},{"inputs":[{"internalType":"bytes32","name":"bridgeTransferId","type":"bytes32","indexed":true}],"type":"event","name":"BridgeTransferAborted","anonymous":false},{"inputs":[{"internalType":"bytes32","name":"bridgeTransferId","type":"bytes32","indexed":true},{"internalType":"bytes32","name":"pre_image","type":"bytes32","indexed":false}],"type":"event","name":"BridgeTransferCompleted","anonymous":false},{"inputs":[{"internalType":"bytes32","name":"bridgeTransferId","type":"bytes32","indexed":true},{"internalType":"address","name":"recipient","type":"address","indexed":true},{"internalType":"uint256","name":"amount","type":"uint256","indexed":false},{"internalType":"bytes32","name":"hashLock","type":"bytes32","indexed":false},{"internalType":"uint256","name":"timeLock","type":"uint256","indexed":false}],"type":"event","name":"BridgeTransferLocked","anonymous":false},{"inputs":[{"internalType":"uint64","name":"version","type":"uint64","indexed":false}],"type":"event","name":"Initialized","anonymous":false},{"inputs":[{"internalType":"address","name":"previousOwner","type":"address","indexed":true},{"internalType":"address","name":"newOwner","type":"address","indexed":true}],"type":"event","name":"OwnershipTransferred","anonymous":false},{"inputs":[{"internalType":"bytes32","name":"bridgeTransferId","type":"bytes32"}],"stateMutability":"nonpayable","type":"function","name":"abortBridgeTransfer"},{"inputs":[],"stateMutability":"view","type":"function","name":"atomicBridgeInitiator","outputs":[{"internalType":"contract AtomicBridgeInitiator","name":"","type":"address"}]},{"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function","name":"bridgeTransfers","outputs":[{"internalType":"bytes32","name":"originator","type":"bytes32"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"bytes32","name":"hashLock","type":"bytes32"},{"internalType":"uint256","name":"timeLock","type":"uint256"},{"internalType":"enum AtomicBridgeCounterparty.MessageState","name":"state","type":"uint8"}]},{"inputs":[{"internalType":"bytes32","name":"bridgeTransferId","type":"bytes32"},{"internalType":"bytes32","name":"preImage","type":"bytes32"}],"stateMutability":"nonpayable","type":"function","name":"completeBridgeTransfer"},{"inputs":[],"stateMutability":"view","type":"function","name":"counterpartyTimeLockDuration","outputs":[{"internalType":"uint256","name":"","type":"uint256"}]},{"inputs":[{"internalType":"address","name":"_atomicBridgeInitiator","type":"address"},{"internalType":"address","name":"owner","type":"address"},{"internalType":"uint256","name":"_timeLockDuration","type":"uint256"}],"stateMutability":"nonpayable","type":"function","name":"initialize"},{"inputs":[{"internalType":"bytes32","name":"originator","type":"bytes32"},{"internalType":"bytes32","name":"bridgeTransferId","type":"bytes32"},{"internalType":"bytes32","name":"hashLock","type":"bytes32"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"stateMutability":"nonpayable","type":"function","name":"lockBridgeTransfer","outputs":[{"internalType":"bool","name":"","type":"bool"}]},{"inputs":[],"stateMutability":"view","type":"function","name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}]},{"inputs":[],"stateMutability":"nonpayable","type":"function","name":"renounceOwnership"},{"inputs":[{"internalType":"address","name":"_atomicBridgeInitiator","type":"address"}],"stateMutability":"nonpayable","type":"function","name":"setAtomicBridgeInitiator"},{"inputs":[{"internalType":"uint256","name":"_timeLockDuration","type":"uint256"}],"stateMutability":"nonpayable","type":"function","name":"setTimeLockDuration"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"stateMutability":"nonpayable","type":"function","name":"transferOwnership"}],"devdoc":{"kind":"dev","methods":{"abortBridgeTransfer(bytes32)":{"details":"Cancels the bridge transfer and refunds the initiator if the timelock has expired","params":{"bridgeTransferId":"Unique identifier for the BridgeTransfer"}},"completeBridgeTransfer(bytes32,bytes32)":{"details":"Completes the bridge transfer and withdraws WETH to the recipient","params":{"bridgeTransferId":"Unique identifier for the BridgeTransfer","preImage":"The secret that unlocks the funds"}},"owner()":{"details":"Returns the address of the current owner."},"renounceOwnership()":{"details":"Leaves the contract without owner. It will not be possible to call `onlyOwner` functions. Can only be called by the current owner. NOTE: Renouncing ownership will leave the contract without an owner, thereby disabling any functionality that is only available to the owner."},"transferOwnership(address)":{"details":"Transfers ownership of the contract to a new account (`newOwner`). Can only be called by the current owner."}},"version":1},"userdoc":{"kind":"user","methods":{},"version":1}},"settings":{"remappings":["@openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/","@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/","ds-test/=lib/openzeppelin-contracts/lib/forge-std/lib/ds-test/src/","erc4626-tests/=lib/openzeppelin-contracts/lib/erc4626-tests/","forge-std/=lib/forge-std/src/","openzeppelin-contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/","openzeppelin-contracts/=lib/openzeppelin-contracts/"],"optimizer":{"enabled":true,"runs":200},"metadata":{"bytecodeHash":"ipfs"},"compilationTarget":{"src/AtomicBridgeCounterparty.sol":"AtomicBridgeCounterparty"},"evmVersion":"cancun","libraries":{}},"sources":{"lib/openzeppelin-contracts-upgradeable/contracts/access/OwnableUpgradeable.sol":{"keccak256":"0xc163fcf9bb10138631a9ba5564df1fa25db9adff73bd9ee868a8ae1858fe093a","urls":["bzz-raw://9706d43a0124053d9880f6e31a59f31bc0a6a3dc1acd66ce0a16e1111658c5f6","dweb:/ipfs/QmUFmfowzkRwGtDu36cXV9SPTBHJ3n7dG9xQiK5B28jTf2"],"license":"MIT"},"lib/openzeppelin-contracts-upgradeable/contracts/proxy/utils/Initializable.sol":{"keccak256":"0x631188737069917d2f909d29ce62c4d48611d326686ba6683e26b72a23bfac0b","urls":["bzz-raw://7a61054ae84cd6c4d04c0c4450ba1d6de41e27e0a2c4f1bcdf58f796b401c609","dweb:/ipfs/QmUvtdp7X1mRVyC3CsHrtPbgoqWaXHp3S1ZR24tpAQYJWM"],"license":"MIT"},"lib/openzeppelin-contracts-upgradeable/contracts/utils/ContextUpgradeable.sol":{"keccak256":"0xdbef5f0c787055227243a7318ef74c8a5a1108ca3a07f2b3a00ef67769e1e397","urls":["bzz-raw://08e39f23d5b4692f9a40803e53a8156b72b4c1f9902a88cd65ba964db103dab9","dweb:/ipfs/QmPKn6EYDgpga7KtpkA8wV2yJCYGMtc9K4LkJfhKX2RVSV"],"license":"MIT"},"lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol":{"keccak256":"0xee2337af2dc162a973b4be6d3f7c16f06298259e0af48c5470d2839bfa8a22f4","urls":["bzz-raw://30c476b4b2f405c1bb3f0bae15b006d129c80f1bfd9d0f2038160a3bb9745009","dweb:/ipfs/Qmb3VcuDufv6xbHeVgksC4tHpc5gKYVqBEwjEXW72XzSvN"],"license":"MIT"},"src/AtomicBridgeCounterparty.sol":{"keccak256":"0x1434defb73d03cf07db074faa16221579f36abbf768e6b04f523141dd568cbd6","urls":["bzz-raw://463973fad78438ff422470db13d131c2bbd224544f1dd4c32a41b881d6dc88d3","dweb:/ipfs/QmSYZLrvd19juf4tDoh4dLzv8G4FEA9aCMWnH3CdC7r5U2"],"license":"MIT"},"src/AtomicBridgeInitiator.sol":{"keccak256":"0xa331acc393bc52e014ca46b18b5b3127075c995509fb2b898415087ea3276c32","urls":["bzz-raw://e7f05a3dc5fddae0daf8617027f2704493d0214c95e80888a2f40ddbbc3c85ab","dweb:/ipfs/Qmc9VjwVQ9aNVC5S7Vfsh1pmUbCDQBiXiqJ3JcbEfaZ5wd"],"license":null},"src/IAtomicBridgeCounterparty.sol":{"keccak256":"0xda85d70db6c806f926609d23f74720254568461d432ab101b0c8ddbedf5c7401","urls":["bzz-raw://7a0eff12549e8ed6c853648029ce05c05499dc65a291c0478ca0b0dc6ce91c05","dweb:/ipfs/QmS5CNUa21V9GRyh2e3teLtT83KjhrVyqc3wV2WYtaKCs3"],"license":null},"src/IAtomicBridgeInitiator.sol":{"keccak256":"0x8397d0de470d4fe4f52ae03292085532b2d1681b5bd332992d303d3e630047be","urls":["bzz-raw://4c8826a0ee54c30746ddb91e35a7ef9c0e33acb68c19a0424a05237b3b02753f","dweb:/ipfs/QmPrPd8hpdmiXryYYSycChgrnHwsaTgHp4xJL3wLT3mRmz"],"license":"MIT"},"src/IWETH9.sol":{"keccak256":"0xff29528c0f48eaad5156c8d66ccd8f754037bba234434807db302e604db9f9a5","urls":["bzz-raw://a7322b029eb6cd2bbf204fb879947a91aad09bca79373583e5c5b7a2630a6c6b","dweb:/ipfs/QmfLWmtiEhdTiD5kFNruHiMYs4dY5joGgJQ4HU2wCTHF6E"],"license":null}},"version":1},"id":39} diff --git a/protocol-units/bridge/chains/ethereum/abis/AtomicBridgeInitiator.json b/protocol-units/bridge/chains/ethereum/abis/AtomicBridgeInitiator.json new file mode 100644 index 000000000..0f2fb1f2e --- /dev/null +++ b/protocol-units/bridge/chains/ethereum/abis/AtomicBridgeInitiator.json @@ -0,0 +1 @@ +{"abi":[{"type":"function","name":"bridgeTransfers","inputs":[{"name":"","type":"bytes32","internalType":"bytes32"}],"outputs":[{"name":"amount","type":"uint256","internalType":"uint256"},{"name":"originator","type":"address","internalType":"address"},{"name":"recipient","type":"bytes32","internalType":"bytes32"},{"name":"hashLock","type":"bytes32","internalType":"bytes32"},{"name":"timeLock","type":"uint256","internalType":"uint256"},{"name":"state","type":"uint8","internalType":"enum AtomicBridgeInitiator.MessageState"}],"stateMutability":"view"},{"type":"function","name":"completeBridgeTransfer","inputs":[{"name":"bridgeTransferId","type":"bytes32","internalType":"bytes32"},{"name":"preImage","type":"bytes32","internalType":"bytes32"}],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"counterpartyAddress","inputs":[],"outputs":[{"name":"","type":"address","internalType":"address"}],"stateMutability":"view"},{"type":"function","name":"initialize","inputs":[{"name":"_weth","type":"address","internalType":"address"},{"name":"owner","type":"address","internalType":"address"},{"name":"_timeLockDuration","type":"uint256","internalType":"uint256"}],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"initiateBridgeTransfer","inputs":[{"name":"wethAmount","type":"uint256","internalType":"uint256"},{"name":"recipient","type":"bytes32","internalType":"bytes32"},{"name":"hashLock","type":"bytes32","internalType":"bytes32"}],"outputs":[{"name":"bridgeTransferId","type":"bytes32","internalType":"bytes32"}],"stateMutability":"payable"},{"type":"function","name":"initiatorTimeLockDuration","inputs":[],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"type":"function","name":"owner","inputs":[],"outputs":[{"name":"","type":"address","internalType":"address"}],"stateMutability":"view"},{"type":"function","name":"poolBalance","inputs":[],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"type":"function","name":"refundBridgeTransfer","inputs":[{"name":"bridgeTransferId","type":"bytes32","internalType":"bytes32"}],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"renounceOwnership","inputs":[],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"setCounterpartyAddress","inputs":[{"name":"_counterpartyAddress","type":"address","internalType":"address"}],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"transferOwnership","inputs":[{"name":"newOwner","type":"address","internalType":"address"}],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"weth","inputs":[],"outputs":[{"name":"","type":"address","internalType":"contract IWETH9"}],"stateMutability":"view"},{"type":"function","name":"withdrawWETH","inputs":[{"name":"recipient","type":"address","internalType":"address"},{"name":"amount","type":"uint256","internalType":"uint256"}],"outputs":[],"stateMutability":"nonpayable"},{"type":"event","name":"BridgeTransferCompleted","inputs":[{"name":"_bridgeTransferId","type":"bytes32","indexed":true,"internalType":"bytes32"},{"name":"pre_image","type":"bytes32","indexed":false,"internalType":"bytes32"}],"anonymous":false},{"type":"event","name":"BridgeTransferInitiated","inputs":[{"name":"_bridgeTransferId","type":"bytes32","indexed":true,"internalType":"bytes32"},{"name":"_originator","type":"address","indexed":true,"internalType":"address"},{"name":"_recipient","type":"bytes32","indexed":true,"internalType":"bytes32"},{"name":"amount","type":"uint256","indexed":false,"internalType":"uint256"},{"name":"_hashLock","type":"bytes32","indexed":false,"internalType":"bytes32"},{"name":"_timeLock","type":"uint256","indexed":false,"internalType":"uint256"}],"anonymous":false},{"type":"event","name":"BridgeTransferRefunded","inputs":[{"name":"_bridgeTransferId","type":"bytes32","indexed":true,"internalType":"bytes32"}],"anonymous":false},{"type":"event","name":"Initialized","inputs":[{"name":"version","type":"uint64","indexed":false,"internalType":"uint64"}],"anonymous":false},{"type":"event","name":"OwnershipTransferred","inputs":[{"name":"previousOwner","type":"address","indexed":true,"internalType":"address"},{"name":"newOwner","type":"address","indexed":true,"internalType":"address"}],"anonymous":false},{"type":"error","name":"BridgeTransferHasBeenCompleted","inputs":[]},{"type":"error","name":"BridgeTransferInvalid","inputs":[]},{"type":"error","name":"BridgeTransferStateNotInitialized","inputs":[]},{"type":"error","name":"InsufficientWethBalance","inputs":[]},{"type":"error","name":"InvalidInitialization","inputs":[]},{"type":"error","name":"InvalidSecret","inputs":[]},{"type":"error","name":"NotInitializing","inputs":[]},{"type":"error","name":"OwnableInvalidOwner","inputs":[{"name":"owner","type":"address","internalType":"address"}]},{"type":"error","name":"OwnableUnauthorizedAccount","inputs":[{"name":"account","type":"address","internalType":"address"}]},{"type":"error","name":"TimeLockExpired","inputs":[]},{"type":"error","name":"TimeLockNotExpired","inputs":[]},{"type":"error","name":"Unauthorized","inputs":[]},{"type":"error","name":"WETHTransferFailed","inputs":[]},{"type":"error","name":"ZeroAddress","inputs":[]},{"type":"error","name":"ZeroAmount","inputs":[]}],"bytecode":{"object":"0x6080604052348015600e575f80fd5b50610e738061001c5f395ff3fe6080604052600436106100d8575f3560e01c8063715018a61161007c57806396365d441161005757806396365d441461023b5780639f8f879914610250578063e0d9cbc41461026f578063f2fde38b146102dc575f80fd5b8063715018a6146101cc5780638da5cb5b146101e057806393113b5c1461021c575f80fd5b80631f92c08e116100b75780631f92c08e146101425780632b3948bb146101795780633fc8cef31461018e57806357808020146101ad575f80fd5b80621a153e146100dc5780631794bb3c146100fd5780631a8849a41461011c575b5f80fd5b3480156100e7575f80fd5b506100fb6100f6366004610c78565b6102fb565b005b348015610108575f80fd5b506100fb610117366004610c98565b61034c565b61012f61012a366004610cd2565b6104a3565b6040519081526020015b60405180910390f35b34801561014d575f80fd5b50600254610161906001600160a01b031681565b6040516001600160a01b039091168152602001610139565b348015610184575f80fd5b5061012f60055481565b348015610199575f80fd5b50600354610161906001600160a01b031681565b3480156101b8575f80fd5b506100fb6101c7366004610cfb565b61078c565b3480156101d7575f80fd5b506100fb6108eb565b3480156101eb575f80fd5b507f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c199300546001600160a01b0316610161565b348015610227575f80fd5b506100fb610236366004610d12565b6108fe565b348015610246575f80fd5b5061012f60015481565b34801561025b575f80fd5b506100fb61026a366004610d3a565b6109f6565b34801561027a575f80fd5b506102ca610289366004610cfb565b5f6020819052908152604090208054600182015460028301546003840154600485015460059095015493946001600160a01b03909316939192909160ff1686565b60405161013996959493929190610d6e565b3480156102e7575f80fd5b506100fb6102f6366004610c78565b610aee565b610303610b30565b6001600160a01b03811661032a5760405163d92e233d60e01b815260040160405180910390fd5b600280546001600160a01b0319166001600160a01b0392909216919091179055565b7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a008054600160401b810460ff16159067ffffffffffffffff165f811580156103915750825b90505f8267ffffffffffffffff1660011480156103ad5750303b155b9050811580156103bb575080155b156103d95760405163f92ee8a960e01b815260040160405180910390fd5b845467ffffffffffffffff19166001178555831561040357845460ff60401b1916600160401b1785555b6001600160a01b03881661042a5760405163d92e233d60e01b815260040160405180910390fd5b600380546001600160a01b0319166001600160a01b038a1617905561044e87610b8b565b6005869055831561049957845460ff60401b19168555604051600181527fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d29060200160405180910390a15b5050505050505050565b5f3334826104b18288610dda565b9050805f036104d357604051631f2a200560e01b815260040160405180910390fd5b811561053e5760035f9054906101000a90046001600160a01b03166001600160a01b031663d0e30db0836040518263ffffffff1660e01b81526004015f604051808303818588803b158015610526575f80fd5b505af1158015610538573d5f803e3d5ffd5b50505050505b86156105db576003546040516323b872dd60e01b81526001600160a01b038581166004830152306024830152604482018a9052909116906323b872dd906064016020604051808303815f875af115801561059a573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906105be9190610df3565b6105db57604051630dd0c8f760e41b815260040160405180910390fd5b8060015f8282546105ec9190610dda565b90915550506005546004805485928992899242915f61060a83610e12565b9091555060405160609690961b6bffffffffffffffffffffffff19166020870152603486019490945260548501929092526074840152609483015260b482015260d4016040516020818303038152906040528051906020012093506040518060c00160405280828152602001846001600160a01b031681526020018781526020018681526020016005544261069f9190610dda565b81525f60209182018190528681528082526040908190208351815591830151600180840180546001600160a01b0319166001600160a01b039093169290921790915590830151600280840191909155606084015160038401556080840151600484015560a08401516005840180549193909260ff1990921691849081111561072957610729610d5a565b021790555050600554604080518481526020810189905280820192909252518892506001600160a01b0386169187917f44e287be4fbd3a2dcc143a376301094fd2f809dcc2a8d3c09d0a0715224766c49181900360600190a45050509392505050565b610794610b30565b5f81815260208190526040812090600582015460ff1660028111156107bb576107bb610d5a565b146107d957604051630ffb9dcb60e01b815260040160405180910390fd5b80600401544210156107fe5760405163191f4d1b60e31b815260040160405180910390fd5b60058101805460ff191660021790558054600180545f90610820908490610e2a565b90915550506003546001820154825460405163a9059cbb60e01b81526001600160a01b039283166004820152602481019190915291169063a9059cbb906044016020604051808303815f875af115801561087c573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906108a09190610df3565b6108bd57604051630dd0c8f760e41b815260040160405180910390fd5b60405182907f4fee0a65c921e50a9623c3abe10a4067e49c03ef491e7b406dace7cb79c12c61905f90a25050565b6108f3610b30565b6108fc5f610b9c565b565b6002546001600160a01b03163314610928576040516282b42960e81b815260040160405180910390fd5b80600154101561094b576040516318fb0fc360e11b815260040160405180910390fd5b8060015f82825461095c9190610e2a565b909155505060035460405163a9059cbb60e01b81526001600160a01b038481166004830152602482018490529091169063a9059cbb906044016020604051808303815f875af11580156109b1573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906109d59190610df3565b6109f257604051630dd0c8f760e41b815260040160405180910390fd5b5050565b5f82815260208190526040812090600582015460ff166002811115610a1d57610a1d610d5a565b14610a3b57604051630437880960e41b815260040160405180910390fd5b60038101546040805160208101859052016040516020818303038152906040528051906020012014610a805760405163abab6bd760e01b815260040160405180910390fd5b8060040154421115610aa55760405163179a39d160e01b815260040160405180910390fd5b60058101805460ff1916600117905560405182815283907f05ddc886acde01b77731bfad1dcfb6abf529f05c28ea66556fe87429bb2789ea9060200160405180910390a2505050565b610af6610b30565b6001600160a01b038116610b2457604051631e4fbdf760e01b81525f60048201526024015b60405180910390fd5b610b2d81610b9c565b50565b33610b627f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c199300546001600160a01b031690565b6001600160a01b0316146108fc5760405163118cdaa760e01b8152336004820152602401610b1b565b610b93610c0c565b610b2d81610c55565b7f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c19930080546001600160a01b031981166001600160a01b03848116918217845560405192169182907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0905f90a3505050565b7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a0054600160401b900460ff166108fc57604051631afcd79f60e31b815260040160405180910390fd5b610af6610c0c565b80356001600160a01b0381168114610c73575f80fd5b919050565b5f60208284031215610c88575f80fd5b610c9182610c5d565b9392505050565b5f805f60608486031215610caa575f80fd5b610cb384610c5d565b9250610cc160208501610c5d565b929592945050506040919091013590565b5f805f60608486031215610ce4575f80fd5b505081359360208301359350604090920135919050565b5f60208284031215610d0b575f80fd5b5035919050565b5f8060408385031215610d23575f80fd5b610d2c83610c5d565b946020939093013593505050565b5f8060408385031215610d4b575f80fd5b50508035926020909101359150565b634e487b7160e01b5f52602160045260245ffd5b8681526001600160a01b038616602082015260408101859052606081018490526080810183905260c0810160038310610db557634e487b7160e01b5f52602160045260245ffd5b8260a0830152979650505050505050565b634e487b7160e01b5f52601160045260245ffd5b80820180821115610ded57610ded610dc6565b92915050565b5f60208284031215610e03575f80fd5b81518015158114610c91575f80fd5b5f60018201610e2357610e23610dc6565b5060010190565b81810381811115610ded57610ded610dc656fea26469706673582212200b428544028cc6bec95136cf2d0b11c649293794be703f89b2fd17fe5426433d64736f6c634300081a0033","sourceMap":"235:4658:40:-:0;;;;;;;;;;;;;;;;;;;","linkReferences":{}},"deployedBytecode":{"object":"0x6080604052600436106100d8575f3560e01c8063715018a61161007c57806396365d441161005757806396365d441461023b5780639f8f879914610250578063e0d9cbc41461026f578063f2fde38b146102dc575f80fd5b8063715018a6146101cc5780638da5cb5b146101e057806393113b5c1461021c575f80fd5b80631f92c08e116100b75780631f92c08e146101425780632b3948bb146101795780633fc8cef31461018e57806357808020146101ad575f80fd5b80621a153e146100dc5780631794bb3c146100fd5780631a8849a41461011c575b5f80fd5b3480156100e7575f80fd5b506100fb6100f6366004610c78565b6102fb565b005b348015610108575f80fd5b506100fb610117366004610c98565b61034c565b61012f61012a366004610cd2565b6104a3565b6040519081526020015b60405180910390f35b34801561014d575f80fd5b50600254610161906001600160a01b031681565b6040516001600160a01b039091168152602001610139565b348015610184575f80fd5b5061012f60055481565b348015610199575f80fd5b50600354610161906001600160a01b031681565b3480156101b8575f80fd5b506100fb6101c7366004610cfb565b61078c565b3480156101d7575f80fd5b506100fb6108eb565b3480156101eb575f80fd5b507f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c199300546001600160a01b0316610161565b348015610227575f80fd5b506100fb610236366004610d12565b6108fe565b348015610246575f80fd5b5061012f60015481565b34801561025b575f80fd5b506100fb61026a366004610d3a565b6109f6565b34801561027a575f80fd5b506102ca610289366004610cfb565b5f6020819052908152604090208054600182015460028301546003840154600485015460059095015493946001600160a01b03909316939192909160ff1686565b60405161013996959493929190610d6e565b3480156102e7575f80fd5b506100fb6102f6366004610c78565b610aee565b610303610b30565b6001600160a01b03811661032a5760405163d92e233d60e01b815260040160405180910390fd5b600280546001600160a01b0319166001600160a01b0392909216919091179055565b7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a008054600160401b810460ff16159067ffffffffffffffff165f811580156103915750825b90505f8267ffffffffffffffff1660011480156103ad5750303b155b9050811580156103bb575080155b156103d95760405163f92ee8a960e01b815260040160405180910390fd5b845467ffffffffffffffff19166001178555831561040357845460ff60401b1916600160401b1785555b6001600160a01b03881661042a5760405163d92e233d60e01b815260040160405180910390fd5b600380546001600160a01b0319166001600160a01b038a1617905561044e87610b8b565b6005869055831561049957845460ff60401b19168555604051600181527fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d29060200160405180910390a15b5050505050505050565b5f3334826104b18288610dda565b9050805f036104d357604051631f2a200560e01b815260040160405180910390fd5b811561053e5760035f9054906101000a90046001600160a01b03166001600160a01b031663d0e30db0836040518263ffffffff1660e01b81526004015f604051808303818588803b158015610526575f80fd5b505af1158015610538573d5f803e3d5ffd5b50505050505b86156105db576003546040516323b872dd60e01b81526001600160a01b038581166004830152306024830152604482018a9052909116906323b872dd906064016020604051808303815f875af115801561059a573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906105be9190610df3565b6105db57604051630dd0c8f760e41b815260040160405180910390fd5b8060015f8282546105ec9190610dda565b90915550506005546004805485928992899242915f61060a83610e12565b9091555060405160609690961b6bffffffffffffffffffffffff19166020870152603486019490945260548501929092526074840152609483015260b482015260d4016040516020818303038152906040528051906020012093506040518060c00160405280828152602001846001600160a01b031681526020018781526020018681526020016005544261069f9190610dda565b81525f60209182018190528681528082526040908190208351815591830151600180840180546001600160a01b0319166001600160a01b039093169290921790915590830151600280840191909155606084015160038401556080840151600484015560a08401516005840180549193909260ff1990921691849081111561072957610729610d5a565b021790555050600554604080518481526020810189905280820192909252518892506001600160a01b0386169187917f44e287be4fbd3a2dcc143a376301094fd2f809dcc2a8d3c09d0a0715224766c49181900360600190a45050509392505050565b610794610b30565b5f81815260208190526040812090600582015460ff1660028111156107bb576107bb610d5a565b146107d957604051630ffb9dcb60e01b815260040160405180910390fd5b80600401544210156107fe5760405163191f4d1b60e31b815260040160405180910390fd5b60058101805460ff191660021790558054600180545f90610820908490610e2a565b90915550506003546001820154825460405163a9059cbb60e01b81526001600160a01b039283166004820152602481019190915291169063a9059cbb906044016020604051808303815f875af115801561087c573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906108a09190610df3565b6108bd57604051630dd0c8f760e41b815260040160405180910390fd5b60405182907f4fee0a65c921e50a9623c3abe10a4067e49c03ef491e7b406dace7cb79c12c61905f90a25050565b6108f3610b30565b6108fc5f610b9c565b565b6002546001600160a01b03163314610928576040516282b42960e81b815260040160405180910390fd5b80600154101561094b576040516318fb0fc360e11b815260040160405180910390fd5b8060015f82825461095c9190610e2a565b909155505060035460405163a9059cbb60e01b81526001600160a01b038481166004830152602482018490529091169063a9059cbb906044016020604051808303815f875af11580156109b1573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906109d59190610df3565b6109f257604051630dd0c8f760e41b815260040160405180910390fd5b5050565b5f82815260208190526040812090600582015460ff166002811115610a1d57610a1d610d5a565b14610a3b57604051630437880960e41b815260040160405180910390fd5b60038101546040805160208101859052016040516020818303038152906040528051906020012014610a805760405163abab6bd760e01b815260040160405180910390fd5b8060040154421115610aa55760405163179a39d160e01b815260040160405180910390fd5b60058101805460ff1916600117905560405182815283907f05ddc886acde01b77731bfad1dcfb6abf529f05c28ea66556fe87429bb2789ea9060200160405180910390a2505050565b610af6610b30565b6001600160a01b038116610b2457604051631e4fbdf760e01b81525f60048201526024015b60405180910390fd5b610b2d81610b9c565b50565b33610b627f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c199300546001600160a01b031690565b6001600160a01b0316146108fc5760405163118cdaa760e01b8152336004820152602401610b1b565b610b93610c0c565b610b2d81610c55565b7f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c19930080546001600160a01b031981166001600160a01b03848116918217845560405192169182907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0905f90a3505050565b7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a0054600160401b900460ff166108fc57604051631afcd79f60e31b815260040160405180910390fd5b610af6610c0c565b80356001600160a01b0381168114610c73575f80fd5b919050565b5f60208284031215610c88575f80fd5b610c9182610c5d565b9392505050565b5f805f60608486031215610caa575f80fd5b610cb384610c5d565b9250610cc160208501610c5d565b929592945050506040919091013590565b5f805f60608486031215610ce4575f80fd5b505081359360208301359350604090920135919050565b5f60208284031215610d0b575f80fd5b5035919050565b5f8060408385031215610d23575f80fd5b610d2c83610c5d565b946020939093013593505050565b5f8060408385031215610d4b575f80fd5b50508035926020909101359150565b634e487b7160e01b5f52602160045260245ffd5b8681526001600160a01b038616602082015260408101859052606081018490526080810183905260c0810160038310610db557634e487b7160e01b5f52602160045260245ffd5b8260a0830152979650505050505050565b634e487b7160e01b5f52601160045260245ffd5b80820180821115610ded57610ded610dc6565b92915050565b5f60208284031215610e03575f80fd5b81518015158114610c91575f80fd5b5f60018201610e2357610e23610dc6565b5060010190565b81810381811115610ded57610ded610dc656fea26469706673582212200b428544028cc6bec95136cf2d0b11c649293794be703f89b2fd17fe5426433d64736f6c634300081a0033","sourceMap":"235:4658:40:-:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;1423:210;;;;;;;;;;-1:-1:-1;1423:210:40;;;;;:::i;:::-;;:::i;:::-;;1073:344;;;;;;;;;;-1:-1:-1;1073:344:40;;;;;:::i;:::-;;:::i;1639:1575::-;;;;;;:::i;:::-;;:::i;:::-;;;1379:25:47;;;1367:2;1352:18;1639:1575:40;;;;;;;;805:34;;;;;;;;;;-1:-1:-1;805:34:40;;;;-1:-1:-1;;;;;805:34:40;;;;;;-1:-1:-1;;;;;1579:32:47;;;1561:51;;1549:2;1534:18;805:34:40;1415:203:47;937:40:40;;;;;;;;;;;;;;;;846:18;;;;;;;;;;-1:-1:-1;846:18:40;;;;-1:-1:-1;;;;;846:18:40;;;3809:697;;;;;;;;;;-1:-1:-1;3809:697:40;;;;;:::i;:::-;;:::i;3155:101:23:-;;;;;;;;;;;;;:::i;2441:144::-;;;;;;;;;;-1:-1:-1;1313:22:23;2570:8;-1:-1:-1;;;;;2570:8:23;2441:144;;4573:318:40;;;;;;;;;;-1:-1:-1;4573:318:40;;;;;:::i;:::-;;:::i;772:26::-;;;;;;;;;;;;;;;;3220:583;;;;;;;;;;-1:-1:-1;3220:583:40;;;;;:::i;:::-;;:::i;677:57::-;;;;;;;;;;-1:-1:-1;677:57:40;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;;;;677:57:40;;;;;;;;;;;;;;;;;;;;;;;;:::i;3405:215:23:-;;;;;;;;;;-1:-1:-1;3405:215:23;;;;;:::i;:::-;;:::i;1423:210:40:-;2334:13:23;:11;:13::i;:::-;-1:-1:-1;;;;;1518:34:40;::::1;1514:60;;1561:13;;-1:-1:-1::0;;;1561:13:40::1;;;;;;;;;;;1514:60;1584:19;:42:::0;;-1:-1:-1;;;;;;1584:42:40::1;-1:-1:-1::0;;;;;1584:42:40;;;::::1;::::0;;;::::1;::::0;;1423:210::o;1073:344::-;8870:21:24;4302:15;;-1:-1:-1;;;4302:15:24;;;;4301:16;;4348:14;;4158:30;4726:16;;:34;;;;;4746:14;4726:34;4706:54;;4770:17;4790:11;:16;;4805:1;4790:16;:50;;;;-1:-1:-1;4818:4:24;4810:25;:30;4790:50;4770:70;;4856:12;4855:13;:30;;;;;4873:12;4872:13;4855:30;4851:91;;;4908:23;;-1:-1:-1;;;4908:23:24;;;;;;;;;;;4851:91;4951:18;;-1:-1:-1;;4951:18:24;4968:1;4951:18;;;4979:67;;;;5013:22;;-1:-1:-1;;;;5013:22:24;-1:-1:-1;;;5013:22:24;;;4979:67;-1:-1:-1;;;;;1183:19:40;::::1;1179:70;;1225:13;;-1:-1:-1::0;;;1225:13:40::1;;;;;;;;;;;1179:70;1258:4;:20:::0;;-1:-1:-1;;;;;;1258:20:40::1;-1:-1:-1::0;;;;;1258:20:40;::::1;;::::0;;1288:21:::1;1303:5:::0;1288:14:::1;:21::i;:::-;1365:25;:45:::0;;;5066:101:24;;;;5100:23;;-1:-1:-1;;;;5100:23:24;;;5142:14;;-1:-1:-1;3936:50:47;;5142:14:24;;3924:2:47;3909:18;5142:14:24;;;;;;;5066:101;4092:1081;;;;;1073:344:40;;;:::o;1639:1575::-;1778:24;1839:10;1879:9;1778:24;1920:22;1879:9;1920:10;:22;:::i;:::-;1898:44;;2005:11;2020:1;2005:16;2001:66;;2044:12;;-1:-1:-1;;;2044:12:40;;;;;;;;;;;2001:66;2152:13;;2148:51;;2167:4;;;;;;;;;-1:-1:-1;;;;;2167:4:40;-1:-1:-1;;;;;2167:12:40;;2187:9;2167:32;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2148:51;2290:14;;2286:135;;2325:4;;:56;;-1:-1:-1;;;2325:56:40;;-1:-1:-1;;;;;4479:32:47;;;2325:56:40;;;4461:51:47;2363:4:40;4528:18:47;;;4521:60;4597:18;;;4590:34;;;2325:4:40;;;;:17;;4434:18:47;;2325:56:40;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;2320:90;;2390:20;;-1:-1:-1;;;2390:20:40;;;;;;;;;;;2320:90;2481:11;2466;;:26;;;;;;;:::i;:::-;;;;-1:-1:-1;;2671:25:40;;2715:5;:7;;2638:10;;2650:9;;2661:8;;2698:15;;2715:5;:7;;;:::i;:::-;;;;-1:-1:-1;2621:102:40;;5346:2:47;5342:15;;;;-1:-1:-1;;5338:53:47;2621:102:40;;;5326:66:47;5408:12;;;5401:28;;;;5445:12;;;5438:28;;;;5482:12;;;5475:28;5519:13;;;5512:29;5557:13;;;5550:29;5595:13;;2621:102:40;;;;;;;;;;;;2611:113;;;;;;2592:132;;2771:273;;;;;;;;2808:11;2771:273;;;;2845:10;-1:-1:-1;;;;;2771:273:40;;;;;2880:9;2771:273;;;;2913:8;2771:273;;;;2963:25;;2945:15;:43;;;;:::i;:::-;2771:273;;3009:24;2771:273;;;;;;;2735:33;;;;;;;;;;;:309;;;;;;;;;;;;;;-1:-1:-1;;;;;;2735:309:40;-1:-1:-1;;;;;2735:309:40;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;2735:309:40;;;;;;;;;;;;;:::i;:::-;;;;;-1:-1:-1;;3148:25:40;;3060:114;;;5821:25:47;;;5877:2;5862:18;;5855:34;;;5905:18;;;5898:34;;;;3060:114:40;3114:9;;-1:-1:-1;;;;;;3060:114:40;;;3084:16;;3060:114;;;;;5809:2:47;3060:114:40;;;3184:23;;;1639:1575;;;;;:::o;3809:697::-;2334:13:23;:11;:13::i;:::-;3894:37:40::1;3934:33:::0;;;::::1;::::0;;;;;;;3981:20:::1;::::0;::::1;::::0;::::1;;:48;::::0;::::1;;;;;;:::i;:::-;;3977:96;;4038:35;;-1:-1:-1::0;;;4038:35:40::1;;;;;;;;;;;3977:96;4105:14;:23;;;4087:15;:41;4083:74;;;4137:20;;-1:-1:-1::0;;;4137:20:40::1;;;;;;;;;;;4083:74;4167:20;::::0;::::1;:44:::0;;-1:-1:-1;;4167:44:40::1;4190:21;4167:44;::::0;;4315:21;;4167:44;4300:36;;-1:-1:-1;;4300:36:40::1;::::0;4315:21;;4300:36:::1;:::i;:::-;::::0;;;-1:-1:-1;;4351:4:40::1;::::0;;4365:25;::::1;::::0;4392:21;;4351:63:::1;::::0;-1:-1:-1;;;4351:63:40;;-1:-1:-1;;;;;4365:25:40;;::::1;4351:63;::::0;::::1;6250:51:47::0;6317:18;;;6310:34;;;;4351:4:40;::::1;::::0;:13:::1;::::0;6223:18:47;;4351:63:40::1;;;;;;;;;;;;;;;;;;::::0;::::1;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;4346:97;;4423:20;;-1:-1:-1::0;;;4423:20:40::1;;;;;;;;;;;4346:97;4459:40;::::0;4482:16;;4459:40:::1;::::0;;;::::1;3884:622;3809:697:::0;:::o;3155:101:23:-;2334:13;:11;:13::i;:::-;3219:30:::1;3246:1;3219:18;:30::i;:::-;3155:101::o:0;4573:318:40:-;4667:19;;-1:-1:-1;;;;;4667:19:40;4653:10;:33;4649:60;;4695:14;;-1:-1:-1;;;4695:14:40;;;;;;;;;;;4649:60;4737:6;4723:11;;:20;4719:58;;;4752:25;;-1:-1:-1;;;4752:25:40;;;;;;;;;;;4719:58;4802:6;4787:11;;:21;;;;;;;:::i;:::-;;;;-1:-1:-1;;4823:4:40;;:32;;-1:-1:-1;;;4823:32:40;;-1:-1:-1;;;;;6268:32:47;;;4823::40;;;6250:51:47;6317:18;;;6310:34;;;4823:4:40;;;;:13;;6223:18:47;;4823:32:40;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;4818:66;;4864:20;;-1:-1:-1;;;4864:20:40;;;;;;;;;;;4818:66;4573:318;;:::o;3220:583::-;3315:37;3355:33;;;;;;;;;;;3402:20;;;;;;:48;;;;;;;;:::i;:::-;;3398:93;;3459:32;;-1:-1:-1;;;3459:32:40;;;;;;;;;;;3398:93;3546:23;;;;3515:26;;;;;;6484:19:47;;;6519:12;3515:26:40;;;;;;;;;;;;3505:37;;;;;;:64;3501:92;;3578:15;;-1:-1:-1;;;3578:15:40;;;;;;;;;;;3501:92;3625:14;:23;;;3607:15;:41;3603:71;;;3657:17;;-1:-1:-1;;;3657:17:40;;;;;;;;;;;3603:71;3684:20;;;:45;;-1:-1:-1;;3684:45:40;3707:22;3684:45;;;3745:51;;1379:25:47;;;3769:16:40;;3745:51;;1367:2:47;1352:18;3745:51:40;;;;;;;3305:498;3220:583;;:::o;3405:215:23:-;2334:13;:11;:13::i;:::-;-1:-1:-1;;;;;3489:22:23;::::1;3485:91;;3534:31;::::0;-1:-1:-1;;;3534:31:23;;3562:1:::1;3534:31;::::0;::::1;1561:51:47::0;1534:18;;3534:31:23::1;;;;;;;;3485:91;3585:28;3604:8;3585:18;:28::i;:::-;3405:215:::0;:::o;2658:162::-;966:10:25;2717:7:23;1313:22;2570:8;-1:-1:-1;;;;;2570:8:23;;2441:144;2717:7;-1:-1:-1;;;;;2717:23:23;;2713:101;;2763:40;;-1:-1:-1;;;2763:40:23;;966:10:25;2763:40:23;;;1561:51:47;1534:18;;2763:40:23;1415:203:47;1847:127:23;6931:20:24;:18;:20::i;:::-;1929:38:23::1;1954:12;1929:24;:38::i;3774:248::-:0;1313:22;3923:8;;-1:-1:-1;;;;;;3941:19:23;;-1:-1:-1;;;;;3941:19:23;;;;;;;;3975:40;;3923:8;;;;;3975:40;;3847:24;;3975:40;3837:185;;3774:248;:::o;7084:141:24:-;8870:21;8560:40;-1:-1:-1;;;8560:40:24;;;;7146:73;;7191:17;;-1:-1:-1;;;7191:17:24;;;;;;;;;;;1980:235:23;6931:20:24;:18;:20::i;14:173:47:-;82:20;;-1:-1:-1;;;;;131:31:47;;121:42;;111:70;;177:1;174;167:12;111:70;14:173;;;:::o;192:186::-;251:6;304:2;292:9;283:7;279:23;275:32;272:52;;;320:1;317;310:12;272:52;343:29;362:9;343:29;:::i;:::-;333:39;192:186;-1:-1:-1;;;192:186:47:o;383:374::-;460:6;468;476;529:2;517:9;508:7;504:23;500:32;497:52;;;545:1;542;535:12;497:52;568:29;587:9;568:29;:::i;:::-;558:39;;616:38;650:2;639:9;635:18;616:38;:::i;:::-;383:374;;606:48;;-1:-1:-1;;;723:2:47;708:18;;;;695:32;;383:374::o;762:466::-;839:6;847;855;908:2;896:9;887:7;883:23;879:32;876:52;;;924:1;921;914:12;876:52;-1:-1:-1;;969:23:47;;;1089:2;1074:18;;1061:32;;-1:-1:-1;1192:2:47;1177:18;;;1164:32;;762:466;-1:-1:-1;762:466:47:o;2029:226::-;2088:6;2141:2;2129:9;2120:7;2116:23;2112:32;2109:52;;;2157:1;2154;2147:12;2109:52;-1:-1:-1;2202:23:47;;2029:226;-1:-1:-1;2029:226:47:o;2260:300::-;2328:6;2336;2389:2;2377:9;2368:7;2364:23;2360:32;2357:52;;;2405:1;2402;2395:12;2357:52;2428:29;2447:9;2428:29;:::i;:::-;2418:39;2526:2;2511:18;;;;2498:32;;-1:-1:-1;;;2260:300:47:o;2565:346::-;2633:6;2641;2694:2;2682:9;2673:7;2669:23;2665:32;2662:52;;;2710:1;2707;2700:12;2662:52;-1:-1:-1;;2755:23:47;;;2875:2;2860:18;;;2847:32;;-1:-1:-1;2565:346:47:o;2916:127::-;2977:10;2972:3;2968:20;2965:1;2958:31;3008:4;3005:1;2998:15;3032:4;3029:1;3022:15;3048:730;3351:25;;;-1:-1:-1;;;;;3412:32:47;;3407:2;3392:18;;3385:60;3476:2;3461:18;;3454:34;;;3519:2;3504:18;;3497:34;;;3562:3;3547:19;;3540:35;;;3338:3;3323:19;;3605:1;3594:13;;3584:144;;3650:10;3645:3;3641:20;3638:1;3631:31;3685:4;3682:1;3675:15;3713:4;3710:1;3703:15;3584:144;3765:6;3759:3;3748:9;3744:19;3737:35;3048:730;;;;;;;;;:::o;3997:127::-;4058:10;4053:3;4049:20;4046:1;4039:31;4089:4;4086:1;4079:15;4113:4;4110:1;4103:15;4129:125;4194:9;;;4215:10;;;4212:36;;;4228:18;;:::i;:::-;4129:125;;;;:::o;4635:277::-;4702:6;4755:2;4743:9;4734:7;4730:23;4726:32;4723:52;;;4771:1;4768;4761:12;4723:52;4803:9;4797:16;4856:5;4849:13;4842:21;4835:5;4832:32;4822:60;;4878:1;4875;4868:12;4917:135;4956:3;4977:17;;;4974:43;;4997:18;;:::i;:::-;-1:-1:-1;5044:1:47;5033:13;;4917:135::o;5943:128::-;6010:9;;;6031:11;;;6028:37;;;6045:18;;:::i","linkReferences":{}},"methodIdentifiers":{"bridgeTransfers(bytes32)":"e0d9cbc4","completeBridgeTransfer(bytes32,bytes32)":"9f8f8799","counterpartyAddress()":"1f92c08e","initialize(address,address,uint256)":"1794bb3c","initiateBridgeTransfer(uint256,bytes32,bytes32)":"1a8849a4","initiatorTimeLockDuration()":"2b3948bb","owner()":"8da5cb5b","poolBalance()":"96365d44","refundBridgeTransfer(bytes32)":"57808020","renounceOwnership()":"715018a6","setCounterpartyAddress(address)":"001a153e","transferOwnership(address)":"f2fde38b","weth()":"3fc8cef3","withdrawWETH(address,uint256)":"93113b5c"},"rawMetadata":"{\"compiler\":{\"version\":\"0.8.26+commit.8a97fa7a\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"inputs\":[],\"name\":\"BridgeTransferHasBeenCompleted\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"BridgeTransferInvalid\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"BridgeTransferStateNotInitialized\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InsufficientWethBalance\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidInitialization\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidSecret\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"NotInitializing\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"}],\"name\":\"OwnableInvalidOwner\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"}],\"name\":\"OwnableUnauthorizedAccount\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"TimeLockExpired\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"TimeLockNotExpired\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"Unauthorized\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"WETHTransferFailed\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"ZeroAddress\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"ZeroAmount\",\"type\":\"error\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"_bridgeTransferId\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"bytes32\",\"name\":\"pre_image\",\"type\":\"bytes32\"}],\"name\":\"BridgeTransferCompleted\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"_bridgeTransferId\",\"type\":\"bytes32\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"_originator\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"_recipient\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"bytes32\",\"name\":\"_hashLock\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"_timeLock\",\"type\":\"uint256\"}],\"name\":\"BridgeTransferInitiated\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"_bridgeTransferId\",\"type\":\"bytes32\"}],\"name\":\"BridgeTransferRefunded\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint64\",\"name\":\"version\",\"type\":\"uint64\"}],\"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\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"name\":\"bridgeTransfers\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"},{\"internalType\":\"address\",\"name\":\"originator\",\"type\":\"address\"},{\"internalType\":\"bytes32\",\"name\":\"recipient\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"hashLock\",\"type\":\"bytes32\"},{\"internalType\":\"uint256\",\"name\":\"timeLock\",\"type\":\"uint256\"},{\"internalType\":\"enum AtomicBridgeInitiator.MessageState\",\"name\":\"state\",\"type\":\"uint8\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"bridgeTransferId\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"preImage\",\"type\":\"bytes32\"}],\"name\":\"completeBridgeTransfer\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"counterpartyAddress\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_weth\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"_timeLockDuration\",\"type\":\"uint256\"}],\"name\":\"initialize\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"wethAmount\",\"type\":\"uint256\"},{\"internalType\":\"bytes32\",\"name\":\"recipient\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"hashLock\",\"type\":\"bytes32\"}],\"name\":\"initiateBridgeTransfer\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"bridgeTransferId\",\"type\":\"bytes32\"}],\"stateMutability\":\"payable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"initiatorTimeLockDuration\",\"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\":\"poolBalance\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"bridgeTransferId\",\"type\":\"bytes32\"}],\"name\":\"refundBridgeTransfer\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"renounceOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_counterpartyAddress\",\"type\":\"address\"}],\"name\":\"setCounterpartyAddress\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"transferOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"weth\",\"outputs\":[{\"internalType\":\"contract IWETH9\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"withdrawWETH\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}],\"devdoc\":{\"errors\":{\"InvalidInitialization()\":[{\"details\":\"The contract is already initialized.\"}],\"NotInitializing()\":[{\"details\":\"The contract is not initializing.\"}],\"OwnableInvalidOwner(address)\":[{\"details\":\"The owner is not a valid owner account. (eg. `address(0)`)\"}],\"OwnableUnauthorizedAccount(address)\":[{\"details\":\"The caller account is not authorized to perform an operation.\"}]},\"events\":{\"Initialized(uint64)\":{\"details\":\"Triggered when the contract has been initialized or reinitialized.\"}},\"kind\":\"dev\",\"methods\":{\"owner()\":{\"details\":\"Returns the address of the current owner.\"},\"renounceOwnership()\":{\"details\":\"Leaves the contract without owner. It will not be possible to call `onlyOwner` functions. Can only be called by the current owner. NOTE: Renouncing ownership will leave the contract without an owner, thereby disabling any functionality that is only available to the owner.\"},\"transferOwnership(address)\":{\"details\":\"Transfers ownership of the contract to a new account (`newOwner`). Can only be called by the current owner.\"}},\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{},\"version\":1}},\"settings\":{\"compilationTarget\":{\"src/AtomicBridgeInitiator.sol\":\"AtomicBridgeInitiator\"},\"evmVersion\":\"cancun\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"ipfs\"},\"optimizer\":{\"enabled\":true,\"runs\":200},\"remappings\":[\":@openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/\",\":@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/\",\":ds-test/=lib/openzeppelin-contracts/lib/forge-std/lib/ds-test/src/\",\":erc4626-tests/=lib/openzeppelin-contracts/lib/erc4626-tests/\",\":forge-std/=lib/forge-std/src/\",\":openzeppelin-contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/\",\":openzeppelin-contracts/=lib/openzeppelin-contracts/\"]},\"sources\":{\"lib/openzeppelin-contracts-upgradeable/contracts/access/OwnableUpgradeable.sol\":{\"keccak256\":\"0xc163fcf9bb10138631a9ba5564df1fa25db9adff73bd9ee868a8ae1858fe093a\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://9706d43a0124053d9880f6e31a59f31bc0a6a3dc1acd66ce0a16e1111658c5f6\",\"dweb:/ipfs/QmUFmfowzkRwGtDu36cXV9SPTBHJ3n7dG9xQiK5B28jTf2\"]},\"lib/openzeppelin-contracts-upgradeable/contracts/proxy/utils/Initializable.sol\":{\"keccak256\":\"0x631188737069917d2f909d29ce62c4d48611d326686ba6683e26b72a23bfac0b\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://7a61054ae84cd6c4d04c0c4450ba1d6de41e27e0a2c4f1bcdf58f796b401c609\",\"dweb:/ipfs/QmUvtdp7X1mRVyC3CsHrtPbgoqWaXHp3S1ZR24tpAQYJWM\"]},\"lib/openzeppelin-contracts-upgradeable/contracts/utils/ContextUpgradeable.sol\":{\"keccak256\":\"0xdbef5f0c787055227243a7318ef74c8a5a1108ca3a07f2b3a00ef67769e1e397\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://08e39f23d5b4692f9a40803e53a8156b72b4c1f9902a88cd65ba964db103dab9\",\"dweb:/ipfs/QmPKn6EYDgpga7KtpkA8wV2yJCYGMtc9K4LkJfhKX2RVSV\"]},\"lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol\":{\"keccak256\":\"0xee2337af2dc162a973b4be6d3f7c16f06298259e0af48c5470d2839bfa8a22f4\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://30c476b4b2f405c1bb3f0bae15b006d129c80f1bfd9d0f2038160a3bb9745009\",\"dweb:/ipfs/Qmb3VcuDufv6xbHeVgksC4tHpc5gKYVqBEwjEXW72XzSvN\"]},\"src/AtomicBridgeInitiator.sol\":{\"keccak256\":\"0xa331acc393bc52e014ca46b18b5b3127075c995509fb2b898415087ea3276c32\",\"urls\":[\"bzz-raw://e7f05a3dc5fddae0daf8617027f2704493d0214c95e80888a2f40ddbbc3c85ab\",\"dweb:/ipfs/Qmc9VjwVQ9aNVC5S7Vfsh1pmUbCDQBiXiqJ3JcbEfaZ5wd\"]},\"src/IAtomicBridgeInitiator.sol\":{\"keccak256\":\"0x8397d0de470d4fe4f52ae03292085532b2d1681b5bd332992d303d3e630047be\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://4c8826a0ee54c30746ddb91e35a7ef9c0e33acb68c19a0424a05237b3b02753f\",\"dweb:/ipfs/QmPrPd8hpdmiXryYYSycChgrnHwsaTgHp4xJL3wLT3mRmz\"]},\"src/IWETH9.sol\":{\"keccak256\":\"0xff29528c0f48eaad5156c8d66ccd8f754037bba234434807db302e604db9f9a5\",\"urls\":[\"bzz-raw://a7322b029eb6cd2bbf204fb879947a91aad09bca79373583e5c5b7a2630a6c6b\",\"dweb:/ipfs/QmfLWmtiEhdTiD5kFNruHiMYs4dY5joGgJQ4HU2wCTHF6E\"]}},\"version\":1}","metadata":{"compiler":{"version":"0.8.26+commit.8a97fa7a"},"language":"Solidity","output":{"abi":[{"inputs":[],"type":"error","name":"BridgeTransferHasBeenCompleted"},{"inputs":[],"type":"error","name":"BridgeTransferInvalid"},{"inputs":[],"type":"error","name":"BridgeTransferStateNotInitialized"},{"inputs":[],"type":"error","name":"InsufficientWethBalance"},{"inputs":[],"type":"error","name":"InvalidInitialization"},{"inputs":[],"type":"error","name":"InvalidSecret"},{"inputs":[],"type":"error","name":"NotInitializing"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"type":"error","name":"OwnableInvalidOwner"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"type":"error","name":"OwnableUnauthorizedAccount"},{"inputs":[],"type":"error","name":"TimeLockExpired"},{"inputs":[],"type":"error","name":"TimeLockNotExpired"},{"inputs":[],"type":"error","name":"Unauthorized"},{"inputs":[],"type":"error","name":"WETHTransferFailed"},{"inputs":[],"type":"error","name":"ZeroAddress"},{"inputs":[],"type":"error","name":"ZeroAmount"},{"inputs":[{"internalType":"bytes32","name":"_bridgeTransferId","type":"bytes32","indexed":true},{"internalType":"bytes32","name":"pre_image","type":"bytes32","indexed":false}],"type":"event","name":"BridgeTransferCompleted","anonymous":false},{"inputs":[{"internalType":"bytes32","name":"_bridgeTransferId","type":"bytes32","indexed":true},{"internalType":"address","name":"_originator","type":"address","indexed":true},{"internalType":"bytes32","name":"_recipient","type":"bytes32","indexed":true},{"internalType":"uint256","name":"amount","type":"uint256","indexed":false},{"internalType":"bytes32","name":"_hashLock","type":"bytes32","indexed":false},{"internalType":"uint256","name":"_timeLock","type":"uint256","indexed":false}],"type":"event","name":"BridgeTransferInitiated","anonymous":false},{"inputs":[{"internalType":"bytes32","name":"_bridgeTransferId","type":"bytes32","indexed":true}],"type":"event","name":"BridgeTransferRefunded","anonymous":false},{"inputs":[{"internalType":"uint64","name":"version","type":"uint64","indexed":false}],"type":"event","name":"Initialized","anonymous":false},{"inputs":[{"internalType":"address","name":"previousOwner","type":"address","indexed":true},{"internalType":"address","name":"newOwner","type":"address","indexed":true}],"type":"event","name":"OwnershipTransferred","anonymous":false},{"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function","name":"bridgeTransfers","outputs":[{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"address","name":"originator","type":"address"},{"internalType":"bytes32","name":"recipient","type":"bytes32"},{"internalType":"bytes32","name":"hashLock","type":"bytes32"},{"internalType":"uint256","name":"timeLock","type":"uint256"},{"internalType":"enum AtomicBridgeInitiator.MessageState","name":"state","type":"uint8"}]},{"inputs":[{"internalType":"bytes32","name":"bridgeTransferId","type":"bytes32"},{"internalType":"bytes32","name":"preImage","type":"bytes32"}],"stateMutability":"nonpayable","type":"function","name":"completeBridgeTransfer"},{"inputs":[],"stateMutability":"view","type":"function","name":"counterpartyAddress","outputs":[{"internalType":"address","name":"","type":"address"}]},{"inputs":[{"internalType":"address","name":"_weth","type":"address"},{"internalType":"address","name":"owner","type":"address"},{"internalType":"uint256","name":"_timeLockDuration","type":"uint256"}],"stateMutability":"nonpayable","type":"function","name":"initialize"},{"inputs":[{"internalType":"uint256","name":"wethAmount","type":"uint256"},{"internalType":"bytes32","name":"recipient","type":"bytes32"},{"internalType":"bytes32","name":"hashLock","type":"bytes32"}],"stateMutability":"payable","type":"function","name":"initiateBridgeTransfer","outputs":[{"internalType":"bytes32","name":"bridgeTransferId","type":"bytes32"}]},{"inputs":[],"stateMutability":"view","type":"function","name":"initiatorTimeLockDuration","outputs":[{"internalType":"uint256","name":"","type":"uint256"}]},{"inputs":[],"stateMutability":"view","type":"function","name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}]},{"inputs":[],"stateMutability":"view","type":"function","name":"poolBalance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}]},{"inputs":[{"internalType":"bytes32","name":"bridgeTransferId","type":"bytes32"}],"stateMutability":"nonpayable","type":"function","name":"refundBridgeTransfer"},{"inputs":[],"stateMutability":"nonpayable","type":"function","name":"renounceOwnership"},{"inputs":[{"internalType":"address","name":"_counterpartyAddress","type":"address"}],"stateMutability":"nonpayable","type":"function","name":"setCounterpartyAddress"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"stateMutability":"nonpayable","type":"function","name":"transferOwnership"},{"inputs":[],"stateMutability":"view","type":"function","name":"weth","outputs":[{"internalType":"contract IWETH9","name":"","type":"address"}]},{"inputs":[{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"stateMutability":"nonpayable","type":"function","name":"withdrawWETH"}],"devdoc":{"kind":"dev","methods":{"owner()":{"details":"Returns the address of the current owner."},"renounceOwnership()":{"details":"Leaves the contract without owner. It will not be possible to call `onlyOwner` functions. Can only be called by the current owner. NOTE: Renouncing ownership will leave the contract without an owner, thereby disabling any functionality that is only available to the owner."},"transferOwnership(address)":{"details":"Transfers ownership of the contract to a new account (`newOwner`). Can only be called by the current owner."}},"version":1},"userdoc":{"kind":"user","methods":{},"version":1}},"settings":{"remappings":["@openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/","@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/","ds-test/=lib/openzeppelin-contracts/lib/forge-std/lib/ds-test/src/","erc4626-tests/=lib/openzeppelin-contracts/lib/erc4626-tests/","forge-std/=lib/forge-std/src/","openzeppelin-contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/","openzeppelin-contracts/=lib/openzeppelin-contracts/"],"optimizer":{"enabled":true,"runs":200},"metadata":{"bytecodeHash":"ipfs"},"compilationTarget":{"src/AtomicBridgeInitiator.sol":"AtomicBridgeInitiator"},"evmVersion":"cancun","libraries":{}},"sources":{"lib/openzeppelin-contracts-upgradeable/contracts/access/OwnableUpgradeable.sol":{"keccak256":"0xc163fcf9bb10138631a9ba5564df1fa25db9adff73bd9ee868a8ae1858fe093a","urls":["bzz-raw://9706d43a0124053d9880f6e31a59f31bc0a6a3dc1acd66ce0a16e1111658c5f6","dweb:/ipfs/QmUFmfowzkRwGtDu36cXV9SPTBHJ3n7dG9xQiK5B28jTf2"],"license":"MIT"},"lib/openzeppelin-contracts-upgradeable/contracts/proxy/utils/Initializable.sol":{"keccak256":"0x631188737069917d2f909d29ce62c4d48611d326686ba6683e26b72a23bfac0b","urls":["bzz-raw://7a61054ae84cd6c4d04c0c4450ba1d6de41e27e0a2c4f1bcdf58f796b401c609","dweb:/ipfs/QmUvtdp7X1mRVyC3CsHrtPbgoqWaXHp3S1ZR24tpAQYJWM"],"license":"MIT"},"lib/openzeppelin-contracts-upgradeable/contracts/utils/ContextUpgradeable.sol":{"keccak256":"0xdbef5f0c787055227243a7318ef74c8a5a1108ca3a07f2b3a00ef67769e1e397","urls":["bzz-raw://08e39f23d5b4692f9a40803e53a8156b72b4c1f9902a88cd65ba964db103dab9","dweb:/ipfs/QmPKn6EYDgpga7KtpkA8wV2yJCYGMtc9K4LkJfhKX2RVSV"],"license":"MIT"},"lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol":{"keccak256":"0xee2337af2dc162a973b4be6d3f7c16f06298259e0af48c5470d2839bfa8a22f4","urls":["bzz-raw://30c476b4b2f405c1bb3f0bae15b006d129c80f1bfd9d0f2038160a3bb9745009","dweb:/ipfs/Qmb3VcuDufv6xbHeVgksC4tHpc5gKYVqBEwjEXW72XzSvN"],"license":"MIT"},"src/AtomicBridgeInitiator.sol":{"keccak256":"0xa331acc393bc52e014ca46b18b5b3127075c995509fb2b898415087ea3276c32","urls":["bzz-raw://e7f05a3dc5fddae0daf8617027f2704493d0214c95e80888a2f40ddbbc3c85ab","dweb:/ipfs/Qmc9VjwVQ9aNVC5S7Vfsh1pmUbCDQBiXiqJ3JcbEfaZ5wd"],"license":null},"src/IAtomicBridgeInitiator.sol":{"keccak256":"0x8397d0de470d4fe4f52ae03292085532b2d1681b5bd332992d303d3e630047be","urls":["bzz-raw://4c8826a0ee54c30746ddb91e35a7ef9c0e33acb68c19a0424a05237b3b02753f","dweb:/ipfs/QmPrPd8hpdmiXryYYSycChgrnHwsaTgHp4xJL3wLT3mRmz"],"license":"MIT"},"src/IWETH9.sol":{"keccak256":"0xff29528c0f48eaad5156c8d66ccd8f754037bba234434807db302e604db9f9a5","urls":["bzz-raw://a7322b029eb6cd2bbf204fb879947a91aad09bca79373583e5c5b7a2630a6c6b","dweb:/ipfs/QmfLWmtiEhdTiD5kFNruHiMYs4dY5joGgJQ4HU2wCTHF6E"],"license":null}},"version":1},"id":40} diff --git a/protocol-units/bridge/chains/ethereum/abis/WETH9.json b/protocol-units/bridge/chains/ethereum/abis/WETH9.json new file mode 100644 index 000000000..1fafad4ea --- /dev/null +++ b/protocol-units/bridge/chains/ethereum/abis/WETH9.json @@ -0,0 +1 @@ +{"abi":[{"type":"receive","stateMutability":"payable"},{"type":"function","name":"allowance","inputs":[{"name":"","type":"address","internalType":"address"},{"name":"","type":"address","internalType":"address"}],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"type":"function","name":"approve","inputs":[{"name":"guy","type":"address","internalType":"address"},{"name":"wad","type":"uint256","internalType":"uint256"}],"outputs":[{"name":"","type":"bool","internalType":"bool"}],"stateMutability":"nonpayable"},{"type":"function","name":"balanceOf","inputs":[{"name":"","type":"address","internalType":"address"}],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"type":"function","name":"decimals","inputs":[],"outputs":[{"name":"","type":"uint8","internalType":"uint8"}],"stateMutability":"view"},{"type":"function","name":"deposit","inputs":[],"outputs":[],"stateMutability":"payable"},{"type":"function","name":"name","inputs":[],"outputs":[{"name":"","type":"string","internalType":"string"}],"stateMutability":"view"},{"type":"function","name":"symbol","inputs":[],"outputs":[{"name":"","type":"string","internalType":"string"}],"stateMutability":"view"},{"type":"function","name":"totalSupply","inputs":[],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"type":"function","name":"transfer","inputs":[{"name":"dst","type":"address","internalType":"address"},{"name":"wad","type":"uint256","internalType":"uint256"}],"outputs":[{"name":"","type":"bool","internalType":"bool"}],"stateMutability":"nonpayable"},{"type":"function","name":"transferFrom","inputs":[{"name":"src","type":"address","internalType":"address"},{"name":"dst","type":"address","internalType":"address"},{"name":"wad","type":"uint256","internalType":"uint256"}],"outputs":[{"name":"","type":"bool","internalType":"bool"}],"stateMutability":"nonpayable"},{"type":"function","name":"withdraw","inputs":[{"name":"wad","type":"uint256","internalType":"uint256"}],"outputs":[],"stateMutability":"nonpayable"},{"type":"event","name":"Approval","inputs":[{"name":"src","type":"address","indexed":true,"internalType":"address"},{"name":"guy","type":"address","indexed":true,"internalType":"address"},{"name":"wad","type":"uint256","indexed":false,"internalType":"uint256"}],"anonymous":false},{"type":"event","name":"Deposit","inputs":[{"name":"dst","type":"address","indexed":true,"internalType":"address"},{"name":"wad","type":"uint256","indexed":false,"internalType":"uint256"}],"anonymous":false},{"type":"event","name":"Transfer","inputs":[{"name":"src","type":"address","indexed":true,"internalType":"address"},{"name":"dst","type":"address","indexed":true,"internalType":"address"},{"name":"wad","type":"uint256","indexed":false,"internalType":"uint256"}],"anonymous":false},{"type":"event","name":"Withdrawal","inputs":[{"name":"src","type":"address","indexed":true,"internalType":"address"},{"name":"wad","type":"uint256","indexed":false,"internalType":"uint256"}],"anonymous":false}],"bytecode":{"object":"0x60c0604052600d60809081526c2bb930b83832b21022ba3432b960991b60a05260009061002c9082610116565b506040805180820190915260048152630ae8aa8960e31b60208201526001906100559082610116565b506002805460ff1916601217905534801561006f57600080fd5b506101d5565b634e487b7160e01b600052604160045260246000fd5b600181811c9082168061009f57607f821691505b6020821081036100bf57634e487b7160e01b600052602260045260246000fd5b50919050565b601f821115610111576000816000526020600020601f850160051c810160208610156100ee5750805b601f850160051c820191505b8181101561010d578281556001016100fa565b5050505b505050565b81516001600160401b0381111561012f5761012f610075565b6101438161013d845461008b565b846100c5565b602080601f83116001811461017857600084156101605750858301515b600019600386901b1c1916600185901b17855561010d565b600085815260208120601f198616915b828110156101a757888601518255948401946001909101908401610188565b50858210156101c55787850151600019600388901b60f8161c191681555b5050505050600190811b01905550565b610870806101e46000396000f3fe6080604052600436106100a05760003560e01c8063313ce56711610064578063313ce5671461016c57806370a082311461019857806395d89b41146101c5578063a9059cbb146101da578063d0e30db0146101fa578063dd62ed3e1461020257600080fd5b806306fdde03146100b4578063095ea7b3146100df57806318160ddd1461010f57806323b872dd1461012c5780632e1a7d4d1461014c57600080fd5b366100af576100ad61023a565b005b600080fd5b3480156100c057600080fd5b506100c9610295565b6040516100d6919061068c565b60405180910390f35b3480156100eb57600080fd5b506100ff6100fa3660046106f7565b610323565b60405190151581526020016100d6565b34801561011b57600080fd5b50475b6040519081526020016100d6565b34801561013857600080fd5b506100ff610147366004610721565b610390565b34801561015857600080fd5b506100ad61016736600461075d565b61058b565b34801561017857600080fd5b506002546101869060ff1681565b60405160ff90911681526020016100d6565b3480156101a457600080fd5b5061011e6101b3366004610776565b60036020526000908152604090205481565b3480156101d157600080fd5b506100c961066b565b3480156101e657600080fd5b506100ff6101f53660046106f7565b610678565b6100ad61023a565b34801561020e57600080fd5b5061011e61021d366004610791565b600460209081526000928352604080842090915290825290205481565b33600090815260036020526040812080543492906102599084906107da565b909155505060405134815233907fe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c9060200160405180910390a2565b600080546102a2906107ed565b80601f01602080910402602001604051908101604052809291908181526020018280546102ce906107ed565b801561031b5780601f106102f05761010080835404028352916020019161031b565b820191906000526020600020905b8154815290600101906020018083116102fe57829003601f168201915b505050505081565b3360008181526004602090815260408083206001600160a01b038716808552925280832085905551919290917f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9259061037e9086815260200190565b60405180910390a35060015b92915050565b6001600160a01b0383166000908152600360205260408120548211156103f45760405162461bcd60e51b8152602060048201526014602482015273496e73756666696369656e742062616c616e636560601b60448201526064015b60405180910390fd5b6001600160a01b038416331480159061043257506001600160a01b038416600090815260046020908152604080832033845290915290205460001914155b156104d8576001600160a01b038416600090815260046020908152604080832033845290915290205482111561049f5760405162461bcd60e51b8152602060048201526012602482015271105b1b1bddd85b98d948195e18d95959195960721b60448201526064016103eb565b6001600160a01b0384166000908152600460209081526040808320338452909152812080548492906104d2908490610827565b90915550505b6001600160a01b03841660009081526003602052604081208054849290610500908490610827565b90915550506001600160a01b0383166000908152600360205260408120805484929061052d9084906107da565b92505081905550826001600160a01b0316846001600160a01b03167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef8460405161057991815260200190565b60405180910390a35060019392505050565b336000908152600360205260409020548111156105e15760405162461bcd60e51b8152602060048201526014602482015273496e73756666696369656e742062616c616e636560601b60448201526064016103eb565b3360009081526003602052604081208054839290610600908490610827565b9091555050604051339082156108fc029083906000818181858888f19350505050158015610632573d6000803e3d6000fd5b5060405181815233907f7fcf532c15f0a6db0bd6d0e038bea71d30d808c7d98cb3bf7268a95bf5081b659060200160405180910390a250565b600180546102a2906107ed565b6000610685338484610390565b9392505050565b60006020808352835180602085015260005b818110156106ba5785810183015185820160400152820161069e565b506000604082860101526040601f19601f8301168501019250505092915050565b80356001600160a01b03811681146106f257600080fd5b919050565b6000806040838503121561070a57600080fd5b610713836106db565b946020939093013593505050565b60008060006060848603121561073657600080fd5b61073f846106db565b925061074d602085016106db565b9150604084013590509250925092565b60006020828403121561076f57600080fd5b5035919050565b60006020828403121561078857600080fd5b610685826106db565b600080604083850312156107a457600080fd5b6107ad836106db565b91506107bb602084016106db565b90509250929050565b634e487b7160e01b600052601160045260246000fd5b8082018082111561038a5761038a6107c4565b600181811c9082168061080157607f821691505b60208210810361082157634e487b7160e01b600052602260045260246000fd5b50919050565b8181038181111561038a5761038a6107c456fea2646970667358221220a68f540e30adfc98f1052daaf184851217473dfab10939c4e81666787666250c64736f6c63430008170033","sourceMap":"733:40:39:-:0;712:1895;733:40;;712:1895;733:40;;;-1:-1:-1;;;733:40:39;;-1:-1:-1;;733:40:39;;-1:-1:-1;733:40:39;:::i;:::-;-1:-1:-1;779:31:39;;;;;;;;;;;;-1:-1:-1;;;779:31:39;;;;;;;;;;:::i;:::-;-1:-1:-1;816:27:39;;;-1:-1:-1;;816:27:39;841:2;816:27;;;712:1895;;;;;;;;;;;;14:127:41;75:10;70:3;66:20;63:1;56:31;106:4;103:1;96:15;130:4;127:1;120:15;146:380;225:1;221:12;;;;268;;;289:61;;343:4;335:6;331:17;321:27;;289:61;396:2;388:6;385:14;365:18;362:38;359:161;;442:10;437:3;433:20;430:1;423:31;477:4;474:1;467:15;505:4;502:1;495:15;359:161;;146:380;;;:::o;657:543::-;759:2;754:3;751:11;748:446;;;795:1;819:5;816:1;809:16;863:4;860:1;850:18;933:2;921:10;917:19;914:1;910:27;904:4;900:38;969:4;957:10;954:20;951:47;;;-1:-1:-1;992:4:41;951:47;1047:2;1042:3;1038:12;1035:1;1031:20;1025:4;1021:31;1011:41;;1102:82;1120:2;1113:5;1110:13;1102:82;;;1165:17;;;1146:1;1135:13;1102:82;;;1106:3;;;748:446;657:543;;;:::o;1376:1345::-;1496:10;;-1:-1:-1;;;;;1518:30:41;;1515:56;;;1551:18;;:::i;:::-;1580:97;1670:6;1630:38;1662:4;1656:11;1630:38;:::i;:::-;1624:4;1580:97;:::i;:::-;1732:4;;1789:2;1778:14;;1806:1;1801:663;;;;2508:1;2525:6;2522:89;;;-1:-1:-1;2577:19:41;;;2571:26;2522:89;-1:-1:-1;;1333:1:41;1329:11;;;1325:24;1321:29;1311:40;1357:1;1353:11;;;1308:57;2624:81;;1771:944;;1801:663;604:1;597:14;;;641:4;628:18;;-1:-1:-1;;1837:20:41;;;1955:236;1969:7;1966:1;1963:14;1955:236;;;2058:19;;;2052:26;2037:42;;2150:27;;;;2118:1;2106:14;;;;1985:19;;1955:236;;;1959:3;2219:6;2210:7;2207:19;2204:201;;;2280:19;;;2274:26;-1:-1:-1;;2363:1:41;2359:14;;;2375:3;2355:24;2351:37;2347:42;2332:58;2317:74;;2204:201;-1:-1:-1;;;;;2451:1:41;2435:14;;;2431:22;2418:36;;-1:-1:-1;1376:1345:41:o;:::-;712:1895:39;;;;;;","linkReferences":{}},"deployedBytecode":{"object":"0x6080604052600436106100a05760003560e01c8063313ce56711610064578063313ce5671461016c57806370a082311461019857806395d89b41146101c5578063a9059cbb146101da578063d0e30db0146101fa578063dd62ed3e1461020257600080fd5b806306fdde03146100b4578063095ea7b3146100df57806318160ddd1461010f57806323b872dd1461012c5780632e1a7d4d1461014c57600080fd5b366100af576100ad61023a565b005b600080fd5b3480156100c057600080fd5b506100c9610295565b6040516100d6919061068c565b60405180910390f35b3480156100eb57600080fd5b506100ff6100fa3660046106f7565b610323565b60405190151581526020016100d6565b34801561011b57600080fd5b50475b6040519081526020016100d6565b34801561013857600080fd5b506100ff610147366004610721565b610390565b34801561015857600080fd5b506100ad61016736600461075d565b61058b565b34801561017857600080fd5b506002546101869060ff1681565b60405160ff90911681526020016100d6565b3480156101a457600080fd5b5061011e6101b3366004610776565b60036020526000908152604090205481565b3480156101d157600080fd5b506100c961066b565b3480156101e657600080fd5b506100ff6101f53660046106f7565b610678565b6100ad61023a565b34801561020e57600080fd5b5061011e61021d366004610791565b600460209081526000928352604080842090915290825290205481565b33600090815260036020526040812080543492906102599084906107da565b909155505060405134815233907fe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c9060200160405180910390a2565b600080546102a2906107ed565b80601f01602080910402602001604051908101604052809291908181526020018280546102ce906107ed565b801561031b5780601f106102f05761010080835404028352916020019161031b565b820191906000526020600020905b8154815290600101906020018083116102fe57829003601f168201915b505050505081565b3360008181526004602090815260408083206001600160a01b038716808552925280832085905551919290917f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9259061037e9086815260200190565b60405180910390a35060015b92915050565b6001600160a01b0383166000908152600360205260408120548211156103f45760405162461bcd60e51b8152602060048201526014602482015273496e73756666696369656e742062616c616e636560601b60448201526064015b60405180910390fd5b6001600160a01b038416331480159061043257506001600160a01b038416600090815260046020908152604080832033845290915290205460001914155b156104d8576001600160a01b038416600090815260046020908152604080832033845290915290205482111561049f5760405162461bcd60e51b8152602060048201526012602482015271105b1b1bddd85b98d948195e18d95959195960721b60448201526064016103eb565b6001600160a01b0384166000908152600460209081526040808320338452909152812080548492906104d2908490610827565b90915550505b6001600160a01b03841660009081526003602052604081208054849290610500908490610827565b90915550506001600160a01b0383166000908152600360205260408120805484929061052d9084906107da565b92505081905550826001600160a01b0316846001600160a01b03167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef8460405161057991815260200190565b60405180910390a35060019392505050565b336000908152600360205260409020548111156105e15760405162461bcd60e51b8152602060048201526014602482015273496e73756666696369656e742062616c616e636560601b60448201526064016103eb565b3360009081526003602052604081208054839290610600908490610827565b9091555050604051339082156108fc029083906000818181858888f19350505050158015610632573d6000803e3d6000fd5b5060405181815233907f7fcf532c15f0a6db0bd6d0e038bea71d30d808c7d98cb3bf7268a95bf5081b659060200160405180910390a250565b600180546102a2906107ed565b6000610685338484610390565b9392505050565b60006020808352835180602085015260005b818110156106ba5785810183015185820160400152820161069e565b506000604082860101526040601f19601f8301168501019250505092915050565b80356001600160a01b03811681146106f257600080fd5b919050565b6000806040838503121561070a57600080fd5b610713836106db565b946020939093013593505050565b60008060006060848603121561073657600080fd5b61073f846106db565b925061074d602085016106db565b9150604084013590509250925092565b60006020828403121561076f57600080fd5b5035919050565b60006020828403121561078857600080fd5b610685826106db565b600080604083850312156107a457600080fd5b6107ad836106db565b91506107bb602084016106db565b90509250929050565b634e487b7160e01b600052601160045260246000fd5b8082018082111561038a5761038a6107c4565b600181811c9082168061080157607f821691505b60208210810361082157634e487b7160e01b600052602260045260246000fd5b50919050565b8181038181111561038a5761038a6107c456fea2646970667358221220a68f540e30adfc98f1052daaf184851217473dfab10939c4e81666787666250c64736f6c63430008170033","sourceMap":"712:1895:39:-:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;1282:9;:7;:9::i;:::-;712:1895;;;;;733:40;;;;;;;;;;;;;:::i;:::-;;;;;;;:::i;:::-;;;;;;;;1783:177;;;;;;;;;;-1:-1:-1;1783:177:39;;;;;:::i;:::-;;:::i;:::-;;;1169:14:41;;1162:22;1144:41;;1132:2;1117:18;1783:177:39;1004:187:41;1682:95:39;;;;;;;;;;-1:-1:-1;1749:21:39;1682:95;;;1342:25:41;;;1330:2;1315:18;1682:95:39;1196:177:41;2093:512:39;;;;;;;;;;-1:-1:-1;2093:512:39;;;;;:::i;:::-;;:::i;1440:236::-;;;;;;;;;;-1:-1:-1;1440:236:39;;;;;:::i;:::-;;:::i;816:27::-;;;;;;;;;;-1:-1:-1;816:27:39;;;;;;;;;;;2068:4:41;2056:17;;;2038:36;;2026:2;2011:18;816:27:39;1896:184:41;1102:65:39;;;;;;;;;;-1:-1:-1;1102:65:39;;;;;:::i;:::-;;;;;;;;;;;;;;779:31;;;;;;;;;;;;;:::i;1966:121::-;;;;;;;;;;-1:-1:-1;1966:121:39;;;;;:::i;:::-;;:::i;1304:130::-;;;:::i;1173:65::-;;;;;;;;;;-1:-1:-1;1173:65:39;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;1304:130;1358:10;1348:21;;;;:9;:21;;;;;:34;;1373:9;;1348:21;:34;;1373:9;;1348:34;:::i;:::-;;;;-1:-1:-1;;1397:30:39;;1417:9;1342:25:41;;1405:10:39;;1397:30;;1330:2:41;1315:18;1397:30:39;;;;;;;1304:130::o;733:40::-;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::o;1783:177::-;1865:10;1839:4;1855:21;;;:9;:21;;;;;;;;-1:-1:-1;;;;;1855:26:39;;;;;;;;;;:32;;;1902:30;1839:4;;1855:26;;1902:30;;;;1884:3;1342:25:41;;1330:2;1315:18;;1196:177;1902:30:39;;;;;;;;-1:-1:-1;1949:4:39;1783:177;;;;;:::o;2093:512::-;-1:-1:-1;;;;;2211:14:39;;2183:4;2211:14;;;:9;:14;;;;;;:21;-1:-1:-1;2211:21:39;2203:54;;;;-1:-1:-1;;;2203:54:39;;3390:2:41;2203:54:39;;;3372:21:41;3429:2;3409:18;;;3402:30;-1:-1:-1;;;3448:18:41;;;3441:50;3508:18;;2203:54:39;;;;;;;;;-1:-1:-1;;;;;2272:17:39;;2279:10;2272:17;;;;:65;;-1:-1:-1;;;;;;2293:14:39;;;;;;:9;:14;;;;;;;;2308:10;2293:26;;;;;;;;-1:-1:-1;;2293:44:39;;2272:65;2268:207;;;-1:-1:-1;;;;;2361:14:39;;;;;;:9;:14;;;;;;;;2376:10;2361:26;;;;;;;;:33;-1:-1:-1;2361:33:39;2353:64;;;;-1:-1:-1;;;2353:64:39;;3739:2:41;2353:64:39;;;3721:21:41;3778:2;3758:18;;;3751:30;-1:-1:-1;;;3797:18:41;;;3790:48;3855:18;;2353:64:39;3537:342:41;2353:64:39;-1:-1:-1;;;;;2431:14:39;;;;;;:9;:14;;;;;;;;2446:10;2431:26;;;;;;;:33;;2461:3;;2431:14;:33;;2461:3;;2431:33;:::i;:::-;;;;-1:-1:-1;;2268:207:39;-1:-1:-1;;;;;2485:14:39;;;;;;:9;:14;;;;;:21;;2503:3;;2485:14;:21;;2503:3;;2485:21;:::i;:::-;;;;-1:-1:-1;;;;;;;2516:14:39;;;;;;:9;:14;;;;;:21;;2534:3;;2516:14;:21;;2534:3;;2516:21;:::i;:::-;;;;;;;;2567:3;-1:-1:-1;;;;;2553:23:39;2562:3;-1:-1:-1;;;;;2553:23:39;;2572:3;2553:23;;;;1342:25:41;;1330:2;1315:18;;1196:177;2553:23:39;;;;;;;;-1:-1:-1;2594:4:39;2093:512;;;;;:::o;1440:236::-;1503:10;1493:21;;;;:9;:21;;;;;;:28;-1:-1:-1;1493:28:39;1485:61;;;;-1:-1:-1;;;1485:61:39;;3390:2:41;1485:61:39;;;3372:21:41;3429:2;3409:18;;;3402:30;-1:-1:-1;;;3448:18:41;;;3441:50;3508:18;;1485:61:39;3188:344:41;1485:61:39;1566:10;1556:21;;;;:9;:21;;;;;:28;;1581:3;;1556:21;:28;;1581:3;;1556:28;:::i;:::-;;;;-1:-1:-1;;1594:33:39;;1602:10;;1594:33;;;;;1623:3;;1594:33;;;;1623:3;1602:10;1594:33;;;;;;;;;;;;;;;;;;;;-1:-1:-1;1642:27:39;;1342:25:41;;;1653:10:39;;1642:27;;1330:2:41;1315:18;1642:27:39;;;;;;;1440:236;:::o;779:31::-;;;;;;;:::i;1966:121::-;2023:4;2046:34;2059:10;2071:3;2076;2046:12;:34::i;:::-;2039:41;1966:121;-1:-1:-1;;;1966:121:39:o;14:548:41:-;126:4;155:2;184;173:9;166:21;216:6;210:13;259:6;254:2;243:9;239:18;232:34;284:1;294:140;308:6;305:1;302:13;294:140;;;403:14;;;399:23;;393:30;369:17;;;388:2;365:26;358:66;323:10;;294:140;;;298:3;483:1;478:2;469:6;458:9;454:22;450:31;443:42;553:2;546;542:7;537:2;529:6;525:15;521:29;510:9;506:45;502:54;494:62;;;;14:548;;;;:::o;567:173::-;635:20;;-1:-1:-1;;;;;684:31:41;;674:42;;664:70;;730:1;727;720:12;664:70;567:173;;;:::o;745:254::-;813:6;821;874:2;862:9;853:7;849:23;845:32;842:52;;;890:1;887;880:12;842:52;913:29;932:9;913:29;:::i;:::-;903:39;989:2;974:18;;;;961:32;;-1:-1:-1;;;745:254:41:o;1378:328::-;1455:6;1463;1471;1524:2;1512:9;1503:7;1499:23;1495:32;1492:52;;;1540:1;1537;1530:12;1492:52;1563:29;1582:9;1563:29;:::i;:::-;1553:39;;1611:38;1645:2;1634:9;1630:18;1611:38;:::i;:::-;1601:48;;1696:2;1685:9;1681:18;1668:32;1658:42;;1378:328;;;;;:::o;1711:180::-;1770:6;1823:2;1811:9;1802:7;1798:23;1794:32;1791:52;;;1839:1;1836;1829:12;1791:52;-1:-1:-1;1862:23:41;;1711:180;-1:-1:-1;1711:180:41:o;2085:186::-;2144:6;2197:2;2185:9;2176:7;2172:23;2168:32;2165:52;;;2213:1;2210;2203:12;2165:52;2236:29;2255:9;2236:29;:::i;2276:260::-;2344:6;2352;2405:2;2393:9;2384:7;2380:23;2376:32;2373:52;;;2421:1;2418;2411:12;2373:52;2444:29;2463:9;2444:29;:::i;:::-;2434:39;;2492:38;2526:2;2515:9;2511:18;2492:38;:::i;:::-;2482:48;;2276:260;;;;;:::o;2541:127::-;2602:10;2597:3;2593:20;2590:1;2583:31;2633:4;2630:1;2623:15;2657:4;2654:1;2647:15;2673:125;2738:9;;;2759:10;;;2756:36;;;2772:18;;:::i;2803:380::-;2882:1;2878:12;;;;2925;;;2946:61;;3000:4;2992:6;2988:17;2978:27;;2946:61;3053:2;3045:6;3042:14;3022:18;3019:38;3016:161;;3099:10;3094:3;3090:20;3087:1;3080:31;3134:4;3131:1;3124:15;3162:4;3159:1;3152:15;3016:161;;2803:380;;;:::o;3884:128::-;3951:9;;;3972:11;;;3969:37;;;3986:18;;:::i","linkReferences":{}},"methodIdentifiers":{"allowance(address,address)":"dd62ed3e","approve(address,uint256)":"095ea7b3","balanceOf(address)":"70a08231","decimals()":"313ce567","deposit()":"d0e30db0","name()":"06fdde03","symbol()":"95d89b41","totalSupply()":"18160ddd","transfer(address,uint256)":"a9059cbb","transferFrom(address,address,uint256)":"23b872dd","withdraw(uint256)":"2e1a7d4d"},"rawMetadata":"{\"compiler\":{\"version\":\"0.8.23+commit.f704f362\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"src\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"guy\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"wad\",\"type\":\"uint256\"}],\"name\":\"Approval\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"dst\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"wad\",\"type\":\"uint256\"}],\"name\":\"Deposit\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"src\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"dst\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"wad\",\"type\":\"uint256\"}],\"name\":\"Transfer\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"src\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"wad\",\"type\":\"uint256\"}],\"name\":\"Withdrawal\",\"type\":\"event\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"name\":\"allowance\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"guy\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"wad\",\"type\":\"uint256\"}],\"name\":\"approve\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"name\":\"balanceOf\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"decimals\",\"outputs\":[{\"internalType\":\"uint8\",\"name\":\"\",\"type\":\"uint8\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"deposit\",\"outputs\":[],\"stateMutability\":\"payable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"name\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"symbol\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"totalSupply\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"dst\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"wad\",\"type\":\"uint256\"}],\"name\":\"transfer\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"src\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"dst\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"wad\",\"type\":\"uint256\"}],\"name\":\"transferFrom\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"wad\",\"type\":\"uint256\"}],\"name\":\"withdraw\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"stateMutability\":\"payable\",\"type\":\"receive\"}],\"devdoc\":{\"kind\":\"dev\",\"methods\":{},\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{},\"version\":1}},\"settings\":{\"compilationTarget\":{\"src/WETH9.sol\":\"WETH9\"},\"evmVersion\":\"paris\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"ipfs\"},\"optimizer\":{\"enabled\":true,\"runs\":200},\"remappings\":[\":@openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/\",\":@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/\",\":ds-test/=lib/openzeppelin-contracts/lib/forge-std/lib/ds-test/src/\",\":erc4626-tests/=lib/openzeppelin-contracts/lib/erc4626-tests/\",\":forge-std/=lib/forge-std/src/\",\":halmos-cheatcodes/=lib/openzeppelin-contracts-upgradeable/lib/halmos-cheatcodes/src/\",\":openzeppelin-contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/\",\":openzeppelin-contracts/=lib/openzeppelin-contracts/\"]},\"sources\":{\"src/WETH9.sol\":{\"keccak256\":\"0xf9caf30bdfdfdb7c09858c39a86ee272a31e69a66d8d03d13a466e009a786bf6\",\"urls\":[\"bzz-raw://1528bbb5eb679a5b20ee72079486d904d1236b3cc4a09a4955798fdd88dc16a4\",\"dweb:/ipfs/QmWZW4PQFKiSf854qsGmUVVZ2mPrAxRFbQbfA5o1mYEDnV\"]}},\"version\":1}","metadata":{"compiler":{"version":"0.8.23+commit.f704f362"},"language":"Solidity","output":{"abi":[{"inputs":[{"internalType":"address","name":"src","type":"address","indexed":true},{"internalType":"address","name":"guy","type":"address","indexed":true},{"internalType":"uint256","name":"wad","type":"uint256","indexed":false}],"type":"event","name":"Approval","anonymous":false},{"inputs":[{"internalType":"address","name":"dst","type":"address","indexed":true},{"internalType":"uint256","name":"wad","type":"uint256","indexed":false}],"type":"event","name":"Deposit","anonymous":false},{"inputs":[{"internalType":"address","name":"src","type":"address","indexed":true},{"internalType":"address","name":"dst","type":"address","indexed":true},{"internalType":"uint256","name":"wad","type":"uint256","indexed":false}],"type":"event","name":"Transfer","anonymous":false},{"inputs":[{"internalType":"address","name":"src","type":"address","indexed":true},{"internalType":"uint256","name":"wad","type":"uint256","indexed":false}],"type":"event","name":"Withdrawal","anonymous":false},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function","name":"allowance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}]},{"inputs":[{"internalType":"address","name":"guy","type":"address"},{"internalType":"uint256","name":"wad","type":"uint256"}],"stateMutability":"nonpayable","type":"function","name":"approve","outputs":[{"internalType":"bool","name":"","type":"bool"}]},{"inputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function","name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}]},{"inputs":[],"stateMutability":"view","type":"function","name":"decimals","outputs":[{"internalType":"uint8","name":"","type":"uint8"}]},{"inputs":[],"stateMutability":"payable","type":"function","name":"deposit"},{"inputs":[],"stateMutability":"view","type":"function","name":"name","outputs":[{"internalType":"string","name":"","type":"string"}]},{"inputs":[],"stateMutability":"view","type":"function","name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}]},{"inputs":[],"stateMutability":"view","type":"function","name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}]},{"inputs":[{"internalType":"address","name":"dst","type":"address"},{"internalType":"uint256","name":"wad","type":"uint256"}],"stateMutability":"nonpayable","type":"function","name":"transfer","outputs":[{"internalType":"bool","name":"","type":"bool"}]},{"inputs":[{"internalType":"address","name":"src","type":"address"},{"internalType":"address","name":"dst","type":"address"},{"internalType":"uint256","name":"wad","type":"uint256"}],"stateMutability":"nonpayable","type":"function","name":"transferFrom","outputs":[{"internalType":"bool","name":"","type":"bool"}]},{"inputs":[{"internalType":"uint256","name":"wad","type":"uint256"}],"stateMutability":"nonpayable","type":"function","name":"withdraw"},{"inputs":[],"stateMutability":"payable","type":"receive"}],"devdoc":{"kind":"dev","methods":{},"version":1},"userdoc":{"kind":"user","methods":{},"version":1}},"settings":{"remappings":["@openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/","@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/","ds-test/=lib/openzeppelin-contracts/lib/forge-std/lib/ds-test/src/","erc4626-tests/=lib/openzeppelin-contracts/lib/erc4626-tests/","forge-std/=lib/forge-std/src/","halmos-cheatcodes/=lib/openzeppelin-contracts-upgradeable/lib/halmos-cheatcodes/src/","openzeppelin-contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/","openzeppelin-contracts/=lib/openzeppelin-contracts/"],"optimizer":{"enabled":true,"runs":200},"metadata":{"bytecodeHash":"ipfs"},"compilationTarget":{"src/WETH9.sol":"WETH9"},"evmVersion":"paris","libraries":{}},"sources":{"src/WETH9.sol":{"keccak256":"0xf9caf30bdfdfdb7c09858c39a86ee272a31e69a66d8d03d13a466e009a786bf6","urls":["bzz-raw://1528bbb5eb679a5b20ee72079486d904d1236b3cc4a09a4955798fdd88dc16a4","dweb:/ipfs/QmWZW4PQFKiSf854qsGmUVVZ2mPrAxRFbQbfA5o1mYEDnV"],"license":null}},"version":1},"id":39} diff --git a/protocol-units/bridge/chains/ethereum/src/client.rs b/protocol-units/bridge/chains/ethereum/src/client.rs new file mode 100644 index 000000000..39433700b --- /dev/null +++ b/protocol-units/bridge/chains/ethereum/src/client.rs @@ -0,0 +1,549 @@ +use crate::utils::{calculate_storage_slot, send_transaction, send_transaction_rules}; +use alloy::primitives::{private::serde::Deserialize, Address, FixedBytes, U256}; +use alloy::providers::{Provider, ProviderBuilder, RootProvider}; +use alloy::signers::k256::elliptic_curve::SecretKey; +use alloy::signers::k256::Secp256k1; +use alloy::signers::local::LocalSigner; +use alloy::{ + network::EthereumWallet, + rlp::{RlpDecodable, RlpEncodable}, +}; +use alloy::{pubsub::PubSubFrontend, signers::local::PrivateKeySigner}; +use alloy_rlp::Decodable; +use bridge_shared::bridge_contracts::{ + BridgeContractCounterparty, BridgeContractCounterpartyError, BridgeContractCounterpartyResult, + BridgeContractInitiator, BridgeContractInitiatorError, BridgeContractInitiatorResult, + BridgeContractWETH9Error, BridgeContractWETH9Result, +}; +use bridge_shared::types::{ + Amount, AssetType, BridgeTransferDetails, BridgeTransferId, HashLock, HashLockPreImage, + InitiatorAddress, RecipientAddress, TimeLock, +}; +use serde_with::serde_as; +use std::fmt::{self, Debug}; +use url::Url; + +use crate::types::{ + AlloyProvider, AtomicBridgeCounterparty, AtomicBridgeInitiator, CounterpartyContract, + EthAddress, EthHash, InitiatorContract, WETH9Contract, WETH9, +}; + +const GAS_LIMIT: u128 = 10_000_000_000_000_000; +const RETRIES: u32 = 6; + +impl fmt::Debug for AtomicBridgeInitiator::wethReturn { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + // Assuming the return type is an address, for example: + write!(f, "{:?}", self._0) + } +} + +///Configuration for the Ethereum Bridge Client +#[serde_as] +#[derive(Clone, Debug, Deserialize)] +pub struct Config { + pub rpc_url: Url, + pub ws_url: Url, + #[serde_as(as = "serde_with::DisplayFromStr")] + pub signer_private_key: PrivateKeySigner, + pub initiator_contract: Option
, + pub counterparty_contract: Option
, + pub weth_contract: Option
, + pub gas_limit: u64, +} + +impl Config { + pub fn build_for_test() -> Self { + Config { + rpc_url: "http://localhost:8545".parse().unwrap(), + ws_url: "ws://localhost:8545".parse().unwrap(), + signer_private_key: PrivateKeySigner::random(), + initiator_contract: None, + counterparty_contract: None, + weth_contract: None, + gas_limit: 10_000_000_000, + } + } +} + +#[derive(RlpDecodable, RlpEncodable)] +struct EthBridgeTransferDetails { + pub amount: U256, + pub originator: EthAddress, + pub recipient: [u8; 32], + pub hash_lock: [u8; 32], + pub time_lock: U256, + pub state: u8, +} + +// We need to be able to build the client and deploy the contracts +// using that clients, therfore the `initiator_contract` and `counterparty_contract` +// should be optional, as their values will be unknown at the time of building the client. +// This is true for the integration tests. +#[allow(dead_code)] +#[derive(Clone)] +pub struct EthClient { + rpc_provider: AlloyProvider, + rpc_port: u16, + ws_provider: Option>, + initiator_contract: Option, + counterparty_contract: Option, + weth_contract: Option, + config: Config, +} + +impl EthClient { + pub async fn new(config: impl Into) -> Result { + let config = config.into(); + let rpc_provider = ProviderBuilder::new() + .with_recommended_fillers() + .wallet(EthereumWallet::from(config.signer_private_key.clone())) + .on_builtin(config.rpc_url.as_str()) + .await?; + + // let ws = WsConnect::new(ws_url); + // println!("ws {:?}", ws); + // let ws_provider = ProviderBuilder::new().on_ws(ws).await?; + // println!("ws_provider {:?}", ws_provider); + Ok(EthClient { + rpc_provider, + rpc_port: 8545, + ws_provider: None, + initiator_contract: None, + counterparty_contract: None, + weth_contract: None, + config, + }) + } + + pub fn set_initiator_contract(&mut self, contract: InitiatorContract) { + self.initiator_contract = Some(contract); + } + + pub fn set_counterparty_contract(&mut self, contract: CounterpartyContract) { + self.counterparty_contract = Some(contract); + } + + pub fn set_weth_contract(&mut self, contract: WETH9Contract) { + self.weth_contract = Some(contract); + } + + pub async fn initialize_initiator_contract( + &self, + weth: EthAddress, + owner: EthAddress, + timelock: u64, + ) -> Result<(), anyhow::Error> { + let contract = self.initiator_contract().expect("Initiator contract not set"); + let call = contract.initialize(weth.0, owner.0, U256::from(timelock)); + send_transaction(call.to_owned(), &send_transaction_rules(), RETRIES, GAS_LIMIT) + .await + .expect("Failed to send transaction"); + Ok(()) + } + + pub async fn deposit_weth_and_approve( + &mut self, + _caller: Address, + amount: U256, + ) -> Result<(), anyhow::Error> { + let deposit_weth_signer = self.get_signer_address(); + let contract = self.weth_contract().expect("WETH contract not set"); + let call = contract.deposit().value(amount); + send_transaction(call, &send_transaction_rules(), RETRIES, GAS_LIMIT) + .await + .expect("Failed to deposit eth to weth contract"); + + let approve_call: alloy::contract::CallBuilder<_, &_, _> = + contract.approve(self.initiator_contract_address()?, amount); + let WETH9::balanceOfReturn { _0: _balance } = contract + .balanceOf(deposit_weth_signer) + .call() + .await + .expect("Failed to get balance"); + + send_transaction(approve_call, &send_transaction_rules(), RETRIES, GAS_LIMIT) + .await + .expect("Failed to approve"); + Ok(()) + } + + pub async fn get_block_number(&self) -> Result { + self.rpc_provider + .get_block_number() + .await + .map_err(|e| anyhow::anyhow!("Failed to get block number: {}", e)) + } + + pub fn get_signer_address(&self) -> Address { + self.config.signer_private_key.address() + } + + pub fn set_signer_address(&mut self, key: SecretKey) { + self.config.signer_private_key = LocalSigner::from(key); + } + + pub fn rpc_provider(&self) -> &AlloyProvider { + &self.rpc_provider + } + + pub fn rpc_provider_mut(&mut self) -> &mut AlloyProvider { + &mut self.rpc_provider + } + + pub fn rpc_port(&self) -> u16 { + self.rpc_port + } + + pub async fn get_weth_initiator_contract(&self) -> BridgeContractInitiatorResult<()> { + let contract = + AtomicBridgeInitiator::new(self.initiator_contract_address()?, &self.rpc_provider); + let AtomicBridgeInitiator::wethReturn { _0: address } = + contract.weth().call().await.map_err(|e| { + BridgeContractInitiatorError::GenericError(format!("Failed to get weth: {}", e)) + })?; + println!("weth_return: {:?}", address); + Ok(()) + } + + pub fn initiator_contract_address(&self) -> BridgeContractInitiatorResult
{ + match &self.initiator_contract { + Some(contract) => Ok(contract.address().to_owned()), + None => Err(BridgeContractInitiatorError::GenericError( + "Initiator contract address not set".to_string(), + )), + } + } + + pub fn weth_contract_address(&self) -> BridgeContractWETH9Result
{ + match &self.weth_contract { + Some(contract) => Ok(contract.address().to_owned()), + None => Err(BridgeContractWETH9Error::GenericError( + "WETH9 contract address not set".to_string(), + )), + } + } + + pub fn weth_contract(&self) -> BridgeContractWETH9Result<&WETH9Contract> { + match &self.weth_contract { + Some(contract) => Ok(contract), + None => Err(BridgeContractWETH9Error::GenericError( + "Initiator contract not set".to_string(), + )), + } + } + + pub fn counterparty_contract_address(&self) -> BridgeContractCounterpartyResult
{ + match &self.counterparty_contract { + Some(contract) => Ok(contract.address().to_owned()), + None => Err(BridgeContractCounterpartyError::GenericError( + "Counterparty contract address not set".to_string(), + )), + } + } + + pub fn initiator_contract(&self) -> BridgeContractInitiatorResult<&InitiatorContract> { + match &self.initiator_contract { + Some(contract) => Ok(contract), + None => Err(BridgeContractInitiatorError::GenericError( + "Initiator contract not set".to_string(), + )), + } + } + + pub fn counterparty_contract(&self) -> BridgeContractCounterpartyResult<&CounterpartyContract> { + match &self.counterparty_contract { + Some(contract) => Ok(contract), + None => Err(BridgeContractCounterpartyError::GenericError( + "Counterparty contract not set".to_string(), + )), + } + } +} + +#[async_trait::async_trait] +impl BridgeContractInitiator for EthClient { + type Address = EthAddress; + type Hash = EthHash; + + // `_initiator_address`, or in the contract, `originator` is set + // via the `msg.sender`, which is stored in the `rpc_provider`. + // So `initiator_address` arg is not used here. + async fn initiate_bridge_transfer( + &mut self, + initiator_address: InitiatorAddress, + recipient_address: RecipientAddress>, + hash_lock: HashLock, + amount: Amount, + ) -> BridgeContractInitiatorResult<()> { + let contract = + AtomicBridgeInitiator::new(self.initiator_contract_address()?, &self.rpc_provider); + let recipient_bytes: [u8; 32] = + recipient_address.0.try_into().expect("Recipient address must be 32 bytes"); + let call = contract + .initiateBridgeTransfer( + U256::from(amount.weth()), + FixedBytes(recipient_bytes), + FixedBytes(hash_lock.0), + ) + .value(U256::from(amount.eth())) + .from(initiator_address.0 .0); + + send_transaction(call, &send_transaction_rules(), RETRIES, GAS_LIMIT) + .await + .map_err(|e| { + BridgeContractInitiatorError::GenericError(format!( + "Failed to send transaction: {}", + e + )) + })?; + + Ok(()) + } + + async fn complete_bridge_transfer( + &mut self, + bridge_transfer_id: BridgeTransferId, + pre_image: HashLockPreImage, + ) -> BridgeContractInitiatorResult<()> { + // The Alloy generated type for smart contract`pre_image` arg is `FixedBytes<32>` + // so it must be converted to `[u8; 32]`. + let generic_error = |desc| BridgeContractInitiatorError::GenericError(String::from(desc)); + let pre_image: [u8; 32] = pre_image + .0 + .get(0..32) + .ok_or(generic_error("Could not get required slice from pre-image"))? + .try_into() + .map_err(|_| generic_error("Could not convert pre-image to [u8; 32]"))?; + + let contract = + AtomicBridgeInitiator::new(self.initiator_contract_address()?, &self.rpc_provider); + let call = contract + .completeBridgeTransfer(FixedBytes(bridge_transfer_id.0), FixedBytes(pre_image)); + + send_transaction(call, &send_transaction_rules(), RETRIES, GAS_LIMIT) + .await + .map_err(|e| { + BridgeContractInitiatorError::GenericError(format!( + "Failed to send transaction: {}", + e + )) + })?; + + Ok(()) + } + + async fn refund_bridge_transfer( + &mut self, + bridge_transfer_id: BridgeTransferId, + ) -> BridgeContractInitiatorResult<()> { + let contract = + AtomicBridgeInitiator::new(self.initiator_contract_address()?, &self.rpc_provider); + let call = contract.refundBridgeTransfer(FixedBytes(bridge_transfer_id.0)); + + send_transaction(call, &send_transaction_rules(), RETRIES, GAS_LIMIT) + .await + .map_err(|e| { + BridgeContractInitiatorError::GenericError(format!( + "Failed to send transaction: {}", + e + )) + })?; + + Ok(()) + } + + async fn get_bridge_transfer_details( + &mut self, + bridge_transfer_id: BridgeTransferId, + ) -> BridgeContractInitiatorResult>> { + let generic_error = |desc| BridgeContractInitiatorError::GenericError(String::from(desc)); + + let mapping_slot = U256::from(0); // the mapping is the zeroth slot in the contract + let key = bridge_transfer_id.0; + let storage_slot = calculate_storage_slot(key, mapping_slot); + let storage: U256 = self + .rpc_provider + .get_storage_at(self.initiator_contract_address()?, storage_slot) + .await + .map_err(|_| generic_error("could not find storage"))?; + let storage_bytes = storage.to_be_bytes::<32>(); + + println!("storage_bytes: {:?}", storage_bytes); + let mut storage_slice = &storage_bytes[..]; + let eth_details = EthBridgeTransferDetails::decode(&mut storage_slice) + .map_err(|_| generic_error("could not decode storage"))?; + + Ok(Some(BridgeTransferDetails { + bridge_transfer_id, + initiator_address: InitiatorAddress(eth_details.originator), + recipient_address: RecipientAddress(eth_details.recipient.to_vec()), + hash_lock: HashLock(eth_details.hash_lock), + amount: Amount(AssetType::EthAndWeth((0, eth_details.amount.wrapping_to::()))), + state: eth_details.state, + })) + } +} + +#[async_trait::async_trait] +impl BridgeContractCounterparty for EthClient { + type Address = EthAddress; + type Hash = EthHash; + + async fn lock_bridge_transfer( + &mut self, + bridge_transfer_id: BridgeTransferId, + hash_lock: HashLock, + initiator: InitiatorAddress>, + recipient: RecipientAddress, + amount: Amount, + ) -> BridgeContractCounterpartyResult<()> { + let contract = AtomicBridgeCounterparty::new( + self.counterparty_contract_address()?, + &self.rpc_provider, + ); + let initiator: [u8; 32] = initiator.0.try_into().unwrap(); + let call = contract.lockBridgeTransfer( + FixedBytes(initiator), + FixedBytes(bridge_transfer_id.0), + FixedBytes(hash_lock.0), + recipient.0 .0, + U256::try_from(amount.0) + .map_err(|_| BridgeContractCounterpartyError::ConversionError)?, + ); + + send_transaction(call, &send_transaction_rules(), RETRIES, GAS_LIMIT) + .await + .map_err(|e| { + BridgeContractCounterpartyError::GenericError(format!( + "Failed to send transaction: {}", + e + )) + })?; + + Ok(()) + } + + async fn complete_bridge_transfer( + &mut self, + bridge_transfer_id: BridgeTransferId, + secret: HashLockPreImage, + ) -> BridgeContractCounterpartyResult<()> { + let contract = AtomicBridgeCounterparty::new( + self.counterparty_contract_address()?, + &self.rpc_provider, + ); + let secret: [u8; 32] = secret.0.try_into().unwrap(); + let call = + contract.completeBridgeTransfer(FixedBytes(bridge_transfer_id.0), FixedBytes(secret)); + + send_transaction(call, &send_transaction_rules(), RETRIES, GAS_LIMIT) + .await + .map_err(|e| { + BridgeContractCounterpartyError::GenericError(format!( + "Failed to send transaction: {}", + e + )) + })?; + + Ok(()) + } + + async fn abort_bridge_transfer( + &mut self, + bridge_transfer_id: BridgeTransferId, + ) -> BridgeContractCounterpartyResult<()> { + let contract = AtomicBridgeCounterparty::new( + self.counterparty_contract_address()?, + &self.rpc_provider, + ); + let call = contract.abortBridgeTransfer(FixedBytes(bridge_transfer_id.0)); + + send_transaction(call, &send_transaction_rules(), RETRIES, GAS_LIMIT) + .await + .map_err(|e| { + BridgeContractCounterpartyError::GenericError(format!( + "Failed to send transaction: {}", + e + )) + })?; + + Ok(()) + } + + async fn get_bridge_transfer_details( + &mut self, + bridge_transfer_id: BridgeTransferId, + ) -> BridgeContractCounterpartyResult>> + { + let generic_error = + |desc| BridgeContractCounterpartyError::GenericError(String::from(desc)); + + let mapping_slot = U256::from(1); // the mapping is the 1st slot in the contract + let key = bridge_transfer_id.0; + let storage_slot = calculate_storage_slot(key, mapping_slot); + let storage: U256 = self + .rpc_provider + .get_storage_at(self.counterparty_contract_address()?, storage_slot) + .await + .map_err(|_| generic_error("could not find storage"))?; + let storage_bytes = storage.to_be_bytes::<32>(); + let mut storage_slice = &storage_bytes[..]; + let eth_details = EthBridgeTransferDetails::decode(&mut storage_slice) + .map_err(|_| generic_error("could not decode storage"))?; + + Ok(Some(BridgeTransferDetails { + bridge_transfer_id, + initiator_address: InitiatorAddress(eth_details.originator), + recipient_address: RecipientAddress(eth_details.recipient.to_vec()), + hash_lock: HashLock(eth_details.hash_lock), + amount: Amount(AssetType::EthAndWeth((0, eth_details.amount.wrapping_to::()))), + state: eth_details.state, + })) + } +} + +#[cfg(test)] +fn test_wrapping_to(a: &U256, b: u64) { + assert_eq!(a.wrapping_to::(), b); +} + +#[cfg(test)] +mod tests { + use super::*; + use std::time::{SystemTime, UNIX_EPOCH}; + + #[test] + fn test_wrapping_to_on_eth_details() { + let current_time = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs(); + let eth_details = EthBridgeTransferDetails { + amount: U256::from(10u64.pow(18)), + originator: EthAddress([0; 20].into()), + recipient: [0; 32], + hash_lock: [0; 32], + time_lock: U256::from(current_time + 84600), // 1 day + state: 1, + }; + test_wrapping_to(ð_details.amount, 10u64.pow(18)); + test_wrapping_to(ð_details.time_lock, current_time + 84600); + } + + #[test] + fn fuzz_test_wrapping_to_on_eth_details() { + for _ in 0..100 { + let current_time = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs(); + let additional_time = rand::random::(); + let random_amount = rand::random::(); + let eth_details = EthBridgeTransferDetails { + amount: U256::from(random_amount), + originator: EthAddress([0; 20].into()), + recipient: [0; 32], + hash_lock: [0; 32], + time_lock: U256::from(current_time + additional_time), + state: 1, + }; + test_wrapping_to(ð_details.amount, random_amount); + test_wrapping_to(ð_details.time_lock, current_time + additional_time); + } + } +} diff --git a/protocol-units/bridge/chains/ethereum/src/event_logging.rs b/protocol-units/bridge/chains/ethereum/src/event_logging.rs new file mode 100644 index 000000000..d5d40f22f --- /dev/null +++ b/protocol-units/bridge/chains/ethereum/src/event_logging.rs @@ -0,0 +1,257 @@ +use crate::types::{EthAddress, EventName}; +use crate::EthChainEvent; +use alloy::dyn_abi::EventExt; +use alloy::eips::BlockNumberOrTag; +use alloy::primitives::{address, LogData}; +use alloy::providers::{Provider, ProviderBuilder, RootProvider, WsConnect}; +use alloy::rpc::types::{Filter, Log}; +use alloy::{ + json_abi::{Event, EventParam}, + pubsub::PubSubFrontend, +}; +use bridge_shared::initiator_contract::SmartContractInitiatorEvent; +use bridge_shared::{ + bridge_monitoring::{BridgeContractInitiatorEvent, BridgeContractInitiatorMonitoring}, + types::{ + BridgeTransferDetails, BridgeTransferId, HashLock, InitiatorAddress, RecipientAddress, + }, +}; +use futures::{channel::mpsc::UnboundedReceiver, Stream, StreamExt}; +use std::{pin::Pin, task::Poll}; + +use crate::types::{EthHash, COMPLETED_SELECT, INITIATED_SELECT, REFUNDED_SELECT}; + +#[allow(unused)] +pub struct EthInitiatorMonitoring { + listener: UnboundedReceiver>, + ws: RootProvider, +} + +impl BridgeContractInitiatorMonitoring for EthInitiatorMonitoring { + type Address = EthAddress; + type Hash = EthHash; +} + +#[allow(unused)] +impl EthInitiatorMonitoring { + async fn run( + rpc_url: &str, + listener: UnboundedReceiver>, + ) -> Result { + let ws = WsConnect::new(rpc_url); + let ws = ProviderBuilder::new().on_ws(ws).await?; + + //TODO: this should be an arg + let initiator_address = address!("f39Fd6e51aad88F6F4ce6aB8827279cffFb92266"); + let filter = Filter::new() + .address(initiator_address) + .event("BridgeTransferInitiated(bytes32,address,bytes32,uint256)") + .event("BridgeTransferCompleted(bytes32,bytes32)") + .from_block(BlockNumberOrTag::Latest); + + let sub = ws.subscribe_logs(&filter).await?; + let mut sub_stream = sub.into_stream(); + + // Spawn a task to forward events to the listener channel + let (sender, _) = + tokio::sync::mpsc::unbounded_channel::>(); + + tokio::spawn(async move { + while let Some(log) = sub_stream.next().await { + let event = decode_log_data(log) + .map_err(|e| { + tracing::error!("Failed to decode log data: {:?}", e); + }) + .expect("Failed to decode log data"); + let event = EthChainEvent::InitiatorContractEvent(Ok(event.into())); + if sender.send(event).is_err() { + tracing::error!("Failed to send event to listener channel"); + break; + } + } + }); + + Ok(Self { listener, ws }) + } +} + +impl Stream for EthInitiatorMonitoring { + type Item = BridgeContractInitiatorEvent< + ::Address, + ::Hash, + >; + + fn poll_next(self: Pin<&mut Self>, cx: &mut std::task::Context) -> Poll> { + let this = self.get_mut(); + if let Poll::Ready(Some(EthChainEvent::InitiatorContractEvent(contract_result))) = + this.listener.poll_next_unpin(cx) + { + tracing::trace!( + "InitiatorContractMonitoring: Received contract event: {:?}", + contract_result + ); + + // Only listen to the initiator contract events + match contract_result { + Ok(contract_event) => match contract_event { + SmartContractInitiatorEvent::InitiatedBridgeTransfer(details) => { + return Poll::Ready(Some(BridgeContractInitiatorEvent::Initiated(details))) + } + SmartContractInitiatorEvent::CompletedBridgeTransfer(bridge_transfer_id) => { + return Poll::Ready(Some(BridgeContractInitiatorEvent::Completed( + bridge_transfer_id, + ))) + } + SmartContractInitiatorEvent::RefundedBridgeTransfer(bridge_transfer_id) => { + return Poll::Ready(Some(BridgeContractInitiatorEvent::Refunded( + bridge_transfer_id, + ))) + } + }, + Err(e) => { + tracing::error!("Error in contract event: {:?}", e); + } + } + } + Poll::Pending + } +} + +fn decode_log_data( + log: Log, +) -> Result, anyhow::Error> { + let topics = log.topics().to_owned(); + let log_data = + LogData::new(topics.clone(), log.data().data.clone()).expect("Failed to create log data"); + + // Build the event + let event = topics + .iter() + .find_map(|topic| { + match *topic { + INITIATED_SELECT => Some(Event { + name: EventName::Initiated.as_str().to_string(), + inputs: EventName::Initiated + .params() + .iter() + .map(|p| EventParam { + ty: p.to_string(), + name: EventName::Completed.as_str().to_string(), + indexed: true, + components: EventName::Initiated.params(), + internal_type: None, // for now + }) + .collect(), + anonymous: false, + }), + COMPLETED_SELECT => Some(Event { + name: EventName::Completed.as_str().to_string(), + inputs: EventName::Completed + .params() + .iter() + .map(|p| EventParam { + ty: p.to_string(), + name: p.name.clone(), + indexed: true, + components: EventName::Completed.params(), + internal_type: None, // for now + }) + .collect(), + anonymous: false, + }), + REFUNDED_SELECT => Some(Event { + name: EventName::Refunded.as_str().to_string(), + inputs: EventName::Refunded + .params() + .iter() + .map(|p| EventParam { + ty: p.to_string(), + name: p.name.clone(), + indexed: true, + components: EventName::Refunded.params(), + internal_type: None, // for now + }) + .collect(), + anonymous: false, + }), + + _ => None, + } + }) + .ok_or_else(|| anyhow::anyhow!("Failed to find event"))?; + + let decoded = event.decode_log(&log_data, true).expect("Failed to decode log"); + + let coerce_bytes = |(bytes, _): (&[u8], usize)| { + let mut array = [0u8; 32]; + array.copy_from_slice(bytes); + array + }; + + if let Some(selector) = decoded.selector { + match selector { + INITIATED_SELECT => { + let bridge_transfer_id = decoded.indexed[0] + .as_fixed_bytes() + .map(coerce_bytes) + .ok_or_else(|| anyhow::anyhow!("Failed to decode BridgeTransferId"))?; + let initiator_address = decoded.indexed[1] + .as_address() + .map(EthAddress) + .ok_or_else(|| anyhow::anyhow!("Failed to decode InitiatorAddress"))?; + let recipient_address = decoded.indexed[2] + .as_fixed_bytes() + .map(coerce_bytes) + .ok_or_else(|| anyhow::anyhow!("Failed to decode RecipientAddress"))?; + let amount = decoded.indexed[3] + .as_uint() + .map(|(u, _)| u.into()) + .ok_or_else(|| anyhow::anyhow!("Failed to decode Amount"))?; + let hash_lock = decoded.indexed[4] + .as_fixed_bytes() + .map(coerce_bytes) + .ok_or_else(|| anyhow::anyhow!("Failed to decode HashLock"))?; + let state = decoded + .indexed + .get(6) + .and_then(|val| val.as_uint()) + .and_then(|(u, _)| u.try_into().ok()) // Try converting to u8 + .ok_or_else(|| anyhow::anyhow!("Failed to decode state as u8"))?; + + let details: BridgeTransferDetails = BridgeTransferDetails { + bridge_transfer_id: BridgeTransferId(bridge_transfer_id), + initiator_address: InitiatorAddress(initiator_address), + recipient_address: RecipientAddress(recipient_address.to_vec()), + hash_lock: HashLock(hash_lock), + amount, + state, + }; + + Ok(BridgeContractInitiatorEvent::Initiated(details)) + } + COMPLETED_SELECT => { + let bridge_transfer_id = decoded.indexed[0] + .as_fixed_bytes() + .map(coerce_bytes) + .ok_or_else(|| anyhow::anyhow!("Failed to decode BridgeTransferId"))?; + + Ok(BridgeContractInitiatorEvent::Completed(BridgeTransferId(bridge_transfer_id))) + } + REFUNDED_SELECT => { + let bridge_transfer_id = decoded.indexed[0] + .as_fixed_bytes() + .map(coerce_bytes) + .ok_or_else(|| anyhow::anyhow!("Failed to decode BridgeTransferId"))?; + + Ok(BridgeContractInitiatorEvent::Refunded(BridgeTransferId(bridge_transfer_id))) + } + _ => { + tracing::error!("Unknown event selector: {:x}", selector); + Err(anyhow::anyhow!("failed to decode event selector")) + } + } + } else { + tracing::error!("Failed to decode event selector"); + Err(anyhow::anyhow!("Failed to decode event selector")) + } +} diff --git a/protocol-units/bridge/chains/ethereum/src/event_types.rs b/protocol-units/bridge/chains/ethereum/src/event_types.rs new file mode 100644 index 000000000..10bc4480e --- /dev/null +++ b/protocol-units/bridge/chains/ethereum/src/event_types.rs @@ -0,0 +1,64 @@ +use bridge_shared::{ + bridge_monitoring::BridgeContractInitiatorEvent, + counterparty_contract::SCCResult, + initiator_contract::{SCIResult, SmartContractInitiatorEvent}, + types::LockDetails, +}; + +use crate::types::{CompletedDetails, EthAddress}; +use thiserror::Error; + +#[allow(unused)] +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum MoveCounterpartyEvent { + LockedBridgeTransfer(LockDetails), + CompletedBridgeTransfer(CompletedDetails), +} + +#[allow(unused)] +#[derive(Debug, Error, Clone, PartialEq, Eq)] +pub enum MoveCounterpartyError { + #[error("Transfer not found")] + TransferNotFound, + #[error("Invalid hash lock pre image (secret)")] + InvalidHashLockPreImage, +} + +#[allow(unused)] +#[derive(Debug, Error, Clone, PartialEq, Eq)] +pub enum EthInitiatorError { + #[error("Failed to initiate bridge transfer")] + InitiateTransferError, + #[error("Transfer not found")] + TransferNotFound, + #[error("Invalid hash lock pre image (secret)")] + InvalidHashLockPreImage, +} + +#[allow(unused)] +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum EthChainEvent { + InitiatorContractEvent(SCIResult), + CounterpartyContractEvent(SCCResult), + Noop, +} + +impl From> + for EthChainEvent +{ + fn from(event: BridgeContractInitiatorEvent) -> Self { + match event { + BridgeContractInitiatorEvent::Initiated(details) => { + EthChainEvent::InitiatorContractEvent(Ok( + SmartContractInitiatorEvent::InitiatedBridgeTransfer(details), + )) + } + BridgeContractInitiatorEvent::Completed(id) => EthChainEvent::InitiatorContractEvent( + Ok(SmartContractInitiatorEvent::CompletedBridgeTransfer(id)), + ), + BridgeContractInitiatorEvent::Refunded(id) => EthChainEvent::InitiatorContractEvent( + Ok(SmartContractInitiatorEvent::RefundedBridgeTransfer(id)), + ), + } + } +} diff --git a/protocol-units/bridge/chains/ethereum/src/lib.rs b/protocol-units/bridge/chains/ethereum/src/lib.rs new file mode 100644 index 000000000..2c5eec5b6 --- /dev/null +++ b/protocol-units/bridge/chains/ethereum/src/lib.rs @@ -0,0 +1,229 @@ +use bridge_shared::{ + counterparty_contract::SmartContractCounterparty, + initiator_contract::{InitiatorCall, SmartContractInitiator}, + types::{ + Amount, BridgeAddressType, BridgeHashType, GenUniqueHash, HashLockPreImage, + RecipientAddress, + }, +}; +use event_types::EthChainEvent; +use futures::{channel::mpsc, task::AtomicWaker, Stream, StreamExt}; +use std::fmt::Debug; +use std::{ + collections::HashMap, + future::Future, + pin::Pin, + task::{Context, Poll}, +}; +use types::CounterpartyCall; +use utils::RngSeededClone; + +pub mod client; +mod event_logging; +mod event_types; +pub mod types; +mod utils; + +pub enum SmartContractCall { + Initiator(), + Counterparty(CounterpartyCall), +} + +/// A Bridge Transaction that can occur on any supported network. +#[derive(Debug)] +pub enum Transaction { + Initiator(InitiatorCall), + Counterparty(CounterpartyCall), +} + +pub struct EthereumChain { + pub name: String, + pub time: u64, + pub accounts: HashMap, + pub events: Vec>, + + pub initiator_contract: SmartContractInitiator, + pub counterparty_contract: SmartContractCounterparty, + + pub transaction_sender: mpsc::UnboundedSender>, + pub transaction_receiver: mpsc::UnboundedReceiver>, + + pub event_listeners: Vec>>, + + waker: AtomicWaker, + + pub _phantom: std::marker::PhantomData, +} + +impl EthereumChain +where + A: BridgeAddressType + From>, + H: BridgeHashType + GenUniqueHash, + R: RngSeededClone, + H: From, +{ + pub fn new(mut rng: R, name: impl Into) -> Self { + let accounts = HashMap::new(); + let events = Vec::new(); + let (event_sender, event_receiver) = mpsc::unbounded(); + let event_listeners = Vec::new(); + + Self { + name: name.into(), + time: 0, + accounts, + events, + initiator_contract: SmartContractInitiator::new(rng.seeded_clone()), + counterparty_contract: SmartContractCounterparty::new(), + transaction_sender: event_sender, + transaction_receiver: event_receiver, + event_listeners, + waker: AtomicWaker::new(), + _phantom: std::marker::PhantomData, + } + } + + pub fn add_event_listener(&mut self) -> mpsc::UnboundedReceiver> { + let (sender, receiver) = mpsc::unbounded(); + self.event_listeners.push(sender); + receiver + } + + pub fn add_account(&mut self, address: A, amount: Amount) { + self.accounts.insert(address, amount); + } + + pub fn get_balance(&mut self, address: &A) -> Option<&Amount> { + self.accounts.get(address) + } + + pub fn connection(&self) -> mpsc::UnboundedSender> { + self.transaction_sender.clone() + } +} + +impl Future for EthereumChain +where + A: BridgeAddressType + From>, + H: BridgeHashType + GenUniqueHash, + R: RngSeededClone + Unpin, + H: From, +{ + type Output = (); + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let this = self.get_mut(); + + // This simulates + // async move { while (blockchain_1.next().await).is_some() {} } + while let Poll::Ready(event) = this.poll_next_unpin(cx) { + match event { + Some(_) => {} + None => return Poll::Ready(()), + } + } + Poll::Pending + } +} + +impl Stream for EthereumChain +where + A: BridgeAddressType + From>, + H: BridgeHashType + GenUniqueHash + From, + R: RngSeededClone + Unpin, +{ + type Item = EthChainEvent; + + fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + tracing::trace!("AbstractBlockchain[{}]: Polling for events", self.name); + let this = self.get_mut(); + + match this.transaction_receiver.poll_next_unpin(cx) { + Poll::Ready(Some(transaction)) => { + tracing::trace!( + "AbstractBlockchain[{}]: Received transaction: {:?}", + this.name, + transaction + ); + match transaction { + Transaction::Initiator(call) => match call { + InitiatorCall::InitiateBridgeTransfer( + initiator_address, + recipient_address, + amount, + hash_lock, + ) => { + this.events.push(EthChainEvent::InitiatorContractEvent( + this.initiator_contract.initiate_bridge_transfer( + initiator_address.clone(), + recipient_address.clone(), + amount, + hash_lock.clone(), + ), + )); + } + InitiatorCall::CompleteBridgeTransfer(bridge_transfer_id, secret) => { + this.events.push(EthChainEvent::InitiatorContractEvent( + this.initiator_contract.complete_bridge_transfer( + &mut this.accounts, + bridge_transfer_id.clone(), + secret.clone(), + ), + )); + } + }, + Transaction::Counterparty(call) => match call { + CounterpartyCall::LockBridgeTransfer( + bridge_transfer_id, + hash_lock, + initiator_address, + recipient_address, + amount, + ) => { + this.events.push(EthChainEvent::CounterpartyContractEvent( + this.counterparty_contract.lock_bridge_transfer( + bridge_transfer_id.clone(), + hash_lock.clone(), + initiator_address.clone(), + recipient_address.clone(), + amount, + ), + )); + } + CounterpartyCall::CompleteBridgeTransfer(bridge_transfer_id, pre_image) => { + this.events.push(EthChainEvent::CounterpartyContractEvent( + this.counterparty_contract.complete_bridge_transfer( + &mut this.accounts, + &bridge_transfer_id, + pre_image, + ), + )); + } + }, + } + } + Poll::Ready(None) => { + tracing::warn!("AbstractBlockchain[{}]: Transaction receiver dropped", this.name); + } + Poll::Pending => { + tracing::trace!( + "AbstractBlockchain[{}]: No events in transaction_receiver", + this.name + ); + } + } + + if let Some(event) = this.events.pop() { + for listener in &mut this.event_listeners { + tracing::trace!("AbstractBlockchain[{}]: Sending event to listener", this.name); + listener.unbounded_send(event.clone()).expect("listener dropped"); + } + + tracing::trace!("AbstractBlockchain[{}]: Poll::Ready({:?})", this.name, event); + return Poll::Ready(Some(event)); + } + + tracing::trace!("AbstractBlockchain[{}]: Poll::Pending", this.name); + Poll::Pending + } +} diff --git a/protocol-units/bridge/chains/ethereum/src/types.rs b/protocol-units/bridge/chains/ethereum/src/types.rs new file mode 100644 index 000000000..0c5158376 --- /dev/null +++ b/protocol-units/bridge/chains/ethereum/src/types.rs @@ -0,0 +1,256 @@ +use alloy::json_abi::Param; +use alloy::network::{Ethereum, EthereumWallet}; +use alloy::primitives::{Address, FixedBytes}; +use alloy::providers::fillers::{ + ChainIdFiller, FillProvider, GasFiller, JoinFill, NonceFiller, WalletFiller, +}; +use alloy::providers::RootProvider; +use alloy::rlp::{RlpDecodable, RlpEncodable}; +use alloy::sol_types::SolEvent; +use alloy::transports::BoxTransport; +use bridge_shared::types::{ + Amount, BridgeTransferDetails, BridgeTransferId, HashLock, HashLockPreImage, InitiatorAddress, + LockDetails, RecipientAddress, TimeLock, +}; +use serde::{Deserialize, Serialize}; + +pub const INITIATED_SELECT: FixedBytes<32> = + AtomicBridgeInitiator::BridgeTransferInitiated::SIGNATURE_HASH; +pub const COMPLETED_SELECT: FixedBytes<32> = + AtomicBridgeInitiator::BridgeTransferCompleted::SIGNATURE_HASH; +pub const REFUNDED_SELECT: FixedBytes<32> = + AtomicBridgeInitiator::BridgeTransferRefunded::SIGNATURE_HASH; + +// Codegen from the abis +alloy::sol!( + #[allow(missing_docs)] + #[sol(rpc)] + AtomicBridgeInitiator, + "abis/AtomicBridgeInitiator.json" +); + +alloy::sol!( + #[allow(missing_docs)] + #[sol(rpc)] + AtomicBridgeCounterparty, + "abis/AtomicBridgeCounterparty.json" +); + +alloy::sol!( + #[allow(missing_docs)] + #[sol(rpc)] + WETH9, + "abis/WETH9.json" +); + +pub type EthHash = [u8; 32]; + +pub type InitiatorContract = + AtomicBridgeInitiator::AtomicBridgeInitiatorInstance; +pub type CounterpartyContract = + AtomicBridgeCounterparty::AtomicBridgeCounterpartyInstance; +pub type WETH9Contract = WETH9::WETH9Instance; + +pub type AlloyProvider = FillProvider< + JoinFill< + JoinFill< + JoinFill, NonceFiller>, + ChainIdFiller, + >, + WalletFiller, + >, + RootProvider, + BoxTransport, + Ethereum, +>; + +#[derive(Debug, PartialEq, Eq, Hash, Clone, RlpEncodable, RlpDecodable, Serialize, Deserialize)] +pub struct EthAddress(pub Address); + +impl std::ops::Deref for EthAddress { + type Target = Address; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl From for EthAddress { + fn from(s: String) -> Self { + EthAddress(Address::parse_checksummed(s, None).expect("Invalid Ethereum address")) + } +} + +impl From> for EthAddress { + fn from(vec: Vec) -> Self { + // Ensure the vector has the correct length + assert_eq!(vec.len(), 20); + + let mut bytes = [0u8; 20]; + bytes.copy_from_slice(&vec); + EthAddress(Address(bytes.into())) + } +} + +impl From<[u8; 32]> for EthAddress { + fn from(bytes: [u8; 32]) -> Self { + let mut address_bytes = [0u8; 20]; + address_bytes.copy_from_slice(&bytes[0..20]); + EthAddress(Address(address_bytes.into())) + } +} + +pub(crate) enum AlloyParam { + BridgeTransferId, + InitiatorAddress, + RecipientAddress, + PreImage, + HashLock, + TimeLock, + Amount, +} + +impl AlloyParam { + pub fn fill(&self) -> Param { + match self { + AlloyParam::BridgeTransferId => Param { + name: "_bridgeTransferId".to_string(), + ty: "bytes32".to_string(), + components: vec![], + internal_type: None, + }, + AlloyParam::InitiatorAddress => Param { + name: "_originator".to_string(), + ty: "address".to_string(), + components: vec![], + internal_type: None, + }, + AlloyParam::RecipientAddress => Param { + name: "_recipient".to_string(), + ty: "bytes32".to_string(), + components: vec![], + internal_type: None, + }, + AlloyParam::PreImage => Param { + name: "pre_image".to_string(), + ty: "bytes32".to_string(), + components: vec![], + internal_type: None, + }, + AlloyParam::HashLock => Param { + name: "_hashLock".to_string(), + ty: "bytes32".to_string(), + components: vec![], + internal_type: None, + }, + AlloyParam::TimeLock => Param { + name: "_timeLock".to_string(), + ty: "uint256".to_string(), + components: vec![], + internal_type: None, + }, + AlloyParam::Amount => Param { + name: "amount".to_string(), + ty: "uint256".to_string(), + components: vec![], + internal_type: None, + }, + } + } +} + +pub(crate) enum EventName { + Initiated, + Completed, + Refunded, +} + +impl EventName { + pub fn as_str(&self) -> &str { + match self { + EventName::Initiated => "BridgeTransferInitiated", + EventName::Completed => "BridgeTransferCompleted", + EventName::Refunded => "BridgeTransferRefunded", + } + } + + pub fn params(&self) -> Vec { + match self { + EventName::Initiated => vec![ + AlloyParam::BridgeTransferId.fill(), + AlloyParam::InitiatorAddress.fill(), + AlloyParam::RecipientAddress.fill(), + AlloyParam::PreImage.fill(), + AlloyParam::HashLock.fill(), + AlloyParam::TimeLock.fill(), + AlloyParam::Amount.fill(), + ], + EventName::Completed => { + vec![AlloyParam::BridgeTransferId.fill(), AlloyParam::PreImage.fill()] + } + EventName::Refunded => vec![AlloyParam::BridgeTransferId.fill()], + } + } +} + +impl From<&str> for EventName { + fn from(s: &str) -> Self { + match s { + "BridgeTransferInitiated" => EventName::Initiated, + "BridgeTransferCompleted" => EventName::Completed, + "BridgeTransferRefunded" => EventName::Refunded, + _ => panic!("Invalid event name"), + } + } +} + +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct CompletedDetails { + pub bridge_transfer_id: BridgeTransferId, + pub recipient_address: RecipientAddress, + pub hash_lock: HashLock, + pub secret: HashLockPreImage, + pub amount: Amount, +} + +impl CompletedDetails +where + A: From>, +{ + pub fn from_bridge_transfer_details( + bridge_transfer_details: BridgeTransferDetails, H>, + secret: HashLockPreImage, + ) -> Self { + CompletedDetails { + bridge_transfer_id: bridge_transfer_details.bridge_transfer_id, + recipient_address: RecipientAddress(A::from( + bridge_transfer_details.recipient_address.0, + )), + hash_lock: bridge_transfer_details.hash_lock, + secret, + amount: bridge_transfer_details.amount, + } + } + + pub fn from_lock_details(lock_details: LockDetails, secret: HashLockPreImage) -> Self { + CompletedDetails { + bridge_transfer_id: lock_details.bridge_transfer_id, + recipient_address: lock_details.recipient_address, + hash_lock: lock_details.hash_lock, + secret, + amount: lock_details.amount, + } + } +} + +#[derive(Debug)] +pub enum CounterpartyCall { + CompleteBridgeTransfer(BridgeTransferId, HashLockPreImage), + LockBridgeTransfer( + BridgeTransferId, + HashLock, + InitiatorAddress>, + RecipientAddress, + Amount, + ), +} diff --git a/protocol-units/bridge/chains/ethereum/src/utils.rs b/protocol-units/bridge/chains/ethereum/src/utils.rs new file mode 100644 index 000000000..114957067 --- /dev/null +++ b/protocol-units/bridge/chains/ethereum/src/utils.rs @@ -0,0 +1,177 @@ +use std::str::FromStr; + +use crate::types::EthAddress; +use alloy::contract::{CallBuilder, CallDecoder}; +use alloy::network::Ethereum; +use alloy::primitives::U256; +use alloy::providers::Provider; +use alloy::rlp::{Encodable, RlpEncodable}; +use alloy::rpc::types::TransactionReceipt; +use alloy::transports::Transport; +use keccak_hash::keccak; +use mcr_settlement_client::send_eth_transaction::{ + InsufficentFunds, SendTransactionErrorRule, UnderPriced, VerifyRule, +}; +use thiserror::Error; + +use rand::{rngs::StdRng, SeedableRng}; +use rand::{Rng, RngCore}; +use rand_chacha::ChaChaRng; + +pub trait RngSeededClone: Rng + SeedableRng { + fn seeded_clone(&mut self) -> Self; +} + +impl RngSeededClone for StdRng { + fn seeded_clone(&mut self) -> Self { + self.clone() + } +} + +impl RngSeededClone for ChaChaRng { + fn seeded_clone(&mut self) -> Self { + let mut seed = [0u8; 32]; + self.fill_bytes(&mut seed); + ChaChaRng::from_seed(seed) + } +} + +#[derive(Debug, Error)] +pub enum EthUtilError { + #[error("Failed to decode hex string")] + HexDecodeError, + #[error("Failed to convert Vec to EthAddress")] + LengthError, + #[error("SendTxError: {0}")] + SendTxError(#[from] alloy::contract::Error), + #[error("ReceiptError: {0}")] + GetReceiptError(String), + #[error("RpcTransactionExecution: {0}")] + GasLimitExceed(u128, u128), + #[error("RpcTransactionExecution: {0}")] + RpcTransactionExecution(String), +} + +impl FromStr for EthAddress { + type Err = EthUtilError; + + fn from_str(s: &str) -> Result { + // Try to convert the string to a Vec + let vec = hex::decode(s).map_err(|_| EthUtilError::HexDecodeError)?; + // Ensure the vector has the correct length + if vec.len() != 20 { + return Err(EthUtilError::LengthError); + } + // Try to convert the Vec to EthAddress + Ok(vec.into()) + } +} + +pub fn calculate_storage_slot(key: [u8; 32], mapping_slot: U256) -> U256 { + #[derive(RlpEncodable)] + struct SlotKey<'a> { + key: &'a [u8; 32], + mapping_slot: U256, + } + + let slot_key = SlotKey { key: &key, mapping_slot }; + + let mut buffer = Vec::new(); + slot_key.encode(&mut buffer); + + let hash = keccak(buffer); + U256::from_be_slice(&hash.0) +} + +pub(crate) fn send_transaction_rules() -> Vec> { + let rule1: Box = Box::new(SendTransactionErrorRule::::new()); + let rule2: Box = Box::new(SendTransactionErrorRule::::new()); + vec![rule1, rule2] +} + +pub async fn send_transaction< + P: Provider + Clone, + T: Transport + Clone, + D: CallDecoder + Clone, +>( + base_call_builder: CallBuilder, + send_transaction_error_rules: &[Box], + number_retry: u32, + gas_limit: u128, +) -> Result { + println!("base_call_builder: {:?}", base_call_builder); + println!("Sending transaction with gas limit: {}", gas_limit); + //validate gas price. + let mut estimate_gas = base_call_builder.estimate_gas().await.expect("Failed to estimate gas"); + // Add 20% because initial gas estimate are too low. + estimate_gas += (estimate_gas * 20) / 100; + + println!("estimated_gas: {}", estimate_gas); + + // Sending Transaction automatically can lead to errors that depend on the state for Eth. + // It's convenient to manage some of them automatically to avoid to fail commitment Transaction. + // I define a first one but other should be added depending on the test with mainnet. + for _ in 0..number_retry { + let call_builder = base_call_builder.clone().gas(estimate_gas); + + //detect if the gas price doesn't execeed the limit. + let gas_price = call_builder.provider.get_gas_price().await?; + let transaction_fee_wei = estimate_gas * gas_price; + if transaction_fee_wei > gas_limit { + return Err(EthUtilError::GasLimitExceed(transaction_fee_wei, gas_limit).into()); + } + + println!("Sending transaction with gas: {}", estimate_gas); + + //send the Transaction and detect send error. + let pending_transaction = match call_builder.send().await { + Ok(pending_transaction) => pending_transaction, + Err(err) => { + //apply defined rules. + for rule in send_transaction_error_rules { + // Verify all rules. If one rule return true or an error stop verification. + // If true retry with more gas else return the error. + if rule.verify(&err)? { + //increase gas of 10% and retry + estimate_gas += (estimate_gas * 10) / 100; + tokio::time::sleep(tokio::time::Duration::from_secs(1)).await; + continue; + } + } + + return Err(EthUtilError::from(err).into()); + } + }; + + match pending_transaction.get_receipt().await { + // Transaction execution fail + Ok(transaction_receipt) if !transaction_receipt.status() => { + tracing::debug!( + "transaction_receipt.gas_used: {} / estimate_gas: {estimate_gas}", + transaction_receipt.gas_used + ); + // Some valid Tx can abort cause of insufficient gas without consuming all its gas. + // Define a threshold a little less than estimated gas to detect them. + let tx_gas_consumption_threshold = estimate_gas - (estimate_gas * 10) / 100; + if transaction_receipt.gas_used >= tx_gas_consumption_threshold { + tracing::info!("Send commitment Transaction fail because of insufficient gas, receipt:{transaction_receipt:?} "); + estimate_gas += (estimate_gas * 30) / 100; + continue; + } else { + return Err(EthUtilError::RpcTransactionExecution(format!( + "Send commitment Transaction fail, abort Transaction, receipt:{transaction_receipt:?}" + )) + .into()); + } + } + Ok(receipt) => return Ok(receipt), + Err(err) => return Err(EthUtilError::RpcTransactionExecution(err.to_string()).into()), + }; + } + + //Max retry exceed + Err(EthUtilError::RpcTransactionExecution( + "Send commitment Transaction fail because of exceed max retry".to_string(), + ) + .into()) +} diff --git a/protocol-units/bridge/chains/movement/Cargo.toml b/protocol-units/bridge/chains/movement/Cargo.toml new file mode 100644 index 000000000..02832b883 --- /dev/null +++ b/protocol-units/bridge/chains/movement/Cargo.toml @@ -0,0 +1,39 @@ +[package] +name = "movement-bridge" +description = "bridge components for the Movement Atomic Bridge" +authors = { workspace = true } +edition = { workspace = true } +homepage = { workspace = true } +license = { workspace = true } +repository = { workspace = true } + +version = { workspace = true } + +[lib] +path = "src/lib.rs" + +[dependencies] +aptos-sdk = { workspace = true } +aptos-api-types = { workspace = true } +aptos-types = { workspace = true } +aptos-api = { workspace = true } +async-trait = { workspace = true } +anyhow = { workspace = true } +bcs = { workspace = true } +derive-new = { workspace = true } +hex = { workspace = true } +poem = { workspace = true } +keccak-hash = { workspace = true } +tokio = { workspace = true } +thiserror = { workspace = true } +tracing = { workspace = true } +tracing-subscriber = { workspace = true } +serde = { workspace = true } +serde_json = { workspace = true } +rand = { workspace = true } + +url = { workspace = true } +once_cell = { workspace = true } + +bridge-shared = { workspace = true } +mcr-settlement-client = { workspace = true } diff --git a/protocol-units/bridge/chains/movement/src/lib.rs b/protocol-units/bridge/chains/movement/src/lib.rs new file mode 100644 index 000000000..8220ec9c8 --- /dev/null +++ b/protocol-units/bridge/chains/movement/src/lib.rs @@ -0,0 +1,901 @@ +use crate::utils::MovementAddress; +use anyhow::Result; +use aptos_api::accounts::Account; +use aptos_api_types::{Address, EntryFunctionId, MoveModuleId, ViewFunction, ViewRequest}; +use aptos_sdk::{ + move_types::{ + identifier::Identifier, + language_storage::{ModuleId, TypeTag}, + }, + rest_client::{Client, FaucetClient, Response}, + types::LocalAccount, +}; +use aptos_types::account_address::AccountAddress; +use bridge_shared::{ + bridge_contracts::{ + BridgeContractCounterparty, BridgeContractCounterpartyError, + BridgeContractCounterpartyResult, BridgeContractInitiator, BridgeContractInitiatorError, + BridgeContractInitiatorResult, + }, + types::{ + Amount, AssetType, BridgeTransferDetails, BridgeTransferId, HashLock, HashLockPreImage, + InitiatorAddress, RecipientAddress, TimeLock, + }, +}; +use hex::{decode, FromHex}; +use rand::prelude::*; +use rand::Rng; +use serde::{Deserialize, Serialize}; +use serde_json::{json, Value}; +use std::str::FromStr; +use std::sync::{Arc, RwLock}; +use std::{ + env, fs, + io::{Read, Write}, + path::{Path, PathBuf}, + process::{Command, Stdio}, +}; +use tokio::{ + io::{AsyncBufReadExt, BufReader}, + process::Command as TokioCommand, + sync::oneshot, + task, +}; +use tracing::{debug, info}; + +use url::Url; + +mod types; +pub mod utils; + +const DUMMY_ADDRESS: AccountAddress = AccountAddress::new([0; 32]); +const COUNTERPARTY_MODULE_NAME: &str = "atomic_bridge_counterparty"; + +#[allow(dead_code)] +enum Call { + Lock, + Complete, + Abort, + GetDetails, +} + +pub struct Config { + pub rpc_url: Option, + pub ws_url: Option, + pub chain_id: String, + pub signer_private_key: Arc>, + pub initiator_contract: Option, + pub gas_limit: u64, +} + +impl Config { + pub fn build_for_test() -> Self { + let seed = [3u8; 32]; + let mut rng = rand::rngs::StdRng::from_seed(seed); + + Config { + rpc_url: Some("http://localhost:8080".parse().unwrap()), + ws_url: Some("ws://localhost:8080".parse().unwrap()), + chain_id: 4.to_string(), + signer_private_key: Arc::new(RwLock::new(LocalAccount::generate(&mut rng))), + initiator_contract: None, + gas_limit: 10_000_000_000, + } + } +} + +#[allow(dead_code)] +#[derive(Clone)] +pub struct MovementClient { + ///Address of the counterparty moduke + pub counterparty_address: AccountAddress, + ///Address of the initiator module + initiator_address: Vec, + ///The Apotos Rest Client + pub rest_client: Client, + ///The Apotos Rest Client + pub faucet_client: Option>>, + ///The signer account + signer: Arc, +} + +impl MovementClient { + pub async fn new(_config: Config) -> Result { + let node_connection_url = "http://127.0.0.1:8080".to_string(); + let node_connection_url = Url::from_str(node_connection_url.as_str()) + .map_err(|_| BridgeContractCounterpartyError::SerializationError)?; + + let rest_client = Client::new(node_connection_url.clone()); + + let seed = [3u8; 32]; + let mut rng = rand::rngs::StdRng::from_seed(seed); + let signer = LocalAccount::generate(&mut rng); + + let mut address_bytes = [0u8; AccountAddress::LENGTH]; + address_bytes[0..2].copy_from_slice(&[0xca, 0xfe]); + let counterparty_address = AccountAddress::new(address_bytes); + Ok(MovementClient { + counterparty_address, + initiator_address: Vec::new(), //dummy for now + rest_client, + faucet_client: None, + signer: Arc::new(signer), + }) + } + + pub async fn new_for_test( + _config: Config, + ) -> Result<(Self, tokio::process::Child), anyhow::Error> { + let kill_cmd = TokioCommand::new("sh") + .arg("-c") + .arg("PID=$(ps aux | grep 'movement node run-local-testnet' | grep -v grep | awk '{print $2}' | head -n 1); if [ -n \"$PID\" ]; then kill -9 $PID; fi") + .output() + .await?; + + if !kill_cmd.status.success() { + println!("Failed to kill running movement process: {:?}", kill_cmd.stderr); + } else { + println!("Movement process killed if it was running."); + } + + let delete_dir_cmd = TokioCommand::new("sh") + .arg("-c") + .arg("if [ -d '.movement' ]; then rm -rf .movement; fi") + .output() + .await?; + + if !delete_dir_cmd.status.success() { + println!("Failed to delete .movement directory: {:?}", delete_dir_cmd.stderr); + } else { + println!(".movement directory deleted if it was present."); + } + + let (setup_complete_tx, mut setup_complete_rx) = oneshot::channel(); + let mut child = TokioCommand::new("movement") + .args(&["node", "run-local-testnet", "--force-restart", "--assume-yes"]) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn()?; + + let stdout = child.stdout.take().expect("Failed to capture stdout"); + let stderr = child.stderr.take().expect("Failed to capture stderr"); + + task::spawn(async move { + let mut stdout_reader = BufReader::new(stdout).lines(); + let mut stderr_reader = BufReader::new(stderr).lines(); + + loop { + tokio::select! { + line = stdout_reader.next_line() => { + match line { + Ok(Some(line)) => { + println!("STDOUT: {}", line); + if line.contains("Setup is complete") { + println!("Testnet is up and running!"); + let _ = setup_complete_tx.send(()); + return Ok(()); + } + }, + Ok(None) => { + return Err(anyhow::anyhow!("Unexpected end of stdout stream")); + }, + Err(e) => { + return Err(anyhow::anyhow!("Error reading stdout: {}", e)); + } + } + }, + line = stderr_reader.next_line() => { + match line { + Ok(Some(line)) => { + println!("STDERR: {}", line); + if line.contains("Setup is complete") { + println!("Testnet is up and running!"); + let _ = setup_complete_tx.send(()); + return Ok(()); + } + }, + Ok(None) => { + return Err(anyhow::anyhow!("Unexpected end of stderr stream")); + } + Err(e) => { + return Err(anyhow::anyhow!("Error reading stderr: {}", e)); + } + } + } + } + } + }); + + setup_complete_rx.await.expect("Failed to receive setup completion signal"); + println!("Setup complete message received."); + + let node_connection_url = "http://127.0.0.1:8080".to_string(); + let node_connection_url = Url::from_str(node_connection_url.as_str()) + .map_err(|_| BridgeContractCounterpartyError::SerializationError)?; + let rest_client = Client::new(node_connection_url.clone()); + + let faucet_url = "http://127.0.0.1:8081".to_string(); + let faucet_url = Url::from_str(faucet_url.as_str()) + .map_err(|_| BridgeContractCounterpartyError::SerializationError)?; + let faucet_client = Arc::new(RwLock::new(FaucetClient::new( + faucet_url.clone(), + node_connection_url.clone(), + ))); + + let mut rng = ::rand::rngs::StdRng::from_seed([3u8; 32]); + Ok(( + MovementClient { + counterparty_address: DUMMY_ADDRESS, + initiator_address: Vec::new(), // dummy for now + rest_client, + faucet_client: Some(faucet_client), + signer: Arc::new(LocalAccount::generate(&mut rng)), + }, + child, + )) + } + + pub fn publish_for_test(&mut self) -> Result<()> { + let random_seed = rand::thread_rng().gen_range(0, 1000000).to_string(); + + let mut process = Command::new("movement") + .args(&["init"]) + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn() + .expect("Failed to execute command"); + + let private_key_hex = hex::encode(self.signer.private_key().to_bytes()); + + let stdin: &mut std::process::ChildStdin = + process.stdin.as_mut().expect("Failed to open stdin"); + + let movement_dir = PathBuf::from(".movement"); + + if movement_dir.exists() { + stdin.write_all(b"yes\n").expect("Failed to write to stdin"); + } + + stdin.write_all(b"local\n").expect("Failed to write to stdin"); + + let _ = stdin.write_all(format!("{}\n", private_key_hex).as_bytes()); + + drop(stdin); + + let addr_output = process.wait_with_output().expect("Failed to read command output"); + + if !addr_output.stdout.is_empty() { + println!("stdout: {}", String::from_utf8_lossy(&addr_output.stdout)); + } + + if !addr_output.stderr.is_empty() { + eprintln!("stderr: {}", String::from_utf8_lossy(&addr_output.stderr)); + } + let addr_output_str = String::from_utf8_lossy(&addr_output.stderr); + let address = addr_output_str + .split_whitespace() + .find(|word| word.starts_with("0x")) + .expect("Failed to extract the Movement account address"); + + println!("Extracted address: {}", address); + + let resource_output = Command::new("movement") + .args(&[ + "account", + "derive-resource-account-address", + "--address", + address, + "--seed", + &random_seed, + ]) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .output() + .expect("Failed to execute command"); + + // Print the output of the resource address command for debugging + if !resource_output.stdout.is_empty() { + println!("stdout: {}", String::from_utf8_lossy(&resource_output.stdout)); + } + if !resource_output.stderr.is_empty() { + eprintln!("stderr: {}", String::from_utf8_lossy(&resource_output.stderr)); + } + + // Extract the resource address from the JSON output + let resource_output_str = String::from_utf8_lossy(&resource_output.stdout); + let resource_address = resource_output_str + .lines() + .find(|line| line.contains("\"Result\"")) + .and_then(|line| line.split('"').nth(3)) + .expect("Failed to extract the resource account address"); + + // Ensure the address has a "0x" prefix + let formatted_resource_address = if resource_address.starts_with("0x") { + resource_address.to_string() + } else { + format!("0x{}", resource_address) + }; + + // Set counterparty module address to resource address, for function calls: + self.counterparty_address = AccountAddress::from_hex_literal(&formatted_resource_address)?; + + println!("Derived resource address: {}", formatted_resource_address); + + let current_dir = env::current_dir().expect("Failed to get current directory"); + println!("Current directory: {:?}", current_dir); + + let move_toml_path = PathBuf::from("../move-modules/Move.toml"); + + // Read the existing content of Move.toml + let move_toml_content = + fs::read_to_string(&move_toml_path).expect("Failed to read Move.toml file"); + + // Update the content of Move.toml with the new addresses + let updated_content = move_toml_content + .lines() + .map(|line| match line { + _ if line.starts_with("resource_addr = ") => { + format!(r#"resource_addr = "{}""#, formatted_resource_address) + } + _ if line.starts_with("atomic_bridge = ") => { + format!(r#"atomic_bridge = "{}""#, formatted_resource_address) + } + _ if line.starts_with("moveth = ") => { + format!(r#"moveth = "{}""#, formatted_resource_address) + } + _ if line.starts_with("master_minter = ") => { + format!(r#"master_minter = "{}""#, formatted_resource_address) + } + _ if line.starts_with("minter = ") => { + format!(r#"minter = "{}""#, formatted_resource_address) + } + _ if line.starts_with("admin = ") => { + format!(r#"admin = "{}""#, formatted_resource_address) + } + _ if line.starts_with("origin_addr = ") => { + format!(r#"origin_addr = "{}""#, address) + } + _ if line.starts_with("source_account = ") => { + format!(r#"source_account = "{}""#, address) + } + _ => line.to_string(), + }) + .collect::>() + .join("\n"); + + // Write the updated content back to Move.toml + let mut file = + fs::File::create(&move_toml_path).expect("Failed to open Move.toml file for writing"); + file.write_all(updated_content.as_bytes()) + .expect("Failed to write updated Move.toml file"); + + println!("Move.toml updated successfully."); + + let output2 = Command::new("movement") + .args(&[ + "move", + "create-resource-account-and-publish-package", + "--assume-yes", + "--address-name", + "moveth", + "--seed", + &random_seed, + "--package-dir", + "../move-modules", + ]) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .output() + .expect("Failed to execute command"); + + if !output2.stdout.is_empty() { + eprintln!("stdout: {}", String::from_utf8_lossy(&output2.stdout)); + } + + if !output2.stderr.is_empty() { + eprintln!("stderr: {}", String::from_utf8_lossy(&output2.stderr)); + } + + if movement_dir.exists() { + fs::remove_dir_all(movement_dir).expect("Failed to delete .movement directory"); + println!(".movement directory deleted successfully."); + } + + // Read the existing content of Move.toml + let move_toml_content = + fs::read_to_string(&move_toml_path).expect("Failed to read Move.toml file"); + + // Directly assign the address + let final_address = "0xcafe"; + + // Directly assign the formatted resource address + let final_formatted_resource_address = + "0xc3bb8488ab1a5815a9d543d7e41b0e0df46a7396f89b22821f07a4362f75ddc5"; + + let updated_content = move_toml_content + .lines() + .map(|line| match line { + _ if line.starts_with("resource_addr = ") => { + format!(r#"resource_addr = "{}""#, final_formatted_resource_address) + } + _ if line.starts_with("atomic_bridge = ") => { + format!(r#"atomic_bridge = "{}""#, final_formatted_resource_address) + } + _ if line.starts_with("moveth = ") => { + format!(r#"moveth = "{}""#, final_formatted_resource_address) + } + _ if line.starts_with("master_minter = ") => { + format!(r#"master_minter = "{}""#, final_formatted_resource_address) + } + _ if line.starts_with("minter = ") => { + format!(r#"minter = "{}""#, final_formatted_resource_address) + } + _ if line.starts_with("admin = ") => { + format!(r#"admin = "{}""#, final_formatted_resource_address) + } + _ if line.starts_with("origin_addr = ") => { + format!(r#"origin_addr = "{}""#, final_address) + } + _ if line.starts_with("pauser = ") => { + format!(r#"pauser = "{}""#, "0xdafe") + } + _ if line.starts_with("denylister = ") => { + format!(r#"denylister = "{}""#, "0xcade") + } + _ => line.to_string(), + }) + .collect::>() + .join("\n"); + + // Write the updated content back to Move.toml + let mut file = + fs::File::create(&move_toml_path).expect("Failed to open Move.toml file for writing"); + file.write_all(updated_content.as_bytes()) + .expect("Failed to write updated Move.toml file"); + + println!("Move.toml addresses updated successfully at the end of the test."); + + Ok(()) + } + + pub fn rest_client(&self) -> &Client { + &self.rest_client + } + + pub fn signer(&self) -> &LocalAccount { + &self.signer + } + + pub fn faucet_client(&self) -> Result<&Arc>> { + if let Some(faucet_client) = &self.faucet_client { + Ok(faucet_client) + } else { + Err(anyhow::anyhow!("Faucet client not initialized")) + } + } +} + +#[async_trait::async_trait] +impl BridgeContractCounterparty for MovementClient { + type Address = MovementAddress; + type Hash = [u8; 32]; + + async fn lock_bridge_transfer( + &mut self, + bridge_transfer_id: BridgeTransferId, + hash_lock: HashLock, + initiator: InitiatorAddress>, + recipient: RecipientAddress, + amount: Amount, + ) -> BridgeContractCounterpartyResult<()> { + let amount_value = match amount.0 { + AssetType::Moveth(value) => value, + _ => return Err(BridgeContractCounterpartyError::SerializationError), + }; + + let args = vec![ + utils::serialize_vec(&initiator.0)?, + utils::serialize_vec(&bridge_transfer_id.0[..])?, + utils::serialize_vec(&hash_lock.0[..])?, + utils::serialize_vec(&recipient.0 .0)?, + utils::serialize_u64(&amount_value)?, + ]; + + let payload = utils::make_aptos_payload( + self.counterparty_address, + COUNTERPARTY_MODULE_NAME, + "lock_bridge_transfer", + Vec::new(), + args, + ); + + utils::send_and_confirm_aptos_transaction(&self.rest_client, self.signer.as_ref(), payload) + .await + .map_err(|_| BridgeContractCounterpartyError::LockTransferError)?; + + Ok(()) + } + + async fn complete_bridge_transfer( + &mut self, + bridge_transfer_id: BridgeTransferId, + preimage: HashLockPreImage, + ) -> BridgeContractCounterpartyResult<()> { + let args2 = vec![ + utils::serialize_vec(&bridge_transfer_id.0[..])?, + utils::serialize_vec(&preimage.0)?, + ]; + + let payload = utils::make_aptos_payload( + self.counterparty_address, + COUNTERPARTY_MODULE_NAME, + "complete_bridge_transfer", + Vec::new(), + args2, + ); + + utils::send_and_confirm_aptos_transaction(&self.rest_client, self.signer.as_ref(), payload) + .await + .map_err(|_| BridgeContractCounterpartyError::CompleteTransferError)?; + + Ok(()) + } + + async fn abort_bridge_transfer( + &mut self, + bridge_transfer_id: BridgeTransferId, + ) -> BridgeContractCounterpartyResult<()> { + let args3 = vec![utils::serialize_vec(&bridge_transfer_id.0[..])?]; + let payload = utils::make_aptos_payload( + self.counterparty_address, + COUNTERPARTY_MODULE_NAME, + "abort_bridge_transfer", + Vec::new(), + args3, + ); + + utils::send_and_confirm_aptos_transaction(&self.rest_client, self.signer.as_ref(), payload) + .await + .map_err(|_| BridgeContractCounterpartyError::AbortTransferError)?; + + Ok(()) + } + + async fn get_bridge_transfer_details( + &mut self, + bridge_transfer_id: BridgeTransferId<[u8; 32]>, + ) -> Result< + Option>, + BridgeContractCounterpartyError, + > { + // Convert the bridge_transfer_id to a hex string + let bridge_transfer_id_hex = format!("0x{}", hex::encode(bridge_transfer_id.0)); + + // Construct the ViewRequest + let view_request = ViewRequest { + function: EntryFunctionId { + module: MoveModuleId { + address: self.counterparty_address.clone().into(), + name: aptos_api_types::IdentifierWrapper( + Identifier::new("atomic_bridge_counterparty") + .map_err(|_| BridgeContractCounterpartyError::FunctionViewError)?, + ), + }, + name: aptos_api_types::IdentifierWrapper( + Identifier::new("bridge_transfers") + .map_err(|_| BridgeContractCounterpartyError::FunctionViewError)?, + ), + }, + type_arguments: vec![], + arguments: vec![serde_json::json!(bridge_transfer_id_hex)], + }; + + // Send the request to the "/view" endpoint using JSON + let response: Response> = self + .rest_client + .view(&view_request, None) + .await + .map_err(|_| BridgeContractCounterpartyError::CallError)?; + + // Extract and parse the response + let values = response.inner(); + + if values.len() != 6 { + return Err(BridgeContractCounterpartyError::InvalidResponseLength); + } + + let originator = utils::val_as_str(values.get(0))?; + let recipient = utils::val_as_str(values.get(1))?; + let amount = utils::val_as_str(values.get(2))? + .parse::() + .map_err(|_| BridgeContractCounterpartyError::SerializationError)?; + let hash_lock = utils::val_as_str(values.get(3))?; + let state = utils::val_as_u64(values.get(5))? as u8; + + // Convert the originator, recipient, and hash_lock + let originator_address = AccountAddress::from_hex_literal(originator) + .map_err(|_| BridgeContractCounterpartyError::SerializationError)?; + let recipient_address_bytes = hex::decode(&recipient[2..]) + .map_err(|_| BridgeContractCounterpartyError::SerializationError)?; + let hash_lock_array: [u8; 32] = hex::decode(&hash_lock[2..]) + .map_err(|_| BridgeContractCounterpartyError::SerializationError)? + .try_into() + .map_err(|_| BridgeContractCounterpartyError::SerializationError)?; + + // Create the BridgeTransferDetails struct + let details: BridgeTransferDetails = BridgeTransferDetails { + bridge_transfer_id, + initiator_address: InitiatorAddress(MovementAddress(originator_address)), + recipient_address: RecipientAddress(recipient_address_bytes), + amount: Amount(AssetType::Moveth(amount)), + hash_lock: HashLock(hash_lock_array), + state, + }; + Ok(Some(details)) + } +} + +#[async_trait::async_trait] +impl BridgeContractInitiator for MovementClient { + type Address = MovementAddress; + type Hash = [u8; 32]; + + async fn initiate_bridge_transfer( + &mut self, + _initiator: InitiatorAddress, + recipient: RecipientAddress>, + hash_lock: HashLock, + amount: Amount, + ) -> BridgeContractInitiatorResult<()> { + let amount_value = match amount.0 { + AssetType::Moveth(value) => value, + _ => return Err(BridgeContractInitiatorError::ConversionError), + }; + debug!("Amount value: {:?}", amount_value); + + let args = vec![ + utils::serialize_vec_initiator(&recipient.0)?, + utils::serialize_vec_initiator(&hash_lock.0[..])?, + utils::serialize_u64_initiator(&amount_value)?, + ]; + + let payload = utils::make_aptos_payload( + self.counterparty_address, + "atomic_bridge_initiator", + "initiate_bridge_transfer", + Vec::new(), + args, + ); + + utils::send_and_confirm_aptos_transaction(&self.rest_client, self.signer.as_ref(), payload) + .await + .map_err(|_| BridgeContractInitiatorError::InitiateTransferError)?; + + Ok(()) + } + + async fn complete_bridge_transfer( + &mut self, + bridge_transfer_id: BridgeTransferId<::Hash>, + secret: HashLockPreImage, + ) -> BridgeContractInitiatorResult<()> { + let args = vec![ + utils::serialize_vec_initiator(&bridge_transfer_id.0[..])?, + utils::serialize_vec_initiator(&secret.0)?, + ]; + + let payload = utils::make_aptos_payload( + self.counterparty_address, + "atomic_bridge_initiator", + "complete_bridge_transfer", + Vec::new(), + args, + ); + + utils::send_and_confirm_aptos_transaction(&self.rest_client, self.signer.as_ref(), payload) + .await + .map_err(|_| BridgeContractInitiatorError::CompleteTransferError)?; + + Ok(()) + } + + async fn refund_bridge_transfer( + &mut self, + bridge_transfer_id: BridgeTransferId<::Hash>, + ) -> BridgeContractInitiatorResult<()> { + let args = vec![utils::serialize_vec_initiator(&bridge_transfer_id.0[..])?]; + + let payload = utils::make_aptos_payload( + self.counterparty_address, + "atomic_bridge_initiator", + "refund_bridge_transfer", + Vec::new(), + args, + ); + + utils::send_and_confirm_aptos_transaction(&self.rest_client, self.signer.as_ref(), payload) + .await + .map_err(|_| BridgeContractInitiatorError::ConversionError)?; + + Ok(()) + } + + async fn get_bridge_transfer_details( + &mut self, + bridge_transfer_id: BridgeTransferId<::Hash>, + ) -> BridgeContractInitiatorResult>> { + let bridge_transfer_id_hex = format!("0x{}", hex::encode(bridge_transfer_id.0)); + + let view_request = ViewRequest { + function: EntryFunctionId { + module: MoveModuleId { + address: self.counterparty_address.clone().into(), + name: aptos_api_types::IdentifierWrapper( + Identifier::new("atomic_bridge_initiator") + .map_err(|_| BridgeContractInitiatorError::FunctionViewError)?, + ), + }, + name: aptos_api_types::IdentifierWrapper( + Identifier::new("bridge_transfers") + .map_err(|_| BridgeContractInitiatorError::FunctionViewError)?, + ), + }, + type_arguments: vec![], + arguments: vec![serde_json::json!(bridge_transfer_id_hex)], + }; + + let response: Response> = self + .rest_client + .view(&view_request, None) + .await + .map_err(|_| BridgeContractInitiatorError::CallError)?; + + let values = response.inner(); + + if values.len() != 6 { + return Err(BridgeContractInitiatorError::InvalidResponseLength); + } + + let originator = utils::val_as_str_initiator(values.get(0))?; + let recipient = utils::val_as_str_initiator(values.get(1))?; + let amount = utils::val_as_str_initiator(values.get(2))? + .parse::() + .map_err(|_| BridgeContractInitiatorError::SerializationError)?; + let hash_lock = utils::val_as_str_initiator(values.get(3))?; + let state = utils::val_as_u64_initiator(values.get(5))? as u8; + + let originator_address = AccountAddress::from_hex_literal(originator) + .map_err(|_| BridgeContractInitiatorError::SerializationError)?; + let recipient_address_bytes = hex::decode(&recipient[2..]) + .map_err(|_| BridgeContractInitiatorError::SerializationError)?; + let hash_lock_array: [u8; 32] = hex::decode(&hash_lock[2..]) + .map_err(|_| BridgeContractInitiatorError::SerializationError)? + .try_into() + .map_err(|_| BridgeContractInitiatorError::SerializationError)?; + + let details = BridgeTransferDetails { + bridge_transfer_id, + initiator_address: InitiatorAddress(MovementAddress(originator_address)), + recipient_address: RecipientAddress(recipient_address_bytes), + amount: Amount(AssetType::Moveth(amount)), + hash_lock: HashLock(hash_lock_array), + state, + }; + + Ok(Some(details)) + } +} + +// Should feature `bridge-test` flag after https://github.com/movementlabsxyz/movement/pull/574 is merged +impl MovementClient { + pub async fn counterparty_set_timelock( + &mut self, + time_lock: u64, + ) -> Result<(), BridgeContractCounterpartyError> { + let args = vec![utils::serialize_u64(&time_lock)?]; + + let payload = utils::make_aptos_payload( + self.counterparty_address, + "atomic_bridge_counterparty", + "set_time_lock_duration", + Vec::new(), + args, + ); + + println!("payload {:?}", &payload); + + utils::send_and_confirm_aptos_transaction(&self.rest_client, self.signer.as_ref(), payload) + .await + .map_err(|_| BridgeContractCounterpartyError::CallError)?; + + Ok(()) + } + + pub async fn counterparty_time_lock_duration( + &mut self, + ) -> Result { + let view_request = ViewRequest { + function: EntryFunctionId { + module: MoveModuleId { + address: self.counterparty_address.into(), + name: aptos_api_types::IdentifierWrapper( + Identifier::new("atomic_bridge_counterparty") + .map_err(|_| BridgeContractCounterpartyError::FunctionViewError)?, + ), + }, + name: aptos_api_types::IdentifierWrapper( + Identifier::new("get_time_lock_duration") + .map_err(|_| BridgeContractCounterpartyError::FunctionViewError)?, + ), + }, + type_arguments: vec![], + arguments: vec![], + }; + + let response: Response> = self + .rest_client + .view(&view_request, None) + .await + .map_err(|_| BridgeContractCounterpartyError::CallError)?; + + let values = response.inner(); + let timelock = utils::val_as_u64(values.first())?; + Ok(timelock) + } + + pub async fn initiator_set_timelock( + &mut self, + time_lock: u64, + ) -> Result<(), BridgeContractInitiatorError> { + let args = vec![utils::serialize_u64(&time_lock).expect("Failed to serialize time lock")]; + + println!("counterparty address {:?}", &self.counterparty_address); + + let payload = utils::make_aptos_payload( + self.counterparty_address, + "atomic_bridge_initiator", + "set_time_lock_duration", + Vec::new(), + args, + ); + + utils::send_and_confirm_aptos_transaction(&self.rest_client, self.signer.as_ref(), payload) + .await + .map_err(|_| BridgeContractInitiatorError::CallError)?; + + Ok(()) + } + + pub async fn initiator_time_lock_duration( + &mut self, + ) -> Result { + let view_request = ViewRequest { + function: EntryFunctionId { + module: MoveModuleId { + address: self.counterparty_address.into(), + name: aptos_api_types::IdentifierWrapper( + Identifier::new("atomic_bridge_initiator") + .map_err(|_| BridgeContractInitiatorError::FunctionViewError)?, + ), + }, + name: aptos_api_types::IdentifierWrapper( + Identifier::new("get_time_lock_duration") + .map_err(|_| BridgeContractInitiatorError::FunctionViewError)?, + ), + }, + type_arguments: vec![], + arguments: vec![], + }; + + let response: Response> = self + .rest_client + .view(&view_request, None) + .await + .map_err(|_| BridgeContractInitiatorError::CallError)?; + + let values = response.inner(); + let timelock = utils::val_as_u64_initiator(values.first())?; + Ok(timelock) + } +} diff --git a/protocol-units/bridge/chains/movement/src/types.rs b/protocol-units/bridge/chains/movement/src/types.rs new file mode 100644 index 000000000..b559b9d20 --- /dev/null +++ b/protocol-units/bridge/chains/movement/src/types.rs @@ -0,0 +1 @@ +pub type MovementValue = u64; diff --git a/protocol-units/bridge/chains/movement/src/utils.rs b/protocol-units/bridge/chains/movement/src/utils.rs new file mode 100644 index 000000000..c7bad0b77 --- /dev/null +++ b/protocol-units/bridge/chains/movement/src/utils.rs @@ -0,0 +1,286 @@ +use crate::MovementClient; +use anyhow::{Context, Result}; +use aptos_sdk::{ + crypto::ed25519::Ed25519Signature, + move_types::{ + account_address::AccountAddressParseError, + ident_str, + language_storage::{ModuleId, TypeTag}, + }, + rest_client::{ + aptos_api_types::{ + EntryFunctionId, MoveType, Transaction as AptosTransaction, TransactionInfo, + ViewRequest, + }, + Client as RestClient, Transaction, + }, + transaction_builder::TransactionFactory, + types::{ + account_address::AccountAddress, + chain_id::ChainId, + transaction::{EntryFunction, SignedTransaction, TransactionPayload}, + LocalAccount, + }, +}; +use bridge_shared::bridge_contracts::{BridgeContractCounterpartyError, BridgeContractInitiatorError}; +use derive_new::new; +use serde::{Deserialize, Serialize}; +use serde_json::Value; +use std::str::FromStr; +use thiserror::Error; +use tracing::log::{debug, info, error}; + +#[derive(Debug, Error)] +pub enum MovementAddressError { + #[error("Invalid hex string")] + InvalidHexString, + #[error("Invalid byte length for AccountAddress")] + InvalidByteLength, + #[error("Invalid AccountAddress")] + AccountParseError(#[from] AccountAddressParseError), +} + +#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)] +pub struct MovementAddress(pub AccountAddress); + +impl From<&MovementAddress> for Vec { + fn from(address: &MovementAddress) -> Vec { + address.0.to_vec() + } +} + +impl FromStr for MovementAddress { + type Err = MovementAddressError; + + fn from_str(s: &str) -> Result { + AccountAddress::from_str(s).map(MovementAddress).map_err(From::from) + } +} + +impl std::fmt::Display for MovementAddress { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.0.to_standard_string()) + } +} + +impl From> for MovementAddress { + fn from(vec: Vec) -> Self { + // Ensure the vector has the correct length + assert_eq!(vec.len(), AccountAddress::LENGTH); + + let account_address = + AccountAddress::from_bytes(vec).expect("Invalid byte length for AccountAddress"); + MovementAddress(account_address) + } +} + +impl From<&str> for MovementAddress { + fn from(s: &str) -> Self { + let s = s.trim_start_matches("0x"); + let bytes = hex::decode(s).expect("Invalid hex string"); + bytes.into() + } +} + +/// limit of gas unit +const GAS_UNIT_LIMIT: u64 = 100000; +/// minimum price of gas unit of aptos chains +pub const GAS_UNIT_PRICE: u64 = 100; + +/// Wrapper struct that adds indexing information to a type +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, new)] +pub struct Indexed { + inner: T, + #[new(default)] + /// Optional sequence data that is useful during indexing + pub sequence: Option, +} + +/// Send Aptos Transaction +pub async fn send_and_confirm_aptos_transaction( + rest_client: &RestClient, + signer: &LocalAccount, + payload: TransactionPayload, +) -> Result { + info!("Starting send_aptos_transaction"); + let state = rest_client + .get_ledger_information() + .await + .map_err(|e| format!("Failed in getting chain id: {}", e))? + .into_inner(); + info!("Ledger information retrieved: chain_id = {}", state.chain_id); + + let transaction_factory = TransactionFactory::new(ChainId::new(state.chain_id)) + .with_gas_unit_price(100) + .with_max_gas_amount(GAS_UNIT_LIMIT); + let latest_account_info = rest_client + .get_account(signer.address()) + .await + .map_err(|e| format!("Failed to get account information: {}", e))?; + let account = latest_account_info.into_inner(); + let latest_sequence_number = account.sequence_number; + + let raw_tx = transaction_factory + .payload(payload) + .sender(signer.address()) + .sequence_number(latest_sequence_number) + .build(); + + let signed_tx = signer.sign_transaction(raw_tx); + + debug!("Signed TX: {:?}", signed_tx); + + let response = rest_client + .submit_and_wait(&signed_tx) + .await + + .map_err(|e| { + let err_msg = format!("Transaction submission error: {}", e.to_string()); + error!("{}", err_msg); // Log the error in detail + err_msg + })?; + + let txn = response.into_inner(); + debug!("Response: {:?}", txn); + + match &txn { + Transaction::UserTransaction(user_txn) => { + if !user_txn.info.success { + return Err(format!( + "Transaction failed with status: {}",user_txn.info.vm_status)); + } + }, + _ => return Err("Expected a UserTransaction, but got a different transaction type.".to_string()), + } + + Ok(txn) +} + +pub fn val_as_str(value: Option<&Value>) -> Result<&str, BridgeContractCounterpartyError> { + value + .as_ref() + .and_then(|v| v.as_str()) + .ok_or(BridgeContractCounterpartyError::SerializationError) +} + +pub fn val_as_u64(value: Option<&Value>) -> Result { + value + .as_ref() + .and_then(|v| v.as_u64()) + .ok_or(BridgeContractCounterpartyError::SerializationError) +} + +pub fn val_as_str_initiator(value: Option<&Value>) -> Result<&str, BridgeContractInitiatorError> { + value.as_ref().and_then(|v| v.as_str()).ok_or(BridgeContractInitiatorError::SerializationError) +} + +pub fn val_as_u64_initiator(value: Option<&Value>) -> Result { + value + .as_ref() + .and_then(|v| v.as_u64()) + .ok_or(BridgeContractInitiatorError::SerializationError) +} + +pub fn serialize_u64(value: &u64) -> Result, BridgeContractCounterpartyError> { + bcs::to_bytes(value).map_err(|_| BridgeContractCounterpartyError::SerializationError) +} + +pub fn serialize_vec( + value: &T, +) -> Result, BridgeContractCounterpartyError> { + bcs::to_bytes(value).map_err(|_| BridgeContractCounterpartyError::SerializationError) +} + +pub fn serialize_u64_initiator(value: &u64) -> Result, BridgeContractInitiatorError> { + bcs::to_bytes(value).map_err(|_| BridgeContractInitiatorError::SerializationError) +} + +pub fn serialize_address_initiator(address: &AccountAddress) -> Result, BridgeContractInitiatorError> { + bcs::to_bytes(address).map_err(|_| BridgeContractInitiatorError::SerializationError) +} + +pub fn serialize_vec_initiator(value: &T) -> Result, BridgeContractInitiatorError> { + bcs::to_bytes(value).map_err(|_| BridgeContractInitiatorError::SerializationError) +} + + +// This is not used for now, but we may need to use it in later for estimating gas. +pub async fn simulate_aptos_transaction( + aptos_client: &MovementClient, + signer: &mut LocalAccount, + payload: TransactionPayload, +) -> Result { + let state = aptos_client + .rest_client + .get_ledger_information() + .await + .context("Failed in getting chain id")? + .into_inner(); + + let transaction_factory = TransactionFactory::new(ChainId::new(state.chain_id)) + .with_gas_unit_price(GAS_UNIT_PRICE) + .with_max_gas_amount(GAS_UNIT_LIMIT); + + let latest_account_info = aptos_client.rest_client.get_account(signer.address()).await?; + let account = latest_account_info.into_inner(); + let latest_sequence_number = account.sequence_number; + + let raw_tx = transaction_factory + .payload(payload) + .sender(signer.address()) + .sequence_number(latest_sequence_number) + .build(); + + let signed_tx = SignedTransaction::new( + raw_tx, + signer.public_key().clone(), + Ed25519Signature::try_from([0u8; 64].as_ref()).unwrap(), + ); + + let response_txns = aptos_client.rest_client.simulate(&signed_tx).await?.into_inner(); + let response = response_txns[0].clone(); + + Ok(response.info) +} + +/// Make Aptos Transaction Payload +pub fn make_aptos_payload( + package_address: AccountAddress, + module_name: &'static str, + function_name: &'static str, + ty_args: Vec, + args: Vec>, +) -> TransactionPayload { + TransactionPayload::EntryFunction(EntryFunction::new( + ModuleId::new(package_address, ident_str!(module_name).to_owned()), + ident_str!(function_name).to_owned(), + ty_args, + args, + )) +} + +/// Send View Request +pub async fn send_view_request( + aptos_client: &MovementClient, + package_address: String, + module_name: String, + function_name: String, + type_arguments: Vec, + arguments: Vec, +) -> Result, anyhow::Error> { + let view_response = aptos_client + .rest_client + .view( + &ViewRequest { + function: EntryFunctionId::from_str(&format!( + "{package_address}::{module_name}::{function_name}" + )) + .unwrap(), + type_arguments, + arguments, + }, + Option::None, + ) + .await?; + Ok(view_response.inner().clone()) +} diff --git a/protocol-units/bridge/cli/Cargo.toml b/protocol-units/bridge/cli/Cargo.toml index 77a031573..d32e55638 100644 --- a/protocol-units/bridge/cli/Cargo.toml +++ b/protocol-units/bridge/cli/Cargo.toml @@ -9,9 +9,26 @@ homepage.workspace = true publish.workspace = true rust-version.workspace = true -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - [dependencies] +bridge-shared.workspace = true +ethereum-bridge.workspace = true +movement-bridge.workspace = true + +clap.workspace = true +tokio.workspace = true +anyhow.workspace = true +tracing.workspace = true +serde.workspace = true +tracing-subscriber.workspace = true +serde_json.workspace = true +uuid.workspace = true + +alloy.workspace = true + +directories = "5.0" +url.workspace = true +eyre = "0.6.12" + [lints] workspace = true diff --git a/protocol-units/bridge/cli/src/clap.rs b/protocol-units/bridge/cli/src/clap.rs new file mode 100644 index 000000000..a01b1b5fb --- /dev/null +++ b/protocol-units/bridge/cli/src/clap.rs @@ -0,0 +1,17 @@ +pub mod eth_to_movement; +use clap::{Parser, Subcommand}; + +#[derive(Parser)] +#[command(name = "Movementlabs Bridge CLI")] +#[command(about = "Command line interface to perform an atomic bridge transfers", long_about = None)] +pub struct CliOptions { + #[command(subcommand)] + pub command: Commands, +} + +#[derive(Subcommand)] +pub enum Commands { + /// Ethereum to Movement Labs bridge commands + #[command(subcommand)] + BridgeEthToMovETH(eth_to_movement::Commands), +} diff --git a/protocol-units/bridge/cli/src/clap/eth_to_movement.rs b/protocol-units/bridge/cli/src/clap/eth_to_movement.rs new file mode 100644 index 000000000..27c7663f8 --- /dev/null +++ b/protocol-units/bridge/cli/src/clap/eth_to_movement.rs @@ -0,0 +1,60 @@ +use alloy::signers::local::PrivateKeySigner; +use clap::{Args, Subcommand}; +use ethereum_bridge::types::EthAddress; +use movement_bridge::utils::MovementAddress; +use url::Url; + +#[derive(Args, Clone, Debug)] +pub struct EthSharedArgs { + /// Private key of the Ethereum signer + #[arg(long)] + pub eth_private_key: PrivateKeySigner, + + /// URL for the Ethereum RPC + #[arg(long, default_value = "http://localhost:8545")] + pub eth_rpc_url: Url, + + /// URL for the Ethereum WebSocket + #[arg(long, default_value = "ws://localhost:8545")] + pub eth_ws_url: Url, + + /// Ethereum contract address for the initiator + #[arg(long, default_value = "0x0000000000000000000000000000000000000000")] + pub eth_initiator_contract: EthAddress, + + /// Ethereum contract address for the counterparty + #[arg(long, default_value = "0x0000000000000000000000000000000000000000")] + pub eth_counterparty_contract: EthAddress, + + /// Ethereum contract address for the counterparty + #[arg(long, default_value = "0x0000000000000000000000000000000000000000")] + pub eth_weth_contract: EthAddress, + + /// Gas limit for Ethereum transactions + #[arg(long, default_value_t = 10_000_000_000)] + pub eth_gas_limit: u64, +} + +#[derive(Subcommand)] +pub enum Commands { + /// Initiate a bridge transfer + Swap { + #[command(flatten)] + args: EthSharedArgs, + + /// The recipient address on the movement labs chain + recipient: MovementAddress, + + /// The amount of Ethereum to transfer in WEI + amount: u64, + }, + /// Resume a bridge transfer + Resume { + #[command(flatten)] + args: EthSharedArgs, + + /// The ID of the transfer to resume + #[arg(long)] + transfer_id: String, + }, +} diff --git a/protocol-units/bridge/cli/src/eth_to_moveth.rs b/protocol-units/bridge/cli/src/eth_to_moveth.rs new file mode 100644 index 000000000..739849fb3 --- /dev/null +++ b/protocol-units/bridge/cli/src/eth_to_moveth.rs @@ -0,0 +1,63 @@ +use crate::clap::eth_to_movement::{Commands, EthSharedArgs}; +use alloy::primitives::keccak256; +use anyhow::Result; +use bridge_shared::bridge_contracts::BridgeContractInitiator; +use bridge_shared::types::{ + Amount, AssetType, HashLock, HashLockPreImage, InitiatorAddress, RecipientAddress, +}; +use ethereum_bridge::{client::EthClient, types::EthAddress}; +use movement_bridge::utils::MovementAddress; + +pub async fn execute(command: &Commands) -> Result<()> { + match command { + Commands::Swap { args, recipient, amount } => initiate_swap(args, recipient, *amount).await, + Commands::Resume { args, transfer_id } => resume_swap(args, transfer_id).await, + } +} + +async fn initiate_swap( + args: &EthSharedArgs, + recipient: &MovementAddress, + amount: u64, +) -> Result<()> { + println!("Initiating swap to {:?} with amount {}", recipient, amount); + + let mut client = EthClient::new(args).await?; + + // Get the current block height + let current_block = client.get_block_number().await?; + println!("Current Ethereum block height: {}", current_block); + + // Convert signer's private key to EthAddress + let initiator_address = EthAddress(client.get_signer_address()); + let recipient_address = RecipientAddress(From::from(recipient)); + let hash_lock_pre_image = HashLockPreImage::random(); + let hash_lock = HashLock(From::from(keccak256(hash_lock_pre_image))); + let amount = Amount(AssetType::EthAndWeth((amount, 0))); + + // TODO: Store the swap details in the local database so they can be resumed in case of failure + + client + .initiate_bridge_transfer( + InitiatorAddress(initiator_address), + recipient_address, + hash_lock, + amount, + ) + .await?; + + // Now we need to listen to the blockchain to receive the correct events and match them accordingly. + + // TODO: I need the bridge transfer ID here to store the state of the swap. Therefore, + // the initiate bridge transfer function needs to be updated. + + println!("Swap initiated successfully"); + + Ok(()) +} + +async fn resume_swap(args: &EthSharedArgs, transfer_id: &str) -> Result<()> { + println!("Resuming transfer with ID: {}", transfer_id); + + Ok(()) +} diff --git a/protocol-units/bridge/cli/src/lib.rs b/protocol-units/bridge/cli/src/lib.rs new file mode 100644 index 000000000..4baeb9943 --- /dev/null +++ b/protocol-units/bridge/cli/src/lib.rs @@ -0,0 +1,4 @@ +pub mod clap; +pub mod eth_to_moveth; +pub mod state; +pub mod types; diff --git a/protocol-units/bridge/cli/src/main.rs b/protocol-units/bridge/cli/src/main.rs index a30eb952c..cd4b58906 100644 --- a/protocol-units/bridge/cli/src/main.rs +++ b/protocol-units/bridge/cli/src/main.rs @@ -1,3 +1,22 @@ -fn main() { - println!("Hello, world!"); +use bridge_cli::clap::{CliOptions, Commands}; +use clap::Parser; +use eyre::Result; + +#[tokio::main] +async fn main() -> Result<()> { + inner_main().await.map_err(|e| eyre::eyre!(e)) +} + +async fn inner_main() -> anyhow::Result<()> { + tracing_subscriber::fmt::init(); + + let cli = CliOptions::parse(); + + match &cli.command { + Commands::BridgeEthToMovETH(command) => { + bridge_cli::eth_to_moveth::execute(command).await?; + } + } + + Ok(()) } diff --git a/protocol-units/bridge/cli/src/state.rs b/protocol-units/bridge/cli/src/state.rs new file mode 100644 index 000000000..e561f1f21 --- /dev/null +++ b/protocol-units/bridge/cli/src/state.rs @@ -0,0 +1,70 @@ +use anyhow::{Context, Result}; +use directories::ProjectDirs; +use serde::{Deserialize, Serialize}; +use std::path::PathBuf; +use std::{fs, path::Path}; + +#[derive(Serialize, Deserialize)] +pub struct SwapState { + pub id: String, + pub swap_type: SwapType, + pub block_height: u64, + pub block_height_timeout: u64, + pub recipient: String, + pub amount: u64, + pub status: SwapStatus, +} + +#[derive(Serialize, Deserialize)] +pub enum SwapType { + EthToMovement, + MovementToEth, +} + +impl std::fmt::Display for SwapType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + SwapType::EthToMovement => write!(f, "eth_to_movement"), + SwapType::MovementToEth => write!(f, "movement_to_eth"), + } + } +} +impl std::convert::AsRef for SwapType { + fn as_ref(&self) -> &Path { + match self { + SwapType::EthToMovement => Path::new("eth_to_movement"), + SwapType::MovementToEth => Path::new("movement_to_eth"), + } + } +} + +#[derive(Serialize, Deserialize)] +pub enum SwapStatus { + Initiated, + Completed, + Failed, +} + +fn ensure_state_dir() -> Result { + let proj_dirs = ProjectDirs::from("xyz", "movementlabs", "bridge-cli") + .context("Failed to get project directories")?; + let state_dir = proj_dirs.data_local_dir(); + fs::create_dir_all(state_dir)?; + Ok(state_dir.to_path_buf()) +} + +pub fn save_swap_state(state: &SwapState) -> Result<()> { + let state_dir = ensure_state_dir()?; + let file_path = state_dir.join(&state.swap_type).join(format!("{}.json", state.id)); + let json = serde_json::to_string_pretty(state)?; + fs::write(file_path, json)?; + Ok(()) +} + +pub fn load_swap_state(swap_type: &SwapType, id: &str) -> Result { + let state_dir = ensure_state_dir()?; + let file_path = state_dir.join(swap_type).join(format!("{}.json", id)); + let json = fs::read_to_string(file_path)?; + let state: SwapState = serde_json::from_str(&json)?; + Ok(state) +} diff --git a/protocol-units/bridge/cli/src/types.rs b/protocol-units/bridge/cli/src/types.rs new file mode 100644 index 000000000..faa5cbe51 --- /dev/null +++ b/protocol-units/bridge/cli/src/types.rs @@ -0,0 +1,22 @@ +use crate::clap::eth_to_movement::EthSharedArgs; +use ethereum_bridge::client::Config; + +impl From for Config { + fn from(args: EthSharedArgs) -> Self { + Self { + rpc_url: args.eth_rpc_url, + ws_url: args.eth_ws_url, + signer_private_key: args.eth_private_key, + initiator_contract: Some(args.eth_initiator_contract.0), + counterparty_contract: Some(args.eth_counterparty_contract.0), + weth_contract: Some(args.eth_weth_contract.0), + gas_limit: args.eth_gas_limit, + } + } +} + +impl From<&EthSharedArgs> for Config { + fn from(args: &EthSharedArgs) -> Self { + From::from(args.clone()) + } +} diff --git a/protocol-units/bridge/cli/tests/integration_tests.rs b/protocol-units/bridge/cli/tests/integration_tests.rs new file mode 100644 index 000000000..ca18105a3 --- /dev/null +++ b/protocol-units/bridge/cli/tests/integration_tests.rs @@ -0,0 +1,60 @@ +use alloy::{ + node_bindings::Anvil, + primitives::{Address, FixedBytes}, + providers::ProviderBuilder, +}; +use bridge_cli::{ + clap::eth_to_movement::{self, EthSharedArgs}, + eth_to_moveth, +}; +use ethereum_bridge::types::{AtomicBridgeInitiator, EthAddress}; +use movement_bridge::utils::MovementAddress; +use std::str::FromStr; +use url::Url; + +#[tokio::test] +async fn test_swap() -> eyre::Result<()> { + // Start Anvil instance + let anvil = Anvil::new().try_spawn()?; + let rpc_url = anvil.endpoint().parse()?; + let provider = ProviderBuilder::new().on_http(rpc_url); + + // Deploy contracts + let wallet = anvil.keys()[0].clone(); + + let initiator_contract = AtomicBridgeInitiator::deploy(provider).await?; + + // Set up EthSharedArgs + let eth_shared_args: EthSharedArgs = EthSharedArgs { + eth_private_key: wallet.into(), + eth_rpc_url: Url::parse(&anvil.endpoint()).unwrap(), + eth_ws_url: Url::parse(&anvil.endpoint().replace("http", "ws")).unwrap(), + eth_initiator_contract: EthAddress(*initiator_contract.address()), + eth_counterparty_contract: EthAddress(Address::ZERO), // Not needed for this test + eth_weth_contract: EthAddress(Address::ZERO), // Not needed for this test + eth_gas_limit: 3000000, + }; + + // Prepare swap parameters + let recipient: MovementAddress = + "0x000000000000000000000000000000000000000000000000000000000000000A" + .parse() + .unwrap(); + let amount = 1000000000000000000u64; // 1 ETH in wei + + // Execute the swap + let result = eth_to_moveth::execute(ð_to_movement::Commands::Swap { + args: eth_shared_args, + recipient: From::from(recipient), + amount, + }) + .await; + + assert!(result.is_ok(), "Swap initiation failed: {:?}", result.err()); + + // Check on the contract if we have a bridge transfer initiated + let bridge_transfer_id = FixedBytes::from_str("0x1234567890123456789012345678901234567890")?; + initiator_contract.bridgeTransfers(bridge_transfer_id).call().await?; + + Ok(()) +} diff --git a/protocol-units/bridge/contracts/README.md b/protocol-units/bridge/contracts/README.md index 9265b4558..f91112bab 100644 --- a/protocol-units/bridge/contracts/README.md +++ b/protocol-units/bridge/contracts/README.md @@ -24,7 +24,7 @@ $ forge build ### Test ```shell -$ forge test +$ forge test --fork-url https://ethereum-sepolia.blockpi.network/v1/rpc/public ``` ### Format diff --git a/protocol-units/bridge/contracts/foundry.toml b/protocol-units/bridge/contracts/foundry.toml index b6449f95a..081338885 100644 --- a/protocol-units/bridge/contracts/foundry.toml +++ b/protocol-units/bridge/contracts/foundry.toml @@ -3,6 +3,8 @@ src = "src" out = "out" libs = ["lib"] -solc_version = "0.8.23" +solc = "0.8.26" +evm_version = "cancun" + # See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options -gas_report = true \ No newline at end of file +gas_report = true diff --git a/protocol-units/bridge/contracts/lib/forge-std b/protocol-units/bridge/contracts/lib/forge-std new file mode 160000 index 000000000..5a802d7c1 --- /dev/null +++ b/protocol-units/bridge/contracts/lib/forge-std @@ -0,0 +1 @@ +Subproject commit 5a802d7c10abb4bbfb3e7214c75052ef9e6a06f8 diff --git a/protocol-units/bridge/contracts/lib/openzeppelin-contracts b/protocol-units/bridge/contracts/lib/openzeppelin-contracts new file mode 160000 index 000000000..530179a71 --- /dev/null +++ b/protocol-units/bridge/contracts/lib/openzeppelin-contracts @@ -0,0 +1 @@ +Subproject commit 530179a71f435e85ae9df9b9f12b5637cf229e5c diff --git a/protocol-units/bridge/contracts/lib/openzeppelin-contracts-upgradeable b/protocol-units/bridge/contracts/lib/openzeppelin-contracts-upgradeable new file mode 160000 index 000000000..f231c5cb8 --- /dev/null +++ b/protocol-units/bridge/contracts/lib/openzeppelin-contracts-upgradeable @@ -0,0 +1 @@ +Subproject commit f231c5cb86c0c045dc0b23d8f3beeaf0d68dccc7 diff --git a/protocol-units/bridge/contracts/src/AtomicBridgeCounterparty.sol b/protocol-units/bridge/contracts/src/AtomicBridgeCounterparty.sol new file mode 100644 index 000000000..ccbe96f82 --- /dev/null +++ b/protocol-units/bridge/contracts/src/AtomicBridgeCounterparty.sol @@ -0,0 +1,98 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.22; + +import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; +import {IAtomicBridgeCounterparty} from "./IAtomicBridgeCounterparty.sol"; +import {AtomicBridgeInitiator} from "./AtomicBridgeInitiator.sol"; + +contract AtomicBridgeCounterparty is IAtomicBridgeCounterparty, OwnableUpgradeable { + enum MessageState { + PENDING, + COMPLETED, + REFUNDED + } + + struct BridgeTransferDetails { + bytes32 originator; + address recipient; + uint256 amount; + bytes32 hashLock; + uint256 timeLock; + MessageState state; + } + + // Reference to the AtomicBridgeInitiator contract + AtomicBridgeInitiator public atomicBridgeInitiator; + mapping(bytes32 => BridgeTransferDetails) public bridgeTransfers; + + // Configurable time lock duration + uint256 public counterpartyTimeLockDuration; + + function initialize(address _atomicBridgeInitiator, address owner, uint256 _timeLockDuration) public initializer { + if (_atomicBridgeInitiator == address(0)) revert ZeroAddress(); + atomicBridgeInitiator = AtomicBridgeInitiator(_atomicBridgeInitiator); + __Ownable_init(owner); + + // Set the configurable time lock duration + counterpartyTimeLockDuration = _timeLockDuration; + } + + function setAtomicBridgeInitiator(address _atomicBridgeInitiator) external onlyOwner { + if (_atomicBridgeInitiator == address(0)) revert ZeroAddress(); + atomicBridgeInitiator = AtomicBridgeInitiator(_atomicBridgeInitiator); + } + + function setTimeLockDuration(uint256 _timeLockDuration) external onlyOwner { + counterpartyTimeLockDuration = _timeLockDuration; + } + + function lockBridgeTransfer( + bytes32 originator, + bytes32 bridgeTransferId, + bytes32 hashLock, + address recipient, + uint256 amount + ) external onlyOwner returns (bool) { + if (amount == 0) revert ZeroAmount(); + if (atomicBridgeInitiator.poolBalance() < amount) revert InsufficientWethBalance(); + + // The time lock is now based on the configurable duration + uint256 timeLock = block.timestamp + counterpartyTimeLockDuration; + + bridgeTransfers[bridgeTransferId] = BridgeTransferDetails({ + recipient: recipient, + originator: originator, + amount: amount, + hashLock: hashLock, + timeLock: timeLock, + state: MessageState.PENDING + }); + + emit BridgeTransferLocked(bridgeTransferId, recipient, amount, hashLock, counterpartyTimeLockDuration); + return true; + } + + function completeBridgeTransfer(bytes32 bridgeTransferId, bytes32 preImage) external { + BridgeTransferDetails storage details = bridgeTransfers[bridgeTransferId]; + if (details.state != MessageState.PENDING) revert BridgeTransferStateNotPending(); + bytes32 computedHash = keccak256(abi.encodePacked(preImage)); + if (computedHash != details.hashLock) revert InvalidSecret(); + if (block.timestamp > details.timeLock) revert TimeLockExpired(); + + details.state = MessageState.COMPLETED; + + atomicBridgeInitiator.withdrawWETH(details.recipient, details.amount); + + emit BridgeTransferCompleted(bridgeTransferId, preImage); + } + + function abortBridgeTransfer(bytes32 bridgeTransferId) external onlyOwner { + BridgeTransferDetails storage details = bridgeTransfers[bridgeTransferId]; + if (details.state != MessageState.PENDING) revert BridgeTransferStateNotPending(); + if (block.timestamp <= details.timeLock) revert TimeLockNotExpired(); + + details.state = MessageState.REFUNDED; + + emit BridgeTransferAborted(bridgeTransferId); + } +} \ No newline at end of file diff --git a/protocol-units/bridge/contracts/src/AtomicBridgeCounterpartyMOVE.sol b/protocol-units/bridge/contracts/src/AtomicBridgeCounterpartyMOVE.sol new file mode 100644 index 000000000..6ef06dbfe --- /dev/null +++ b/protocol-units/bridge/contracts/src/AtomicBridgeCounterpartyMOVE.sol @@ -0,0 +1,97 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.22; + +import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; +import {IAtomicBridgeCounterpartyMOVE} from "./IAtomicBridgeCounterpartyMOVE.sol"; +import {AtomicBridgeInitiatorMOVE} from "./AtomicBridgeInitiatorMOVE.sol"; + +contract AtomicBridgeCounterpartyMOVE is IAtomicBridgeCounterpartyMOVE, OwnableUpgradeable { + enum MessageState { + PENDING, + COMPLETED, + REFUNDED + } + + struct BridgeTransferDetails { + bytes32 originator; + address recipient; + uint256 amount; + bytes32 hashLock; + uint256 timeLock; + MessageState state; + } + + AtomicBridgeInitiatorMOVE public atomicBridgeInitiatorMOVE; + mapping(bytes32 => BridgeTransferDetails) public bridgeTransfers; + + // Configurable time lock duration + uint256 public counterpartyTimeLockDuration; + + function initialize(address _atomicBridgeInitiator, address owner, uint256 _timeLockDuration) public initializer { + if (_atomicBridgeInitiator == address(0)) revert ZeroAddress(); + atomicBridgeInitiatorMOVE = AtomicBridgeInitiatorMOVE(_atomicBridgeInitiator); + __Ownable_init(owner); + + // Set the configurable time lock duration + counterpartyTimeLockDuration = _timeLockDuration; + } + + function setAtomicBridgeInitiator(address _atomicBridgeInitiator) external onlyOwner { + if (_atomicBridgeInitiator == address(0)) revert ZeroAddress(); + atomicBridgeInitiatorMOVE = AtomicBridgeInitiatorMOVE(_atomicBridgeInitiator); + } + + function setTimeLockDuration(uint256 _timeLockDuration) external onlyOwner { + counterpartyTimeLockDuration = _timeLockDuration; + } + + function lockBridgeTransfer( + bytes32 originator, + bytes32 bridgeTransferId, + bytes32 hashLock, + address recipient, + uint256 amount + ) external onlyOwner returns (bool) { + if (amount == 0) revert ZeroAmount(); + if (atomicBridgeInitiatorMOVE.poolBalance() < amount) revert InsufficientMOVEBalance(); + + // The time lock is now based on the configurable duration + uint256 timeLock = block.timestamp + counterpartyTimeLockDuration; + + bridgeTransfers[bridgeTransferId] = BridgeTransferDetails({ + recipient: recipient, + originator: originator, + amount: amount, + hashLock: hashLock, + timeLock: timeLock, + state: MessageState.PENDING + }); + + emit BridgeTransferLocked(bridgeTransferId, recipient, amount, hashLock, counterpartyTimeLockDuration); + return true; + } + + function completeBridgeTransfer(bytes32 bridgeTransferId, bytes32 preImage) external { + BridgeTransferDetails storage details = bridgeTransfers[bridgeTransferId]; + if (details.state != MessageState.PENDING) revert BridgeTransferStateNotPending(); + bytes32 computedHash = keccak256(abi.encodePacked(preImage)); + if (computedHash != details.hashLock) revert InvalidSecret(); + if (block.timestamp > details.timeLock) revert TimeLockNotExpired(); + + details.state = MessageState.COMPLETED; + + atomicBridgeInitiatorMOVE.withdrawMOVE(details.recipient, details.amount); + + emit BridgeTransferCompleted(bridgeTransferId, preImage); + } + + function abortBridgeTransfer(bytes32 bridgeTransferId) external onlyOwner { + BridgeTransferDetails storage details = bridgeTransfers[bridgeTransferId]; + if (details.state != MessageState.PENDING) revert BridgeTransferStateNotPending(); + if (block.timestamp <= details.timeLock) revert TimeLockNotExpired(); + + details.state = MessageState.REFUNDED; + + emit BridgeTransferAborted(bridgeTransferId); + } +} diff --git a/protocol-units/bridge/contracts/src/AtomicBridgeInitator.sol b/protocol-units/bridge/contracts/src/AtomicBridgeInitiator.sol similarity index 54% rename from protocol-units/bridge/contracts/src/AtomicBridgeInitator.sol rename to protocol-units/bridge/contracts/src/AtomicBridgeInitiator.sol index 00c6356e6..3739ea033 100644 --- a/protocol-units/bridge/contracts/src/AtomicBridgeInitator.sol +++ b/protocol-units/bridge/contracts/src/AtomicBridgeInitiator.sol @@ -1,11 +1,10 @@ -// SPDX-License-Identifier: MIT pragma solidity ^0.8.22; import {IAtomicBridgeInitiator} from "./IAtomicBridgeInitiator.sol"; import {IWETH9} from "./IWETH9.sol"; -import {Initializable} from "@openzeppelin/contracts/proxy/utils/Initializable.sol"; +import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; -contract AtomicBridgeInitiator is IAtomicBridgeInitiator, Initializable { +contract AtomicBridgeInitiator is IAtomicBridgeInitiator, OwnableUpgradeable { enum MessageState { INITIALIZED, COMPLETED, @@ -21,18 +20,37 @@ contract AtomicBridgeInitiator is IAtomicBridgeInitiator, Initializable { MessageState state; } + // Mapping of bridge transfer ids to BridgeTransfer structs mapping(bytes32 => BridgeTransfer) public bridgeTransfers; + + // Total WETH pool balance + uint256 public poolBalance; + + address public counterpartyAddress; IWETH9 public weth; uint256 private nonce; - function initialize(address _weth) public initializer { + // Configurable time lock duration + uint256 public initiatorTimeLockDuration; + + // Initialize the contract with WETH address, owner, and a custom time lock duration + function initialize(address _weth, address owner, uint256 _timeLockDuration) public initializer { if (_weth == address(0)) { revert ZeroAddress(); } weth = IWETH9(_weth); + __Ownable_init(owner); + + // Set the custom time lock duration + initiatorTimeLockDuration = _timeLockDuration; + } + + function setCounterpartyAddress(address _counterpartyAddress) external onlyOwner { + if (_counterpartyAddress == address(0)) revert ZeroAddress(); + counterpartyAddress = _counterpartyAddress; } - function initiateBridgeTransfer(uint256 wethAmount, bytes32 recipient, bytes32 hashLock, uint256 timeLock) + function initiateBridgeTransfer(uint256 wethAmount, bytes32 recipient, bytes32 hashLock) external payable returns (bytes32 bridgeTransferId) @@ -40,31 +58,36 @@ contract AtomicBridgeInitiator is IAtomicBridgeInitiator, Initializable { address originator = msg.sender; uint256 ethAmount = msg.value; uint256 totalAmount = wethAmount + ethAmount; + // Ensure there is a valid total amount if (totalAmount == 0) { revert ZeroAmount(); } + // If msg.value is greater than 0, convert ETH to WETH if (ethAmount > 0) weth.deposit{value: ethAmount}(); - //Transfer WETH to this contract, revert if transfer fails + + // Transfer WETH to this contract, revert if transfer fails if (wethAmount > 0) { if (!weth.transferFrom(originator, address(this), wethAmount)) revert WETHTransferFailed(); } - nonce++; //increment the nonce - bridgeTransferId = - keccak256(abi.encodePacked(originator, recipient, hashLock, timeLock, block.number, nonce)); + // Update the pool balance + poolBalance += totalAmount; + + // Generate a unique nonce to prevent replay attacks, and generate a transfer ID + bridgeTransferId = keccak256(abi.encodePacked(originator, recipient, hashLock, initiatorTimeLockDuration, block.timestamp, nonce++)); bridgeTransfers[bridgeTransferId] = BridgeTransfer({ amount: totalAmount, originator: originator, recipient: recipient, hashLock: hashLock, - timeLock: block.number + timeLock, + timeLock: block.timestamp + initiatorTimeLockDuration, state: MessageState.INITIALIZED }); - emit BridgeTransferInitiated(bridgeTransferId, originator, recipient, totalAmount, hashLock, timeLock); + emit BridgeTransferInitiated(bridgeTransferId, originator, recipient, totalAmount, hashLock, initiatorTimeLockDuration); return bridgeTransferId; } @@ -72,19 +95,30 @@ contract AtomicBridgeInitiator is IAtomicBridgeInitiator, Initializable { BridgeTransfer storage bridgeTransfer = bridgeTransfers[bridgeTransferId]; if (bridgeTransfer.state != MessageState.INITIALIZED) revert BridgeTransferHasBeenCompleted(); if (keccak256(abi.encodePacked(preImage)) != bridgeTransfer.hashLock) revert InvalidSecret(); - if (block.number > bridgeTransfer.timeLock) revert TimelockExpired(); + if (block.timestamp > bridgeTransfer.timeLock) revert TimeLockExpired(); bridgeTransfer.state = MessageState.COMPLETED; emit BridgeTransferCompleted(bridgeTransferId, preImage); } - function refundBridgeTransfer(bytes32 bridgeTransferId) external { + function refundBridgeTransfer(bytes32 bridgeTransferId) external onlyOwner { BridgeTransfer storage bridgeTransfer = bridgeTransfers[bridgeTransferId]; if (bridgeTransfer.state != MessageState.INITIALIZED) revert BridgeTransferStateNotInitialized(); - if (block.number < bridgeTransfer.timeLock) revert TimeLockNotExpired(); + if (block.timestamp < bridgeTransfer.timeLock) revert TimeLockNotExpired(); bridgeTransfer.state = MessageState.REFUNDED; + + // Decrease pool balance and transfer WETH back to originator + poolBalance -= bridgeTransfer.amount; if (!weth.transfer(bridgeTransfer.originator, bridgeTransfer.amount)) revert WETHTransferFailed(); emit BridgeTransferRefunded(bridgeTransferId); } -} + + // Counterparty contract to withdraw WETH for originator + function withdrawWETH(address recipient, uint256 amount) external { + if (msg.sender != counterpartyAddress) revert Unauthorized(); + if (poolBalance < amount) revert InsufficientWethBalance(); + poolBalance -= amount; + if (!weth.transfer(recipient, amount)) revert WETHTransferFailed(); + } +} \ No newline at end of file diff --git a/protocol-units/bridge/contracts/src/AtomicBridgeInitiatorMOVE.sol b/protocol-units/bridge/contracts/src/AtomicBridgeInitiatorMOVE.sol new file mode 100644 index 000000000..04bb248a3 --- /dev/null +++ b/protocol-units/bridge/contracts/src/AtomicBridgeInitiatorMOVE.sol @@ -0,0 +1,118 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.22; + +import {IAtomicBridgeInitiatorMOVE} from "./IAtomicBridgeInitiatorMOVE.sol"; +import {MockMOVEToken} from "./MockMOVEToken.sol"; +import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; +import {ERC20Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol"; + +contract AtomicBridgeInitiatorMOVE is IAtomicBridgeInitiatorMOVE, OwnableUpgradeable { + enum MessageState { + INITIALIZED, + COMPLETED, + REFUNDED + } + + struct BridgeTransfer { + uint256 amount; + address originator; + bytes32 recipient; + bytes32 hashLock; + uint256 timeLock; // in seconds (timestamp) + MessageState state; + } + + // Mapping of bridge transfer ids to BridgeTransfer structs + mapping(bytes32 => BridgeTransfer) public bridgeTransfers; + + // Total WETH pool balance + uint256 public poolBalance; + + address public counterpartyAddress; + ERC20Upgradeable public moveToken; + uint256 private nonce; + + // Configurable time lock duration + uint256 public initiatorTimeLockDuration; + + function initialize(address _moveToken, address owner, uint256 _timeLockDuration) public initializer { + if (_moveToken == address(0)) { + revert ZeroAddress(); + } + moveToken = ERC20Upgradeable(_moveToken); + __Ownable_init(owner); + + // Set the custom time lock duration + initiatorTimeLockDuration = _timeLockDuration; + } + + function setCounterpartyAddress(address _counterpartyAddress) external onlyOwner { + if (_counterpartyAddress == address(0)) revert ZeroAddress(); + counterpartyAddress = _counterpartyAddress; + } + + function initiateBridgeTransfer(uint256 moveAmount, bytes32 recipient, bytes32 hashLock) + external + returns (bytes32 bridgeTransferId) + { + address originator = msg.sender; + + // Ensure there is a valid amount + if (moveAmount == 0) { + revert ZeroAmount(); + } + + // Transfer MOVE tokens from the user to the contract + if (!moveToken.transferFrom(originator, address(this), moveAmount)) { + revert MOVETransferFailed(); + } + + // Update the pool balance + poolBalance += moveAmount; + + // Generate a unique nonce to prevent replay attacks, and generate a transfer ID + bridgeTransferId = keccak256(abi.encodePacked(originator, recipient, hashLock, initiatorTimeLockDuration, block.timestamp, nonce++)); + + bridgeTransfers[bridgeTransferId] = BridgeTransfer({ + amount: moveAmount, + originator: originator, + recipient: recipient, + hashLock: hashLock, + timeLock: block.timestamp + initiatorTimeLockDuration, + state: MessageState.INITIALIZED + }); + + emit BridgeTransferInitiated(bridgeTransferId, originator, recipient, moveAmount, hashLock, initiatorTimeLockDuration); + return bridgeTransferId; + } + + function completeBridgeTransfer(bytes32 bridgeTransferId, bytes32 preImage) external { + BridgeTransfer storage bridgeTransfer = bridgeTransfers[bridgeTransferId]; + if (bridgeTransfer.state != MessageState.INITIALIZED) revert BridgeTransferHasBeenCompleted(); + if (keccak256(abi.encodePacked(preImage)) != bridgeTransfer.hashLock) revert InvalidSecret(); + if (block.timestamp > bridgeTransfer.timeLock) revert TimelockExpired(); + bridgeTransfer.state = MessageState.COMPLETED; + + emit BridgeTransferCompleted(bridgeTransferId, preImage); + } + + function refundBridgeTransfer(bytes32 bridgeTransferId) external onlyOwner { + BridgeTransfer storage bridgeTransfer = bridgeTransfers[bridgeTransferId]; + if (bridgeTransfer.state != MessageState.INITIALIZED) revert BridgeTransferStateNotInitialized(); + if (block.timestamp < bridgeTransfer.timeLock) revert TimeLockNotExpired(); + bridgeTransfer.state = MessageState.REFUNDED; + + // Decrease pool balance and transfer MOVE tokens back to the originator + poolBalance -= bridgeTransfer.amount; + if (!moveToken.transfer(bridgeTransfer.originator, bridgeTransfer.amount)) revert MOVETransferFailed(); + + emit BridgeTransferRefunded(bridgeTransferId); + } + + function withdrawMOVE(address recipient, uint256 amount) external { + if (msg.sender != counterpartyAddress) revert Unauthorized(); + if (poolBalance < amount) revert InsufficientMOVEBalance(); + poolBalance -= amount; + if (!moveToken.transfer(recipient, amount)) revert MOVETransferFailed(); + } +} diff --git a/protocol-units/bridge/contracts/src/IAtomicBridgeCounterparty.sol b/protocol-units/bridge/contracts/src/IAtomicBridgeCounterparty.sol new file mode 100644 index 000000000..8bf67cf75 --- /dev/null +++ b/protocol-units/bridge/contracts/src/IAtomicBridgeCounterparty.sol @@ -0,0 +1,61 @@ + +pragma solidity ^0.8.22; + +interface IAtomicBridgeCounterparty { + // Event emitted when a new atomic bridge transfer is locked + event BridgeTransferLocked( + bytes32 indexed bridgeTransferId, address indexed recipient, uint256 amount, bytes32 hashLock, uint256 timeLock + ); + + // Event emitted when a BridgeTransfer is completed + event BridgeTransferCompleted(bytes32 indexed bridgeTransferId, bytes32 pre_image); + + // Event emitted when a BridgeTransfer is aborted + event BridgeTransferAborted(bytes32 indexed bridgeTransferId); + + error ZeroAmount(); + error WETHTransferFailed(); + error BridgeTransferInvalid(); + error InvalidSecret(); + error BridgeTransferHasBeenCompleted(); + error BridgeTransferStateNotInitialized(); + error BridgeTransferStateNotPending(); + error InsufficientWethBalance(); + error TimeLockNotExpired(); + error TimeLockExpired(); + error ZeroAddress(); + error Unauthorized(); + + /** + * @dev Locks the assets for a new atomic bridge transfer + * @param initiator The address of the initiator of the BridgeTransfer + * @param bridgeTransferId A unique id representing this BridgeTransfer + * @param hashLock The hash of the secret (HASH) that will unlock the funds + * @param recipient The address to which to transfer the funds + * @param amount The amount of WETH to lock + * @return bool indicating successful lock + * + */ + function lockBridgeTransfer( + bytes32 initiator, + bytes32 bridgeTransferId, + bytes32 hashLock, + address recipient, + uint256 amount + ) external returns (bool); + + /** + * @dev Completes the bridge transfer and withdraws WETH to the recipient + * @param bridgeTransferId Unique identifier for the BridgeTransfer + * @param preImage The secret that unlocks the funds + * + */ + function completeBridgeTransfer(bytes32 bridgeTransferId, bytes32 preImage) external; + + /** + * @dev Cancels the bridge transfer and refunds the initiator if the timelock has expired + * @param bridgeTransferId Unique identifier for the BridgeTransfer + * + */ + function abortBridgeTransfer(bytes32 bridgeTransferId) external; +} diff --git a/protocol-units/bridge/contracts/src/IAtomicBridgeCounterpartyMOVE.sol b/protocol-units/bridge/contracts/src/IAtomicBridgeCounterpartyMOVE.sol new file mode 100644 index 000000000..e8a3dabfa --- /dev/null +++ b/protocol-units/bridge/contracts/src/IAtomicBridgeCounterpartyMOVE.sol @@ -0,0 +1,59 @@ +pragma solidity ^0.8.22; + +interface IAtomicBridgeCounterpartyMOVE { + // Event emitted when a new atomic bridge transfer is locked + event BridgeTransferLocked( + bytes32 indexed bridgeTransferId, address indexed recipient, uint256 amount, bytes32 hashLock, uint256 timeLock + ); + + // Event emitted when a BridgeTransfer is completed + event BridgeTransferCompleted(bytes32 indexed bridgeTransferId, bytes32 pre_image); + + // Event emitted when a BridgeTransfer is aborted + event BridgeTransferAborted(bytes32 indexed bridgeTransferId); + + error ZeroAmount(); + error MOVETransferFailed(); + error BridgeTransferInvalid(); + error InvalidSecret(); + error BridgeTransferHasBeenCompleted(); + error BridgeTransferStateNotInitialized(); + error BridgeTransferStateNotPending(); + error InsufficientMOVEBalance(); + error TimeLockNotExpired(); + error ZeroAddress(); + error Unauthorized(); + + /** + * @dev Locks the assets for a new atomic bridge transfer + * @param initiator The address of the initiator of the BridgeTransfer + * @param bridgeTransferId A unique id representing this BridgeTransfer + * @param hashLock The hash of the secret (HASH) that will unlock the funds + * @param recipient The address to which to transfer the funds + * @param amount The amount of WETH to lock + * @return bool indicating successful lock + * + */ + function lockBridgeTransfer( + bytes32 initiator, + bytes32 bridgeTransferId, + bytes32 hashLock, + address recipient, + uint256 amount + ) external returns (bool); + + /** + * @dev Completes the bridge transfer and withdraws WETH to the recipient + * @param bridgeTransferId Unique identifier for the BridgeTransfer + * @param preImage The secret that unlocks the funds + * + */ + function completeBridgeTransfer(bytes32 bridgeTransferId, bytes32 preImage) external; + + /** + * @dev Cancels the bridge transfer and refunds the initiator if the timelock has expired + * @param bridgeTransferId Unique identifier for the BridgeTransfer + * + */ + function abortBridgeTransfer(bytes32 bridgeTransferId) external; +} diff --git a/protocol-units/bridge/contracts/src/IAtomicBridgeInitiator.sol b/protocol-units/bridge/contracts/src/IAtomicBridgeInitiator.sol index 032791222..8645a2e46 100644 --- a/protocol-units/bridge/contracts/src/IAtomicBridgeInitiator.sol +++ b/protocol-units/bridge/contracts/src/IAtomicBridgeInitiator.sol @@ -22,8 +22,9 @@ interface IAtomicBridgeInitiator { error InvalidSecret(); error BridgeTransferHasBeenCompleted(); error BridgeTransferStateNotInitialized(); + error InsufficientWethBalance(); error TimeLockNotExpired(); - error TimelockExpired(); + error TimeLockExpired(); error ZeroAddress(); error Unauthorized(); @@ -32,11 +33,10 @@ interface IAtomicBridgeInitiator { * @param _wethAmount The amount of WETH to send * @param _recipient The address on the other chain to which to transfer the funds * @param _hashLock The hash of the secret (HASH) that will unlock the funds - * @param _timeLock The number of blocks until which this BridgeTransfer is valid and can be executed * @return _bridgeTransferId A unique id representing this BridgeTransfer * */ - function initiateBridgeTransfer(uint256 _wethAmount, bytes32 _recipient, bytes32 _hashLock, uint256 _timeLock) + function initiateBridgeTransfer(uint256 _wethAmount, bytes32 _recipient, bytes32 _hashLock) external payable returns (bytes32 _bridgeTransferId); @@ -55,4 +55,4 @@ interface IAtomicBridgeInitiator { * */ function refundBridgeTransfer(bytes32 _bridgeTransferId) external; -} +} \ No newline at end of file diff --git a/protocol-units/bridge/contracts/src/IAtomicBridgeInitiatorMOVE.sol b/protocol-units/bridge/contracts/src/IAtomicBridgeInitiatorMOVE.sol new file mode 100644 index 000000000..45ffc2c61 --- /dev/null +++ b/protocol-units/bridge/contracts/src/IAtomicBridgeInitiatorMOVE.sol @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.22; + +interface IAtomicBridgeInitiatorMOVE { + // Event emitted when a new atomic bridge transfer is created + event BridgeTransferInitiated( + bytes32 indexed _bridgeTransferId, + address indexed _originator, + bytes32 indexed _recipient, + uint256 amount, + bytes32 _hashLock, + uint256 _timeLock + ); + // Event emitted when a BridgeTransfer is completed (withdrawn) + event BridgeTransferCompleted(bytes32 indexed _bridgeTransferId, bytes32 pre_image); + // Event emitted when a BridgeTransfer is refunded + event BridgeTransferRefunded(bytes32 indexed _bridgeTransferId); + + error ZeroAmount(); + error MOVETransferFailed(); + error BridgeTransferInvalid(); + error InvalidSecret(); + error BridgeTransferHasBeenCompleted(); + error BridgeTransferStateNotInitialized(); + error InsufficientMOVEBalance(); + error TimeLockNotExpired(); + error TimelockExpired(); + error ZeroAddress(); + error Unauthorized(); + + + /** + * @dev Creates a new atomic bridge transfer using native ETH + * @param _wethAmount The amount of WETH to send + * @param _recipient The address on the other chain to which to transfer the funds + * @param _hashLock The hash of the secret (HASH) that will unlock the funds + * @return _bridgeTransferId A unique id representing this BridgeTransfer + * + */ + function initiateBridgeTransfer(uint256 _wethAmount, bytes32 _recipient, bytes32 _hashLock) + external + returns (bytes32 _bridgeTransferId); + + /** + * @dev Completes the bridging Counterparty + * @param _bridgeTransferId Unique identifier for the BridgeTransfer + * @param preImage The secret that unlocks the funds + * + */ + function completeBridgeTransfer(bytes32 _bridgeTransferId, bytes32 preImage) external; + + /** + * @dev Refunds the funds back to the initiator if the timelock has expired + * @param _bridgeTransferId Unique identifier for the BridgeTransfer + * + */ + function refundBridgeTransfer(bytes32 _bridgeTransferId) external; +} diff --git a/protocol-units/bridge/contracts/src/MockMOVEToken.sol b/protocol-units/bridge/contracts/src/MockMOVEToken.sol new file mode 100644 index 000000000..b278f981e --- /dev/null +++ b/protocol-units/bridge/contracts/src/MockMOVEToken.sol @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import {ERC20Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol"; + +contract MockMOVEToken is ERC20Upgradeable { + + /** + * @dev Initialize the contract + */ + function initialize(address multisig) public initializer { + __ERC20_init("Movement", "MOVE"); + _mint(multisig, 10000000000 * 10 ** decimals()); + } + + function decimals() public pure override returns (uint8) { + return 8; + } +} \ No newline at end of file diff --git a/protocol-units/bridge/contracts/src/WETH9.sol b/protocol-units/bridge/contracts/src/WETH9.sol new file mode 100644 index 000000000..b7294cef9 --- /dev/null +++ b/protocol-units/bridge/contracts/src/WETH9.sol @@ -0,0 +1,80 @@ +// Copyright (C) 2015, 2016, 2017 Dapphub + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +pragma solidity ^0.8.22; + +contract WETH9 { + string public name = "Wrapped Ether"; + string public symbol = "WETH"; + uint8 public decimals = 18; + + event Approval(address indexed src, address indexed guy, uint wad); + event Transfer(address indexed src, address indexed dst, uint wad); + event Deposit(address indexed dst, uint wad); + event Withdrawal(address indexed src, uint wad); + + mapping (address => uint) public balanceOf; + mapping (address => mapping (address => uint)) public allowance; + + receive() external payable { + deposit(); + } + + function deposit() public payable { + balanceOf[msg.sender] += msg.value; + emit Deposit(msg.sender, msg.value); + } + + function withdraw(uint wad) public { + require(balanceOf[msg.sender] >= wad, "Insufficient balance"); + balanceOf[msg.sender] -= wad; + payable(msg.sender).transfer(wad); + emit Withdrawal(msg.sender, wad); + } + + function totalSupply() public view returns (uint) { + return address(this).balance; + } + + function approve(address guy, uint wad) public returns (bool) { + allowance[msg.sender][guy] = wad; + emit Approval(msg.sender, guy, wad); + return true; + } + + function transfer(address dst, uint wad) public returns (bool) { + return transferFrom(msg.sender, dst, wad); + } + + function transferFrom(address src, address dst, uint wad) + public + returns (bool) + { + require(balanceOf[src] >= wad, "Insufficient balance"); + + if (src != msg.sender && allowance[src][msg.sender] != type(uint).max) { + require(allowance[src][msg.sender] >= wad, "Allowance exceeded"); + allowance[src][msg.sender] -= wad; + } + + balanceOf[src] -= wad; + balanceOf[dst] += wad; + + emit Transfer(src, dst, wad); + + return true; + } +} + diff --git a/protocol-units/bridge/contracts/test/AtomicBridgeCounterparty.t.sol b/protocol-units/bridge/contracts/test/AtomicBridgeCounterparty.t.sol new file mode 100644 index 000000000..7c512f702 --- /dev/null +++ b/protocol-units/bridge/contracts/test/AtomicBridgeCounterparty.t.sol @@ -0,0 +1,194 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.22; +pragma abicoder v2; + +import {Test, console} from "forge-std/Test.sol"; +import {AtomicBridgeCounterparty} from "../src/AtomicBridgeCounterparty.sol"; +import {AtomicBridgeInitiator} from "../src/AtomicBridgeInitiator.sol"; +import {ProxyAdmin} from "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol"; +import {TransparentUpgradeableProxy} from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +import {IWETH9} from "../src/IWETH9.sol"; + +contract AtomicBridgeCounterpartyTest is Test { + AtomicBridgeCounterparty public atomicBridgeCounterpartyImplementation; + AtomicBridgeCounterparty public atomicBridgeCounterparty; + AtomicBridgeInitiator public atomicBridgeInitiatorImplementation; + AtomicBridgeInitiator public atomicBridgeInitiator; + ProxyAdmin public proxyAdmin; + TransparentUpgradeableProxy public proxy; + IWETH9 public weth; + + address public deployer = address(0x1); + address public recipient = address(0x2); + address public otherUser = address(0x3); + bytes32 public hashLock = keccak256(abi.encodePacked("secret")); + uint256 public amount = 1 ether; + bytes32 public initiator = keccak256(abi.encodePacked(deployer)); + bytes32 public bridgeTransferId = + keccak256(abi.encodePacked(block.number, initiator, recipient, amount, hashLock)); + + uint256 public constant COUNTERPARTY_TIME_LOCK_DURATION = 24 * 60 * 60; // 24 hours + + function setUp() public { + // Sepolia WETH9 address + address wethAddress = 0xfFf9976782d46CC05630D1f6eBAb18b2324d6B14; + weth = IWETH9(wethAddress); + + // Time lock durations + uint256 initiatorTimeLockDuration = 48 * 60 * 60; // 48 hours for the initiator + uint256 counterpartyTimeLockDuration = 24 * 60 * 60; // 24 hours for the counterparty + + // Deploy the AtomicBridgeInitiator contract with a 48-hour time lock + atomicBridgeInitiatorImplementation = new AtomicBridgeInitiator(); + proxyAdmin = new ProxyAdmin(msg.sender); + proxy = new TransparentUpgradeableProxy( + address(atomicBridgeInitiatorImplementation), + address(proxyAdmin), + abi.encodeWithSignature( + "initialize(address,address,uint256)", + wethAddress, + deployer, + initiatorTimeLockDuration // Set 48-hour time lock for the initiator + ) + ); + + atomicBridgeInitiator = AtomicBridgeInitiator(address(proxy)); + + // Deploy the AtomicBridgeCounterparty contract with a 24-hour time lock + atomicBridgeCounterpartyImplementation = new AtomicBridgeCounterparty(); + proxy = new TransparentUpgradeableProxy( + address(atomicBridgeCounterpartyImplementation), + address(proxyAdmin), + abi.encodeWithSignature( + "initialize(address,address,uint256)", + address(atomicBridgeInitiator), + deployer, + counterpartyTimeLockDuration // Set 24-hour time lock for the counterparty + ) + ); + + atomicBridgeCounterparty = AtomicBridgeCounterparty(address(proxy)); + + // Set the counterparty contract in the AtomicBridgeInitiator contract + vm.startPrank(deployer); + atomicBridgeInitiator.setCounterpartyAddress(address(atomicBridgeCounterparty)); + vm.stopPrank(); + } + + function testLockBridgeTransfer() public { + vm.startPrank(deployer); + vm.deal(deployer, 1 ether); + + // Deposit WETH into AtomicBridgeInitiator to increase poolBalance + weth.deposit{value: amount}(); + weth.approve(address(atomicBridgeInitiator), amount); + atomicBridgeInitiator.initiateBridgeTransfer(amount, initiator, hashLock); + + bool result = atomicBridgeCounterparty.lockBridgeTransfer( + initiator, bridgeTransferId, hashLock, recipient, amount + ); + + ( + bytes32 pendingInitiator, + address pendingRecipient, + uint256 pendingAmount, + bytes32 pendingHashLock, + uint256 pendingTimelock, + AtomicBridgeCounterparty.MessageState pendingState + ) = atomicBridgeCounterparty.bridgeTransfers(bridgeTransferId); + + assert(result); + assertEq(pendingInitiator, initiator); + assertEq(pendingRecipient, recipient); + assertEq(pendingAmount, amount); + assertEq(pendingHashLock, hashLock); + assertGt(pendingTimelock, block.timestamp); + assertEq(uint8(pendingState), uint8(AtomicBridgeCounterparty.MessageState.PENDING)); + + vm.stopPrank(); + } + + function testCompleteBridgeTransfer() public { + bytes32 preImage = "secret"; + bytes32 testHashLock = keccak256(abi.encodePacked(preImage)); + + vm.deal(deployer, 1 ether); + vm.startPrank(deployer); + + // Deposit WETH into AtomicBridgeInitiator to increase poolBalance + weth.deposit{value: amount}(); + weth.approve(address(atomicBridgeInitiator), amount); + atomicBridgeInitiator.initiateBridgeTransfer(amount, initiator, testHashLock); + + atomicBridgeCounterparty.lockBridgeTransfer( + initiator, bridgeTransferId, testHashLock, recipient, amount + ); + + vm.stopPrank(); + vm.startPrank(otherUser); + + atomicBridgeCounterparty.completeBridgeTransfer(bridgeTransferId, preImage); + + ( + bytes32 completedInitiator, + address completedRecipient, + uint256 completedAmount, + bytes32 completedHashLock, + uint256 completedTimeLock, + AtomicBridgeCounterparty.MessageState completedState + ) = atomicBridgeCounterparty.bridgeTransfers(bridgeTransferId); + + assertEq(completedInitiator, initiator); + assertEq(completedRecipient, recipient); + assertEq(completedAmount, amount); + assertEq(completedHashLock, testHashLock); + assertGt(completedTimeLock, block.timestamp); + assertEq(uint8(completedState), uint8(AtomicBridgeCounterparty.MessageState.COMPLETED)); + + vm.stopPrank(); + } + + function testAbortBridgeTransfer() public { + vm.deal(deployer, 1 ether); + vm.startPrank(deployer); + + // Deposit WETH into AtomicBridgeInitiator to increase poolBalance + weth.deposit{value: amount}(); + weth.approve(address(atomicBridgeInitiator), amount); + atomicBridgeInitiator.initiateBridgeTransfer(amount, initiator, hashLock); + + atomicBridgeCounterparty.lockBridgeTransfer(initiator, bridgeTransferId, hashLock, recipient, amount); + + vm.stopPrank(); + + // Advance the timestamp to beyond the counterparty timelock period (24 hours + 1 second) + vm.warp(block.timestamp + COUNTERPARTY_TIME_LOCK_DURATION + 1); + + // Malicious attempt to abort the bridge transfer + vm.prank(address(0x1337)); + vm.expectRevert(); + atomicBridgeCounterparty.abortBridgeTransfer(bridgeTransferId); + + vm.startPrank(deployer); + + atomicBridgeCounterparty.abortBridgeTransfer(bridgeTransferId); + + ( + bytes32 abortedInitiator, + address abortedRecipient, + uint256 abortedAmount, + bytes32 abortedHashLock, + uint256 abortedTimeLock, + AtomicBridgeCounterparty.MessageState abortedState + ) = atomicBridgeCounterparty.bridgeTransfers(bridgeTransferId); + + assertEq(abortedInitiator, initiator); + assertEq(abortedRecipient, recipient); + assertEq(abortedAmount, amount); + assertEq(abortedHashLock, hashLock); + assertLe(abortedTimeLock, block.timestamp); + assertEq(uint8(abortedState), uint8(AtomicBridgeCounterparty.MessageState.REFUNDED)); + + vm.stopPrank(); + } +} \ No newline at end of file diff --git a/protocol-units/bridge/contracts/test/AtomicBridgeCounterpartyMOVE.t.sol b/protocol-units/bridge/contracts/test/AtomicBridgeCounterpartyMOVE.t.sol new file mode 100644 index 000000000..2c85ad613 --- /dev/null +++ b/protocol-units/bridge/contracts/test/AtomicBridgeCounterpartyMOVE.t.sol @@ -0,0 +1,267 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.22; +pragma abicoder v2; + +import {Test, console} from "forge-std/Test.sol"; +import {AtomicBridgeCounterpartyMOVE} from "../src/AtomicBridgeCounterpartyMOVE.sol"; +import {AtomicBridgeInitiatorMOVE} from "../src/AtomicBridgeInitiatorMOVE.sol"; +import {ProxyAdmin} from "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol"; +import {TransparentUpgradeableProxy} from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +import {MockMOVEToken} from "../src/MockMOVEToken.sol"; + +contract AtomicBridgeCounterpartyMOVETest is Test { + AtomicBridgeCounterpartyMOVE public atomicBridgeCounterpartyMOVEImplementation; + AtomicBridgeCounterpartyMOVE public atomicBridgeCounterpartyMOVE; + AtomicBridgeInitiatorMOVE public atomicBridgeInitiatorMOVEImplementation; + AtomicBridgeInitiatorMOVE public atomicBridgeInitiatorMOVE; + MockMOVEToken public moveToken; + ProxyAdmin public proxyAdmin; + TransparentUpgradeableProxy public proxy; + + address public deployer = address(0x1); + address public originator = address(1); + address public recipient = address(0x2); + address public otherUser = address(0x3); + bytes32 public hashLock = keccak256(abi.encodePacked("secret")); + uint256 public amount = 100 * 10 ** 8; // 100 MOVEToken (assuming 8 decimals) + uint256 public timeLock = 100; + bytes32 public initiator = keccak256(abi.encodePacked(deployer)); + bytes32 public bridgeTransferId = + keccak256( + abi.encodePacked( + block.timestamp, + initiator, + recipient, + amount, + hashLock, + timeLock + ) + ); + + uint256 public constant COUNTERPARTY_TIME_LOCK_DURATION = 24 * 60 * 60; // 24 hours + + function setUp() public { + // Deploy the MOVEToken contract and mint some tokens to the deployer + moveToken = new MockMOVEToken(); + moveToken.initialize(address(this)); // Contract will hold initial MOVE tokens + + // Time lock durations + uint256 initiatorTimeLockDuration = 48 * 60 * 60; // 48 hours for the initiator + uint256 counterpartyTimeLockDuration = 24 * 60 * 60; // 24 hours for the counterparty + + originator = vm.addr(uint256(keccak256(abi.encodePacked(block.timestamp, block.prevrandao)))); + + // Deploy the AtomicBridgeInitiator contract with a 48-hour time lock + atomicBridgeInitiatorMOVEImplementation = new AtomicBridgeInitiatorMOVE(); + proxyAdmin = new ProxyAdmin(deployer); + proxy = new TransparentUpgradeableProxy( + address(atomicBridgeInitiatorMOVEImplementation), + address(proxyAdmin), + abi.encodeWithSignature( + "initialize(address,address,uint256)", + address(moveToken), + deployer, + initiatorTimeLockDuration + ) + ); + atomicBridgeInitiatorMOVE = AtomicBridgeInitiatorMOVE(address(proxy)); + + // Deploy the AtomicBridgeCounterparty contract with a 24-hour time lock + atomicBridgeCounterpartyMOVEImplementation = new AtomicBridgeCounterpartyMOVE(); + proxy = new TransparentUpgradeableProxy( + address(atomicBridgeCounterpartyMOVEImplementation), + address(proxyAdmin), + abi.encodeWithSignature( + "initialize(address,address,uint256)", + address(atomicBridgeInitiatorMOVE), + deployer, + counterpartyTimeLockDuration + ) + ); + atomicBridgeCounterpartyMOVE = AtomicBridgeCounterpartyMOVE(address(proxy)); + + // Set the counterparty contract in the AtomicBridgeInitiator contract + vm.startPrank(deployer); + atomicBridgeInitiatorMOVE.setCounterpartyAddress( + address(atomicBridgeCounterpartyMOVE) + ); + vm.stopPrank(); + } + + function testLockBridgeTransfer() public { + uint256 moveAmount = 100 * 10**8; + moveToken.transfer(originator, moveAmount); + vm.startPrank(originator); + + // Approve the AtomicBridgeInitiatorMOVE contract to spend MOVEToken + moveToken.approve(address(atomicBridgeInitiatorMOVE), amount); + + // Initiate the bridge transfer + atomicBridgeInitiatorMOVE.initiateBridgeTransfer( + amount, + initiator, + hashLock + ); + + vm.stopPrank(); + + vm.startPrank(deployer); // Only the owner (deployer) can call lockBridgeTransfer + bool result = atomicBridgeCounterpartyMOVE.lockBridgeTransfer( + initiator, + bridgeTransferId, + hashLock, + recipient, + amount + ); + vm.stopPrank(); + + ( + bytes32 pendingInitiator, + address pendingRecipient, + uint256 pendingAmount, + bytes32 pendingHashLock, + uint256 pendingTimelock, + AtomicBridgeCounterpartyMOVE.MessageState pendingState + ) = atomicBridgeCounterpartyMOVE.bridgeTransfers(bridgeTransferId); + + assert(result); + assertEq(pendingInitiator, initiator); + assertEq(pendingRecipient, recipient); + assertEq(pendingAmount, amount); + assertEq(pendingHashLock, hashLock); + assertGt(pendingTimelock, block.timestamp); + assertEq( + uint8(pendingState), + uint8(AtomicBridgeCounterpartyMOVE.MessageState.PENDING) + ); + } + + function testCompleteBridgeTransfer() public { + bytes32 preImage = "secret"; + bytes32 testHashLock = keccak256(abi.encodePacked(preImage)); + + uint256 moveAmount = 100 * 10**8; + moveToken.transfer(originator, moveAmount); + vm.startPrank(originator); + + // Approve the AtomicBridgeInitiatorMOVE contract to spend MOVEToken + moveToken.approve(address(atomicBridgeInitiatorMOVE), amount); + + // Initiate the bridge transfer + atomicBridgeInitiatorMOVE.initiateBridgeTransfer( + amount, + initiator, + testHashLock + ); + + vm.stopPrank(); + + vm.startPrank(deployer); // Only the owner (deployer) can call lockBridgeTransfer + atomicBridgeCounterpartyMOVE.lockBridgeTransfer( + initiator, + bridgeTransferId, + testHashLock, + recipient, + amount + ); + vm.stopPrank(); + + vm.startPrank(otherUser); + + atomicBridgeCounterpartyMOVE.completeBridgeTransfer( + bridgeTransferId, + preImage + ); + + ( + bytes32 completedInitiator, + address completedRecipient, + uint256 completedAmount, + bytes32 completedHashLock, + uint256 completedTimeLock, + AtomicBridgeCounterpartyMOVE.MessageState completedState + ) = atomicBridgeCounterpartyMOVE.bridgeTransfers(bridgeTransferId); + + assertEq(completedInitiator, initiator); + assertEq(completedRecipient, recipient); + assertEq(completedAmount, amount); + assertEq(completedHashLock, testHashLock); + assertGt(completedTimeLock, block.timestamp); + assertEq( + uint8(completedState), + uint8(AtomicBridgeCounterpartyMOVE.MessageState.COMPLETED) + ); + + vm.stopPrank(); + } + +function testAbortBridgeTransfer() public { + uint256 moveAmount = 100 * 10**8; + moveToken.transfer(originator, moveAmount); + vm.startPrank(originator); + + // Approve the AtomicBridgeInitiatorMOVE contract to spend MOVEToken + moveToken.approve(address(atomicBridgeInitiatorMOVE), amount); + + // Initiate the bridge transfer + atomicBridgeInitiatorMOVE.initiateBridgeTransfer( + amount, + initiator, + hashLock + ); + + vm.stopPrank(); + + vm.startPrank(deployer); + + atomicBridgeCounterpartyMOVE.lockBridgeTransfer( + initiator, + bridgeTransferId, + hashLock, + recipient, + amount + ); + + vm.stopPrank(); + + // Advance the block number to beyond the timelock period + vm.warp(block.timestamp + COUNTERPARTY_TIME_LOCK_DURATION + 1); + + // Try to abort as a malicious user (this should fail) + //vm.startPrank(otherUser); + //vm.expectRevert("Ownable: caller is not the owner"); + //atomicBridgeCounterpartyMOVE.abortBridgeTransfer(bridgeTransferId); + //vm.stopPrank(); + + // Abort as the owner (this should pass) + vm.startPrank(deployer); // The deployer is the owner + atomicBridgeCounterpartyMOVE.abortBridgeTransfer(bridgeTransferId); + + ( + bytes32 abortedInitiator, + address abortedRecipient, + uint256 abortedAmount, + bytes32 abortedHashLock, + uint256 abortedTimeLock, + AtomicBridgeCounterpartyMOVE.MessageState abortedState + ) = atomicBridgeCounterpartyMOVE.bridgeTransfers(bridgeTransferId); + + assertEq(abortedInitiator, initiator); + assertEq(abortedRecipient, recipient); + assertEq(abortedAmount, amount); + assertEq(abortedHashLock, hashLock); + assertLe( + abortedTimeLock, + block.timestamp, + "Timelock is not less than or equal to current timestamp" + ); + assertEq( + uint8(abortedState), + uint8(AtomicBridgeCounterpartyMOVE.MessageState.REFUNDED) + ); + + vm.stopPrank(); +} + + +} diff --git a/protocol-units/bridge/contracts/test/AtomicBridgeInitiator.t.sol b/protocol-units/bridge/contracts/test/AtomicBridgeInitiator.t.sol index ad6d5077f..c18edc8f4 100644 --- a/protocol-units/bridge/contracts/test/AtomicBridgeInitiator.t.sol +++ b/protocol-units/bridge/contracts/test/AtomicBridgeInitiator.t.sol @@ -3,11 +3,11 @@ pragma solidity ^0.8.22; pragma abicoder v2; import {Test, console} from "forge-std/Test.sol"; -import {AtomicBridgeInitiator} from "../src/AtomicBridgeInitator.sol"; +import {AtomicBridgeInitiator, IAtomicBridgeInitiator, OwnableUpgradeable} from "../src/AtomicBridgeInitiator.sol"; import {ProxyAdmin} from "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol"; import {TransparentUpgradeableProxy} from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; import {IWETH9} from "../src/IWETH9.sol"; - +import {console} from "forge-std/console.sol"; contract AtomicBridgeInitiatorWethTest is Test { AtomicBridgeInitiator public atomicBridgeInitiatorImplementation; @@ -16,29 +16,29 @@ contract AtomicBridgeInitiatorWethTest is Test { TransparentUpgradeableProxy public proxy; AtomicBridgeInitiator public atomicBridgeInitiator; - address public originator = address(1); - // convert to bytes32 + address public originator = address(1); bytes32 public recipient = keccak256(abi.encodePacked(address(2))); bytes32 public hashLock = keccak256(abi.encodePacked("secret")); uint256 public amount = 1 ether; - uint256 public timeLock = 100; + uint256 public constant timeLockDuration = 48 * 60 * 60; // 48 hours in seconds function setUp() public { - //Sepolia WETH9 address + // Sepolia WETH9 address address wethAddress = 0xfFf9976782d46CC05630D1f6eBAb18b2324d6B14; weth = IWETH9(wethAddress); - //generate random address for each test + // generate random address for each test originator = vm.addr(uint256(keccak256(abi.encodePacked(block.number, block.prevrandao)))); - // Deploy the AtomicBridgeInitiator contract with the WETH address + // Deploy the AtomicBridgeInitiator contract with the WETH address and a 48-hour time lock atomicBridgeInitiatorImplementation = new AtomicBridgeInitiator(); proxyAdmin = new ProxyAdmin(msg.sender); proxy = new TransparentUpgradeableProxy( address(atomicBridgeInitiatorImplementation), address(proxyAdmin), - abi.encodeWithSignature("initialize(address)", wethAddress) + abi.encodeWithSignature("initialize(address,address,uint256)", wethAddress, address(this), timeLockDuration) ); + atomicBridgeInitiator = AtomicBridgeInitiator(address(proxy)); } @@ -49,8 +49,7 @@ contract AtomicBridgeInitiatorWethTest is Test { bytes32 bridgeTransferId = atomicBridgeInitiator.initiateBridgeTransfer{value: amount}( 0, // _wethAmount recipient, - hashLock, - timeLock + hashLock ); ( @@ -58,14 +57,16 @@ contract AtomicBridgeInitiatorWethTest is Test { address transferOriginator, bytes32 transferRecipient, bytes32 transferHashLock, - uint256 transferTimeLock - , + uint256 transferTimeLock, + AtomicBridgeInitiator.MessageState transferState ) = atomicBridgeInitiator.bridgeTransfers(bridgeTransferId); + assertEq(transferAmount, amount); assertEq(transferOriginator, originator); assertEq(transferRecipient, recipient); assertEq(transferHashLock, hashLock); - assertGt(transferTimeLock, block.number); + assertGt(transferTimeLock, block.timestamp); + assertEq(uint8(transferState), uint8(AtomicBridgeInitiator.MessageState.INITIALIZED)); vm.stopPrank(); } @@ -80,8 +81,7 @@ contract AtomicBridgeInitiatorWethTest is Test { bytes32 bridgeTransferId = atomicBridgeInitiator.initiateBridgeTransfer{value: amount}( 0, // _wethAmount is 0 recipient, - testHashLock, - timeLock + testHashLock ); vm.stopPrank(); @@ -92,14 +92,16 @@ contract AtomicBridgeInitiatorWethTest is Test { address completedOriginator, bytes32 completedRecipient, bytes32 completedHashLock, - uint256 completedTimeLock - , + uint256 completedTimeLock, + AtomicBridgeInitiator.MessageState completedState ) = atomicBridgeInitiator.bridgeTransfers(bridgeTransferId); + assertEq(completedAmount, amount); assertEq(completedOriginator, originator); assertEq(completedRecipient, recipient); assertEq(completedHashLock, testHashLock); - assertGt(completedTimeLock, block.number); + assertGt(completedTimeLock, block.timestamp); + assertEq(uint8(completedState), uint8(AtomicBridgeInitiator.MessageState.COMPLETED)); } function testInitiateBridgeTransferWithWeth() public { @@ -111,21 +113,23 @@ contract AtomicBridgeInitiatorWethTest is Test { assertEq(weth.balanceOf(originator), wethAmount); weth.approve(address(atomicBridgeInitiator), wethAmount); bytes32 bridgeTransferId = - atomicBridgeInitiator.initiateBridgeTransfer(wethAmount, recipient, hashLock, timeLock); + atomicBridgeInitiator.initiateBridgeTransfer(wethAmount, recipient, hashLock); ( uint256 transferAmount, address transferOriginator, bytes32 transferRecipient, bytes32 transferHashLock, - uint256 transferTimeLock - , + uint256 transferTimeLock, + AtomicBridgeInitiator.MessageState transferState ) = atomicBridgeInitiator.bridgeTransfers(bridgeTransferId); + assertEq(transferAmount, wethAmount); assertEq(transferOriginator, originator); assertEq(transferRecipient, recipient); assertEq(transferHashLock, hashLock); - assertGt(transferTimeLock, block.number); + assertGt(transferTimeLock, block.timestamp); + assertEq(uint8(transferState), uint8(AtomicBridgeInitiator.MessageState.INITIALIZED)); vm.stopPrank(); } @@ -143,13 +147,13 @@ contract AtomicBridgeInitiatorWethTest is Test { weth.deposit{value: wethAmount}(); assertEq(weth.balanceOf(originator), wethAmount, "WETH balance mismatch"); - vm.expectRevert(); - // Try to initiate bridge transfer - atomicBridgeInitiator.initiateBridgeTransfer{value: ethAmount}(wethAmount, recipient, hashLock, timeLock); + + // Approve the transfer weth.approve(address(atomicBridgeInitiator), wethAmount); - // Try to initiate bridge transfer + + // Initiate bridge transfer with both ETH and WETH bytes32 bridgeTransferId = - atomicBridgeInitiator.initiateBridgeTransfer{value: ethAmount}(wethAmount, recipient, hashLock, timeLock); + atomicBridgeInitiator.initiateBridgeTransfer{value: ethAmount}(wethAmount, recipient, hashLock); // Fetch the details of the initiated bridge transfer ( @@ -157,8 +161,8 @@ contract AtomicBridgeInitiatorWethTest is Test { address transferOriginator, bytes32 transferRecipient, bytes32 transferHashLock, - uint256 transferTimeLock - , + uint256 transferTimeLock, + AtomicBridgeInitiator.MessageState transferState ) = atomicBridgeInitiator.bridgeTransfers(bridgeTransferId); // Assertions @@ -166,33 +170,39 @@ contract AtomicBridgeInitiatorWethTest is Test { assertEq(transferOriginator, originator, "Originator address mismatch"); assertEq(transferRecipient, recipient, "Recipient address mismatch"); assertEq(transferHashLock, hashLock, "HashLock mismatch"); - assertGt(transferTimeLock, block.number, "TimeLock is not greater than current block number"); + assertGt(transferTimeLock, block.timestamp, "TimeLock is not greater than current block number"); + assertEq(uint8(transferState), uint8(AtomicBridgeInitiator.MessageState.INITIALIZED)); vm.stopPrank(); } function testRefundBridgeTransfer() public { vm.deal(originator, 1 ether); - vm.startPrank(originator); + // Originator initiates a bridge transfer + vm.startPrank(originator); bytes32 bridgeTransferId = atomicBridgeInitiator.initiateBridgeTransfer{value: amount}( 0, // _wethAmount is 0 recipient, - hashLock, - timeLock + hashLock ); - vm.stopPrank(); - vm.warp(block.number + timeLock + 1); + // Advance time to ensure the time lock has expired (48 hours + 1 second) + vm.warp(block.timestamp + timeLockDuration + 1); + + // Test that a non-owner cannot call refund vm.startPrank(originator); + vm.expectRevert(abi.encodeWithSelector(OwnableUpgradeable.OwnableUnauthorizedAccount.selector, originator)); + atomicBridgeInitiator.refundBridgeTransfer(bridgeTransferId); + vm.stopPrank(); - // increase time / blockheight so that timelock expires - uint256 futureBlockNumber = block.number + timeLock + 4200; - vm.roll(futureBlockNumber); + // Refund should be allowed only by the contract owner + vm.expectEmit(); + emit IAtomicBridgeInitiator.BridgeTransferRefunded(bridgeTransferId); atomicBridgeInitiator.refundBridgeTransfer(bridgeTransferId); + // Verify the WETH balance, originator should receive WETH back assertEq(weth.balanceOf(originator), 1 ether, "WETH balance mismatch"); - vm.stopPrank(); } -} +} \ No newline at end of file diff --git a/protocol-units/bridge/contracts/test/AtomicBridgeInitiatorMOVE.t.sol b/protocol-units/bridge/contracts/test/AtomicBridgeInitiatorMOVE.t.sol new file mode 100644 index 000000000..370d80d22 --- /dev/null +++ b/protocol-units/bridge/contracts/test/AtomicBridgeInitiatorMOVE.t.sol @@ -0,0 +1,146 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.22; +pragma abicoder v2; + +import {Test, console} from "forge-std/Test.sol"; +import {AtomicBridgeInitiatorMOVE, IAtomicBridgeInitiatorMOVE, OwnableUpgradeable} from "../src/AtomicBridgeInitiatorMOVE.sol"; +import {ProxyAdmin} from "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol"; +import {TransparentUpgradeableProxy} from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +import {MockMOVEToken} from "../src/MockMOVEToken.sol"; +import {console} from "forge-std/console.sol"; + +contract AtomicBridgeInitiatorMOVETest is Test { + AtomicBridgeInitiatorMOVE public atomicBridgeInitiatorImplementation; + MockMOVEToken public moveToken; + ProxyAdmin public proxyAdmin; + TransparentUpgradeableProxy public proxy; + AtomicBridgeInitiatorMOVE public atomicBridgeInitiatorMOVE; + + address public originator = address(1); + bytes32 public recipient = keccak256(abi.encodePacked(address(2))); + bytes32 public hashLock = keccak256(abi.encodePacked("secret")); + uint256 public amount = 1 ether; + uint256 public constant timeLockDuration = 48 * 60 * 60; // 48 hours in seconds + + function setUp() public { + // Deploy the MOVEToken contract and mint some tokens to the deployer + moveToken = new MockMOVEToken(); + moveToken.initialize(address(this)); // Contract will hold initial MOVE tokens + + originator = vm.addr(uint256(keccak256(abi.encodePacked(block.timestamp, block.prevrandao)))); + + // Deploy the AtomicBridgeInitiatorMOVE contract + atomicBridgeInitiatorImplementation = new AtomicBridgeInitiatorMOVE(); + proxyAdmin = new ProxyAdmin(msg.sender); + proxy = new TransparentUpgradeableProxy( + address(atomicBridgeInitiatorImplementation), + address(proxyAdmin), + abi.encodeWithSignature( + "initialize(address,address,uint256)", + address(moveToken), + address(this), + timeLockDuration + ) + ); + + atomicBridgeInitiatorMOVE = AtomicBridgeInitiatorMOVE(address(proxy)); + } + + function testInitiateBridgeTransferWithMove() public { + uint256 moveAmount = 100 * 10**8; + moveToken.transfer(originator, moveAmount); + vm.startPrank(originator); + + moveToken.approve(address(atomicBridgeInitiatorMOVE), moveAmount); + + bytes32 bridgeTransferId = atomicBridgeInitiatorMOVE.initiateBridgeTransfer( + moveAmount, + recipient, + hashLock + ); + + ( + uint256 transferAmount, + address transferOriginator, + bytes32 transferRecipient, + bytes32 transferHashLock, + uint256 transferTimeLock, + AtomicBridgeInitiatorMOVE.MessageState transferState + ) = atomicBridgeInitiatorMOVE.bridgeTransfers(bridgeTransferId); + + assertEq(transferAmount, moveAmount); + assertEq(transferOriginator, originator); + assertEq(transferRecipient, recipient); + assertEq(transferHashLock, hashLock); + assertGt(transferTimeLock, block.timestamp); + assertEq(uint8(transferState), uint8(AtomicBridgeInitiatorMOVE.MessageState.INITIALIZED)); + + vm.stopPrank(); + } + + function testCompleteBridgeTransfer() public { + bytes32 secret = "secret"; + bytes32 testHashLock = keccak256(abi.encodePacked(secret)); + uint256 moveAmount = 100 * 10**8; // 100 MOVEToken + + moveToken.transfer(originator, moveAmount); + vm.startPrank(originator); + + moveToken.approve(address(atomicBridgeInitiatorMOVE), moveAmount); + + bytes32 bridgeTransferId = atomicBridgeInitiatorMOVE.initiateBridgeTransfer( + moveAmount, + recipient, + testHashLock + ); + + vm.stopPrank(); + + atomicBridgeInitiatorMOVE.completeBridgeTransfer(bridgeTransferId, secret); + ( + uint256 completedAmount, + address completedOriginator, + bytes32 completedRecipient, + bytes32 completedHashLock, + uint256 completedTimeLock, + AtomicBridgeInitiatorMOVE.MessageState completedState + ) = atomicBridgeInitiatorMOVE.bridgeTransfers(bridgeTransferId); + + assertEq(completedAmount, moveAmount); + assertEq(completedOriginator, originator); + assertEq(completedRecipient, recipient); + assertEq(completedHashLock, testHashLock); + assertGt(completedTimeLock, block.timestamp); + assertEq(uint8(completedState), uint8(AtomicBridgeInitiatorMOVE.MessageState.COMPLETED)); + } + + function testRefundBridgeTransfer() public { + uint256 moveAmount = 100 * 10**8; // 100 MOVEToken + moveToken.transfer(originator, moveAmount); // Transfer tokens to originator + vm.startPrank(originator); + + moveToken.approve(address(atomicBridgeInitiatorMOVE), moveAmount); + + bytes32 bridgeTransferId = atomicBridgeInitiatorMOVE.initiateBridgeTransfer( + moveAmount, + recipient, + hashLock + ); + vm.stopPrank(); + + // Advance time and block height to ensure the time lock has expired + vm.warp(block.timestamp + timeLockDuration + 1); + + // Test that a non-owner cannot call refund + vm.startPrank(originator); + vm.expectRevert(abi.encodeWithSelector(OwnableUpgradeable.OwnableUnauthorizedAccount.selector, originator)); + atomicBridgeInitiatorMOVE.refundBridgeTransfer(bridgeTransferId); + vm.stopPrank(); + + vm.expectEmit(); + emit IAtomicBridgeInitiatorMOVE.BridgeTransferRefunded(bridgeTransferId); + atomicBridgeInitiatorMOVE.refundBridgeTransfer(bridgeTransferId); + + assertEq(moveToken.balanceOf(originator), moveAmount, "MOVE balance mismatch"); + } +} \ No newline at end of file diff --git a/protocol-units/bridge/integration-tests/Cargo.toml b/protocol-units/bridge/integration-tests/Cargo.toml new file mode 100644 index 000000000..1fa9c303c --- /dev/null +++ b/protocol-units/bridge/integration-tests/Cargo.toml @@ -0,0 +1,40 @@ +[package] +name = "bridge-integration-tests" +authors = { workspace = true } +edition = { workspace = true } +homepage = { workspace = true } +license = { workspace = true } +repository = { workspace = true } + +version = { workspace = true } + +[lib] +path = "src/lib.rs" + +[dependencies] +aptos-framework = { workspace = true } +aptos-language-e2e-tests = { workspace = true } +aptos-logger = { workspace = true } +aptos-types = { workspace = true } +aptos-sdk = { workspace = true } +anyhow = { workspace = true } +bcs = { workspace = true } +alloy = { workspace = true } +reqwest = { workspace = true } +alloy-network = { workspace = true } +alloy-sol-types = { workspace = true } +alloy-contract = { workspace = true } +rand = { workspace = true } +serde_json = { workspace = true } +url = { workspace = true } +bridge-shared = { workspace = true } +ethereum-bridge = { workspace = true } +movement-bridge = { workspace = true } +futures = { workspace = true } +tiny-keccak = { workspace = true } +tokio = { workspace = true } +tracing = { workspace = true } +tracing-subscriber = { workspace = true } + +[dev-dependencies] +tokio = { workspace = true } diff --git a/protocol-units/bridge/integration-tests/src/lib.rs b/protocol-units/bridge/integration-tests/src/lib.rs new file mode 100644 index 000000000..9d8f11db4 --- /dev/null +++ b/protocol-units/bridge/integration-tests/src/lib.rs @@ -0,0 +1,212 @@ +use alloy::{ + primitives::{keccak256, Address, U256}, + providers::WalletProvider, + signers::{ + k256::{elliptic_curve::SecretKey, Secp256k1}, + local::LocalSigner, + }, +}; +use alloy_network::{Ethereum, EthereumWallet, NetworkWallet}; +use anyhow::Result; +use aptos_sdk::rest_client::{Client, FaucetClient}; +use aptos_sdk::types::account_address::AccountAddress; +use aptos_sdk::types::LocalAccount; +use bridge_shared::bridge_contracts::{BridgeContractInitiator, BridgeContractInitiatorResult}; +use bridge_shared::types::{Amount, HashLock, InitiatorAddress, RecipientAddress, TimeLock}; +use ethereum_bridge::client::{Config as EthConfig, EthClient}; +use ethereum_bridge::types::{AlloyProvider, AtomicBridgeInitiator, EthAddress, WETH9}; +use movement_bridge::MovementClient; +use movement_bridge::{utils::MovementAddress, Config as MovementConfig}; +use rand::SeedableRng; +use std::sync::{Arc, RwLock}; + +pub mod utils; + +#[derive(Clone)] +pub struct EthToMovementCallArgs { + pub initiator: Vec, + pub recipient: MovementAddress, + pub bridge_transfer_id: [u8; 32], + pub hash_lock: [u8; 32], + pub time_lock: u64, + pub amount: u64, +} + +#[derive(Clone)] +pub struct MovementToEthCallArgs { + pub initiator: MovementAddress, + pub recipient: Vec, + pub bridge_transfer_id: [u8; 32], + pub hash_lock: [u8; 32], + pub time_lock: u64, + pub amount: u64, +} + +impl Default for EthToMovementCallArgs { + fn default() -> Self { + Self { + initiator: b"0x123".to_vec(), + recipient: MovementAddress(AccountAddress::new(*b"0x00000000000000000000000000face")), + bridge_transfer_id: *b"00000000000000000000000transfer1", + hash_lock: *keccak256(b"secret"), + time_lock: 3600, + amount: 100, + } + } +} + +impl Default for MovementToEthCallArgs { + fn default() -> Self { + let preimage = "secret".to_string(); + let serialized_preimage = bcs::to_bytes(&preimage).unwrap(); + let hash_lock = *keccak256(&serialized_preimage); + + Self { + initiator: MovementAddress(AccountAddress::new(*b"0x00000000000000000000000000face")), + recipient: b"0x123".to_vec(), + bridge_transfer_id: *b"00000000000000000000000transfer1", + hash_lock, + time_lock: 3600, + amount: 100, + } + } +} + +pub struct TestHarness { + pub eth_client: Option, + pub movement_client: Option, +} + +impl TestHarness { + pub async fn new_with_movement() -> (Self, tokio::process::Child) { + let (movement_client, child) = + MovementClient::new_for_test(MovementConfig::build_for_test()) + .await + .expect("Failed to create MovementClient"); + (Self { eth_client: None, movement_client: Some(movement_client) }, child) + } + + pub fn movement_rest_client(&self) -> &Client { + self.movement_client().expect("Could not fetch Movement client").rest_client() + } + + pub fn movement_faucet_client(&self) -> &Arc> { + self.movement_client() + .expect("Could not fetch Movement client") + .faucet_client() + .expect("Faucet client not initialized") + } + + pub fn movement_client(&self) -> Result<&MovementClient> { + self.movement_client + .as_ref() + .ok_or(anyhow::Error::msg("MovementClient not initialized")) + } + + pub fn movement_client_mut(&mut self) -> Result<&mut MovementClient> { + self.movement_client + .as_mut() + .ok_or(anyhow::Error::msg("MovementClient not initialized")) + } + + pub async fn new_only_eth() -> Self { + let eth_client = EthClient::new(EthConfig::build_for_test()) + .await + .expect("Failed to create EthClient"); + Self { eth_client: Some(eth_client), movement_client: None } + } + + pub fn eth_client(&self) -> Result<&EthClient> { + self.eth_client.as_ref().ok_or(anyhow::Error::msg("EthClient not initialized")) + } + + pub fn eth_client_mut(&mut self) -> Result<&mut EthClient> { + self.eth_client.as_mut().ok_or(anyhow::Error::msg("EthClient not initialized")) + } + + pub fn set_eth_signer(&mut self, signer: SecretKey) -> Address { + let eth_client = self.eth_client_mut().expect("EthClient not initialized"); + let wallet: &mut EthereumWallet = eth_client.rpc_provider_mut().wallet_mut(); + let clone_signer = signer.clone(); + wallet.register_default_signer(LocalSigner::from(signer)); + eth_client.set_signer_address(clone_signer); + eth_client.get_signer_address() + } + + pub fn eth_signer_address(&self) -> Address { + let eth_client = self.eth_client().expect("EthClient not initialized"); + let wallet: &EthereumWallet = eth_client.rpc_provider().wallet(); + >::default_signer_address(wallet) + } + + pub fn provider(&self) -> &AlloyProvider { + self.eth_client().expect("Could not fetch eth client").rpc_provider() + } + + /// The port that Anvil will listen on. + pub fn rpc_port(&self) -> u16 { + self.eth_client().expect("Could not fetch eth client").rpc_port() + } + + pub async fn deploy_initiator_contract(&mut self) -> Address { + let eth_client: &mut EthClient = self.eth_client_mut().expect("EthClient not initialized"); + let contract = AtomicBridgeInitiator::deploy(eth_client.rpc_provider()) + .await + .expect("Failed to deploy AtomicBridgeInitiator"); + eth_client.set_initiator_contract(contract.with_cloned_provider()); + eth_client.initiator_contract_address().expect("Initiator contract not set") + } + + pub async fn deploy_weth_contract(&mut self) -> Address { + let eth_client = self.eth_client_mut().expect("EthClient not initialized"); + let weth = WETH9::deploy(eth_client.rpc_provider()).await.expect("Failed to deploy WETH9"); + eth_client.set_weth_contract(weth.with_cloned_provider()); + eth_client.weth_contract_address().expect("WETH contract not set") + } + + pub async fn deploy_init_contracts(&mut self) { + let _ = self.deploy_initiator_contract().await; + let weth_address = self.deploy_weth_contract().await; + self.eth_client() + .expect("Failed to get EthClient") + .initialize_initiator_contract( + EthAddress(weth_address), + EthAddress(self.eth_signer_address()), + 1, //Set timelock to 1 for testing + ) + .await + .expect("Failed to initialize contract"); + } + + pub async fn initiate_bridge_transfer( + &mut self, + initiator_address: InitiatorAddress, + recipient_address: RecipientAddress>, + hash_lock: HashLock<[u8; 32]>, + amount: Amount, // the amount + ) -> BridgeContractInitiatorResult<()> { + let eth_client = self.eth_client_mut().expect("EthClient not initialized"); + eth_client + .initiate_bridge_transfer(initiator_address, recipient_address, hash_lock, amount) + .await + } + + pub async fn deposit_weth_and_approve( + &mut self, + initiator_address: InitiatorAddress, + amount: Amount, + ) -> BridgeContractInitiatorResult<()> { + let eth_client = self.eth_client_mut().expect("EthClient not initialized"); + eth_client + .deposit_weth_and_approve(initiator_address.0 .0, U256::from(amount.weth())) + .await + .expect("Failed to deposit WETH"); + Ok(()) + } + + pub fn gen_aptos_account(&self) -> Vec { + let mut rng = ::rand::rngs::StdRng::from_seed([3u8; 32]); + let movement_recipient = LocalAccount::generate(&mut rng); + movement_recipient.public_key().to_bytes().to_vec() + } +} diff --git a/protocol-units/bridge/integration-tests/src/utils.rs b/protocol-units/bridge/integration-tests/src/utils.rs new file mode 100644 index 000000000..17a9b115f --- /dev/null +++ b/protocol-units/bridge/integration-tests/src/utils.rs @@ -0,0 +1,158 @@ +use alloy::hex; +use anyhow::Result; +use aptos_sdk::{ + coin_client::CoinClient, rest_client::Transaction, types::account_address::AccountAddress, +}; +use bridge_shared::bridge_contracts::BridgeContractInitiator; +use bridge_shared::bridge_contracts::BridgeContractInitiatorError; +use bridge_shared::types::{ + Amount, AssetType, BridgeTransferDetails, HashLock, InitiatorAddress, RecipientAddress, +}; +use movement_bridge::utils::{self as movement_utils, MovementAddress}; +use movement_bridge::MovementClient; +use tracing::debug; + +pub fn assert_bridge_transfer_details( + details: &BridgeTransferDetails, // MovementAddress for initiator + expected_bridge_transfer_id: H, + expected_hash_lock: H, + expected_sender_address: AccountAddress, + expected_recipient_address: Vec, + expected_amount: u64, + expected_state: u8, +) where + H: std::fmt::Debug + PartialEq, +{ + assert_eq!(details.bridge_transfer_id.0, expected_bridge_transfer_id); + assert_eq!(details.hash_lock.0, expected_hash_lock); + assert_eq!(details.initiator_address.0 .0, expected_sender_address); + assert_eq!(details.recipient_address.0, expected_recipient_address); + assert_eq!(details.amount.0, AssetType::Moveth(expected_amount)); + assert_eq!(details.state, expected_state, "Bridge transfer state mismatch."); +} + +pub async fn extract_bridge_transfer_id( + movement_client: &mut MovementClient, +) -> Result<[u8; 32], anyhow::Error> { + let sender_address = movement_client.signer().address(); + let sequence_number = 0; // Modify as needed + let rest_client = movement_client.rest_client(); + + let transactions = rest_client + .get_account_transactions(sender_address, Some(sequence_number), Some(20)) + .await + .map_err(|e| anyhow::Error::msg(format!("Failed to get transactions: {:?}", e)))?; + + if let Some(transaction) = transactions.into_inner().last() { + if let Transaction::UserTransaction(user_txn) = transaction { + for event in &user_txn.events { + if let aptos_sdk::rest_client::aptos_api_types::MoveType::Struct(struct_tag) = + &event.typ + { + if struct_tag.module.as_str() == "atomic_bridge_initiator" + && struct_tag.name.as_str() == "BridgeTransferInitiatedEvent" + { + if let Some(bridge_transfer_id) = + event.data.get("bridge_transfer_id").and_then(|v| v.as_str()) + { + let hex_str = bridge_transfer_id.trim_start_matches("0x"); + let decoded_vec = hex::decode(hex_str).map_err(|_| { + anyhow::Error::msg("Failed to decode hex string into Vec") + })?; + return decoded_vec.try_into().map_err(|_| { + anyhow::Error::msg("Failed to convert decoded Vec to [u8; 32]") + }); + } + } + } + } + } + } + Err(anyhow::Error::msg("No matching transaction found")) +} + +pub async fn fund_and_check_balance( + movement_client: &mut MovementClient, + expected_balance: u64, +) -> Result<()> { + let movement_client_signer = movement_client.signer(); + let rest_client = movement_client.rest_client(); + let coin_client = CoinClient::new(&rest_client); + let faucet_client = movement_client + .faucet_client() + .expect("Failed to get FaucetClient") + .write() + .unwrap(); + faucet_client.fund(movement_client_signer.address(), expected_balance).await?; + + let balance = coin_client.get_account_balance(&movement_client_signer.address()).await?; + assert!( + balance >= expected_balance, + "Expected Movement Client to have at least {}, but found {}", + expected_balance, + balance + ); + + Ok(()) +} + +pub async fn publish_for_test(movement_client: &mut MovementClient) { + let _ = movement_client.publish_for_test(); +} + +pub async fn initiate_bridge_transfer_helper( + movement_client: &mut MovementClient, + initiator_address: AccountAddress, + recipient_address: Vec, + hash_lock: [u8; 32], + amount: u64, + timelock_modify: bool, +) -> Result<(), BridgeContractInitiatorError> { + // Publish for test + let _ = movement_client.publish_for_test(); + + if timelock_modify { + // Set the timelock to 1 second for testing + movement_client.initiator_set_timelock(1).await.expect("Failed to set timelock"); + } + + // Mint MovETH to the initiator's address + let mint_amount = 200 * 100_000_000; // Assuming 8 decimals for MovETH + + let mint_args = vec![ + movement_utils::serialize_address_initiator(&movement_client.signer().address())?, // Mint to initiator's address + movement_utils::serialize_u64_initiator(&mint_amount)?, // Amount to mint (200 MovETH) + ]; + + let mint_payload = movement_utils::make_aptos_payload( + movement_client.counterparty_address, // Address where moveth module is published + "moveth", + "mint", + Vec::new(), + mint_args, + ); + + // Send transaction to mint MovETH + movement_utils::send_and_confirm_aptos_transaction( + &movement_client.rest_client(), + movement_client.signer(), + mint_payload, + ) + .await + .map_err(|_| BridgeContractInitiatorError::MintError)?; + + debug!("Successfully minted 200 MovETH to the initiator"); + + // Initiate the bridge transfer + movement_client + .initiate_bridge_transfer( + InitiatorAddress(MovementAddress(initiator_address)), + RecipientAddress(recipient_address), + HashLock(hash_lock), + Amount(AssetType::Moveth(amount)), + ) + .await + .expect("Failed to initiate bridge transfer"); + + Ok(()) +} diff --git a/protocol-units/bridge/integration-tests/tests/eth_movement.rs b/protocol-units/bridge/integration-tests/tests/eth_movement.rs new file mode 100644 index 000000000..61fb3d766 --- /dev/null +++ b/protocol-units/bridge/integration-tests/tests/eth_movement.rs @@ -0,0 +1,456 @@ +use tokio::time::{sleep, Duration}; + +use alloy::{ + node_bindings::Anvil, + primitives::{address, keccak256}, + providers::Provider, +}; +use anyhow::Result; +use aptos_sdk::coin_client::CoinClient; +use bridge_integration_tests::{EthToMovementCallArgs, TestHarness}; +use bridge_shared::{ + bridge_contracts::{BridgeContractCounterparty, BridgeContractInitiator}, + types::{ + Amount, AssetType, BridgeTransferId, HashLock, HashLockPreImage, InitiatorAddress, + RecipientAddress, + }, +}; +use ethereum_bridge::types::EthAddress; +use tokio::{self}; + +#[tokio::test] +async fn test_movement_client_build_and_fund_accounts() -> Result<(), anyhow::Error> { + let (scaffold, mut child) = TestHarness::new_with_movement().await; + let movement_client = scaffold.movement_client().expect("Failed to get MovementClient"); + // + let rest_client = movement_client.rest_client(); + let coin_client = CoinClient::new(&rest_client); + let faucet_client = movement_client.faucet_client().expect("Failed to get // FaucetClient"); + let movement_client_signer = movement_client.signer(); + + let faucet_client = faucet_client.write().unwrap(); + + faucet_client.fund(movement_client_signer.address(), 100_000_000).await?; + let balance = coin_client.get_account_balance(&movement_client_signer.address()).await?; + assert!( + balance >= 100_000_000, + "Expected Movement Client to have at least 100_000_000, but found {}", + balance + ); + + child.kill().await?; + + Ok(()) +} + +#[tokio::test] +async fn test_movement_client_should_publish_package() -> Result<(), anyhow::Error> { + let _ = tracing_subscriber::fmt().try_init(); + + let (mut harness, mut child) = TestHarness::new_with_movement().await; + { + let movement_client = harness.movement_client_mut().expect("Failed to get MovementClient"); + + let _ = movement_client.publish_for_test(); + } + + child.kill().await?; + + Ok(()) +} + +#[tokio::test] + +async fn test_movement_client_should_successfully_call_lock_and_complete( +) -> Result<(), anyhow::Error> { + let _ = tracing_subscriber::fmt().with_max_level(tracing::Level::DEBUG).try_init(); + + let (mut harness, mut child) = TestHarness::new_with_movement().await; + + let args = EthToMovementCallArgs::default(); + + let test_result = async { + let movement_client = harness.movement_client_mut().expect("Failed to get MovementClient"); + let _ = movement_client.publish_for_test(); + + let rest_client = movement_client.rest_client(); + let coin_client = CoinClient::new(&rest_client); + let faucet_client = movement_client.faucet_client().expect("Failed to get FaucetClient"); + let movement_client_signer = movement_client.signer(); + + { + let faucet_client = faucet_client.write().unwrap(); + faucet_client.fund(movement_client_signer.address(), 100_000_000).await?; + } + + let balance = coin_client.get_account_balance(&movement_client_signer.address()).await?; + assert!( + balance >= 100_000_000, + "Expected Movement Client to have at least 100_000_000, but found {}", + balance + ); + + movement_client + .lock_bridge_transfer( + BridgeTransferId(args.bridge_transfer_id), + HashLock(args.hash_lock), + InitiatorAddress(args.initiator.clone()), + RecipientAddress(args.recipient.clone()), + Amount(AssetType::Moveth(args.amount)), + ) + .await + .expect("Failed to lock bridge transfer"); + + let details = BridgeContractCounterparty::get_bridge_transfer_details( + movement_client, + BridgeTransferId(args.bridge_transfer_id), + ) + .await + .expect("Failed to get bridge transfer details") + .expect("Expected to find bridge transfer details, but got None"); + + assert_eq!(details.bridge_transfer_id.0, args.bridge_transfer_id); + assert_eq!(details.hash_lock.0, args.hash_lock); + assert_eq!( + &details.initiator_address.0 .0[32 - args.initiator.len()..], + &args.initiator, + "Initiator address does not match" + ); + assert_eq!(details.recipient_address.0, args.recipient.0.to_vec()); + assert_eq!(details.amount.0, AssetType::Moveth(args.amount)); + assert_eq!(details.state, 1, "Bridge transfer is supposed to be locked but it's not."); + + BridgeContractCounterparty::complete_bridge_transfer( + movement_client, + BridgeTransferId(args.bridge_transfer_id), + HashLockPreImage(b"secret".to_vec()), + ) + .await + .expect("Failed to complete bridge transfer"); + + let details = BridgeContractCounterparty::get_bridge_transfer_details( + movement_client, + BridgeTransferId(args.bridge_transfer_id), + ) + .await + .expect("Failed to get bridge transfer details") + .expect("Expected to find bridge transfer details, but got None"); + + assert_eq!(details.bridge_transfer_id.0, args.bridge_transfer_id); + assert_eq!(details.hash_lock.0, args.hash_lock); + assert_eq!( + &details.initiator_address.0 .0[32 - args.initiator.len()..], + &args.initiator, + "Initiator address does not match" + ); + assert_eq!(details.recipient_address.0, args.recipient.0.to_vec()); + assert_eq!(details.amount.0, AssetType::Moveth(args.amount)); + assert_eq!(details.state, 2, "Bridge transfer is supposed to be completed but it's not."); + + Ok(()) + } + .await; + + if let Err(e) = child.kill().await { + eprintln!("Failed to kill child process: {:?}", e); + } + + test_result +} + +#[tokio::test] +async fn test_movement_client_should_successfully_call_lock_and_abort() -> Result<(), anyhow::Error> +{ + let _ = tracing_subscriber::fmt().with_max_level(tracing::Level::DEBUG).try_init(); + + let (mut harness, mut child) = TestHarness::new_with_movement().await; + + let args = EthToMovementCallArgs::default(); + + let test_result = async { + let movement_client = harness.movement_client_mut().expect("Failed to get MovementClient"); + movement_client.publish_for_test()?; + + let rest_client = movement_client.rest_client(); + let coin_client = CoinClient::new(&rest_client); + let faucet_client = movement_client.faucet_client().expect("Failed to get FaucetClient"); + let movement_client_signer = movement_client.signer(); + + { + let faucet_client = faucet_client.write().unwrap(); + faucet_client.fund(movement_client_signer.address(), 100_000_000).await?; + } + + let balance = coin_client.get_account_balance(&movement_client_signer.address()).await?; + assert!( + balance >= 100_000_000, + "Expected Movement Client to have at least 100_000_000, but found {}", + balance + ); + + // Set the timelock to 1 second for testing + movement_client + .counterparty_set_timelock(1) + .await + .expect("Failed to set timelock"); + + movement_client + .lock_bridge_transfer( + BridgeTransferId(args.bridge_transfer_id), + HashLock(args.hash_lock), + InitiatorAddress(args.initiator.clone()), + RecipientAddress(args.recipient.clone()), + Amount(AssetType::Moveth(args.amount)), + ) + .await + .expect("Failed to lock bridge transfer"); + + let details = BridgeContractCounterparty::get_bridge_transfer_details( + movement_client, + BridgeTransferId(args.bridge_transfer_id), + ) + .await + .expect("Failed to get bridge transfer details") + .expect("Expected to find bridge transfer details, but got None"); + + assert_eq!(details.bridge_transfer_id.0, args.bridge_transfer_id); + assert_eq!(details.hash_lock.0, args.hash_lock); + assert_eq!( + &details.initiator_address.0 .0[32 - args.initiator.len()..], + &args.initiator, + "Initiator address does not match" + ); + assert_eq!(details.recipient_address.0, args.recipient.0.to_vec()); + assert_eq!(details.amount.0, AssetType::Moveth(args.amount)); + assert_eq!(details.state, 1, "Bridge transfer is supposed to be locked but it's not."); + + sleep(Duration::from_secs(5)).await; + + movement_client + .abort_bridge_transfer(BridgeTransferId(args.bridge_transfer_id)) + .await + .expect("Failed to complete bridge transfer"); + + let abort_details = BridgeContractCounterparty::get_bridge_transfer_details( + movement_client, + BridgeTransferId(args.bridge_transfer_id), + ) + .await + .expect("Failed to get bridge transfer details") + .expect("Expected to find bridge transfer details, but got None"); + + assert_eq!(abort_details.bridge_transfer_id.0, args.bridge_transfer_id); + assert_eq!(abort_details.hash_lock.0, args.hash_lock); + assert_eq!( + &abort_details.initiator_address.0 .0[32 - args.initiator.len()..], + &args.initiator, + "Initiator address does not match" + ); + assert_eq!(abort_details.recipient_address.0, args.recipient.0.to_vec()); + assert_eq!(abort_details.amount.0, AssetType::Moveth(args.amount)); + + Ok(()) + } + .await; + + if let Err(e) = child.kill().await { + eprintln!("Failed to kill child process: {:?}", e); + } + + test_result +} + +#[tokio::test] +async fn test_eth_client_should_build_and_fetch_accounts() { + let scaffold: TestHarness = TestHarness::new_only_eth().await; + + let eth_client = scaffold.eth_client().expect("Failed to get EthClient"); + let _anvil = Anvil::new().port(eth_client.rpc_port()).spawn(); + + let expected_accounts = [ + address!("f39fd6e51aad88f6f4ce6ab8827279cfffb92266"), + address!("70997970c51812dc3a010c7d01b50e0d17dc79c8"), + address!("3c44cdddb6a900fa2b585dd299e03d12fa4293bc"), + address!("90f79bf6eb2c4f870365e785982e1f101e93b906"), + address!("15d34aaf54267db7d7c367839aaf71a00a2c6a65"), + address!("9965507d1a55bcc2695c58ba16fb37d819b0a4dc"), + address!("976ea74026e726554db657fa54763abd0c3a0aa9"), + address!("14dc79964da2c08b23698b3d3cc7ca32193d9955"), + address!("23618e81e3f5cdf7f54c3d65f7fbc0abf5b21e8f"), + address!("a0ee7a142d267c1f36714e4a8f75612f20a79720"), + ]; + + let provider = scaffold.eth_client.unwrap().rpc_provider().clone(); + let accounts = provider.get_accounts().await.expect("Failed to get accounts"); + assert_eq!(accounts.len(), expected_accounts.len()); + + for (account, expected) in accounts.iter().zip(expected_accounts.iter()) { + assert_eq!(account, expected); + } +} + +#[tokio::test] +async fn test_eth_client_should_deploy_initiator_contract() { + let mut harness: TestHarness = TestHarness::new_only_eth().await; + let anvil = Anvil::new().port(harness.rpc_port()).spawn(); + + let _ = harness.set_eth_signer(anvil.keys()[0].clone()); + + let initiator_address = harness.deploy_initiator_contract().await; + let expected_address = address!("5fbdb2315678afecb367f032d93f642f64180aa3"); + + assert_eq!(initiator_address, expected_address); +} + +#[tokio::test] +async fn test_eth_client_should_successfully_call_initialize() { + let mut harness: TestHarness = TestHarness::new_only_eth().await; + let anvil = Anvil::new().port(harness.rpc_port()).spawn(); + + let _ = harness.set_eth_signer(anvil.keys()[0].clone()); + harness.deploy_init_contracts().await; +} + +#[tokio::test] +async fn test_eth_client_should_successfully_call_initiate_transfer_only_eth() { + let mut harness: TestHarness = TestHarness::new_only_eth().await; + let anvil = Anvil::new().port(harness.rpc_port()).spawn(); + + let signer_address = harness.set_eth_signer(anvil.keys()[0].clone()); + + harness.deploy_init_contracts().await; + + let recipient = harness.gen_aptos_account(); + let hash_lock: [u8; 32] = keccak256("secret".to_string().as_bytes()).into(); + + harness + .eth_client_mut() + .expect("Failed to get EthClient") + .initiate_bridge_transfer( + InitiatorAddress(EthAddress(signer_address)), + RecipientAddress(recipient), + HashLock(hash_lock), + Amount(AssetType::EthAndWeth((1, 0))), // Eth + ) + .await + .expect("Failed to initiate bridge transfer"); +} + +#[tokio::test] +async fn test_eth_client_should_successfully_call_initiate_transfer_only_weth() { + let mut harness: TestHarness = TestHarness::new_only_eth().await; + let anvil = Anvil::new().port(harness.rpc_port()).spawn(); + + let signer_address = harness.set_eth_signer(anvil.keys()[0].clone()); + + harness.deploy_init_contracts().await; + + let recipient = harness.gen_aptos_account(); + let hash_lock: [u8; 32] = keccak256("secret".to_string().as_bytes()).into(); + harness + .deposit_weth_and_approve( + InitiatorAddress(EthAddress(signer_address)), + Amount(AssetType::EthAndWeth((0, 1))), + ) + .await + .expect("Failed to deposit WETH"); + harness + .initiate_bridge_transfer( + InitiatorAddress(EthAddress(signer_address)), + RecipientAddress(recipient), + HashLock(hash_lock), + Amount(AssetType::EthAndWeth((0, 1))), + ) + .await + .expect("Failed to initiate bridge transfer"); +} + +#[tokio::test] +async fn test_eth_client_should_successfully_call_initiate_transfer_eth_and_weth() { + let mut harness: TestHarness = TestHarness::new_only_eth().await; + let anvil = Anvil::new().port(harness.rpc_port()).spawn(); + + let signer_address = harness.set_eth_signer(anvil.keys()[0].clone()); + let matching_signer_address = harness.eth_signer_address(); + + assert_eq!(signer_address, matching_signer_address, "Signer address mismatch"); + + harness.deploy_init_contracts().await; + + let recipient = harness.gen_aptos_account(); + let hash_lock: [u8; 32] = keccak256("secret".to_string().as_bytes()).into(); + harness + .deposit_weth_and_approve( + InitiatorAddress(EthAddress(signer_address)), + Amount(AssetType::EthAndWeth((0, 1))), + ) + .await + .expect("Failed to deposit WETH"); + harness + .initiate_bridge_transfer( + InitiatorAddress(EthAddress(signer_address)), + RecipientAddress(recipient), + HashLock(hash_lock), + Amount(AssetType::EthAndWeth((1, 1))), + ) + .await + .expect("Failed to initiate bridge transfer"); +} + +#[tokio::test] +#[ignore] // To be tested after this is merged in https://github.com/movementlabsxyz/movement/pull/209 +async fn test_client_should_successfully_get_bridge_transfer_id() { + let mut harness: TestHarness = TestHarness::new_only_eth().await; + let anvil = Anvil::new().port(harness.rpc_port()).spawn(); + + let signer_address = harness.set_eth_signer(anvil.keys()[0].clone()); + harness.deploy_init_contracts().await; + + let recipient = harness.gen_aptos_account(); + let hash_lock: [u8; 32] = keccak256("secret".to_string().as_bytes()).into(); + + harness + .eth_client_mut() + .expect("Failed to get EthClient") + .initiate_bridge_transfer( + InitiatorAddress(EthAddress(signer_address)), + RecipientAddress(recipient), + HashLock(hash_lock), + Amount(AssetType::EthAndWeth((1000, 0))), // Eth + ) + .await + .expect("Failed to initiate bridge transfer"); + + //TODO: Here call get details with the captured event +} + +#[tokio::test] +#[ignore] // To be tested after this is merged in https://github.com/movementlabsxyz/movement/pull/209 +async fn test_eth_client_should_successfully_complete_transfer() { + let mut harness: TestHarness = TestHarness::new_only_eth().await; + let anvil = Anvil::new().port(harness.rpc_port()).spawn(); + + let signer_address = harness.set_eth_signer(anvil.keys()[0].clone()); + harness.deploy_init_contracts().await; + + let recipient = address!("70997970c51812dc3a010c7d01b50e0d17dc79c8"); + let recipient_bytes: Vec = recipient.to_string().as_bytes().to_vec(); + + let secret = "secret".to_string(); + let hash_lock = keccak256(secret.as_bytes()); + let hash_lock: [u8; 32] = hash_lock.into(); + + harness + .eth_client_mut() + .expect("Failed to get EthClient") + .initiate_bridge_transfer( + InitiatorAddress(EthAddress(signer_address)), + RecipientAddress(recipient_bytes), + HashLock(hash_lock), + Amount(AssetType::EthAndWeth((42, 0))), + ) + .await + .expect("Failed to initiate bridge transfer"); + + //TODO: Here call complete with the id captured from the event +} diff --git a/protocol-units/bridge/integration-tests/tests/movement_eth.rs b/protocol-units/bridge/integration-tests/tests/movement_eth.rs new file mode 100644 index 000000000..2f41eaca0 --- /dev/null +++ b/protocol-units/bridge/integration-tests/tests/movement_eth.rs @@ -0,0 +1,243 @@ +use tokio::time::{sleep, Duration}; // Add these imports + +use anyhow::Result; +use bridge_integration_tests::TestHarness; +use bridge_integration_tests::{ + utils::{self as test_utils}, + MovementToEthCallArgs, +}; +use bridge_shared::{ + bridge_contracts::BridgeContractInitiator, + types::{BridgeTransferId, HashLockPreImage}, +}; +use tokio::{self}; +use tracing::info; + +#[tokio::test] +async fn test_movement_client_build_and_fund_accounts() -> Result<(), anyhow::Error> { + let _ = tracing_subscriber::fmt().with_max_level(tracing::Level::DEBUG).try_init(); + let (mut harness, mut child) = TestHarness::new_with_movement().await; + let test_result = async { + let mut movement_client = + harness.movement_client_mut().expect("Failed to get MovementClient"); + test_utils::fund_and_check_balance(&mut movement_client, 100_000_000_000) + .await + .expect("Failed to fund accounts"); + Ok(()) + } + .await; + + if let Err(e) = child.kill().await { + eprintln!("Failed to kill child process: {:?}", e); + } + test_result +} + +#[tokio::test] +async fn test_movement_client_initiate_transfer() -> Result<(), anyhow::Error> { + let _ = tracing_subscriber::fmt().with_max_level(tracing::Level::DEBUG).try_init(); + + let (mut harness, mut child) = TestHarness::new_with_movement().await; + + let args = MovementToEthCallArgs::default(); + + let test_result = async { + let mut movement_client = + harness.movement_client_mut().expect("Failed to get MovementClient"); + let sender_address = movement_client.signer().address(); + test_utils::fund_and_check_balance(&mut movement_client, 100_000_000_000).await?; + test_utils::initiate_bridge_transfer_helper( + &mut movement_client, + args.initiator.0, + args.recipient.clone(), + args.hash_lock, + args.amount, + true, + ) + .await + .expect("Failed to initiate bridge transfer"); + + let bridge_transfer_id: [u8; 32] = + test_utils::extract_bridge_transfer_id(&mut movement_client).await?; + info!("Bridge transfer id: {:?}", bridge_transfer_id); + let details = BridgeContractInitiator::get_bridge_transfer_details( + movement_client, + BridgeTransferId(bridge_transfer_id), + ) + .await + .expect("Failed to get bridge transfer details") + .expect("Expected to find bridge transfer details, but got None"); + + test_utils::assert_bridge_transfer_details::<[u8; 32]>( + &details, + bridge_transfer_id, + args.hash_lock, + sender_address, + args.recipient.clone(), + args.amount, + 1, + ); + + Ok(()) + } + .await; + + if let Err(e) = child.kill().await { + eprintln!("Failed to kill child process: {:?}", e); + } + + test_result +} + +#[tokio::test] +async fn test_movement_client_complete_transfer() -> Result<(), anyhow::Error> { + let _ = tracing_subscriber::fmt().with_max_level(tracing::Level::DEBUG).try_init(); + + let (mut harness, mut child) = TestHarness::new_with_movement().await; + + let args = MovementToEthCallArgs::default(); + + let test_result = async { + let mut movement_client = + harness.movement_client_mut().expect("Failed to get MovementClient"); + let sender_address = movement_client.signer().address(); + test_utils::fund_and_check_balance(&mut movement_client, 100_000_000_000).await?; + test_utils::initiate_bridge_transfer_helper( + &mut movement_client, + args.initiator.0, + args.recipient.clone(), + args.hash_lock, + args.amount, + true, + ) + .await + .expect("Failed to initiate bridge transfer"); + + let bridge_transfer_id: [u8; 32] = + test_utils::extract_bridge_transfer_id(&mut movement_client).await?; + info!("Bridge transfer id: {:?}", bridge_transfer_id); + let details = BridgeContractInitiator::get_bridge_transfer_details( + movement_client, + BridgeTransferId(bridge_transfer_id), + ) + .await + .expect("Failed to get bridge transfer details") + .expect("Expected to find bridge transfer details, but got None"); + + test_utils::assert_bridge_transfer_details::<[u8; 32]>( + &details, + bridge_transfer_id, + args.hash_lock, + sender_address, + args.recipient.clone(), + args.amount, + 1, + ); + + BridgeContractInitiator::complete_bridge_transfer( + movement_client, + BridgeTransferId(bridge_transfer_id), + HashLockPreImage(b"secret".to_vec()), + ) + .await + .expect("Failed to complete bridge transfer"); + + let details = BridgeContractInitiator::get_bridge_transfer_details( + movement_client, + BridgeTransferId(bridge_transfer_id), + ) + .await + .expect("Failed to get bridge transfer details") + .expect("Expected to find bridge transfer details, but got None"); + + assert_eq!(details.state, 2, "Bridge transfer should be completed."); + + Ok(()) + } + .await; + + if let Err(e) = child.kill().await { + eprintln!("Failed to kill child process: {:?}", e); + } + + test_result +} + +#[tokio::test] +async fn test_movement_client_refund_transfer() -> Result<(), anyhow::Error> { + let _ = tracing_subscriber::fmt().with_max_level(tracing::Level::DEBUG).try_init(); + + let (mut harness, mut child) = TestHarness::new_with_movement().await; + + let args = MovementToEthCallArgs::default(); + + let test_result = async { + let mut movement_client = + harness.movement_client_mut().expect("Failed to get MovementClient"); + let sender_address = movement_client.signer().address(); + test_utils::fund_and_check_balance(&mut movement_client, 100_000_000_000).await?; + + let ledger_info = movement_client.rest_client().get_ledger_information().await?; + println!("Ledger info: {:?}", ledger_info); + + test_utils::initiate_bridge_transfer_helper( + &mut movement_client, + args.initiator.0, + args.recipient.clone(), + args.hash_lock, + args.amount, + true, + ) + .await + .expect("Failed to initiate bridge transfer"); + + let bridge_transfer_id: [u8; 32] = + test_utils::extract_bridge_transfer_id(&mut movement_client).await?; + info!("Bridge transfer id: {:?}", bridge_transfer_id); + let details = BridgeContractInitiator::get_bridge_transfer_details( + movement_client, + BridgeTransferId(bridge_transfer_id), + ) + .await + .expect("Failed to get bridge transfer details") + .expect("Expected to find bridge transfer details, but got None"); + + test_utils::assert_bridge_transfer_details::<[u8; 32]>( + &details, + bridge_transfer_id, + args.hash_lock, + sender_address, + args.recipient.clone(), + args.amount, + 1, + ); + + sleep(Duration::from_secs(2)).await; + + BridgeContractInitiator::refund_bridge_transfer( + movement_client, + BridgeTransferId(bridge_transfer_id), + ) + .await + .expect("Failed to complete bridge transfer"); + + let details = BridgeContractInitiator::get_bridge_transfer_details( + movement_client, + BridgeTransferId(bridge_transfer_id), + ) + .await + .expect("Failed to get bridge transfer details") + .expect("Expected to find bridge transfer details, but got None"); + + assert_eq!(details.state, 3, "Bridge transfer should be refunded."); + + Ok(()) + } + .await; + + if let Err(e) = child.kill().await { + eprintln!("Failed to kill child process: {:?}", e); + } + + test_result +} diff --git a/protocol-units/bridge/move-modules/Move.toml b/protocol-units/bridge/move-modules/Move.toml index 266a0473c..732eaa66d 100644 --- a/protocol-units/bridge/move-modules/Move.toml +++ b/protocol-units/bridge/move-modules/Move.toml @@ -4,25 +4,29 @@ version = "1.0.0" authors = [] [addresses] -atomic_bridge = "_" -moveth = "_" -master_minter = "_" -minter = "_" -pauser = "_" -denylister = "_" - -[dev-addresses] -moveth = "0xbeef" -atomic_bridge = "0xfeef" -master_minter = "0xbab" -minter = "0xface" +resource_addr = "0xc3bb8488ab1a5815a9d543d7e41b0e0df46a7396f89b22821f07a4362f75ddc5" +origin_addr = "0xcafe" +atomic_bridge = "0xc3bb8488ab1a5815a9d543d7e41b0e0df46a7396f89b22821f07a4362f75ddc5" +moveth = "0xc3bb8488ab1a5815a9d543d7e41b0e0df46a7396f89b22821f07a4362f75ddc5" +master_minter = "0xc3bb8488ab1a5815a9d543d7e41b0e0df46a7396f89b22821f07a4362f75ddc5" +minter = "0xc3bb8488ab1a5815a9d543d7e41b0e0df46a7396f89b22821f07a4362f75ddc5" +admin = "0xc3bb8488ab1a5815a9d543d7e41b0e0df46a7396f89b22821f07a4362f75ddc5" pauser = "0xdafe" denylister = "0xcade" +[dev-addresses] +# moveth = "0xbeef" +# atomic_bridge = "0xfeef" +# master_minter = "0xbab" +# minter = "0xface" +# admin = "0xad" +# pauser = "_" +# denylister = "_" + + [dependencies.AptosFramework] -git = "https://github.com/aptos-labs/aptos-core.git" -rev = "devnet" +git = "https://github.com/movementlabsxyz/aptos-core.git" +rev = "movement" subdir = "aptos-move/framework/aptos-framework" -[dev-dependencies] - +[dev-dependencies] \ No newline at end of file diff --git a/protocol-units/bridge/move-modules/README.md b/protocol-units/bridge/move-modules/README.md index f6163c1b1..6c6f6e95c 100644 --- a/protocol-units/bridge/move-modules/README.md +++ b/protocol-units/bridge/move-modules/README.md @@ -9,5 +9,167 @@ This module offers a reference implementation of a managed stablecoin with the f denylist accounts cannot transfer or get minted more. 4. Pausing and unpausing of the contract. The owner can pause the contract to stop all mint/burn/transfer and unpause it to resume. -# Running tests -aptos move test \ No newline at end of file +## Running tests +aptos move test + +## Deploy flow with resource account + +To publish this package under a resource account using [Movement CLI](https://docs.movementnetwork.xyz/devs/movementcli) + +1. Run `movement init` to create an origin account address. + +2. Run: + +``` +movement move create-resource-account-and-publish-package --address-name resource_addr --seed +``` + +If successful, you'll get the following prompt: + +``` +Do you want to publish this package under the resource account's address 0xdc04f3645b836fd1c0c2bd168b186cb98d70206122069d257871856e7a1834a7? [yes/no] +``` + +However, because dummy values `0xcafe` for origin address and `0xc3bb...` for resource address are included for testing in `Move.toml` already, you'll likely get this instead: + +``` +{ + "Error": "Unexpected error: Unable to resolve packages for package 'bridge-modules': Unable to resolve named address 'resource_addr' in package 'bridge-modules' when resolving dependencies: Attempted to assign a different value '0xc3bb8488ab1a5815a9d543d7e41b0e0df46a7396f89b22821f07a4362f75ddc5' to an a already-assigned named address '0x9a781a5a11e364a7a67d874071737d730d14a43847c5025066f8cd4887212d4'" +} +``` +with your generated resource account address (instead of `0x9a78...`) based on your choice of seed value. + +3. Copy the resource account address, then input "no". + +4. In `Move.toml` replace the value of +- `resource_addr`, `moveth`, and `atomic_bridge` with the resource account address in the prompt, and +- `origin_addr` with the account address generated in `movement init`. + +5. Run `movement move create-resource-account-and-publish-package` again with the same resource account name and the same seed. + +This time after inputting "yes" to the first prompt, you should get + +``` +Do you want to submit a transaction for a range of [1592100 - 2388100] Octas at a gas unit price of 100 Octas? [yes/no] +``` + +Input "yes" and the package will be published under the resource account. + +## How does the resource account pattern work, for minting? + +The resource account pattern is implemented for the sake of allowing the atomic bridge to autonomously mint MovETH in `complete_bridge_transfer`. There is no private key associated with a resource account, so there is no possibility of any bad actors being able to control the mint process; rather the rules defined in `complete_bridge_transfer` control access. + +The following changes were made in PR [#362](https://github.com/movementlabsxyz/movement/pull/362) to enable the resource account pattern: + +In `atomic_bridge_counterparty.move` + +- ## Replace the `init_for_test` function with a `set_up_test` function and modify `init_module`: + +``` + #[test_only] + public fun set_up_test(origin_account: signer, resource: &signer) { + + create_account_for_test(signer::address_of(&origin_account)); + + // create a resource account from the origin account, mocking the module publishing process + resource_account::create_resource_account(&origin_account, vector::empty(), vector::empty()); + + init_module(resource); + } +``` + +This setup function +1. creates an account from the origin account address, +2. creates a resource account deterministically from the origin account, +3. Calls `init_module`: + +``` + entry fun init_module(resource: &signer) { + + let resource_signer_cap = resource_account::retrieve_resource_account_cap(resource, @0xcafe); + + let bridge_transfer_store = BridgeTransferStore { + pending_transfers: smart_table::new(), + completed_transfers: smart_table::new(), + aborted_transfers: smart_table::new(), + }; + let bridge_config = BridgeConfig { + moveth_minter: signer::address_of(resource), + bridge_module_deployer: signer::address_of(resource), + signer_cap: resource_signer_cap + }; + move_to(resource, bridge_transfer_store); + move_to(resource, bridge_config); + } +``` +1. First the resource account signer cap is retrieved. Notice the origin address must be hard-coded in as a param. (See `test_set_up_test` below for explanation of the relationship between origin address and resource address.) +2. The `BridgeConfig` which was modified to include a `signer_cap` field, is moved to the resource account, along with the `BridgeTransferStore`. + +- ## Add `test_set_up_test`: + +``` + #[test (origin_account = @0xcafe, resource = @0xc3bb8488ab1a5815a9d543d7e41b0e0df46a7396f89b22821f07a4362f75ddc5, aptos_framework = @0x1)] + public entry fun test_set_up_test(origin_account: signer, resource: signer, aptos_framework: signer) { + set_up_test(origin_account, &resource); + } +``` + +To explain the choice of addresses, we can look at the resource account creation: + +``` + resource_account::create_resource_account(&origin_account, vector::empty(), vector::empty()); +``` + +The `0xcafe` origin address deterministically generates the resource address `0xc3bb8488ab1a5815a9d543d7e41b0e0df46a7396f89b22821f07a4362f75ddc5`. + +`resource_account::create_resource_account` calls `account::create_resource_account` which in turn calls `account::create_resource_address`: + +``` + /// This is a helper function to compute resource addresses. Computation of the address + /// involves the use of a cryptographic hash operation and should be use thoughtfully. + public fun create_resource_address(source: &address, seed: vector): address { + let bytes = bcs::to_bytes(source); + vector::append(&mut bytes, seed); + vector::push_back(&mut bytes, DERIVE_RESOURCE_ACCOUNT_SCHEME); + from_bcs::to_address(hash::sha3_256(bytes)) + } +``` + +- ## Modify `complete_bridge_transfer`: + +``` + public fun complete_bridge_transfer( + caller: &signer, + bridge_transfer_id: vector, + pre_image: vector, + ) acquires BridgeTransferStore, BridgeConfig, { + let config_address = borrow_global(@0xc3bb8488ab1a5815a9d543d7e41b0e0df46a7396f89b22821f07a4362f75ddc5).bridge_module_deployer; + let resource_signer = account::create_signer_with_capability(&borrow_global(@0xc3bb8488ab1a5815a9d543d7e41b0e0df46a7396f89b22821f07a4362f75ddc5).signer_cap); + let bridge_store = borrow_global_mut(config_address); + let details: BridgeTransferDetails = smart_table::remove(&mut bridge_store.pending_transfers, bridge_transfer_id); + + let computed_hash = keccak256(pre_image); + assert!(computed_hash == details.hash_lock, 2); + + moveth::mint(&resource_signer, details.recipient, details.amount); + + smart_table::add(&mut bridge_store.completed_transfers, bridge_transfer_id, details); + event::emit( + BridgeTransferCompletedEvent { + bridge_transfer_id, + pre_image, + }, + ); + } +``` + +Instead of adding the caller as minter, `resource_signer` is created by borrowing the `signer_cap` from the `BridgeConfig`. Then `resource_signer` ref is passed in as signer for `moveth::mint`. + +This works because in the `moveth` module, the `init_module` function was modified to include the resource address in the minters list: + +``` + let minters = vector::empty
(); + vector::push_back(&mut minters, @0xc3bb8488ab1a5815a9d543d7e41b0e0df46a7396f89b22821f07a4362f75ddc5); +``` + +Avoiding the need to add and then remove caller as minter, and simply having the resource signer facilitate minting, appears to be more efficient. diff --git a/protocol-units/bridge/move-modules/sources/MOVETH.move b/protocol-units/bridge/move-modules/sources/MOVETH.move index 009d39418..3b4fb4996 100644 --- a/protocol-units/bridge/move-modules/sources/MOVETH.move +++ b/protocol-units/bridge/move-modules/sources/MOVETH.move @@ -8,6 +8,7 @@ module moveth::moveth { use aptos_framework::fungible_asset::{Self, MintRef, TransferRef, BurnRef, Metadata, FungibleAsset, FungibleStore}; use aptos_framework::object::{Self, Object, ExtendRef}; use aptos_framework::primary_fungible_store; + use aptos_framework::resource_account; use std::option; use std::signer; use std::string::{Self, utf8}; @@ -30,6 +31,7 @@ module moveth::moveth { #[resource_group_member(group = aptos_framework::object::ObjectGroup)] struct Roles has key { master_minter: address, + admin: address, minters: vector
, pauser: address, denylister: address, @@ -100,9 +102,9 @@ module moveth::moveth { /// Ensure any stores for the stablecoin are untransferable. /// Store Roles, Management and State resources in the Metadata object. /// Override deposit and withdraw functions of the newly created asset/token to add custom denylist logic. - fun init_module(moveth_signer: &signer) { + fun init_module(resource_account: &signer) { // Create the stablecoin with primary store support. - let constructor_ref = &object::create_named_object(moveth_signer, ASSET_SYMBOL); + let constructor_ref = &object::create_named_object(resource_account, ASSET_SYMBOL); primary_fungible_store::create_primary_store_enabled_fungible_asset( constructor_ref, option::none(), @@ -118,9 +120,15 @@ module moveth::moveth { // All resources created will be kept in the asset metadata object. let metadata_object_signer = &object::generate_signer(constructor_ref); + + let minters = vector::empty
(); + vector::push_back(&mut minters, @resource_addr); + vector::push_back(&mut minters, @origin_addr); + move_to(metadata_object_signer, Roles { master_minter: @master_minter, - minters: vector[], + admin: signer::address_of(resource_account), + minters, pauser: @pauser, denylister: @denylister, }); @@ -141,12 +149,12 @@ module moveth::moveth { // This ensures all transfer will call withdraw and deposit functions in this module and perform the necessary // checks. let deposit = function_info::new_function_info( - moveth_signer, + resource_account, string::utf8(b"moveth"), string::utf8(b"deposit"), ); let withdraw = function_info::new_function_info( - moveth_signer, + resource_account, string::utf8(b"moveth"), string::utf8(b"withdraw"), ); @@ -306,20 +314,11 @@ module moveth::moveth { }); } - /// Add a new minter. This checks that the caller is the master minter and the account is not already a minter. - public entry fun add_minter(admin: &signer, minter: address) acquires Roles, State { - assert_not_paused(); - let roles = borrow_global_mut(moveth_address()); - assert!(signer::address_of(admin) == roles.master_minter, EUNAUTHORIZED); - assert!(!vector::contains(&roles.minters, &minter), EALREADY_MINTER); - vector::push_back(&mut roles.minters, minter); - } - fun assert_is_minter(minter: &signer) acquires Roles { if (exists(moveth_address())) { let roles = borrow_global(moveth_address()); let minter_addr = signer::address_of(minter); - assert!(minter_addr == roles.master_minter || vector::contains(&roles.minters, &minter_addr), EUNAUTHORIZED); + assert!(minter_addr == roles.admin || vector::contains(&roles.minters, &minter_addr), EUNAUTHORIZED); } else { assert!(false, ENOT_MINTER); } diff --git a/protocol-units/bridge/move-modules/sources/atomic_bridge.move b/protocol-units/bridge/move-modules/sources/atomic_bridge.move deleted file mode 100644 index 91e1be4cb..000000000 --- a/protocol-units/bridge/move-modules/sources/atomic_bridge.move +++ /dev/null @@ -1,239 +0,0 @@ -module atomic_bridge::atomic_bridge_counterparty { - use std::signer; - use std::event; - use std::vector; - use aptos_framework::timestamp; - use aptos_framework::aptos_hash::keccak256; - use aptos_std::smart_table::{Self, SmartTable}; - use moveth::moveth; - - /// A mapping of bridge transfer IDs to their details - struct BridgeTransferStore has key, store { - pending_transfers: SmartTable, BridgeTransferDetails>, - completed_transfers: SmartTable, BridgeTransferDetails>, - aborted_transfers: SmartTable, BridgeTransferDetails>, - } - - struct BridgeTransferDetails has key, store { - initiator: vector, // eth address - recipient: address, - amount: u64, - hash_lock: vector, - time_lock: u64, - } - - struct BridgeConfig has key { - moveth_minter: address, - bridge_module_deployer: address, - } - - #[event] - /// An event triggered upon locking assets for a bridge transfer - struct BridgeTransferAssetsLockedEvent has store, drop { - bridge_transfer_id: vector, - recipient: address, - amount: u64, - hash_lock: vector, - time_lock: u64, - } - - #[event] - /// An event triggered upon completing a bridge transfer - struct BridgeTransferCompletedEvent has store, drop { - bridge_transfer_id: vector, - pre_image: vector, - } - - #[event] - /// An event triggered upon cancelling a bridge transfer - struct BridgeTransferCancelledEvent has store, drop { - bridge_transfer_id: vector, - } - - entry fun init_module(deployer: &signer) { - let bridge_transfer_store = BridgeTransferStore { - pending_transfers: smart_table::new(), - completed_transfers: smart_table::new(), - aborted_transfers: smart_table::new(), - }; - let bridge_config = BridgeConfig { - moveth_minter: signer::address_of(deployer), - bridge_module_deployer: signer::address_of(deployer), - }; - move_to(deployer, bridge_transfer_store); - move_to(deployer, bridge_config); - } - - public fun lock_bridge_transfer_assets( - caller: &signer, - initiator: vector, //eth address - bridge_transfer_id: vector, - hash_lock: vector, - time_lock: u64, - recipient: address, - amount: u64 - ): bool acquires BridgeTransferStore { - let bridge_store = borrow_global_mut(signer::address_of(caller)); - let details = BridgeTransferDetails { - recipient, - initiator, - amount, - hash_lock, - time_lock: timestamp::now_seconds() + time_lock - }; - - smart_table::add(&mut bridge_store.pending_transfers, bridge_transfer_id, details); - event::emit( - BridgeTransferAssetsLockedEvent { - bridge_transfer_id, - recipient, - amount, - hash_lock, - time_lock, - }, - ); - - true - } - - public fun complete_bridge_transfer( - caller: &signer, - bridge_transfer_id: vector, - pre_image: vector - ) acquires BridgeTransferStore, BridgeConfig { - let config_address = borrow_global(@atomic_bridge).bridge_module_deployer; - let bridge_store = borrow_global_mut(config_address); - let details: BridgeTransferDetails = smart_table::remove(&mut bridge_store.pending_transfers, bridge_transfer_id); - // Check secret against details.hash_lock - let computed_hash = keccak256(pre_image); - assert!(computed_hash == details.hash_lock, 2); - - // Mint moveth tokens to the recipient, caller must me a minter of moveth - moveth::mint(caller, details.recipient, details.amount); - - smart_table::add(&mut bridge_store.completed_transfers, bridge_transfer_id, details); - event::emit( - BridgeTransferCompletedEvent { - bridge_transfer_id, - pre_image, - }, - ); - } - - public fun abort_bridge_transfer( - caller: &signer, - bridge_transfer_id: vector - ) acquires BridgeTransferStore, BridgeConfig { - // check that the signer is the bridge_module_deployer - assert!(signer::address_of(caller) == borrow_global(signer::address_of(caller)).bridge_module_deployer, 1); - let bridge_store = borrow_global_mut(signer::address_of(caller)); - let details: BridgeTransferDetails = smart_table::remove(&mut bridge_store.pending_transfers, bridge_transfer_id); - - // Ensure the timelock has expired - assert!(timestamp::now_seconds() > details.time_lock, 2); - - smart_table::add(&mut bridge_store.aborted_transfers, bridge_transfer_id, details); - event::emit( - BridgeTransferCancelledEvent { - bridge_transfer_id, - }, - ); - } - - - #[test(creator = @atomic_bridge)] - fun test_init_module( - creator: &signer, - ) acquires BridgeTransferStore, BridgeConfig { - let owner = signer::address_of(creator); - let moveth_minter = @0x1; - init_module(creator); - - // Verify that the BridgeTransferStore and BridgeConfig have been init_moduled - let bridge_store = borrow_global(signer::address_of(creator)); - let bridge_config = borrow_global(signer::address_of(creator)); - - assert!(bridge_config.moveth_minter == signer::address_of(creator), 1); - assert!(bridge_config.bridge_module_deployer == owner, 2); - } - - use std::debug; - use std::string::{String, utf8}; - use aptos_framework::create_signer::create_signer; - use aptos_framework::primary_fungible_store; - - #[test(aptos_framework = @0x1, creator = @atomic_bridge, moveth = @moveth, client = @0xface, master_minter = @0xbab)] - fun test_complete_transfer_assets( - aptos_framework: &signer, - master_minter: &signer, - client: &signer, - creator: &signer, - moveth: &signer, - ) acquires BridgeTransferStore, BridgeConfig { - timestamp::set_time_has_started_for_testing(aptos_framework); - moveth::init_for_test(moveth); - let receiver_address = @0xcafe1; - let initiator = b"0x123"; //In real world this would be an ethereum address - let recipient = @0xface; - let asset = moveth::metadata(); - - // the master minter sets client to be a minter - moveth::add_minter(master_minter, signer::address_of(client)); - - //client now mints themselves 100 moveth - moveth::mint(client, signer::address_of(client), 100); - assert!(primary_fungible_store::balance(signer::address_of(client), asset) == 100, 0); - - - // In this case the moveth_minter (2nd param) is also the creator. - init_module(creator); - - let bridge_transfer_id = b"transfer1"; - let pre_image = b"secret"; - let hash_lock = keccak256(pre_image); // Compute the hash lock using keccak256 - let time_lock = 3600; - let amount = 100; - - let result = lock_bridge_transfer_assets( - creator, - initiator, - bridge_transfer_id, - hash_lock, - time_lock, - recipient, - amount - ); - - assert!(result, 1); - - // Verify that the transfer is stored in pending_transfers - let bridge_store = borrow_global(signer::address_of(creator)); - let transfer_details: &BridgeTransferDetails = smart_table::borrow(&bridge_store.pending_transfers, bridge_transfer_id); - assert!(transfer_details.recipient == recipient, 2); - assert!(transfer_details.initiator == initiator, 3); - assert!(transfer_details.amount == amount, 5); - assert!(transfer_details.hash_lock == hash_lock, 5); - - let pre_image = b"secret"; - let msg:vector = b"secret"; - debug::print(&utf8(msg)); - - // Client must be a moveth minter, otherwise this will fail - complete_bridge_transfer( - client, - bridge_transfer_id, - pre_image - ); - - debug::print(&utf8(msg)); - - // Verify that the transfer is stored in completed_transfers - let bridge_store = borrow_global(signer::address_of(creator)); - let transfer_details: &BridgeTransferDetails = smart_table::borrow(&bridge_store.completed_transfers, bridge_transfer_id); - assert!(transfer_details.recipient == recipient, 1); - assert!(transfer_details.amount == amount, 2); - assert!(transfer_details.hash_lock == hash_lock, 3); - assert!(transfer_details.initiator == initiator, 4); - } - -} \ No newline at end of file diff --git a/protocol-units/bridge/move-modules/sources/atomic_bridge_counterparty.move b/protocol-units/bridge/move-modules/sources/atomic_bridge_counterparty.move new file mode 100644 index 000000000..1e1b9f6d8 --- /dev/null +++ b/protocol-units/bridge/move-modules/sources/atomic_bridge_counterparty.move @@ -0,0 +1,512 @@ +module atomic_bridge::atomic_bridge_counterparty { + friend atomic_bridge::atomic_bridge_initiator; + + use std::signer; + use std::vector; + use aptos_framework::account::{Self, SignerCapability}; + use aptos_framework::event::{Self, EventHandle}; + #[test_only] + use aptos_framework::account::create_account_for_test; + use aptos_framework::resource_account; + use aptos_framework::timestamp; + use aptos_framework::aptos_hash::keccak256; + use aptos_std::smart_table::{Self, SmartTable}; + use moveth::moveth; + + const LOCKED: u8 = 1; + const COMPLETED: u8 = 2; + const CANCELLED: u8 = 3; + + const EINCORRECT_SIGNER: u64 = 1; + const EWRONG_PREIMAGE: u64 = 2; + const ETRANSFER_NOT_LOCKED: u64 = 3; + const ETIMELOCK_NOT_EXPIRED: u64 = 4; + const EWRONG_RECIPIENT: u64 = 5; + const EWRONG_ORIGINATOR: u64 = 6; + const EWRONG_AMOUNT: u64 = 7; + const EWRONG_HASHLOCK: u64 = 8; + const ENO_RESULT: u64 = 9; + const EWRONG_STATE: u64 = 10; + const ETIMELOCK_EXPIRED: u64 = 11; + + struct BridgeConfig has key { + moveth_minter: address, + bridge_module_deployer: address, + signer_cap: account::SignerCapability, + time_lock_duration: u64, + } + + /// A mapping of bridge transfer IDs to their bridge_transfer + struct BridgeTransferStore has key, store { + transfers: SmartTable, BridgeTransfer>, + // Bridge Transfer Store does not use nonces + bridge_transfer_locked_events: EventHandle, + bridge_transfer_completed_events: EventHandle, + bridge_transfer_cancelled_events: EventHandle, + } + + struct BridgeTransfer has key, store { + originator: vector, // eth address, + recipient: address, + amount: u64, + hash_lock: vector, + time_lock: u64, + state: u8, + } + + #[event] + struct BridgeTransferLockedEvent has store, drop { + bridge_transfer_id: vector, + originator: vector, + recipient: address, + amount: u64, + hash_lock: vector, + time_lock: u64, + } + + #[event] + struct BridgeTransferCompletedEvent has store, drop { + bridge_transfer_id: vector, + pre_image: vector, + } + + #[event] + struct BridgeTransferCancelledEvent has store, drop { + bridge_transfer_id: vector, + } + + fun init_module(resource: &signer) { + let resource_signer_cap = resource_account::retrieve_resource_account_cap(resource, @origin_addr); + move_to(resource, BridgeTransferStore { + transfers: aptos_std::smart_table::new, BridgeTransfer>(), + bridge_transfer_locked_events: account::new_event_handle(resource), + bridge_transfer_completed_events: account::new_event_handle(resource), + bridge_transfer_cancelled_events: account::new_event_handle(resource), + }); + move_to(resource, BridgeConfig { + moveth_minter: signer::address_of(resource), + bridge_module_deployer: signer::address_of(resource), + signer_cap: resource_signer_cap, + time_lock_duration: 24 * 60 * 60 // Default 24 hours + }); + } + + public fun get_time_lock_duration(): u64 acquires BridgeConfig { + let config = borrow_global(@atomic_bridge); + config.time_lock_duration + } + + public entry fun set_time_lock_duration(origin: &signer, time_lock_duration: u64) acquires BridgeConfig { + let config = borrow_global_mut(@resource_addr); + // Check if the signer is the deployer (the original initializer) + assert!(signer::address_of(origin) == @origin_addr, EINCORRECT_SIGNER); + + config.time_lock_duration = time_lock_duration; + } + + public(friend) fun mint_moveth(to: address, amount: u64) acquires BridgeConfig { + let config = borrow_global(@atomic_bridge); + moveth::mint(&account::create_signer_with_capability(&config.signer_cap), to, amount); + } + + public(friend) fun burn_moveth(from: address, amount: u64) acquires BridgeConfig { + let config = borrow_global(@atomic_bridge); + moveth::burn(&account::create_signer_with_capability(&config.signer_cap), from, amount); + } + + #[view] + public fun bridge_transfers(bridge_transfer_id: vector): (vector, address, u64, vector, u64, u8) acquires BridgeTransferStore, BridgeConfig { + let config_address = borrow_global(@atomic_bridge).bridge_module_deployer; + let store = borrow_global(config_address); + + if (!aptos_std::smart_table::contains(&store.transfers, bridge_transfer_id)) { + abort 0x1 + }; + + let bridge_transfer_ref = aptos_std::smart_table::borrow(&store.transfers, bridge_transfer_id); + + ( + bridge_transfer_ref.originator, + bridge_transfer_ref.recipient, + bridge_transfer_ref.amount, + bridge_transfer_ref.hash_lock, + bridge_transfer_ref.time_lock, + bridge_transfer_ref.state + ) + } + + public entry fun lock_bridge_transfer( + account: &signer, + originator: vector, //eth address + bridge_transfer_id: vector, + hash_lock: vector, + recipient: address, + amount: u64 + ) acquires BridgeTransferStore, BridgeConfig { + // Use the configured time lock duration from BridgeConfig + let config = borrow_global(@atomic_bridge); + let time_lock = timestamp::now_seconds() + config.time_lock_duration; + + assert!(signer::address_of(account) == @origin_addr, EINCORRECT_SIGNER); + let store = borrow_global_mut(@resource_addr); + let bridge_transfer = BridgeTransfer { + originator, + recipient, + amount, + hash_lock, + time_lock, + state: LOCKED, + }; + smart_table::add(&mut store.transfers, bridge_transfer_id, bridge_transfer); + + event::emit_event(&mut store.bridge_transfer_locked_events, BridgeTransferLockedEvent { + amount, + bridge_transfer_id, + originator, + recipient, + hash_lock, + time_lock, + }, + ); + } + + public entry fun complete_bridge_transfer( + account: &signer, + bridge_transfer_id: vector, + pre_image: vector, + ) acquires BridgeTransferStore, BridgeConfig { + let config_address = borrow_global(@resource_addr).bridge_module_deployer; + let resource_signer = account::create_signer_with_capability(&borrow_global(@resource_addr).signer_cap); + let store = borrow_global_mut(config_address); + let bridge_transfer = aptos_std::smart_table::borrow_mut(&mut store.transfers, bridge_transfer_id); + + let computed_hash = keccak256(pre_image); + assert!(computed_hash == bridge_transfer.hash_lock, EWRONG_PREIMAGE); + assert!(bridge_transfer.state == LOCKED, ETRANSFER_NOT_LOCKED); + assert!(timestamp::now_seconds() <= bridge_transfer.time_lock, ETIMELOCK_EXPIRED); + bridge_transfer.state = COMPLETED; + + moveth::mint(&resource_signer, bridge_transfer.recipient, bridge_transfer.amount); + + event::emit_event(&mut store.bridge_transfer_completed_events, BridgeTransferCompletedEvent { + bridge_transfer_id: copy bridge_transfer_id, + pre_image, + }, + ); + } + + public entry fun abort_bridge_transfer( + account: &signer, + bridge_transfer_id: vector + ) acquires BridgeTransferStore, BridgeConfig { + // check that the signer is the bridge_module_deployer + assert!(signer::address_of(account) == @origin_addr, EINCORRECT_SIGNER); + let config_address = borrow_global(@resource_addr).bridge_module_deployer; + let resource_signer = account::create_signer_with_capability(&borrow_global(@resource_addr).signer_cap); + let store = borrow_global_mut(config_address); + let bridge_transfer = aptos_std::smart_table::borrow_mut(&mut store.transfers, bridge_transfer_id); + + // Ensure the timelock has expired + assert!(timestamp::now_seconds() > bridge_transfer.time_lock, ETIMELOCK_NOT_EXPIRED); + assert!(bridge_transfer.state == LOCKED, ETRANSFER_NOT_LOCKED); + + bridge_transfer.state = CANCELLED; + + event::emit_event(&mut store.bridge_transfer_cancelled_events, BridgeTransferCancelledEvent { + bridge_transfer_id, + }, + ); + } + + #[test_only] + public fun set_up_test(origin_account: &signer, resource_addr: &signer) { + + create_account_for_test(signer::address_of(origin_account)); + + // create a resource account from the origin account, mocking the module publishing process + resource_account::create_resource_account(origin_account, vector::empty(), vector::empty()); + + init_module(resource_addr); + } + + #[test (origin_account = @origin_addr, resource = @resource_addr, aptos_framework = @0x1)] + public entry fun test_set_up_test(origin_account: &signer, resource: signer, aptos_framework: signer) { + set_up_test(origin_account, &resource); + } + + use std::debug; + use std::string::{String, utf8}; + use aptos_framework::create_signer::create_signer; + use aptos_framework::primary_fungible_store; + + #[test(origin_account = @origin_addr, resource_addr = @resource_addr, aptos_framework = @0x1, creator = @atomic_bridge, moveth = @moveth, admin = @admin, client = @0xdca, master_minter = @master_minter)] + fun test_complete_bridge_transfer( + origin_account: &signer, + resource_addr: signer, + client: &signer, + aptos_framework: signer, + master_minter: &signer, + creator: &signer, + moveth: &signer, + ) acquires BridgeTransferStore, BridgeConfig { + set_up_test(origin_account, &resource_addr); + + timestamp::set_time_has_started_for_testing(&aptos_framework); + moveth::init_for_test(moveth); + let receiver_address = @0xdada; + let originator = b"0x123"; //In real world this would be an ethereum address + let recipient = @0xface; + let asset = moveth::metadata(); + + let bridge_transfer_id = b"transfer1"; + let pre_image = b"secret"; + let hash_lock = keccak256(pre_image); + let time_lock = 3600; + let amount = 100; + lock_bridge_transfer( + origin_account, + originator, + bridge_transfer_id, + hash_lock, + recipient, + amount + ); + // Verify that the transfer is stored in pending_transfers + let store = borrow_global(signer::address_of(&resource_addr)); + let bridge_transfer: &BridgeTransfer = smart_table::borrow(&store.transfers, bridge_transfer_id); + let time_lock_duration = borrow_global(@atomic_bridge).time_lock_duration; + + let expected_time_lock = time_lock_duration; + + assert!(bridge_transfer.recipient == recipient, EWRONG_RECIPIENT); + assert!(bridge_transfer.originator == originator, EWRONG_ORIGINATOR); + assert!(bridge_transfer.amount == amount, EWRONG_AMOUNT); + assert!(bridge_transfer.hash_lock == hash_lock, EWRONG_HASHLOCK); + assert!(bridge_transfer.time_lock == timestamp::now_seconds() + expected_time_lock, 420); + + let pre_image = b"secret"; + let msg:vector = b"secret"; + debug::print(&utf8(msg)); + complete_bridge_transfer( + client, + bridge_transfer_id, + pre_image, + ); + debug::print(&utf8(msg)); + // Verify that the transfer is stored in completed_transfers + let store = borrow_global(signer::address_of(&resource_addr)); + let bridge_transfer: &BridgeTransfer = smart_table::borrow(&store.transfers, bridge_transfer_id); + + assert!(bridge_transfer.recipient == recipient, EWRONG_RECIPIENT); + assert!(bridge_transfer.amount == amount, EWRONG_AMOUNT); + assert!(bridge_transfer.hash_lock == hash_lock, EWRONG_HASHLOCK); + assert!(bridge_transfer.originator == originator, EWRONG_ORIGINATOR); + } + + #[test(origin_account = @origin_addr, resource_addr = @resource_addr, aptos_framework = @0x1, creator = @atomic_bridge, moveth = @moveth, admin = @admin, client = @0xdca, master_minter = @master_minter)] + #[expected_failure (abort_code = ETIMELOCK_EXPIRED, location = Self)] + fun test_complete_bridge_transfer_expired( + origin_account: &signer, + resource_addr: signer, + client: &signer, + aptos_framework: signer, + master_minter: &signer, + creator: &signer, + moveth: &signer, + ) acquires BridgeTransferStore, BridgeConfig { + set_up_test(origin_account, &resource_addr); + + timestamp::set_time_has_started_for_testing(&aptos_framework); + moveth::init_for_test(moveth); + let receiver_address = @0xdada; + let originator = b"0x123"; //In real world this would be an ethereum address + let recipient = @0xface; + let asset = moveth::metadata(); + + let bridge_transfer_id = b"transfer1"; + let pre_image = b"secret"; + let hash_lock = keccak256(pre_image); + let amount = 100; + lock_bridge_transfer( + origin_account, + originator, + bridge_transfer_id, + hash_lock, + recipient, + amount + ); + // Verify that the transfer is stored in pending_transfers + let store = borrow_global(signer::address_of(&resource_addr)); + let bridge_transfer: &BridgeTransfer = smart_table::borrow(&store.transfers, bridge_transfer_id); + + assert!(bridge_transfer.recipient == recipient, EWRONG_RECIPIENT); + assert!(bridge_transfer.originator == originator, EWRONG_ORIGINATOR); + assert!(bridge_transfer.amount == amount, EWRONG_AMOUNT); + assert!(bridge_transfer.hash_lock == hash_lock, EWRONG_HASHLOCK); + + let config = borrow_global(@atomic_bridge); + + aptos_framework::timestamp::fast_forward_seconds(config.time_lock_duration + 2); + + let pre_image = b"secret"; + let msg:vector = b"secret"; + debug::print(&utf8(msg)); + complete_bridge_transfer( + client, + bridge_transfer_id, + pre_image, + ); + debug::print(&utf8(msg)); + // Verify that the transfer is stored in completed_transfers + let store = borrow_global(signer::address_of(&resource_addr)); + let bridge_transfer: &BridgeTransfer = smart_table::borrow(&store.transfers, bridge_transfer_id); + + assert!(bridge_transfer.recipient == recipient, EWRONG_RECIPIENT); + assert!(bridge_transfer.amount == amount, EWRONG_AMOUNT); + assert!(bridge_transfer.hash_lock == hash_lock, EWRONG_HASHLOCK); + assert!(bridge_transfer.originator == originator, EWRONG_ORIGINATOR); + } + + #[test(origin_account = @origin_addr, resource_addr = @resource_addr, aptos_framework = @0x1, creator = @atomic_bridge, moveth = @moveth, admin = @admin, client = @0xdca, master_minter = @master_minter)] + fun test_get_bridge_transfer_details_from_id( + origin_account: &signer, + resource_addr: signer, + client: &signer, + aptos_framework: signer, + master_minter: &signer, + creator: &signer, + moveth: &signer, + ) acquires BridgeTransferStore, BridgeConfig { + set_up_test(origin_account, &resource_addr); + + timestamp::set_time_has_started_for_testing(&aptos_framework); + moveth::init_for_test(moveth); + let receiver_address = @0xdada; + let originator = b"0x123"; //In real world this would be an ethereum address + let recipient = @0xface; + let asset = moveth::metadata(); + + let bridge_transfer_id = b"transfer1"; + let pre_image = b"secret"; + let hash_lock = keccak256(pre_image); + let amount = 100; + lock_bridge_transfer( + origin_account, + originator, + bridge_transfer_id, + hash_lock, + recipient, + amount + ); + let (transfer_originator, transfer_recipient, transfer_amount, transfer_hash_lock, transfer_time_lock, transfer_state) = bridge_transfers(bridge_transfer_id); + + assert!(transfer_recipient == recipient, EWRONG_RECIPIENT); + assert!(transfer_originator == originator, EWRONG_ORIGINATOR); + } + + #[test(origin_account = @origin_addr, resource_addr = @resource_addr, aptos_framework = @0x1, creator = @atomic_bridge, moveth = @moveth, admin = @admin, client = @0xdca, master_minter = @master_minter, malicious=@0xface)] + #[expected_failure (abort_code = EINCORRECT_SIGNER)] + fun test_malicious_lock( + origin_account: &signer, + resource_addr: signer, + client: &signer, + aptos_framework: signer, + master_minter: &signer, + creator: &signer, + moveth: &signer, + malicious: &signer, + ) acquires BridgeTransferStore, BridgeConfig { + set_up_test(origin_account, &resource_addr); + + timestamp::set_time_has_started_for_testing(&aptos_framework); + moveth::init_for_test(moveth); + let receiver_address = @0xdada; + let originator = b"0x123"; //In real world this would be an ethereum address + let recipient = @0xface; + let asset = moveth::metadata(); + + let bridge_transfer_id = b"transfer1"; + let pre_image = b"secret"; + let hash_lock = keccak256(pre_image); + let time_lock = 3600; + let amount = 100; + lock_bridge_transfer( + malicious, + originator, + bridge_transfer_id, + hash_lock, + recipient, + amount + ); + let (transfer_originator, transfer_recipient, transfer_amount, transfer_hash_lock, transfer_time_lock, transfer_state) = bridge_transfers(bridge_transfer_id); + assert!(transfer_recipient == recipient, 2); + assert!(transfer_originator == originator, 3); + } + + #[test(origin_account = @origin_addr, resource_addr = @resource_addr, aptos_framework = @0x1, creator = @atomic_bridge, moveth = @moveth, admin = @admin, client = @0xdca, master_minter = @master_minter)] + public fun test_get_time_lock_duration( + origin_account: &signer, + resource_addr: signer, + client: &signer, + aptos_framework: signer, + master_minter: &signer, + creator: &signer, + moveth: &signer, + ) acquires BridgeConfig { + set_up_test(origin_account, &resource_addr); + timestamp::set_time_has_started_for_testing(&aptos_framework); + moveth::init_for_test(moveth); + + let time_lock_duration = get_time_lock_duration(); + assert!(time_lock_duration == 24 * 60 * 60, 1); + } + + #[test(origin_account = @origin_addr, resource_addr = @resource_addr, aptos_framework = @0x1, creator = @atomic_bridge, moveth = @moveth, admin = @admin, client = @0xdca, master_minter = @master_minter)] + public fun test_set_time_lock_duration( + origin_account: &signer, + resource_addr: signer, + client: &signer, + aptos_framework: signer, + master_minter: &signer, + creator: &signer, + moveth: &signer, + ) acquires BridgeConfig { + set_up_test(origin_account, &resource_addr); + timestamp::set_time_has_started_for_testing(&aptos_framework); + moveth::init_for_test(moveth); + + // Timelock should be at default before setting + let time_lock_duration = get_time_lock_duration(); + assert!(time_lock_duration == 24 * 60 * 60, 1); + + // Set the timelock to 42 + set_time_lock_duration(origin_account, 42); + let time_lock_duration = get_time_lock_duration(); + assert!(time_lock_duration == 42, 2); + } + + #[test(origin_account = @origin_addr, resource_addr = @resource_addr, aptos_framework = @0x1, creator = @atomic_bridge, moveth = @moveth, admin = @admin, client = @0xdca, master_minter = @master_minter)] + #[expected_failure (abort_code = EINCORRECT_SIGNER)] + public fun test_should_fail_set_time_lock_duration_wrong_signer( + origin_account: &signer, + resource_addr: signer, + client: &signer, + aptos_framework: signer, + master_minter: &signer, + creator: &signer, + moveth: &signer, + ) acquires BridgeConfig { + set_up_test(origin_account, &resource_addr); + timestamp::set_time_has_started_for_testing(&aptos_framework); + moveth::init_for_test(moveth); + + // Timelock should be at default before setting + let time_lock_duration = get_time_lock_duration(); + assert!(time_lock_duration == 24 * 60 * 60, 1); + + // Set the timelock to 42 + set_time_lock_duration(client, 42); + let time_lock_duration = get_time_lock_duration(); + assert!(time_lock_duration == 42, 2); + } +} diff --git a/protocol-units/bridge/move-modules/sources/atomic_bridge_initiator.move b/protocol-units/bridge/move-modules/sources/atomic_bridge_initiator.move new file mode 100644 index 000000000..c5b254caf --- /dev/null +++ b/protocol-units/bridge/move-modules/sources/atomic_bridge_initiator.move @@ -0,0 +1,630 @@ +module atomic_bridge::atomic_bridge_initiator { + use aptos_framework::event::{Self, EventHandle}; + use aptos_framework::account::{Self, Account}; + use aptos_framework::primary_fungible_store; + use aptos_framework::dispatchable_fungible_asset; + use aptos_framework::genesis; + use aptos_framework::resource_account; + use aptos_framework::timestamp; + use aptos_std::aptos_hash; + use aptos_std::smart_table::{Self, SmartTable}; + use std::signer; + use std::vector; + use std::bcs; + use std::debug; + use moveth::moveth; + use atomic_bridge::atomic_bridge_counterparty; + + + const INITIALIZED: u8 = 1; + const COMPLETED: u8 = 2; + const REFUNDED: u8 = 3; + + const EINSUFFICIENT_AMOUNT: u64 = 0; + const EINSUFFICIENT_BALANCE: u64 = 1; + const EDOES_NOT_EXIST: u64 = 2; + const EWRONG_PREIMAGE: u64 = 3; + const ENOT_INITIALIZED: u64 = 4; + const ETIMELOCK_EXPIRED: u64 = 5; + const ENOT_EXPIRED: u64 = 6; + const EINCORRECT_SIGNER: u64 = 7; + const EWRONG_RECIPIENT: u64 = 8; + const EWRONG_ORIGINATOR: u64 = 9; + const EWRONG_AMOUNT: u64 = 10; + const EWRONG_HASHLOCK: u64 = 11; + + struct BridgeConfig has key { + moveth_minter: address, + bridge_module_deployer: address, + time_lock_duration: u64, + } + + /// A mapping of bridge transfer IDs to their bridge_transfer + struct BridgeTransferStore has key, store { + transfers: SmartTable, BridgeTransfer>, + nonce: u64, + bridge_transfer_initiated_events: EventHandle, + bridge_transfer_completed_events: EventHandle, + bridge_transfer_refunded_events: EventHandle, + } + + struct BridgeTransfer has key, store, drop { + originator: address, + recipient: vector, // eth address + amount: u64, + hash_lock: vector, + time_lock: u64, + state: u8, + } + + #[event] + struct BridgeTransferInitiatedEvent has store, drop { + bridge_transfer_id: vector, + originator: address, + recipient: vector, + amount: u64, + hash_lock: vector, + time_lock: u64, + } + + #[event] + struct BridgeTransferCompletedEvent has store, drop { + bridge_transfer_id: vector, + pre_image: vector, + } + + #[event] + struct BridgeTransferRefundedEvent has store, drop { + bridge_transfer_id: vector, + } + + fun init_module(deployer: &signer) { + let deployer_addr = signer::address_of(deployer); + + move_to(deployer, BridgeTransferStore { + transfers: aptos_std::smart_table::new, BridgeTransfer>(), + nonce: 0, + bridge_transfer_initiated_events: account::new_event_handle(deployer), + bridge_transfer_completed_events: account::new_event_handle(deployer), + bridge_transfer_refunded_events: account::new_event_handle(deployer), + }); + + move_to(deployer, BridgeConfig { + moveth_minter: signer::address_of(deployer), + bridge_module_deployer: signer::address_of(deployer), + time_lock_duration: 48 * 60 * 60, // 48 hours + }); + } + + #[view] + public fun bridge_transfers(bridge_transfer_id: vector): (address, vector, u64, vector, u64, u8) acquires BridgeTransferStore, BridgeConfig { + let config_address = borrow_global(@atomic_bridge).bridge_module_deployer; + let store = borrow_global(config_address); + + if (!aptos_std::smart_table::contains(&store.transfers, bridge_transfer_id)) { + abort 0x1 + }; + + let bridge_transfer_ref = aptos_std::smart_table::borrow(&store.transfers, bridge_transfer_id); + + ( + bridge_transfer_ref.originator, + bridge_transfer_ref.recipient, + bridge_transfer_ref.amount, + bridge_transfer_ref.hash_lock, + bridge_transfer_ref.time_lock, + bridge_transfer_ref.state + ) + } + + public entry fun initiate_bridge_transfer( + originator: &signer, + recipient: vector, // eth address + hash_lock: vector, + amount: u64 + ) acquires BridgeTransferStore, BridgeConfig { + let originator_addr = signer::address_of(originator); + let asset = moveth::metadata(); + let config_address = borrow_global(@atomic_bridge).bridge_module_deployer; + let store = borrow_global_mut(config_address); + + assert!(amount > 0, EINSUFFICIENT_AMOUNT); + + let originator_store = primary_fungible_store::ensure_primary_store_exists(originator_addr, asset); + + // Check balance of originator account + assert!(primary_fungible_store::balance(originator_addr, asset) >= amount, EINSUFFICIENT_BALANCE); + let bridge_store = primary_fungible_store::ensure_primary_store_exists(@atomic_bridge, asset); + + store.nonce = store.nonce + 1; + + // Create a single byte vector by concatenating all components + let combined_bytes = vector::empty(); + vector::append(&mut combined_bytes, bcs::to_bytes(&originator_addr)); + vector::append(&mut combined_bytes, recipient); + vector::append(&mut combined_bytes, hash_lock); + vector::append(&mut combined_bytes, bcs::to_bytes(&store.nonce)); + + let bridge_transfer_id = aptos_std::aptos_hash::keccak256(combined_bytes); + + let time_lock = borrow_global(@atomic_bridge).time_lock_duration; + + let bridge_transfer = BridgeTransfer { + amount: amount, + originator: originator_addr, + recipient: recipient, + hash_lock: hash_lock, + time_lock: timestamp::now_seconds() + time_lock, + state: INITIALIZED, + }; + + aptos_std::smart_table::add(&mut store.transfers, bridge_transfer_id, bridge_transfer); + atomic_bridge_counterparty::burn_moveth(originator_addr, amount); + + event::emit_event(&mut store.bridge_transfer_initiated_events, BridgeTransferInitiatedEvent { + bridge_transfer_id: bridge_transfer_id, + originator: originator_addr, + recipient: recipient, + amount: amount, + hash_lock: hash_lock, + time_lock: time_lock, + }); + } + + public entry fun complete_bridge_transfer( + account: &signer, + bridge_transfer_id: vector, + pre_image: vector, + ) acquires BridgeTransferStore, BridgeConfig { + let config_address = borrow_global(@atomic_bridge).bridge_module_deployer; + let store = borrow_global_mut(config_address); + let bridge_transfer = aptos_std::smart_table::borrow_mut(&mut store.transfers, bridge_transfer_id); + + assert!(bridge_transfer.state == INITIALIZED, ENOT_INITIALIZED); + assert!(aptos_std::aptos_hash::keccak256(bcs::to_bytes(&pre_image)) == bridge_transfer.hash_lock, EWRONG_PREIMAGE); + assert!(timestamp::now_seconds() <= bridge_transfer.time_lock, ETIMELOCK_EXPIRED); + + bridge_transfer.state = COMPLETED; + + event::emit_event(&mut store.bridge_transfer_completed_events, BridgeTransferCompletedEvent { + bridge_transfer_id: copy bridge_transfer_id, + pre_image: pre_image, + }); + } + + public entry fun refund_bridge_transfer( + account: &signer, + bridge_transfer_id: vector, + ) acquires BridgeTransferStore, BridgeConfig { + assert!(signer::address_of(account) == @origin_addr, EINCORRECT_SIGNER); + let config_address = borrow_global(@atomic_bridge).bridge_module_deployer; + let store = borrow_global_mut(config_address); + let bridge_transfer = aptos_std::smart_table::borrow_mut(&mut store.transfers, bridge_transfer_id); + + assert!(bridge_transfer.state == INITIALIZED, ENOT_INITIALIZED); + assert!(timestamp::now_seconds() > bridge_transfer.time_lock, ENOT_EXPIRED); + + let originator_addr = bridge_transfer.originator; + let asset = moveth::metadata(); + + // Transfer amount of asset from atomic bridge primary fungible store to originator's primary fungible store + let initiator_store = primary_fungible_store::ensure_primary_store_exists(originator_addr, asset); + let bridge_store = primary_fungible_store::ensure_primary_store_exists(@atomic_bridge, asset); + + atomic_bridge_counterparty::mint_moveth(bridge_transfer.originator, bridge_transfer.amount); + + bridge_transfer.state = REFUNDED; + + event::emit_event(&mut store.bridge_transfer_refunded_events, BridgeTransferRefundedEvent { + bridge_transfer_id: copy bridge_transfer_id, + }); + + //aptos_std::smart_table::remove(&mut store.transfers, bridge_transfer_id); + } + + public fun get_time_lock_duration(): u64 acquires BridgeConfig { + let config = borrow_global(@atomic_bridge); + config.time_lock_duration + } + + public entry fun set_time_lock_duration(caller: &signer, time_lock_duration: u64) acquires BridgeConfig { + let config = borrow_global_mut(@atomic_bridge); + // Check if the signer is the deployer (the original initializer) + assert!(signer::address_of(caller) == @origin_addr, EINCORRECT_SIGNER); + + config.time_lock_duration = time_lock_duration; + } + + #[test_only] + public fun init_test( + sender: &signer, + origin_account: &signer, + aptos_framework: &signer, + atomic_bridge: &signer, + ) { + genesis::setup(); + moveth::init_for_test(atomic_bridge); + atomic_bridge_counterparty::set_up_test(origin_account, atomic_bridge); + let bridge_addr = signer::address_of(atomic_bridge); + account::create_account_if_does_not_exist(bridge_addr); + init_module(atomic_bridge); + assert!(exists(bridge_addr), EDOES_NOT_EXIST); + } + + #[test(sender = @0xdaff)] + public fun test_initialize ( + sender: &signer + ) acquires BridgeTransferStore { + let addr = signer::address_of(sender); + + // Ensure Account resource exists for the sender + account::create_account_if_does_not_exist(addr); + + init_module(sender); + + assert!(exists(addr), 999); + + let addr = signer::address_of(sender); + let store = borrow_global(addr); + + assert!(aptos_std::smart_table::length(&store.transfers) == 0, 100); + assert!(store.nonce == 0, 101); + } + + #[test(creator = @origin_addr, aptos_framework = @0x1, sender = @0xdaff, atomic_bridge = @atomic_bridge)] + public fun test_initiate_bridge_transfer( + sender: &signer, + creator: &signer, + aptos_framework: &signer, + atomic_bridge: &signer, + ) acquires BridgeTransferStore, BridgeConfig { + init_test(sender, creator, aptos_framework, atomic_bridge); + + let sender_addr = signer::address_of(sender); + let recipient = b"recipient_address"; + let hash_lock = b"hash_lock_value"; + let time_lock:u64 = 1000; + let amount:u64 = 1000; + let nonce:u64 = 1; + + let combined_bytes = vector::empty(); + vector::append(&mut combined_bytes, bcs::to_bytes(&sender_addr)); + vector::append(&mut combined_bytes, recipient); + vector::append(&mut combined_bytes, hash_lock); + vector::append(&mut combined_bytes, bcs::to_bytes(&nonce)); + + let bridge_transfer_id = aptos_std::aptos_hash::keccak256(combined_bytes); + + // Mint amount of tokens to sender + let sender_address = signer::address_of(sender); + moveth::mint(atomic_bridge, sender_address, amount); + + initiate_bridge_transfer( + sender, + recipient, + hash_lock, + amount + ); + + let addr = signer::address_of(sender); + let bridge_addr = signer::address_of(atomic_bridge); + let store = borrow_global(bridge_addr); + let transfer = aptos_std::smart_table::borrow(&store.transfers, bridge_transfer_id); + + // The timelock is internally doubled by the initiator module + let expected_time_lock = timestamp::now_seconds() + get_time_lock_duration(); + + assert!(transfer.amount == amount, 200); + assert!(transfer.originator == addr, 201); + assert!(transfer.recipient == b"recipient_address", 202); + assert!(transfer.hash_lock == b"hash_lock_value", 203); + assert!(transfer.time_lock == expected_time_lock, 204); + assert!(transfer.state == INITIALIZED, 205); + } + + #[test(creator = @moveth, aptos_framework = @0x1, sender = @0xdaff, atomic_bridge = @atomic_bridge)] + public fun test_get_time_lock_duration( + sender: &signer, + creator: &signer, + aptos_framework: &signer, + atomic_bridge: &signer, + ) acquires BridgeConfig { + timestamp::set_time_has_started_for_testing(aptos_framework); + moveth::init_for_test(creator); + let bridge_addr = signer::address_of(atomic_bridge); + account::create_account_if_does_not_exist(bridge_addr); + + init_module(atomic_bridge); + + let time_lock_duration = get_time_lock_duration(); + assert!(time_lock_duration == 48 * 60 * 60, 0); + } + + #[test(creator = @origin_addr, aptos_framework = @0x1, sender = @0xdaff, atomic_bridge = @atomic_bridge)] + public fun test_set_time_lock_duration( + sender: &signer, + creator: &signer, + aptos_framework: &signer, + atomic_bridge: &signer, + ) acquires BridgeConfig { + timestamp::set_time_has_started_for_testing(aptos_framework); + moveth::init_for_test(atomic_bridge); + let bridge_addr = signer::address_of(atomic_bridge); + account::create_account_if_does_not_exist(bridge_addr); + + init_module(atomic_bridge); + + let new_time_lock_duration = 42; + set_time_lock_duration(creator, new_time_lock_duration); + + let time_lock_duration = get_time_lock_duration(); + assert!(time_lock_duration == 42, 0); + } + + #[test(creator = @moveth, aptos_framework = @0x1, sender = @0xdaff, atomic_bridge = @atomic_bridge)] + #[expected_failure (abort_code = EINSUFFICIENT_BALANCE, location = Self)] + public fun test_initiate_bridge_transfer_no_moveth( + sender: &signer, + creator: &signer, + aptos_framework: &signer, + atomic_bridge: &signer, + ) acquires BridgeTransferStore, BridgeConfig { + timestamp::set_time_has_started_for_testing(aptos_framework); + moveth::init_for_test(creator); + let bridge_addr = signer::address_of(atomic_bridge); + account::create_account_if_does_not_exist(bridge_addr); + init_module(atomic_bridge); + assert!(exists(bridge_addr), EDOES_NOT_EXIST); + + let recipient = b"recipient_address"; + let hash_lock = b"hash_lock_value"; + let time_lock = 1000; + let amount = 1000; + + // Do not mint tokens to sender; sender has no MovETH + + initiate_bridge_transfer( + sender, + recipient, + hash_lock, + amount + ); + } + + #[test(creator = @origin_addr, aptos_framework = @0x1, sender = @0xdaff, atomic_bridge = @atomic_bridge)] + public fun test_complete_bridge_transfer( + sender: &signer, + atomic_bridge: &signer, + creator: &signer, + aptos_framework: &signer + ) acquires BridgeTransferStore, BridgeConfig { + init_test(sender, creator, aptos_framework, atomic_bridge); + + let recipient = b"recipient_address"; + let pre_image = b"pre_image_value"; + let hash_lock = aptos_std::aptos_hash::keccak256(bcs::to_bytes(&pre_image)); + assert!(aptos_std::aptos_hash::keccak256(bcs::to_bytes(&pre_image)) == hash_lock, EWRONG_PREIMAGE); + let time_lock = 1000; + let amount = 1000; + let nonce = 1; + let sender_addr = signer::address_of(sender); + moveth::mint(atomic_bridge, sender_addr, amount); + + let combined_bytes = vector::empty(); + vector::append(&mut combined_bytes, bcs::to_bytes(&sender_addr)); + vector::append(&mut combined_bytes, recipient); + vector::append(&mut combined_bytes, hash_lock); + vector::append(&mut combined_bytes, bcs::to_bytes(&nonce)); + + let bridge_transfer_id = aptos_std::aptos_hash::keccak256(combined_bytes); + + initiate_bridge_transfer( + sender, + recipient, + hash_lock, + amount + ); + + complete_bridge_transfer( + sender, + bridge_transfer_id, + pre_image, + ); + let bridge_addr = signer::address_of(atomic_bridge); + let store = borrow_global(bridge_addr); + // complete bridge doesn't delete the transfer from the store + assert!(aptos_std::smart_table::contains(&store.transfers, copy bridge_transfer_id), 300); + let bridge_transfer: &BridgeTransfer = smart_table::borrow(&store.transfers, bridge_transfer_id); + + assert!(bridge_transfer.recipient == recipient, EWRONG_RECIPIENT); + assert!(bridge_transfer.originator == sender_addr, EWRONG_ORIGINATOR); + assert!(bridge_transfer.amount == amount, EWRONG_AMOUNT); + assert!(bridge_transfer.hash_lock == hash_lock, EWRONG_HASHLOCK); + } + + #[test(creator = @origin_addr, aptos_framework = @0x1, sender = @0xdaff, atomic_bridge = @atomic_bridge)] + #[expected_failure(abort_code = EWRONG_PREIMAGE, location = Self)] + public fun test_complete_bridge_transfer_wrong_preimage( + sender: &signer, + atomic_bridge: &signer, + creator: &signer, + aptos_framework: &signer + ) acquires BridgeTransferStore, BridgeConfig { + init_test(sender, creator, aptos_framework, atomic_bridge); + + let recipient = b"recipient_address"; + let pre_image = b"pre_image_value"; + let wrong_pre_image = b"wrong_pre_image_value"; + let hash_lock = aptos_std::aptos_hash::keccak256(bcs::to_bytes(&pre_image)); + let amount = 1000; + let nonce = 1; + let sender_address = signer::address_of(sender); + moveth::mint(atomic_bridge, sender_address, amount); + + let combined_bytes = vector::empty(); + vector::append(&mut combined_bytes, bcs::to_bytes(&sender_address)); + vector::append(&mut combined_bytes, recipient); + vector::append(&mut combined_bytes, hash_lock); + vector::append(&mut combined_bytes, bcs::to_bytes(&nonce)); + + let bridge_transfer_id = aptos_std::aptos_hash::keccak256(combined_bytes); + + initiate_bridge_transfer( + sender, + recipient, + hash_lock, + amount + ); + + complete_bridge_transfer( + sender, + bridge_transfer_id, + wrong_pre_image, + ); + } + + #[test(creator = @origin_addr, aptos_framework = @0x1, sender = @origin_addr, atomic_bridge = @atomic_bridge)] + // see tracking issue https://github.com/movementlabsxyz/movement/issues/272 + public fun test_refund_bridge_transfer( + sender: &signer, + atomic_bridge: &signer, + creator: &signer, + aptos_framework: &signer + ) acquires BridgeTransferStore, BridgeConfig { + init_test(sender, creator, aptos_framework, atomic_bridge); + let recipient = b"recipient_address"; + let hash_lock = b"hash_lock_value"; + let amount = 1000; + let nonce = 1; + + let sender_address = signer::address_of(sender); + moveth::mint(atomic_bridge, sender_address, amount); + + let combined_bytes = vector::empty(); + vector::append(&mut combined_bytes, bcs::to_bytes(&sender_address)); + vector::append(&mut combined_bytes, recipient); + vector::append(&mut combined_bytes, hash_lock); + vector::append(&mut combined_bytes, bcs::to_bytes(&nonce)); + + let bridge_transfer_id = aptos_std::aptos_hash::keccak256(combined_bytes); + + initiate_bridge_transfer( + sender, + recipient, + hash_lock, + amount + ); + + // Push timestamp forward by double the timelock (since initiator doubles it) + let time_lock = get_time_lock_duration(); + aptos_framework::timestamp::fast_forward_seconds(time_lock + 2); + + refund_bridge_transfer( + sender, + bridge_transfer_id, + ); + + let addr = signer::address_of(sender); + let asset = moveth::metadata(); + assert!(primary_fungible_store::balance(addr, asset) == amount, 0); + let bridge_addr = signer::address_of(atomic_bridge); + let store = borrow_global(bridge_addr); + assert!(aptos_std::smart_table::contains(&store.transfers, copy bridge_transfer_id), 300); + let transfer = aptos_std::smart_table::borrow(&store.transfers, bridge_transfer_id); + + assert!(transfer.state == REFUNDED, 300); + } + + #[test(creator = @origin_addr, aptos_framework = @0x1, sender = @origin_addr, atomic_bridge = @atomic_bridge)] + #[expected_failure(abort_code = ENOT_INITIALIZED, location = Self)] + public fun test_refund_completed_transfer( + sender: &signer, + creator: &signer, + aptos_framework: &signer, + atomic_bridge: &signer + ) acquires BridgeTransferStore, BridgeConfig { + init_test(sender, creator, aptos_framework, atomic_bridge); + + let recipient = b"recipient_address"; + let pre_image = b"pre_image_value"; + let hash_lock = aptos_std::aptos_hash::keccak256(bcs::to_bytes(&pre_image)); + assert!(aptos_std::aptos_hash::keccak256(bcs::to_bytes(&pre_image)) == hash_lock, 5); + let amount = 1000; + let nonce = 1; + let sender_address = signer::address_of(sender); + moveth::mint(atomic_bridge, sender_address, amount); + + let combined_bytes = vector::empty(); + vector::append(&mut combined_bytes, bcs::to_bytes(&sender_address)); + vector::append(&mut combined_bytes, recipient); + vector::append(&mut combined_bytes, hash_lock); + vector::append(&mut combined_bytes, bcs::to_bytes(&nonce)); + + let bridge_transfer_id = aptos_std::aptos_hash::keccak256(combined_bytes); + + initiate_bridge_transfer( + sender, + recipient, + hash_lock, + amount + ); + + complete_bridge_transfer( + sender, + bridge_transfer_id, + pre_image, + ); + + refund_bridge_transfer( + sender, + bridge_transfer_id, + ); + } + + #[test(creator = @origin_addr, aptos_framework = @0x1, sender = @0xdaff, atomic_bridge = @atomic_bridge)] + public fun test_bridge_transfers_view( + sender: &signer, + creator: &signer, + aptos_framework: &signer, + atomic_bridge: &signer + ) acquires BridgeTransferStore, BridgeConfig { + init_test(sender, creator, aptos_framework, atomic_bridge); + + let recipient = b"recipient_address"; + let pre_image = b"pre_image_value"; + let hash_lock = aptos_std::aptos_hash::keccak256(bcs::to_bytes(&pre_image)); + assert!(aptos_std::aptos_hash::keccak256(bcs::to_bytes(&pre_image)) == hash_lock, 5); + let amount = 1000; + let nonce = 1; + let sender_address = signer::address_of(sender); + moveth::mint(atomic_bridge, sender_address, amount); + + let combined_bytes = vector::empty(); + vector::append(&mut combined_bytes, bcs::to_bytes(&sender_address)); + vector::append(&mut combined_bytes, recipient); + vector::append(&mut combined_bytes, hash_lock); + vector::append(&mut combined_bytes, bcs::to_bytes(&nonce)); + + let bridge_transfer_id = aptos_std::aptos_hash::keccak256(combined_bytes); + + initiate_bridge_transfer( + sender, + recipient, + hash_lock, + amount + ); + + aptos_std::debug::print(&bridge_transfer_id); + // returns a valid transfer + let (transfer_originator, transfer_recipient, transfer_amount, transfer_hash_lock, transfer_time_lock, transfer_state) = bridge_transfers(bridge_transfer_id); + + assert!(transfer_state == INITIALIZED, 6); + aptos_std::debug::print(&transfer_state); + complete_bridge_transfer( + sender, + bridge_transfer_id, + pre_image, + ); + aptos_std::debug::print(&transfer_state); + } +} diff --git a/protocol-units/bridge/move-modules/sources/tests/MOVETH_tests.move b/protocol-units/bridge/move-modules/sources/tests/MOVETH_tests.move index 37b0496ae..34537d6af 100644 --- a/protocol-units/bridge/move-modules/sources/tests/MOVETH_tests.move +++ b/protocol-units/bridge/move-modules/sources/tests/MOVETH_tests.move @@ -6,15 +6,13 @@ module moveth::moveth_tests{ use moveth::moveth; use aptos_framework::object; - #[test(creator = @moveth, minter = @0xface, master_minter = @0xbab, denylister = @0xcade)] - fun test_basic_flow(creator: &signer, minter: &signer, master_minter: &signer, denylister: &signer) { + #[test(creator = @moveth, minter = @0xface, admin = @admin, master_minter = @master_minter, denylister = @0xcade)] + fun test_basic_flow(creator: &signer, minter: &signer, admin: &signer, master_minter: &signer, denylister: &signer) { moveth::init_for_test(creator); let receiver_address = @0xcafe1; let minter_address = signer::address_of(minter); - // set minter and have minter call mint, check balance - moveth::add_minter(master_minter, minter_address); - moveth::mint(minter, minter_address, 100); + moveth::mint(admin, minter_address, 100); let asset = moveth::metadata(); assert!(primary_fungible_store::balance(minter_address, asset) == 100, 0); @@ -30,20 +28,10 @@ module moveth::moveth_tests{ assert!(!primary_fungible_store::is_frozen(receiver_address, asset), 0); // burn tokens, check balance - moveth::burn(minter, minter_address, 90); + moveth::burn(admin, minter_address, 90); assert!(primary_fungible_store::balance(minter_address, asset) == 0, 0); } - - #[test(creator = @moveth, pauser = @0xdafe, minter = @0xface, master_minter = @0xbab)] - #[expected_failure(abort_code = 2, location = moveth::moveth)] - fun test_pause(creator: &signer, pauser: &signer, minter: &signer, master_minter: &signer) { - moveth::init_for_test(creator); - let minter_address = signer::address_of(minter); - moveth::set_pause(pauser, true); - moveth::add_minter(master_minter, minter_address); - } - //test the ability of a denylisted account to transfer out newly created store #[test(creator = @moveth, denylister = @0xcade, receiver = @0xdead)] #[expected_failure(abort_code = 327683, location = aptos_framework::object)] diff --git a/protocol-units/bridge/shared/Cargo.toml b/protocol-units/bridge/shared/Cargo.toml index 0308e170d..87b3b01ba 100644 --- a/protocol-units/bridge/shared/Cargo.toml +++ b/protocol-units/bridge/shared/Cargo.toml @@ -14,15 +14,24 @@ rust-version.workspace = true [dependencies] async-trait = "0.1.80" delegate = "0.12.0" -derive_more = { workspace = true, features = ["deref", "deref_mut"] } +derive_more = { workspace = true, features = ["deref", "deref_mut"] } futures.workspace = true futures-timer = "3.0.3" +hex = { workspace = true } thiserror.workspace = true tracing.workspace = true rand.workspace = true rand_chacha = "0.2.2" futures-time = "3.0.0" - +alloy = { workspace = true, features = [ + "full", + "rpc", + "rpc-types", + "serde", + "rlp", + "contract", + "sol-types", +] } [dev-dependencies] dashmap = "6.0.1" static_str_ops = "0.1.2" diff --git a/protocol-units/bridge/shared/README.mkd b/protocol-units/bridge/shared/README.mkd new file mode 100644 index 000000000..a98e6e6a8 --- /dev/null +++ b/protocol-units/bridge/shared/README.mkd @@ -0,0 +1,55 @@ +# Bridge Service Based on Atomic Swaps + +This repository provides an abstract implementation of a bridge service designed to facilitate atomic swaps between two blockchains. This implementation can serve as the foundation for developing a robust bridge application for cross-chain asset transfers. + +## What is an Atomic Swap? + +An atomic swap, or cross-chain atomic swap, is a decentralized method of exchanging cryptocurrencies from different blockchains without relying on a trusted third party or centralized exchange. The term "atomic" signifies that the transaction is indivisible: it either completes entirely or not at all, ensuring both parties can securely exchange assets without the risk of fraud. + +### Key Concepts of Atomic Swaps: +1. **Hash Time-Locked Contracts (HTLCs)**: A smart contract mechanism that requires the recipient to acknowledge receiving a payment before a deadline by generating cryptographic proof (hash lock). If the recipient fails to provide this proof, the funds are returned to the sender. +2. **Hash Locks**: A cryptographic lock that requires a secret (pre-image) to unlock. +3. **Time Locks**: A condition that defines the time frame in which the cryptographic proof must be provided. + +## Overview of the Implementation + +This implementation sets up a bridge service that handles the process of initiating and completing atomic swaps between two blockchains. The code is designed to be modular and extensible, allowing for easy integration with various blockchain clients and smart contracts. + +### Key Components: +1. **BridgeServiceConfig**: Configuration for the bridge service, including settings for error handling and contract call timeouts. +2. **ActiveSwapConfig**: Configuration for active swaps, defining the number of error attempts, delay between attempts, and contract call timeout duration. +3. **BridgeContractInitiator & BridgeContractCounterparty**: Interfaces for the bridge contracts on the initiating and counterparty blockchains. +4. **Event Handling**: The bridge service listens for specific contract events to progress through the stages of the swap. +5. **Swap Stages**: + - **Initiation**: The initiator locks assets in a smart contract on Blockchain 1. + - **Locking on Counterparty**: The bridge service recognizes the initiation event and locks corresponding assets on Blockchain 2. + - **Completion by Client**: The client reveals the hash lock pre-image to complete the swap on Blockchain 2. + - **Completion by Bridge Service**: The bridge service completes the swap on Blockchain 1 using the revealed pre-image. + +### Usage + +To use this implementation, follow these steps: + +1. **Set Up Configuration**: Define the configurations for the bridge service and active swaps in `BridgeServiceConfig` and `ActiveSwapConfig`. +2. **Implement Blockchain Clients**: Create implementations for the blockchain clients (`B1Client` and `B2Client`) to interact with the respective blockchains. +3. **Deploy Smart Contracts**: Deploy the necessary HTLC smart contracts on both blockchains. +4. **Run the Bridge Service**: Initialize and run the bridge service to start listening for events and handling swaps. + +### Example + +The provided integration tests demonstrate the complete workflow of an atomic swap between two blockchains. These tests can be used as a reference for implementing and testing your bridge service. + +```rust +#[tokio::test(flavor = "multi_thread", worker_threads = 4)] +async fn test_bridge_service_integration_a_to_b() { + // Setup and test the bridge service for a swap from Blockchain A to Blockchain B +} +``` + +## Contributing + +Contributions to enhance the functionality and compatibility of this bridge service are welcome. Please submit pull requests with clear descriptions and test cases. + +## License + +This project is licensed under the MIT License. See the LICENSE file for details. diff --git a/protocol-units/bridge/shared/src/blockchain_service.rs b/protocol-units/bridge/shared/src/blockchain_service.rs index 748cdc985..404100bf3 100644 --- a/protocol-units/bridge/shared/src/blockchain_service.rs +++ b/protocol-units/bridge/shared/src/blockchain_service.rs @@ -17,7 +17,7 @@ use crate::{ #[derive(Debug, PartialEq, Eq)] pub enum ContractEvent { InitiatorEvent(BridgeContractInitiatorEvent), - CounterpartyEvent(BridgeContractCounterpartyEvent), + CounterpartyEvent(BridgeContractCounterpartyEvent), } pub trait BlockchainService: diff --git a/protocol-units/bridge/shared/src/bridge_contracts.rs b/protocol-units/bridge/shared/src/bridge_contracts.rs index c5ff68a56..beb9f1987 100644 --- a/protocol-units/bridge/shared/src/bridge_contracts.rs +++ b/protocol-units/bridge/shared/src/bridge_contracts.rs @@ -2,15 +2,33 @@ use thiserror::Error; use crate::types::{ Amount, BridgeAddressType, BridgeHashType, BridgeTransferDetails, BridgeTransferId, HashLock, - HashLockPreImage, InitiatorAddress, RecipientAddress, TimeLock, + HashLockPreImage, InitiatorAddress, RecipientAddress, }; -#[derive(Error, Debug, Clone)] +#[derive(Error, Debug, Clone, PartialEq, Eq)] pub enum BridgeContractInitiatorError { + #[error("Failed to extract transfer Id")] + TransferIdExtractionError, + #[error("Failed to mint")] + MintError, + #[error("Failed to call function")] + CallError, + #[error("Failed to serialize or deserialize")] + SerializationError, + #[error("Invalid response length")] + InvalidResponseLength, + #[error("Failed to view function")] + FunctionViewError, #[error("Failed to initiate bridge transfer")] InitiateTransferError, #[error("Failed to complete bridge transfer")] CompleteTransferError, + #[error("Failed to parse preimage")] + ParsePreimageError, + #[error("Initiator address not set")] + InitiatorAddressNotSet, + #[error("Failed to convert")] + ConversionError, #[error("Generic error: {0}")] GenericError(String), } @@ -23,12 +41,30 @@ impl BridgeContractInitiatorError { #[derive(Error, Debug, Clone, PartialEq, Eq)] pub enum BridgeContractCounterpartyError { - #[error("Failed to lock bridge transfer assets")] - LockTransferAssetsError, + #[error("Invalid response length")] + InvalidResponseLength, + #[error("Function call failed")] + CallError, + #[error("Failed to view module")] + ModuleViewError, + #[error("Failed to view function")] + FunctionViewError, + #[error("Failed to serialize view args")] + ViewSerializationError, + #[error("Failed to serialize or deserialize")] + SerializationError, + #[error("Failed to lock bridge transfer")] + LockTransferError, #[error("Failed to complete bridge transfer")] CompleteTransferError, #[error("Failed to abort bridge transfer")] AbortTransferError, + #[error("Counterparty address not set")] + CounterpartyAddressNotSet, + #[error("Error getting the signer")] + SignerError, + #[error("Failed to convert")] + ConversionError, #[error("Generic error: {0}")] GenericError(String), } @@ -39,8 +75,24 @@ impl BridgeContractCounterpartyError { } } +#[derive(Error, Debug, Clone, PartialEq, Eq)] +pub enum BridgeContractWETH9Error { + #[error("Insufficient balance")] + BalanceError, + #[error("Allowance exceeded")] + AllowanceError, + #[error("Generic error: {0}")] + GenericError(String), +} +impl BridgeContractWETH9Error { + pub fn generic(e: E) -> Self { + Self::GenericError(e.to_string()) + } +} + pub type BridgeContractInitiatorResult = Result; pub type BridgeContractCounterpartyResult = Result; +pub type BridgeContractWETH9Result = Result; #[async_trait::async_trait] pub trait BridgeContractInitiator: Clone + Unpin + Send + Sync { @@ -50,9 +102,8 @@ pub trait BridgeContractInitiator: Clone + Unpin + Send + Sync { async fn initiate_bridge_transfer( &mut self, initiator_address: InitiatorAddress, - recipient_address: RecipientAddress, + recipient_address: RecipientAddress>, hash_lock: HashLock, - time_lock: TimeLock, amount: Amount, ) -> BridgeContractInitiatorResult<()>; @@ -70,7 +121,7 @@ pub trait BridgeContractInitiator: Clone + Unpin + Send + Sync { async fn get_bridge_transfer_details( &mut self, bridge_transfer_id: BridgeTransferId, - ) -> BridgeContractInitiatorResult>>; + ) -> BridgeContractInitiatorResult>>; } #[async_trait::async_trait] @@ -78,12 +129,12 @@ pub trait BridgeContractCounterparty: Clone + Unpin + Send + Sync { type Address: BridgeAddressType; type Hash: BridgeHashType; - async fn lock_bridge_transfer_assets( + async fn lock_bridge_transfer( &mut self, bridge_transfer_id: BridgeTransferId, hash_lock: HashLock, - time_lock: TimeLock, - recipient: RecipientAddress, + initiator: InitiatorAddress>, + recipient: RecipientAddress, amount: Amount, ) -> BridgeContractCounterpartyResult<()>; @@ -101,5 +152,13 @@ pub trait BridgeContractCounterparty: Clone + Unpin + Send + Sync { async fn get_bridge_transfer_details( &mut self, bridge_transfer_id: BridgeTransferId, - ) -> BridgeContractCounterpartyResult>>; + ) -> BridgeContractCounterpartyResult>>; +} + +#[async_trait::async_trait] +pub trait BridgeContractWETH9: Clone + Unpin + Send + Sync { + type Address: BridgeAddressType; + type Hash: BridgeHashType; + + async fn deposit_weth(&mut self, amount: Amount) -> BridgeContractWETH9Result<()>; } diff --git a/protocol-units/bridge/shared/src/bridge_monitoring.rs b/protocol-units/bridge/shared/src/bridge_monitoring.rs index fb6d54e86..548cb2db6 100644 --- a/protocol-units/bridge/shared/src/bridge_monitoring.rs +++ b/protocol-units/bridge/shared/src/bridge_monitoring.rs @@ -1,8 +1,10 @@ use futures::Stream; -use crate::types::{BridgeTransferDetails, BridgeTransferId, CompletedDetails, LockDetails}; +use crate::types::{ + BridgeTransferDetails, BridgeTransferId, CounterpartyCompletedDetails, LockDetails, +}; -#[derive(Debug, PartialEq, Eq)] +#[derive(Clone, Debug, PartialEq, Eq)] pub enum BridgeContractInitiatorEvent { Initiated(BridgeTransferDetails), Completed(BridgeTransferId), @@ -18,10 +20,10 @@ impl BridgeContractInitiatorEvent { } } -#[derive(Debug, PartialEq, Eq)] -pub enum BridgeContractCounterpartyEvent { - Locked(LockDetails), - Completed(CompletedDetails), +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum BridgeContractCounterpartyEvent { + Locked(LockDetails), + Completed(CounterpartyCompletedDetails), } pub trait BridgeContractInitiatorMonitoring: @@ -32,7 +34,7 @@ pub trait BridgeContractInitiatorMonitoring: } pub trait BridgeContractCounterpartyMonitoring: - Stream> + Unpin + Stream> + Unpin { type Address; type Hash; diff --git a/protocol-units/bridge/shared/src/bridge_service.rs b/protocol-units/bridge/shared/src/bridge_service.rs index c5fa185b0..5a0e62dd4 100644 --- a/protocol-units/bridge/shared/src/bridge_service.rs +++ b/protocol-units/bridge/shared/src/bridge_service.rs @@ -41,6 +41,8 @@ impl BridgeService where B1: BlockchainService + 'static, B2: BlockchainService + 'static, + Vec: From, + Vec: From, { pub fn new(blockchain_1: B1, blockchain_2: B2, config: BridgeServiceConfig) -> Self { Self { @@ -68,6 +70,10 @@ where BFrom: BlockchainService + 'static, BTo: BlockchainService + 'static, BTo::Hash: From, + BTo::Address: From>, + + Vec: From, + Vec: From, { match initiator_event { BridgeContractInitiatorEvent::Initiated(ref details) => { @@ -84,13 +90,16 @@ where } fn handle_counterparty_event( - event: BridgeContractCounterpartyEvent, + event: BridgeContractCounterpartyEvent, active_swaps: &mut ActiveSwapMap, -) -> Option> +) -> Option> where BFrom: BlockchainService + 'static, BTo: BlockchainService + 'static, BFrom::Hash: From, + + Vec: From, + Vec: From, { use BridgeContractCounterpartyEvent::*; match event { @@ -119,6 +128,12 @@ where B1::Hash: From, B2::Hash: From, + + B1::Address: From>, + B2::Address: From>, + + Vec: From, + Vec: From, { type Item = Event; @@ -223,14 +238,18 @@ where // Initiator events pertain to the initiator contract, while counterparty events are associated // with the counterparty contract. -enum HandleActiveSwapEvent { - InitiatorEvent(IEvent), - CounterpartyEvent(CEvent

), +enum HandleActiveSwapEvent +where + BFrom: BlockchainService, + BTo: BlockchainService, +{ + InitiatorEvent(IEvent), + CounterpartyEvent(CEvent), } fn handle_active_swap_event( active_swap_event: Poll>>, -) -> Option> +) -> Option> where BFrom: BlockchainService + 'static, BTo: BlockchainService + 'static, diff --git a/protocol-units/bridge/shared/src/bridge_service/active_swap.rs b/protocol-units/bridge/shared/src/bridge_service/active_swap.rs index f561686af..b695270e7 100644 --- a/protocol-units/bridge/shared/src/bridge_service/active_swap.rs +++ b/protocol-units/bridge/shared/src/bridge_service/active_swap.rs @@ -11,15 +11,18 @@ use futures_time::future::{FutureExt as TimeoutFutureExt, Timeout}; use futures_timer::Delay; use thiserror::Error; -use crate::bridge_contracts::{BridgeContractCounterparty, BridgeContractInitiator}; use crate::{ blockchain_service::BlockchainService, bridge_contracts::{BridgeContractCounterpartyError, BridgeContractInitiatorError}, types::{ - convert_bridge_transfer_id, BridgeTransferDetails, BridgeTransferId, CompletedDetails, - HashLock, + convert_bridge_transfer_id, BridgeTransferDetails, BridgeTransferId, + CounterpartyCompletedDetails, HashLock, InitiatorAddress, }, }; +use crate::{ + bridge_contracts::{BridgeContractCounterparty, BridgeContractInitiator}, + types::RecipientAddress, +}; pub type BoxedFuture = Timeout> + Send>>, Delay>; @@ -51,15 +54,15 @@ pub enum ActiveSwapState where BTo: BlockchainService, { - LockingTokens(BoxedFuture<(), LockBridgeTransferAssetsError>, Attempts), + LockingTokens(BoxedFuture<(), LockBridgeTransferError>, Attempts), LockingTokensError(Delay, Attempts), WaitingForUnlockedEvent, CompletingBridging( BoxedFuture<(), CompleteBridgeTransferError>, - CompletedDetails, + CounterpartyCompletedDetails, Attempts, ), - CompletingBridgingError(Delay, CompletedDetails, Attempts), + CompletingBridgingError(Delay, CounterpartyCompletedDetails, Attempts), Completed, Aborted, } @@ -142,6 +145,8 @@ impl ActiveSwapMap where BTo: BlockchainService + 'static, BFrom: BlockchainService + 'static, + Vec: From, + Vec: From, { pub fn build( initiator_contract: BFrom::InitiatorContract, @@ -178,7 +183,7 @@ where ) where BTo::Hash: From, { - assert!(self.swaps.get(&details.bridge_transfer_id).is_none()); + assert!(!self.swaps.contains_key(&details.bridge_transfer_id)); let counterparty_contract = self.counterparty_contract.clone(); let bridge_transfer_id = details.bridge_transfer_id.clone(); @@ -190,7 +195,7 @@ where ActiveSwap { details: details.clone(), state: ActiveSwapState::LockingTokens( - call_lock_bridge_transfer_assets::(counterparty_contract, details) + call_lock_bridge_transfer::(counterparty_contract, details) .boxed() .timeout(Delay::new(self.config.contract_call_timeout)), 0, @@ -203,7 +208,7 @@ where pub fn complete_bridge_transfer( &mut self, - details: CompletedDetails, + details: CounterpartyCompletedDetails, ) -> Result<(), ActiveSwapMapError> where BFrom::Hash: From, @@ -239,7 +244,7 @@ where #[derive(Debug)] pub enum ActiveSwapEvent { BridgeAssetsLocked(BridgeTransferId), - BridgeAssetsLockingError(LockBridgeTransferAssetsError), + BridgeAssetsLockingError(LockBridgeTransferError), BridgeAssetsRetryLocking(BridgeTransferId), BridgeAssetsCompleted(BridgeTransferId), BridgeAssetsCompletingError(BridgeTransferId, CompleteBridgeTransferError), @@ -265,6 +270,8 @@ where BFrom::Hash: From, BTo::Hash: From, + + Vec: From, { type Item = ActiveSwapEvent; @@ -330,7 +337,7 @@ where bridge_transfer_id ); *state = ActiveSwapState::LockingTokens( - call_lock_bridge_transfer_assets::( + call_lock_bridge_transfer::( this.counterparty_contract.clone(), bridge_transfer.clone(), ) @@ -440,7 +447,7 @@ trait HasTimeoutError { } #[derive(Debug, Error, PartialEq, Eq)] -pub enum LockBridgeTransferAssetsError { +pub enum LockBridgeTransferError { #[error("Failed to lock assets")] LockingError, #[error("Timeout while performing contract call")] @@ -449,41 +456,35 @@ pub enum LockBridgeTransferAssetsError { ContractCallError(#[from] BridgeContractCounterpartyError), } -impl HasTimeoutError for LockBridgeTransferAssetsError { +impl HasTimeoutError for LockBridgeTransferError { fn timeout_error() -> Self { - LockBridgeTransferAssetsError::ContractCallTimeoutError + LockBridgeTransferError::ContractCallTimeoutError } } -async fn call_lock_bridge_transfer_assets( +async fn call_lock_bridge_transfer( mut counterparty_contract: BTo::CounterpartyContract, - BridgeTransferDetails { - bridge_transfer_id, - hash_lock, - time_lock, - recipient_address, - amount, - .. - }: BridgeTransferDetails, -) -> Result<(), LockBridgeTransferAssetsError> + details: BridgeTransferDetails, +) -> Result<(), LockBridgeTransferError> where BTo::Hash: From, + Vec: From, { - let bridge_transfer_id = BridgeTransferId(From::from(bridge_transfer_id.0)); - let hash_lock = HashLock(From::from(hash_lock.0)); + let bridge_transfer_id = BridgeTransferId(From::from(details.bridge_transfer_id.0)); + let hash_lock = HashLock(From::from(details.hash_lock.0)); tracing::trace!( - "Calling lock_bridge_transfer_assets on counterparty contract for bridge transfer {:?}", + "Calling lock_bridge_transfer on counterparty contract for bridge transfer {:?}", bridge_transfer_id ); counterparty_contract - .lock_bridge_transfer_assets( + .lock_bridge_transfer( bridge_transfer_id, hash_lock, - time_lock, - recipient_address, - amount, + InitiatorAddress(From::from(details.initiator_address.0)), + RecipientAddress(From::from(details.recipient_address.0)), + details.amount, ) .await?; @@ -508,7 +509,10 @@ impl HasTimeoutError for CompleteBridgeTransferError { async fn call_complete_bridge_transfer( mut initiator_contract: BFrom::InitiatorContract, - CompletedDetails { bridge_transfer_id, secret, .. }: CompletedDetails, + CounterpartyCompletedDetails { bridge_transfer_id, secret, .. }: CounterpartyCompletedDetails< + BTo::Address, + BTo::Hash, + >, ) -> Result<(), CompleteBridgeTransferError> where BFrom::Hash: From, diff --git a/protocol-units/bridge/shared/src/bridge_service/events.rs b/protocol-units/bridge/shared/src/bridge_service/events.rs index b71cc7165..a59bf0ea7 100644 --- a/protocol-units/bridge/shared/src/bridge_service/events.rs +++ b/protocol-units/bridge/shared/src/bridge_service/events.rs @@ -1,10 +1,10 @@ use crate::{ blockchain_service::BlockchainService, bridge_monitoring::{BridgeContractCounterpartyEvent, BridgeContractInitiatorEvent}, - types::{BridgeTransferDetails, BridgeTransferId, CompletedDetails}, + types::{BridgeTransferDetails, BridgeTransferId, CounterpartyCompletedDetails}, }; -use super::active_swap::LockBridgeTransferAssetsError; +use super::active_swap::LockBridgeTransferError; #[derive(Debug, PartialEq, Eq)] pub enum IWarn { @@ -36,28 +36,28 @@ impl IEvent { } #[derive(Debug, PartialEq, Eq)] -pub enum CWarn { - BridgeAssetsLockingError(LockBridgeTransferAssetsError), - CannotCompleteUnexistingSwap(CompletedDetails), +pub enum CWarn { + BridgeAssetsLockingError(LockBridgeTransferError), + CannotCompleteUnexistingSwap(CounterpartyCompletedDetails), LockingAbortedTooManyAttempts(BridgeTransferId), } #[derive(Debug, PartialEq, Eq)] -pub enum CEvent { +pub enum CEvent { RetryLockingAssets(BridgeTransferId), - ContractEvent(BridgeContractCounterpartyEvent), - Warn(CWarn), + ContractEvent(BridgeContractCounterpartyEvent), + Warn(CWarn), } -impl CEvent { - pub fn contract_event(&self) -> Option<&BridgeContractCounterpartyEvent> { +impl CEvent { + pub fn contract_event(&self) -> Option<&BridgeContractCounterpartyEvent> { match self { CEvent::ContractEvent(event) => Some(event), _ => None, } } - pub fn warn(&self) -> Option<&CWarn> { + pub fn warn(&self) -> Option<&CWarn> { match self { CEvent::Warn(warn) => Some(warn), _ => None, @@ -72,9 +72,9 @@ where B2: BlockchainService, { B1I(IEvent), - B1C(CEvent), + B1C(CEvent), B2I(IEvent), - B2C(CEvent), + B2C(CEvent), } #[allow(non_snake_case)] @@ -91,14 +91,16 @@ impl Event { self.B1I()?.contract_event() } - pub fn B1C(&self) -> Option<&CEvent> { + pub fn B1C(&self) -> Option<&CEvent> { match self { Event::B1C(event) => Some(event), _ => None, } } - pub fn B1C_ContractEvent(&self) -> Option<&BridgeContractCounterpartyEvent> { + pub fn B1C_ContractEvent( + &self, + ) -> Option<&BridgeContractCounterpartyEvent> { self.B1C()?.contract_event() } @@ -114,14 +116,16 @@ impl Event { self.B2I()?.contract_event() } - pub fn B2C(&self) -> Option<&CEvent> { + pub fn B2C(&self) -> Option<&CEvent> { match self { Event::B2C(event) => Some(event), _ => None, } } - pub fn B2C_ContractEvent(&self) -> Option<&BridgeContractCounterpartyEvent> { + pub fn B2C_ContractEvent( + &self, + ) -> Option<&BridgeContractCounterpartyEvent> { self.B2C()?.contract_event() } } diff --git a/protocol-units/bridge/shared/src/counterparty_contract.rs b/protocol-units/bridge/shared/src/counterparty_contract.rs new file mode 100644 index 000000000..b2ebafcdc --- /dev/null +++ b/protocol-units/bridge/shared/src/counterparty_contract.rs @@ -0,0 +1,121 @@ +use crate::types::{ + Amount, AssetType, BridgeAddressType, BridgeHashType, BridgeTransferId, + CounterpartyCompletedDetails, GenUniqueHash, HashLock, HashLockPreImage, InitiatorAddress, + LockDetails, RecipientAddress, TimeLock, +}; +use std::collections::HashMap; +use std::fmt::Debug; +use thiserror::Error; + +pub type SCCResult = + Result, SmartContractCounterpartyError>; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum SmartContractCounterpartyEvent { + LockedBridgeTransfer(LockDetails), + CompletedBridgeTransfer(CounterpartyCompletedDetails), +} + +#[derive(Debug, Error, Clone, PartialEq, Eq)] +pub enum SmartContractCounterpartyError { + #[error("Transfer not found")] + TransferNotFound, + #[error("Invalid hash lock pre image (secret)")] + InvalidHashLockPreImage, +} + +#[derive(Debug)] +pub enum CounterpartyCall { + CompleteBridgeTransfer(BridgeTransferId, HashLockPreImage), + LockBridgeTransfer( + BridgeTransferId, + HashLock, + TimeLock, + InitiatorAddress>, + RecipientAddress, + Amount, + ), +} + +#[derive(Debug)] +pub struct SmartContractCounterparty { + pub locked_transfers: HashMap, LockDetails>, +} + +impl SmartContractCounterparty +where + A: BridgeAddressType + From>, + H: BridgeHashType + GenUniqueHash, + H: From, +{ + pub fn new() -> Self { + Self { locked_transfers: HashMap::new() } + } + + pub fn lock_bridge_transfer( + &mut self, + bridge_transfer_id: BridgeTransferId, + hash_lock: HashLock, + initiator_address: InitiatorAddress>, + recipient_address: RecipientAddress, + amount: Amount, + ) -> SCCResult { + tracing::trace!( + "SmartContractCounterparty: Locking bridge transfer: {:?}", + bridge_transfer_id + ); + self.locked_transfers.insert( + bridge_transfer_id.clone(), + LockDetails { + bridge_transfer_id: bridge_transfer_id.clone(), + initiator_address: initiator_address.clone(), + recipient_address: recipient_address.clone(), + hash_lock: hash_lock.clone(), + amount, + }, + ); + + Ok(SmartContractCounterpartyEvent::LockedBridgeTransfer(LockDetails { + bridge_transfer_id, + initiator_address, + recipient_address, + hash_lock, + amount, + })) + } + + pub fn complete_bridge_transfer( + &mut self, + accounts: &mut HashMap, + bridge_transfer_id: &BridgeTransferId, + pre_image: HashLockPreImage, + ) -> SCCResult { + let transfer = self + .locked_transfers + .remove(bridge_transfer_id) + .ok_or(SmartContractCounterpartyError::TransferNotFound)?; + + tracing::trace!("SmartContractCounterparty: Completing bridge transfer: {:?}", transfer); + + // check if the secret is correct + let secret_hash = H::from(pre_image.clone()); + if transfer.hash_lock.0 != secret_hash { + tracing::warn!( + "Invalid hash lock pre image {pre_image:?} hash {secret_hash:?} != hash_lock {:?}", + transfer.hash_lock.0 + ); + return Err(SmartContractCounterpartyError::InvalidHashLockPreImage); + } + + // TODO: fix this + let account = A::from(transfer.recipient_address.clone()); + + let balance = accounts.entry(account).or_insert(Amount(AssetType::EthAndWeth((0, 0)))); + todo!(); + // balance += **transfer.amount; + + Ok(SmartContractCounterpartyEvent::CompletedBridgeTransfer( + CounterpartyCompletedDetails::from_lock_details(transfer, pre_image), + )) + } +} diff --git a/protocol-units/bridge/shared/src/initiator_contract.rs b/protocol-units/bridge/shared/src/initiator_contract.rs new file mode 100644 index 000000000..7dda6e540 --- /dev/null +++ b/protocol-units/bridge/shared/src/initiator_contract.rs @@ -0,0 +1,146 @@ +use std::collections::HashMap; +use thiserror::Error; + +use rand::Rng; + +use crate::{ + bridge_monitoring::BridgeContractInitiatorEvent, + types::{ + Amount, BridgeAddressType, BridgeHashType, BridgeTransferDetails, BridgeTransferId, + GenUniqueHash, HashLock, HashLockPreImage, InitiatorAddress, RecipientAddress, + }, +}; + +pub type SCIResult = Result, SmartContractInitiatorError>; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum SmartContractInitiatorEvent { + InitiatedBridgeTransfer(BridgeTransferDetails), + CompletedBridgeTransfer(BridgeTransferId), + RefundedBridgeTransfer(BridgeTransferId), +} + +impl From> for SmartContractInitiatorEvent { + fn from(event: BridgeContractInitiatorEvent) -> Self { + match event { + BridgeContractInitiatorEvent::Initiated(details) => { + SmartContractInitiatorEvent::InitiatedBridgeTransfer(details) + } + BridgeContractInitiatorEvent::Completed(id) => { + SmartContractInitiatorEvent::CompletedBridgeTransfer(id) + } + BridgeContractInitiatorEvent::Refunded(id) => { + SmartContractInitiatorEvent::RefundedBridgeTransfer(id) + } + } + } +} + +#[derive(Debug, Error, Clone, PartialEq, Eq)] +pub enum SmartContractInitiatorError { + #[error("Failed to initiate bridge transfer")] + InitiateTransferError, + #[error("Transfer not found")] + TransferNotFound, + #[error("Invalid hash lock pre image (secret)")] + InvalidHashLockPreImage, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum InitiatorEvent { + Initiated(BridgeTransferDetails), + Completed(BridgeTransferId), + Refunded(BridgeTransferId), +} + +#[derive(Debug)] +pub enum InitiatorCall { + InitiateBridgeTransfer(InitiatorAddress, RecipientAddress>, Amount, HashLock), + CompleteBridgeTransfer(BridgeTransferId, HashLockPreImage), +} + +#[derive(Debug)] +pub struct SmartContractInitiator { + pub initiated_transfers: HashMap, BridgeTransferDetails>, + pub accounts: HashMap, + pub rng: R, +} + +impl SmartContractInitiator +where + A: BridgeAddressType, + H: BridgeHashType + GenUniqueHash + From, + R: Rng, +{ + pub fn new(rng: R) -> Self { + Self { initiated_transfers: HashMap::new(), accounts: HashMap::default(), rng } + } + + pub fn initiate_bridge_transfer( + &mut self, + initiator: InitiatorAddress, + recipient: RecipientAddress>, + amount: Amount, + hash_lock: HashLock, + ) -> SCIResult { + let bridge_transfer_id = BridgeTransferId::::gen_unique_hash(&mut self.rng); + + tracing::trace!( + "SmartContractInitiator: Initiating bridge transfer: {:?}", + bridge_transfer_id + ); + + // // TODO: fix this + // let balance = self.accounts.entry(initiator.0.clone()).or_insert(Amount(0)); + // **balance -= amount.0; + + // initiate bridge transfer + self.initiated_transfers.insert( + bridge_transfer_id.clone(), + BridgeTransferDetails { + bridge_transfer_id: bridge_transfer_id.clone(), + initiator_address: initiator.clone(), + recipient_address: recipient.clone(), + hash_lock: hash_lock.clone(), + amount, + state: 1, + }, + ); + + Ok(SmartContractInitiatorEvent::InitiatedBridgeTransfer(BridgeTransferDetails { + bridge_transfer_id, + initiator_address: initiator, + recipient_address: recipient, + hash_lock, + amount, + state: 1, + })) + } + + pub fn complete_bridge_transfer( + &mut self, + _accounts: &mut HashMap, + transfer_id: BridgeTransferId, + pre_image: HashLockPreImage, + ) -> SCIResult { + tracing::trace!("SmartContractInitiator: Completing bridge transfer: {:?}", transfer_id); + + // complete bridge transfer + let transfer = self + .initiated_transfers + .get(&transfer_id) + .ok_or(SmartContractInitiatorError::TransferNotFound)?; + + // check if the secret is correct + let secret_hash = H::from(pre_image.clone()); + if transfer.hash_lock.0 != secret_hash { + tracing::warn!( + "Invalid hash lock pre image {pre_image:?} hash {secret_hash:?} != hash_lock {:?}", + transfer.hash_lock.0 + ); + return Err(SmartContractInitiatorError::InvalidHashLockPreImage); + } + + Ok(SmartContractInitiatorEvent::CompletedBridgeTransfer(transfer_id)) + } +} diff --git a/protocol-units/bridge/shared/src/lib.rs b/protocol-units/bridge/shared/src/lib.rs index f6562a5ac..31ea5b40d 100644 --- a/protocol-units/bridge/shared/src/lib.rs +++ b/protocol-units/bridge/shared/src/lib.rs @@ -2,4 +2,7 @@ pub mod blockchain_service; pub mod bridge_contracts; pub mod bridge_monitoring; pub mod bridge_service; +pub mod counterparty_contract; +pub mod initiator_contract; +pub mod multiple_sources_of_truth; pub mod types; diff --git a/protocol-units/bridge/shared/src/multiple_sources_of_truth.rs b/protocol-units/bridge/shared/src/multiple_sources_of_truth.rs new file mode 100644 index 000000000..a4fff8a25 --- /dev/null +++ b/protocol-units/bridge/shared/src/multiple_sources_of_truth.rs @@ -0,0 +1,272 @@ +use futures::task::{Context, Poll}; +use futures::{stream::Stream, FutureExt, StreamExt}; +use futures_timer::Delay; +use std::{ + cmp::Eq, + collections::{HashMap, HashSet}, + hash::{DefaultHasher, Hash, Hasher}, + pin::Pin, + time::Duration, +}; + +#[derive(Debug)] +struct EventInfo { + sources: HashSet, + timestamp: Delay, +} + +pub struct MultipleSourceOfTruth { + sources: Vec, + emitted_events: HashMap, + threshold: usize, + processed: HashSet, + timeout: Duration, +} + +impl MultipleSourceOfTruth +where + S: Stream + Unpin + Hash, + E: Eq + Hash + Clone, +{ + pub fn new(sources: Vec, threshold: usize, timeout: Duration) -> Self { + Self { + sources, + emitted_events: HashMap::new(), + threshold, + processed: HashSet::new(), + timeout, + } + } +} + +pub fn hash_of(source: &S) -> u64 { + let mut hasher = DefaultHasher::new(); + source.hash(&mut hasher); + hasher.finish() +} + +impl Stream for MultipleSourceOfTruth +where + S: Stream + Hash + Unpin, + E: Eq + Hash + Clone + Unpin + std::fmt::Debug, +{ + type Item = E; + + #[tracing::instrument(skip_all)] + fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + tracing::trace!("poll_next called"); + let this = self.get_mut(); + tracing::trace!("Current state: {:?}", this.emitted_events); + + // Remove expired events + this.emitted_events.retain(|event, info| { + tracing::trace!("Checking event: {:?}", event); + match info.timestamp.poll_unpin(cx) { + Poll::Ready(()) => { + tracing::trace!("Event expired: {:?}", event); + this.processed.insert(event.clone()); + false + } + Poll::Pending => true, + } + }); + + for i in (0..this.sources.len()).rev() { + let source = &mut this.sources[i]; + let source_span = tracing::trace_span!("source", i=i, hash=?hash_of(source)); + let _enter_source = source_span.enter(); + tracing::trace!("polling source"); + match source.poll_next_unpin(cx) { + Poll::Ready(Some(event)) => { + let event_span = tracing::trace_span!("event", event=?event); + let _enter_event = event_span.enter(); + tracing::trace!("received event"); + if this.processed.contains(&event) { + tracing::trace!("already processed"); + continue; + } + + let info = this.emitted_events.entry(event.clone()).or_insert_with(|| { + tracing::trace!("new event, initiatlizing event info"); + EventInfo { sources: HashSet::new(), timestamp: Delay::new(this.timeout) } + }); + info.sources.insert(hash_of(source)); + tracing::trace!("event info: {:?}", info); + if info.sources.len() >= this.threshold { + tracing::trace!("threshold reached for event, emitting"); + this.processed.insert(event.clone()); + return Poll::Ready(Some(event)); + } + } + Poll::Ready(None) => { + tracing::warn!("Source ended: {:?}", hash_of(source)); + this.sources.remove(i); + if this.sources.len() < this.threshold { + tracing::warn!("Not enough sources left, ending stream"); + return Poll::Ready(None); + } + } + Poll::Pending => {} + } + } + + tracing::trace!("No event emitted, returning Poll::Pending"); + Poll::Pending + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::time::Duration; + + use futures::stream::Stream; + use futures::task::{Context, Poll}; + use std::collections::VecDeque; + use std::pin::Pin; + + struct TestSource { + name: String, + events: VecDeque<(usize, usize)>, + event: Option, + delay: Option, + } + + impl TestSource { + fn new(name: &str, events: Vec<(usize, usize)>) -> Self { + Self { + name: name.to_string(), + events: VecDeque::from(events), + event: None, + delay: None, + } + } + } + + impl Stream for TestSource { + type Item = usize; + + #[tracing::instrument(skip_all, fields(name = self.name.as_str()))] + fn poll_next(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll> { + let this = self.get_mut(); + + loop { + tracing::trace!("poll_next loop"); + if let Some(delay) = &mut this.delay { + match delay.poll_unpin(_cx) { + Poll::Ready(()) => { + tracing::trace!("emitting event: {:?}", this.event); + this.delay = None; + return Poll::Ready(this.event.take()); + } + Poll::Pending => return Poll::Pending, + } + } + + if let Some((event, delay)) = this.events.pop_front() { + tracing::trace!("Setting event: {} with delay: {}", event, delay); + this.delay = Some(Delay::new(Duration::from_millis(delay as u64))); + this.event = Some(event); + continue; + } else { + return Poll::Ready(None); + } + } + } + } + + impl std::hash::Hash for TestSource { + fn hash(&self, state: &mut H) { + self.name.hash(state); + } + } + + #[test_log::test(tokio::test)] + async fn test_multiple_sources_of_truth() { + tracing::trace!("test_multiple_sources_of_truth"); + let source1 = TestSource::new("source1", vec![(1, 100), (2, 100), (3, 100)]); + let source2 = TestSource::new("source2", vec![(1, 150), (2, 150), (3, 150)]); + let source3 = TestSource::new("source3", vec![(1, 200), (2, 50), (3, 300)]); + let sources = vec![source1, source2, source3]; + let mut msot = MultipleSourceOfTruth::new(sources, 2, Duration::from_secs(5)); + + let mut events = Vec::new(); + while let Some(event) = msot.next().await { + tracing::trace!("event: {:?}", event); + events.push(event); + } + + assert_eq!(events, vec![1, 2, 3]); + } + + #[test_log::test(tokio::test)] + async fn test_multiple_sources_of_truth_edge_cases() { + tracing::trace!("test_multiple_sources_of_truth_edge_cases"); + + // Scenario 1: Different delays for events + let source1 = TestSource::new("source1", vec![(1, 100), (2, 200), (3, 300)]); + let source2 = TestSource::new("source2", vec![(1, 150), (2, 250), (3, 350)]); + let source3 = TestSource::new("source3", vec![(1, 200), (2, 300), (3, 400)]); + let sources = vec![source1, source2, source3]; + let mut msot = MultipleSourceOfTruth::new(sources, 2, Duration::from_secs(5)); + + let mut events = Vec::new(); + while let Some(event) = msot.next().await { + tracing::trace!("event: {:?}", event); + events.push(event); + } + + assert_eq!(events, vec![1, 2, 3]); + + // Scenario 2: Events that do not meet the threshold + let source1 = TestSource::new("source1", vec![(4, 100)]); + let source2 = TestSource::new("source2", vec![(5, 150)]); + let source3 = TestSource::new("source3", vec![(6, 200)]); + let sources = vec![source1, source2, source3]; + let mut msot = MultipleSourceOfTruth::new(sources, 2, Duration::from_secs(5)); + + let mut events = Vec::new(); + while let Some(event) = msot.next().await { + tracing::trace!("event: {:?}", event); + events.push(event); + } + + assert!(events.is_empty()); + + // Scenario 3: Sources that end prematurely + let source1 = TestSource::new("source1", vec![(7, 100), (8, 200), (9, 300)]); + let source2 = TestSource::new("source2", vec![(7, 150)]); + let source3 = TestSource::new("source3", vec![(7, 200), (8, 300), (9, 400)]); + let sources = vec![source1, source2, source3]; + let mut msot = MultipleSourceOfTruth::new(sources, 2, Duration::from_secs(5)); + + let mut events = Vec::new(); + while let Some(event) = msot.next().await { + tracing::trace!("event: {:?}", event); + events.push(event); + } + + assert_eq!(events, vec![7, 8, 9]); + } + + #[test_log::test(tokio::test)] + async fn test_multiple_sources_of_truth_sources_end_prematurely() { + tracing::trace!("test_multiple_sources_of_truth_sources_end_prematurely"); + + // Scenario: Two sources end after a few messages, not reaching the threshold + let source1 = TestSource::new("source1", vec![(1, 100), (2, 200)]); + let source2 = TestSource::new("source2", vec![(1, 150)]); + let source3 = TestSource::new("source3", vec![(1, 200), (2, 300), (3, 400)]); + let sources = vec![source1, source2, source3]; + let mut msot = MultipleSourceOfTruth::new(sources, 2, Duration::from_secs(5)); + + let mut events = Vec::new(); + while let Some(event) = msot.next().await { + tracing::trace!("event: {:?}", event); + events.push(event); + } + + // Since the threshold is 3 and only one event (1) reaches the threshold, the stream should end without emitting any events. + assert_eq!(events, [1, 2]); + } +} diff --git a/protocol-units/bridge/shared/src/types.rs b/protocol-units/bridge/shared/src/types.rs index 749aae176..2142bac17 100644 --- a/protocol-units/bridge/shared/src/types.rs +++ b/protocol-units/bridge/shared/src/types.rs @@ -1,11 +1,29 @@ -use std::{fmt::Debug, hash::Hash}; - +use alloy::primitives::Uint; use derive_more::{Deref, DerefMut}; -use rand::Rng; - +use hex::{self, FromHexError}; +use rand::{Rng, RngCore}; +use std::convert::TryFrom; +use std::ops::AddAssign; +use std::{fmt::Debug, hash::Hash}; +use thiserror::Error; #[derive(Deref, Debug, Clone, PartialEq, Eq, Hash)] pub struct BridgeTransferId(pub H); +impl BridgeTransferId { + pub fn inner(&self) -> &H { + &self.0 + } +} + +impl BridgeTransferId<[u8; 32]> { + pub fn parse(s: &str) -> Result { + let bytes = hex::decode(s)?; + let array: [u8; 32] = + bytes.as_slice().try_into().map_err(|_| FromHexError::InvalidStringLength)?; + Ok(BridgeTransferId(array)) + } +} + impl Convert> for BridgeTransferId where H: Convert, @@ -39,18 +57,57 @@ where #[derive(Deref, Debug, Clone, PartialEq, Eq, Hash)] pub struct InitiatorAddress(pub A); +impl From<&str> for InitiatorAddress> { + fn from(value: &str) -> Self { + Self(value.as_bytes().to_vec()) + } +} + +impl From for InitiatorAddress> { + fn from(value: String) -> Self { + Self(value.as_bytes().to_vec()) + } +} + #[derive(Deref, Debug, Clone, PartialEq, Eq, Hash)] -pub struct RecipientAddress(pub Vec); +pub struct RecipientAddress(pub A); -impl From<&str> for RecipientAddress { +impl From<&str> for RecipientAddress> { fn from(value: &str) -> Self { - RecipientAddress(value.as_bytes().to_vec()) + Self(value.as_bytes().to_vec()) + } +} + +impl From for RecipientAddress> { + fn from(value: String) -> Self { + Self(value.as_bytes().to_vec()) } } +#[derive(Deref, Debug, Clone, PartialEq, Eq, Hash)] +pub struct RecipientAddressCounterparty(pub A); + +#[derive(Deref, Debug, Clone, PartialEq, Eq, Hash)] +pub struct InitiatorAddressCounterParty(pub Vec); + #[derive(Deref, Debug, Clone, PartialEq, Eq, Hash)] pub struct HashLock(pub H); +impl HashLock { + pub fn inner(&self) -> &H { + &self.0 + } +} + +impl HashLock<[u8; 32]> { + pub fn parse(s: &str) -> Result { + let bytes = hex::decode(s)?; + let array: [u8; 32] = + bytes.as_slice().try_into().map_err(|_| FromHexError::InvalidStringLength)?; + Ok(HashLock(array)) + } +} + pub fn convert_hash_lock, O>(other: HashLock) -> HashLock { HashLock(From::from(other.0)) } @@ -58,57 +115,180 @@ pub fn convert_hash_lock, O>(other: HashLock) -> HashLock { #[derive(Deref, Debug, Clone, PartialEq, Eq)] pub struct HashLockPreImage(pub Vec); +impl AsRef<[u8]> for HashLockPreImage { + fn as_ref(&self) -> &[u8] { + &self.0 + } +} + +impl HashLockPreImage { + /// Generate a cryptographically secure random secret + pub fn random() -> Self { + let mut rng = rand::thread_rng(); + let mut secret = vec![0u8; 32]; + rng.fill_bytes(&mut secret); + HashLockPreImage(secret) + } +} + #[derive(Deref, Debug, Clone, PartialEq, Eq)] pub struct TimeLock(pub u64); +impl From> for TimeLock { + fn from(value: Uint<256, 4>) -> Self { + // Extract the lower 64 bits. + let lower_64_bits = value.as_limbs()[0]; + TimeLock(lower_64_bits) + } +} + #[derive(Deref, DerefMut, Debug, Clone, Copy, PartialEq, Eq)] -pub struct Amount(pub u64); +pub struct Amount(pub AssetType); +/// The type of Asset being used +#[derive(Clone, Debug, PartialEq, Eq, Copy)] +pub enum AssetType { + /// Where the first tuple value is `Eth` and the second tuple value is `Weth` + EthAndWeth((u64, u64)), + Moveth(u64), +} + +#[derive(Error, Debug)] +pub enum ConversionError { + #[error("Invalid conversion from AssetType to Uint")] + InvalidConversion, +} + +impl TryFrom for Uint<256, 4> { + type Error = ConversionError; + + fn try_from(value: AssetType) -> Result { + match value { + AssetType::EthAndWeth((eth_value, weth_value)) => { + // Example logic: combine the values or whatever makes sense in your context + let combined_value = eth_value as u128 + weth_value as u128; + Ok(Uint::from(combined_value)) + } + AssetType::Moveth(value) => Ok(Uint::from(value as u128)), + _ => Err(ConversionError::InvalidConversion), // Add more cases as needed + } + } +} + +impl AddAssign for AssetType { + fn add_assign(&mut self, other: Self) { + match (self, other) { + (AssetType::Moveth(ref mut a), AssetType::Moveth(b)) => *a += b, + (AssetType::EthAndWeth((ref mut a, ref mut b)), AssetType::EthAndWeth((c, d))) => { + *a += c; + *b += d; + } + _ => (), + } + } +} + +impl Amount { + pub fn weth(&self) -> u64 { + match self.0 { + AssetType::EthAndWeth((_, weth_value)) => weth_value, + _ => 0, + } + } + pub fn eth(&self) -> u64 { + match self.0 { + AssetType::EthAndWeth((eth_value, _)) => eth_value, + _ => 0, + } + } + pub fn moveth(&self) -> u64 { + match self.0 { + AssetType::Moveth(value) => value, + _ => 0, + } + } + pub fn value(&self) -> u64 { + match self.0 { + AssetType::EthAndWeth((weth_value, eth_value)) => weth_value + eth_value, + AssetType::Moveth(value) => value, + } + } +} + +impl From> for Amount { + fn from(value: Uint<256, 4>) -> Self { + // Extract the lower 64 bits. + let lower_64_bits = value.as_limbs()[0]; + Amount(AssetType::EthAndWeth((0, lower_64_bits))) + } +} + +//#[derive(Debug, PartialEq, Eq, Clone)] +//enum State { +// INITIALIZED, +// COMPLETED, +// REFUNDED +//} #[derive(Debug, PartialEq, Eq, Clone)] pub struct BridgeTransferDetails { pub bridge_transfer_id: BridgeTransferId, pub initiator_address: InitiatorAddress, - pub recipient_address: RecipientAddress, + pub recipient_address: RecipientAddress>, pub hash_lock: HashLock, - pub time_lock: TimeLock, pub amount: Amount, + pub state: u8, +} + +impl Default for BridgeTransferDetails { + fn default() -> Self { + todo!() + } } #[derive(Debug, PartialEq, Eq, Clone)] -pub struct LockDetails { +pub struct LockDetails { pub bridge_transfer_id: BridgeTransferId, - pub recipient_address: RecipientAddress, + pub initiator_address: InitiatorAddress>, + pub recipient_address: RecipientAddress, pub hash_lock: HashLock, - pub time_lock: TimeLock, pub amount: Amount, } #[derive(Debug, PartialEq, Eq, Clone)] -pub struct CompletedDetails { +pub struct CounterpartyCompletedDetails { pub bridge_transfer_id: BridgeTransferId, - pub recipient_address: RecipientAddress, + pub initiator_address: InitiatorAddress>, + pub recipient_address: RecipientAddress, pub hash_lock: HashLock, pub secret: HashLockPreImage, pub amount: Amount, } -impl CompletedDetails { - pub fn from_bridge_transfer_details( +impl CounterpartyCompletedDetails +where + InitiatorAddress>: From>, + RecipientAddress: From>>, +{ + pub fn from_bridge_transfer_details( bridge_transfer_details: BridgeTransferDetails, secret: HashLockPreImage, ) -> Self { - CompletedDetails { + CounterpartyCompletedDetails { bridge_transfer_id: bridge_transfer_details.bridge_transfer_id, - recipient_address: bridge_transfer_details.recipient_address, + initiator_address: From::from(bridge_transfer_details.initiator_address), + recipient_address: From::from(bridge_transfer_details.recipient_address), hash_lock: bridge_transfer_details.hash_lock, secret, amount: bridge_transfer_details.amount, } } +} - pub fn from_lock_details(lock_details: LockDetails, secret: HashLockPreImage) -> Self { - CompletedDetails { +impl CounterpartyCompletedDetails { + pub fn from_lock_details(lock_details: LockDetails, secret: HashLockPreImage) -> Self { + CounterpartyCompletedDetails { bridge_transfer_id: lock_details.bridge_transfer_id, + initiator_address: lock_details.initiator_address, recipient_address: lock_details.recipient_address, hash_lock: lock_details.hash_lock, secret, @@ -119,7 +299,11 @@ impl CompletedDetails { // Types pub trait BridgeHashType: Debug + PartialEq + Eq + Hash + Unpin + Send + Sync + Clone {} -pub trait BridgeAddressType: Debug + PartialEq + Eq + Hash + Unpin + Send + Sync + Clone {} +pub trait BridgeAddressType: + Debug + PartialEq + Eq + Hash + Unpin + Send + Sync + Clone + From> +{ +} +pub trait BridgeValueType: Debug + PartialEq + Eq + Clone + Send + Sync + Unpin {} pub trait Convert { fn convert(other: &Self) -> O; @@ -127,7 +311,10 @@ pub trait Convert { // Blankets impl BridgeHashType for T where T: Debug + PartialEq + Eq + Hash + Unpin + Send + Sync + Clone {} -impl BridgeAddressType for T where T: Debug + PartialEq + Eq + Hash + Unpin + Send + Sync + Clone {} +impl BridgeAddressType for T where + T: Debug + PartialEq + Eq + Hash + Unpin + Send + Sync + Clone + From> +{ +} pub trait GenUniqueHash { fn gen_unique_hash(rng: &mut R) -> Self; diff --git a/protocol-units/bridge/shared/tests/abstract_blockchain.rs b/protocol-units/bridge/shared/tests/abstract_blockchain.rs index 2c7dc91a8..e154d1363 100644 --- a/protocol-units/bridge/shared/tests/abstract_blockchain.rs +++ b/protocol-units/bridge/shared/tests/abstract_blockchain.rs @@ -1,6 +1,6 @@ use bridge_shared::types::{ - Amount, BridgeTransferDetails, BridgeTransferId, GenUniqueHash, HashLock, InitiatorAddress, - RecipientAddress, TimeLock, + Amount, AssetType, BridgeTransferDetails, BridgeTransferId, GenUniqueHash, HashLock, + InitiatorAddress, RecipientAddress, TimeLock, }; use bridge_shared::types::{HashLockPreImage, LockDetails}; use futures::StreamExt; @@ -23,17 +23,35 @@ use crate::shared::testing::blockchain::{ #[derive(Debug, Clone, PartialEq, Eq, Hash)] struct TestAddress(pub &'static str); +impl From> for TestAddress { + fn from(value: RecipientAddress) -> Self { + value.0.clone() + } +} + +impl From for Vec { + fn from(value: TestAddress) -> Self { + value.0.as_bytes().to_vec() + } +} + +impl From> for TestAddress { + fn from(value: Vec) -> Self { + Self(static_str_ops::staticize(&String::from_utf8(value).expect("Invalid UTF-8"))) + } +} + #[derive(Debug, Clone, PartialEq, Eq, Hash)] struct TestHash(pub &'static str); -impl From for RecipientAddress { +impl From for RecipientAddress> { fn from(value: TestAddress) -> Self { RecipientAddress(value.0.as_bytes().to_vec()) } } -impl From for TestAddress { - fn from(value: RecipientAddress) -> Self { +impl From>> for TestAddress { + fn from(value: RecipientAddress>) -> Self { Self(static_str_ops::staticize(&String::from_utf8(value.0).expect("Invalid UTF-8"))) } } @@ -59,7 +77,7 @@ async fn test_initiate_bridge_transfer() { let initiator_address = InitiatorAddress(TestAddress("initiator")); let recipient_address = RecipientAddress::from(TestAddress("recipient")); - let amount = Amount(1000); + let amount = Amount(AssetType::EthAndWeth((1000, 0))); let time_lock = TimeLock(100); let hash_lock = HashLock(TestHash("hash_lock")); @@ -67,7 +85,6 @@ async fn test_initiate_bridge_transfer() { initiator_address.clone(), recipient_address.clone(), amount, - time_lock.clone(), hash_lock.clone(), )); @@ -88,8 +105,8 @@ async fn test_initiate_bridge_transfer() { initiator_address: initiator_address.clone(), recipient_address: recipient_address.clone(), amount: amount.clone(), - time_lock: time_lock.clone(), hash_lock: hash_lock.clone(), + state: 1 }) )) ); @@ -104,7 +121,6 @@ async fn test_initiate_bridge_transfer() { assert_eq!(details.initiator_address, initiator_address); assert_eq!(details.recipient_address, recipient_address); assert_eq!(details.amount, amount); - assert_eq!(details.time_lock, time_lock); assert_eq!(details.hash_lock, hash_lock); } @@ -118,16 +134,18 @@ async fn test_lock_bridge_transfer() { let bridge_transfer_id = BridgeTransferId(TestHash("unique_hash")); let hash_lock = HashLock(TestHash("hash_lock")); let time_lock = TimeLock(100); - let recipient_address = RecipientAddress::from(TestAddress("recipient")); - let amount = Amount(1000); - - let transaction = Transaction::Counterparty(CounterpartyCall::LockBridgeTransfer( - bridge_transfer_id.clone(), - hash_lock.clone(), - time_lock.clone(), - recipient_address.clone(), - amount, - )); + let initiator_adress = InitiatorAddress(vec![]); + let recipient_address = RecipientAddress(TestAddress("recipient")); + let amount = Amount(AssetType::EthAndWeth((1000, 0))); + + let transaction = + Transaction::Counterparty::(CounterpartyCall::LockBridgeTransfer( + bridge_transfer_id.clone(), + hash_lock.clone(), + initiator_adress.clone(), + recipient_address.clone(), + amount, + )); blockchain.transaction_sender.unbounded_send(transaction).unwrap(); @@ -144,7 +162,7 @@ async fn test_lock_bridge_transfer() { SmartContractCounterpartyEvent::LockedBridgeTransfer(LockDetails { bridge_transfer_id: bridge_transfer_id.clone(), hash_lock: hash_lock.clone(), - time_lock: time_lock.clone(), + initiator_address: InitiatorAddress(Vec::new()), recipient_address: recipient_address.clone(), amount, }) @@ -158,6 +176,5 @@ async fn test_lock_bridge_transfer() { assert_eq!(details.bridge_transfer_id, bridge_transfer_id); assert_eq!(details.recipient_address, recipient_address); assert_eq!(details.hash_lock, hash_lock); - assert_eq!(details.time_lock, time_lock); assert_eq!(details.amount, amount); } diff --git a/protocol-units/bridge/shared/tests/blockchain_service.rs b/protocol-units/bridge/shared/tests/blockchain_service.rs index 2dd9995f7..f8ce9e95b 100644 --- a/protocol-units/bridge/shared/tests/blockchain_service.rs +++ b/protocol-units/bridge/shared/tests/blockchain_service.rs @@ -1,7 +1,7 @@ use bridge_shared::bridge_monitoring::BridgeContractInitiatorEvent; use bridge_shared::types::{ - Amount, BridgeTransferDetails, BridgeTransferId, HashLock, InitiatorAddress, RecipientAddress, - TimeLock, + Amount, AssetType, BridgeTransferDetails, BridgeTransferId, HashLock, InitiatorAddress, + RecipientAddress, }; use bridge_shared::{blockchain_service::ContractEvent, bridge_contracts::BridgeContractInitiator}; use futures::StreamExt; @@ -19,11 +19,10 @@ async fn test_bridge_transfer_initiated() { .initiator_contract .with_next_bridge_transfer_id("transfer_id") .initiate_bridge_transfer( - InitiatorAddress("initiator"), + InitiatorAddress::from("initiator"), RecipientAddress::from("recipient"), HashLock("hash_lock"), - TimeLock(100), - Amount(1000), + Amount(AssetType::EthAndWeth((1000, 0))), ) .await .expect("initiate_bridge_transfer failed"); @@ -36,11 +35,11 @@ async fn test_bridge_transfer_initiated() { Poll::Ready(Some(ContractEvent::InitiatorEvent(BridgeContractInitiatorEvent::Initiated( BridgeTransferDetails { bridge_transfer_id: BridgeTransferId("transfer_id"), - initiator_address: InitiatorAddress("initiator"), + initiator_address: InitiatorAddress::from("initiator"), recipient_address: RecipientAddress::from("recipient"), hash_lock: HashLock("hash_lock"), - time_lock: TimeLock(100), - amount: Amount(1000), + amount: Amount(AssetType::EthAndWeth((1000, 0))), + state: 1 } )))) ); diff --git a/protocol-units/bridge/shared/tests/bridge_service.rs b/protocol-units/bridge/shared/tests/bridge_service.rs index 391389f87..ad4704a60 100644 --- a/protocol-units/bridge/shared/tests/bridge_service.rs +++ b/protocol-units/bridge/shared/tests/bridge_service.rs @@ -8,8 +8,8 @@ use bridge_shared::{ bridge_monitoring::{BridgeContractCounterpartyEvent, BridgeContractInitiatorEvent}, bridge_service::{active_swap::ActiveSwapConfig, BridgeServiceConfig}, types::{ - Amount, BridgeTransferDetails, CompletedDetails, Convert, HashLock, HashLockPreImage, - InitiatorAddress, LockDetails, RecipientAddress, TimeLock, + Amount, AssetType, BridgeTransferDetails, Convert, CounterpartyCompletedDetails, HashLock, + HashLockPreImage, InitiatorAddress, LockDetails, RecipientAddress, }, }; @@ -49,8 +49,7 @@ async fn test_bridge_service_integration_a_to_b() { InitiatorAddress(BC1Address("initiator")), RecipientAddress::from(BC1Address("recipient")), HashLock(BC1Hash::from("hash_lock")), - TimeLock(100), - Amount(1000), + Amount(AssetType::EthAndWeth((0, 1000))), ) .await .expect("initiate_bridge_transfer failed"); @@ -67,8 +66,8 @@ async fn test_bridge_service_integration_a_to_b() { initiator_address: InitiatorAddress(BC1Address("initiator")), recipient_address: RecipientAddress::from(BC1Address("recipient")), hash_lock: HashLock(BC1Hash::from("hash_lock")), - time_lock: TimeLock(100), - amount: Amount(1000) + amount: Amount(AssetType::EthAndWeth((0, 1000))), + state: 1 }) ); @@ -87,9 +86,9 @@ async fn test_bridge_service_integration_a_to_b() { &BridgeContractCounterpartyEvent::Locked(LockDetails { bridge_transfer_id: Convert::convert(transfer_initiated_event.bridge_transfer_id()), hash_lock: HashLock(BC2Hash::from("hash_lock")), - time_lock: TimeLock(100), - recipient_address: RecipientAddress::from(BC2Address("recipient")), - amount: Amount(1000), + initiator_address: InitiatorAddress::from(BC1Address("initiator")), + recipient_address: RecipientAddress(BC2Address("recipient")), + amount: Amount(AssetType::EthAndWeth((0, 1000))), }) ); @@ -115,12 +114,13 @@ async fn test_bridge_service_integration_a_to_b() { tracing::debug!(?completed_event_counterparty); assert_eq!( completed_event_counterparty, - &BridgeContractCounterpartyEvent::Completed(CompletedDetails { + &BridgeContractCounterpartyEvent::Completed(CounterpartyCompletedDetails { bridge_transfer_id: Convert::convert(transfer_initiated_event.bridge_transfer_id()), - recipient_address: RecipientAddress::from(BC2Address("recipient")), + initiator_address: InitiatorAddress::from(BC1Address("initiator")), + recipient_address: RecipientAddress(BC2Address("recipient")), hash_lock: HashLock(BC2Hash::from("hash_lock")), secret: HashLockPreImage(b"hash_lock".to_vec()), - amount: Amount(1000), + amount: Amount(AssetType::EthAndWeth((0, 1000))), }) ); @@ -172,8 +172,7 @@ async fn test_bridge_service_integration_b_to_a() { InitiatorAddress(BC2Address("initiator")), RecipientAddress::from(BC2Address("recipient")), HashLock(BC2Hash::from("hash_lock")), - TimeLock(100), - Amount(1000), + Amount(AssetType::EthAndWeth((0, 1000))), ) .await .expect("initiate_bridge_transfer failed"); @@ -190,8 +189,8 @@ async fn test_bridge_service_integration_b_to_a() { initiator_address: InitiatorAddress(BC2Address("initiator")), recipient_address: RecipientAddress::from(BC2Address("recipient")), hash_lock: HashLock(BC2Hash::from("hash_lock")), - time_lock: TimeLock(100), - amount: Amount(1000) + amount: Amount(AssetType::EthAndWeth((0, 1000))), + state: 1 }) ); @@ -210,9 +209,9 @@ async fn test_bridge_service_integration_b_to_a() { &BridgeContractCounterpartyEvent::Locked(LockDetails { bridge_transfer_id: Convert::convert(transfer_initiated_event.bridge_transfer_id()), hash_lock: HashLock(BC1Hash::from("hash_lock")), - time_lock: TimeLock(100), - recipient_address: RecipientAddress::from(BC1Address("recipient")), - amount: Amount(1000), + initiator_address: InitiatorAddress::from(BC1Address("initiator")), + recipient_address: RecipientAddress(BC1Address("recipient")), + amount: Amount(AssetType::EthAndWeth((0, 1000))), }) ); @@ -238,12 +237,13 @@ async fn test_bridge_service_integration_b_to_a() { tracing::debug!(?completed_event_counterparty); assert_eq!( completed_event_counterparty, - &BridgeContractCounterpartyEvent::Completed(CompletedDetails { + &BridgeContractCounterpartyEvent::Completed(CounterpartyCompletedDetails { bridge_transfer_id: Convert::convert(transfer_initiated_event.bridge_transfer_id()), - recipient_address: RecipientAddress::from(BC1Address("recipient")), + initiator_address: InitiatorAddress::from(BC1Address("initiator")), + recipient_address: RecipientAddress(BC1Address("recipient")), hash_lock: HashLock(BC1Hash::from("hash_lock")), secret: HashLockPreImage(b"hash_lock".to_vec()), - amount: Amount(1000), + amount: Amount(AssetType::EthAndWeth((0, 1000))), }) ); diff --git a/protocol-units/bridge/shared/tests/bridge_service_error_handling.rs b/protocol-units/bridge/shared/tests/bridge_service_error_handling.rs index c66ecaaba..4270e14e3 100644 --- a/protocol-units/bridge/shared/tests/bridge_service_error_handling.rs +++ b/protocol-units/bridge/shared/tests/bridge_service_error_handling.rs @@ -10,13 +10,13 @@ use bridge_shared::{ }, bridge_monitoring::{BridgeContractCounterpartyEvent, BridgeContractInitiatorEvent}, bridge_service::{ - active_swap::{ActiveSwapConfig, LockBridgeTransferAssetsError}, + active_swap::{ActiveSwapConfig, LockBridgeTransferError}, events::{CEvent, CWarn, Event, IEvent, IWarn}, BridgeServiceConfig, }, types::{ - Amount, BridgeTransferDetails, CompletedDetails, Convert, HashLock, HashLockPreImage, - InitiatorAddress, RecipientAddress, TimeLock, + Amount, AssetType, BridgeTransferDetails, Convert, CounterpartyCompletedDetails, HashLock, + HashLockPreImage, InitiatorAddress, RecipientAddress, }, }; @@ -50,11 +50,11 @@ async fn test_bridge_service_error_handling() { // Lets make the blockchain_2_client fail on the locking of assets blockchain_2_client.set_call_config( - MethodName::LockBridgeTransferAssets, + MethodName::LockBridgeTransfer, 1, CallConfig { error: ErrorConfig::CounterpartyError( - BridgeContractCounterpartyError::LockTransferAssetsError, + BridgeContractCounterpartyError::LockTransferError, ), delay: None, }, @@ -68,8 +68,7 @@ async fn test_bridge_service_error_handling() { InitiatorAddress(BC1Address("initiator")), RecipientAddress::from(BC1Address("recipient")), HashLock(BC1Hash::from("hash_lock")), - TimeLock(100), - Amount(1000), + Amount(AssetType::EthAndWeth((0, 1000))), ) .await .expect("initiate_bridge_transfer failed"); @@ -86,8 +85,8 @@ async fn test_bridge_service_error_handling() { initiator_address: InitiatorAddress(BC1Address("initiator")), recipient_address: RecipientAddress::from(BC1Address("recipient")), hash_lock: HashLock(BC1Hash::from("hash_lock")), - time_lock: TimeLock(100), - amount: Amount(1000) + amount: Amount(AssetType::EthAndWeth((0, 1000))), + state: 1 }) ); @@ -133,12 +132,13 @@ async fn test_bridge_service_error_handling() { tracing::debug!(?completed_event_counterparty); assert_eq!( completed_event_counterparty, - &BridgeContractCounterpartyEvent::Completed(CompletedDetails { + &BridgeContractCounterpartyEvent::Completed(CounterpartyCompletedDetails { bridge_transfer_id: Convert::convert(transfer_initiated_event.bridge_transfer_id()), - recipient_address: RecipientAddress::from(BC2Address("recipient")), + initiator_address: InitiatorAddress::from(BC1Address("initiator")), + recipient_address: RecipientAddress(BC2Address("recipient")), hash_lock: HashLock(BC2Hash::from("hash_lock")), secret: HashLockPreImage(b"hash_lock".to_vec()), - amount: Amount(1000), + amount: Amount(AssetType::EthAndWeth((0, 1000))), }) ); @@ -209,11 +209,11 @@ async fn test_bridge_service_locking_termination_after_errors() { // Configure blockchain_2_client to fail 3 times on locking assets for n in 1..5 { blockchain_2_client.set_call_config( - MethodName::LockBridgeTransferAssets, + MethodName::LockBridgeTransfer, n, CallConfig { error: ErrorConfig::CounterpartyError( - BridgeContractCounterpartyError::LockTransferAssetsError, + BridgeContractCounterpartyError::LockTransferError, ), delay: None, }, @@ -227,8 +227,7 @@ async fn test_bridge_service_locking_termination_after_errors() { InitiatorAddress(BC1Address("initiator")), RecipientAddress::from(BC1Address("recipient")), HashLock(BC1Hash::from("hash_lock")), - TimeLock(100), - Amount(1000), + Amount(AssetType::EthAndWeth((0, 1000))), ) .await .expect("initiate_bridge_transfer failed"); @@ -245,8 +244,8 @@ async fn test_bridge_service_locking_termination_after_errors() { initiator_address: InitiatorAddress(BC1Address("initiator")), recipient_address: RecipientAddress::from(BC1Address("recipient")), hash_lock: HashLock(BC1Hash::from("hash_lock")), - time_lock: TimeLock(100), - amount: Amount(1000) + amount: Amount(AssetType::EthAndWeth((0, 1000))), + state: 1 }) ); @@ -308,8 +307,7 @@ async fn test_bridge_service_completion_abort_after_errors() { InitiatorAddress(BC1Address("initiator")), RecipientAddress::from(BC1Address("recipient")), HashLock(BC1Hash::from("hash_lock")), - TimeLock(100), - Amount(1000), + Amount(AssetType::EthAndWeth((0, 1000))), ) .await .expect("initiate_bridge_transfer failed"); @@ -326,8 +324,8 @@ async fn test_bridge_service_completion_abort_after_errors() { initiator_address: InitiatorAddress(BC1Address("initiator")), recipient_address: RecipientAddress::from(BC1Address("recipient")), hash_lock: HashLock(BC1Hash::from("hash_lock")), - time_lock: TimeLock(100), - amount: Amount(1000) + amount: Amount(AssetType::EthAndWeth((0, 1000))), + state: 1 }) ); @@ -369,12 +367,13 @@ async fn test_bridge_service_completion_abort_after_errors() { tracing::debug!(?completed_event_counterparty); assert_eq!( completed_event_counterparty, - &BridgeContractCounterpartyEvent::Completed(CompletedDetails { + &BridgeContractCounterpartyEvent::Completed(CounterpartyCompletedDetails { bridge_transfer_id: Convert::convert(transfer_initiated_event.bridge_transfer_id()), - recipient_address: RecipientAddress::from(BC2Address("recipient")), + initiator_address: InitiatorAddress::from(BC1Address("initiator")), + recipient_address: RecipientAddress(BC2Address("recipient")), hash_lock: HashLock(BC2Hash::from("hash_lock")), secret: HashLockPreImage(b"hash_lock".to_vec()), - amount: Amount(1000), + amount: Amount(AssetType::EthAndWeth((0, 1000))), }) ); @@ -430,7 +429,7 @@ async fn test_bridge_service_timeout_error_handling() { // Lets make the blockchain_2_client fail on the locking of assets blockchain_2_client.set_call_config( - MethodName::LockBridgeTransferAssets, + MethodName::LockBridgeTransfer, 1, // Longer delay than the timeout, to trigger timeout CallConfig { error: ErrorConfig::None, delay: Some(Duration::from_secs(1)) }, @@ -443,8 +442,7 @@ async fn test_bridge_service_timeout_error_handling() { InitiatorAddress(BC1Address("initiator")), RecipientAddress::from(BC1Address("recipient")), HashLock(BC1Hash::from("hash_lock")), - TimeLock(100), - Amount(1000), + Amount(AssetType::EthAndWeth((0, 1000))), ) .await .expect("initiate_bridge_transfer failed"); @@ -461,8 +459,8 @@ async fn test_bridge_service_timeout_error_handling() { initiator_address: InitiatorAddress(BC1Address("initiator")), recipient_address: RecipientAddress::from(BC1Address("recipient")), hash_lock: HashLock(BC1Hash::from("hash_lock")), - time_lock: TimeLock(100), - amount: Amount(1000) + amount: Amount(AssetType::EthAndWeth((0, 1000))), + state: 1 }) ); @@ -471,7 +469,7 @@ async fn test_bridge_service_timeout_error_handling() { tracing::debug!(?event); assert!(matches!( event.B2C().and_then(CEvent::warn).expect("not a b2c warn event"), - CWarn::BridgeAssetsLockingError(LockBridgeTransferAssetsError::ContractCallTimeoutError) + CWarn::BridgeAssetsLockingError(LockBridgeTransferError::ContractCallTimeoutError) )); // The Bridge is expected to retry the operation after the configured delay in case of an error. diff --git a/protocol-units/bridge/shared/tests/shared/mod.rs b/protocol-units/bridge/shared/tests/shared/mod.rs index 52b1415f0..80158d46f 100644 --- a/protocol-units/bridge/shared/tests/shared/mod.rs +++ b/protocol-units/bridge/shared/tests/shared/mod.rs @@ -7,7 +7,7 @@ use bridge_shared::{ BridgeContractInitiatorEvent, BridgeContractInitiatorMonitoring, }, bridge_service::{BridgeService, BridgeServiceConfig}, - types::{Convert, GenUniqueHash, HashLockPreImage, RecipientAddress}, + types::{Convert, GenUniqueHash, HashLockPreImage, InitiatorAddress, RecipientAddress}, }; use futures::{channel::mpsc::UnboundedReceiver, Stream, StreamExt}; @@ -100,28 +100,70 @@ impl Debug for BC2Hash { #[derive(Debug, Clone, Eq, PartialEq, Hash)] pub struct BC1Address(pub &'static str); -impl From for BC1Address { - fn from(value: RecipientAddress) -> Self { +impl From> for BC1Address { + fn from(value: Vec) -> Self { + Self(static_str_ops::staticize(&String::from_utf8(value).expect("Invalid UTF-8"))) + } +} + +impl From for Vec { + fn from(address: BC1Address) -> Self { + address.0.as_bytes().to_vec() + } +} + +impl From> for BC1Address { + fn from(RecipientAddress(address): RecipientAddress) -> Self { + address + } +} + +impl From>> for BC1Address { + fn from(value: RecipientAddress>) -> Self { Self(static_str_ops::staticize(&String::from_utf8(value.0).expect("Invalid UTF-8"))) } } -impl From for RecipientAddress { +impl From for RecipientAddress> { fn from(value: BC1Address) -> Self { RecipientAddress(value.0.as_bytes().to_vec()) } } +impl From for InitiatorAddress> { + fn from(address: BC1Address) -> Self { + InitiatorAddress(address.0.as_bytes().to_vec()) + } +} + #[derive(Debug, Clone, Eq, PartialEq, Hash)] pub struct BC2Address(pub &'static str); -impl From for BC2Address { - fn from(value: RecipientAddress) -> Self { +impl From> for BC2Address { + fn from(value: Vec) -> Self { + Self(static_str_ops::staticize(&String::from_utf8(value).expect("Invalid UTF-8"))) + } +} + +impl From for Vec { + fn from(address: BC2Address) -> Self { + address.0.as_bytes().to_vec() + } +} + +impl From> for BC2Address { + fn from(RecipientAddress(address): RecipientAddress) -> Self { + address + } +} + +impl From>> for BC2Address { + fn from(value: RecipientAddress>) -> Self { Self(static_str_ops::staticize(&String::from_utf8(value.0).expect("Invalid UTF-8"))) } } -impl From for RecipientAddress { +impl From for RecipientAddress> { fn from(value: BC2Address) -> Self { RecipientAddress(value.0.as_bytes().to_vec()) } @@ -233,7 +275,7 @@ impl BridgeContractCounterpartyMonitoring } impl Stream for CounterpartyContractMonitoring { - type Item = BridgeContractCounterpartyEvent; + type Item = BridgeContractCounterpartyEvent; fn poll_next(self: Pin<&mut Self>, cx: &mut Context) -> Poll> { let this = self.get_mut(); diff --git a/protocol-units/bridge/shared/tests/shared/testing/blockchain.rs b/protocol-units/bridge/shared/tests/shared/testing/blockchain.rs index d8fcc1ecc..de5ce278d 100644 --- a/protocol-units/bridge/shared/tests/shared/testing/blockchain.rs +++ b/protocol-units/bridge/shared/tests/shared/testing/blockchain.rs @@ -23,22 +23,22 @@ pub mod counterparty_contract; pub mod hasher; pub mod initiator_contract; -pub enum SmartContractCall { +pub enum SmartContractCall { Initiator(), - Counterparty(CounterpartyCall), + Counterparty(CounterpartyCall), } #[derive(Debug, Clone, PartialEq, Eq)] pub enum AbstractBlockchainEvent { InitiatorContractEvent(SCIResult), - CounterpartyContractEvent(SCCResult), + CounterpartyContractEvent(SCCResult), Noop, } #[derive(Debug)] pub enum Transaction { Initiator(InitiatorCall), - Counterparty(CounterpartyCall), + Counterparty(CounterpartyCall), } #[derive(Debug)] @@ -64,7 +64,7 @@ pub struct AbstractBlockchain { impl AbstractBlockchain where - A: BridgeAddressType + From, + A: BridgeAddressType + From>, H: BridgeHashType + GenUniqueHash, R: RngSeededClone, H: From, @@ -129,7 +129,7 @@ where impl Future for AbstractBlockchain where - A: BridgeAddressType + From, + A: BridgeAddressType + From>, H: BridgeHashType + GenUniqueHash, R: Rng + Unpin, H: From, @@ -153,7 +153,7 @@ where impl Stream for AbstractBlockchain where - A: BridgeAddressType + From, + A: BridgeAddressType + From>, H: BridgeHashType + GenUniqueHash, R: Rng + Unpin, H: From, @@ -177,7 +177,6 @@ where initiator_address, recipient_address, amount, - time_lock, hash_lock, ) => { this.events.push(AbstractBlockchainEvent::InitiatorContractEvent( @@ -185,7 +184,6 @@ where initiator_address.clone(), recipient_address.clone(), amount, - time_lock.clone(), hash_lock.clone(), ), )); @@ -204,7 +202,7 @@ where CounterpartyCall::LockBridgeTransfer( bridge_transfer_id, hash_lock, - time_lock, + initiator_address, recipient_address, amount, ) => { @@ -212,7 +210,7 @@ where this.counterparty_contract.lock_bridge_transfer( bridge_transfer_id.clone(), hash_lock.clone(), - time_lock.clone(), + initiator_address.clone(), recipient_address.clone(), amount, ), diff --git a/protocol-units/bridge/shared/tests/shared/testing/blockchain/client.rs b/protocol-units/bridge/shared/tests/shared/testing/blockchain/client.rs index 64a921a80..6d27002fc 100644 --- a/protocol-units/bridge/shared/tests/shared/testing/blockchain/client.rs +++ b/protocol-units/bridge/shared/tests/shared/testing/blockchain/client.rs @@ -7,7 +7,7 @@ use bridge_shared::{ }, types::{ Amount, BridgeAddressType, BridgeHashType, BridgeTransferDetails, BridgeTransferId, - HashLock, HashLockPreImage, InitiatorAddress, RecipientAddress, TimeLock, + HashLock, HashLockPreImage, InitiatorAddress, RecipientAddress, }, }; use dashmap::DashMap; @@ -24,7 +24,7 @@ pub enum MethodName { CompleteBridgeTransferCounterparty, RefundBridgeTransfer, GetBridgeTransferDetails, - LockBridgeTransferAssets, + LockBridgeTransfer, AbortBridgeTransfer, } @@ -203,16 +203,14 @@ where async fn initiate_bridge_transfer( &mut self, initiator_address: InitiatorAddress, - recipient_address: RecipientAddress, + recipient_address: RecipientAddress>, hash_lock: HashLock, - time_lock: TimeLock, amount: Amount, ) -> BridgeContractInitiatorResult<()> { let transaction = Transaction::Initiator(InitiatorCall::InitiateBridgeTransfer( initiator_address, recipient_address, amount, - time_lock, hash_lock, )); self.register_call(MethodName::InitiateBridgeTransfer); @@ -263,7 +261,7 @@ where async fn get_bridge_transfer_details( &mut self, _bridge_transfer_id: BridgeTransferId, - ) -> BridgeContractInitiatorResult>> { + ) -> BridgeContractInitiatorResult>> { unimplemented!() } } @@ -278,17 +276,17 @@ where type Address = A; type Hash = H; - async fn lock_bridge_transfer_assets( + async fn lock_bridge_transfer( &mut self, bridge_transfer_id: BridgeTransferId, hash_lock: HashLock, - time_lock: TimeLock, - recipient: RecipientAddress, + initiator: InitiatorAddress>, + recipient: RecipientAddress, amount: Amount, ) -> BridgeContractCounterpartyResult<()> { - self.register_call(MethodName::LockBridgeTransferAssets); - if let Some(config) = self.have_call_config(MethodName::LockBridgeTransferAssets) { - tracing::error!("lock_bridge_transfer_assets {:?}", config); + self.register_call(MethodName::LockBridgeTransfer); + if let Some(config) = self.have_call_config(MethodName::LockBridgeTransfer) { + tracing::error!("lock_bridge_transfer {:?}", config); if let Some(delay) = config.delay { tokio::time::sleep(delay).await; } @@ -298,7 +296,7 @@ where let transaction = Transaction::Counterparty(CounterpartyCall::LockBridgeTransfer( bridge_transfer_id, hash_lock, - time_lock, + initiator, recipient, amount, )); @@ -339,7 +337,7 @@ where async fn get_bridge_transfer_details( &mut self, _bridge_transfer_id: BridgeTransferId, - ) -> BridgeContractCounterpartyResult>> + ) -> BridgeContractCounterpartyResult>> { unimplemented!() } diff --git a/protocol-units/bridge/shared/tests/shared/testing/blockchain/counterparty_contract.rs b/protocol-units/bridge/shared/tests/shared/testing/blockchain/counterparty_contract.rs index 86a514fd6..b1f284144 100644 --- a/protocol-units/bridge/shared/tests/shared/testing/blockchain/counterparty_contract.rs +++ b/protocol-units/bridge/shared/tests/shared/testing/blockchain/counterparty_contract.rs @@ -1,15 +1,16 @@ use std::collections::HashMap; use bridge_shared::types::{ - Amount, BridgeAddressType, BridgeHashType, BridgeTransferId, CompletedDetails, GenUniqueHash, - HashLock, HashLockPreImage, LockDetails, RecipientAddress, TimeLock, + Amount, AssetType, BridgeAddressType, BridgeHashType, BridgeTransferId, + CounterpartyCompletedDetails, GenUniqueHash, HashLock, HashLockPreImage, InitiatorAddress, + LockDetails, RecipientAddress, }; use thiserror::Error; #[derive(Debug, Clone, PartialEq, Eq)] -pub enum SmartContractCounterpartyEvent { - LockedBridgeTransfer(LockDetails), - CompletedBridgeTransfer(CompletedDetails), +pub enum SmartContractCounterpartyEvent { + LockedBridgeTransfer(LockDetails), + CompletedBridgeTransfer(CounterpartyCompletedDetails), } #[derive(Debug, Error, Clone, PartialEq, Eq)] @@ -21,38 +22,43 @@ pub enum SmartContractCounterpartyError { } #[derive(Debug)] -pub enum CounterpartyCall { +pub enum CounterpartyCall { CompleteBridgeTransfer(BridgeTransferId, HashLockPreImage), - LockBridgeTransfer(BridgeTransferId, HashLock, TimeLock, RecipientAddress, Amount), + LockBridgeTransfer( + BridgeTransferId, + HashLock, + InitiatorAddress>, + RecipientAddress, + Amount, + ), } #[derive(Debug)] pub struct SmartContractCounterparty { - pub locked_transfers: HashMap, LockDetails>, - pub _phantom: std::marker::PhantomData, + pub locked_transfers: HashMap, LockDetails>, } -pub type SCCResult = Result, SmartContractCounterpartyError>; +pub type SCCResult = + Result, SmartContractCounterpartyError>; impl SmartContractCounterparty where - A: BridgeAddressType + From, + A: BridgeAddressType + From>, H: BridgeHashType + GenUniqueHash, H: From, { pub fn new() -> Self { - Self { locked_transfers: HashMap::new(), _phantom: std::marker::PhantomData } + Self { locked_transfers: HashMap::new() } } pub fn lock_bridge_transfer( &mut self, - bridge_transfer_id: BridgeTransferId, hash_lock: HashLock, - time_lock: TimeLock, - recipient_address: RecipientAddress, + initiator_address: InitiatorAddress>, + recipient_address: RecipientAddress, amount: Amount, - ) -> SCCResult { + ) -> SCCResult { tracing::trace!( "SmartContractCounterparty: Locking bridge transfer: {:?}", bridge_transfer_id @@ -61,18 +67,18 @@ where bridge_transfer_id.clone(), LockDetails { bridge_transfer_id: bridge_transfer_id.clone(), + initiator_address: initiator_address.clone(), recipient_address: recipient_address.clone(), hash_lock: hash_lock.clone(), - time_lock: time_lock.clone(), amount, }, ); Ok(SmartContractCounterpartyEvent::LockedBridgeTransfer(LockDetails { bridge_transfer_id, + initiator_address, recipient_address, hash_lock, - time_lock, amount, })) } @@ -82,7 +88,7 @@ where accounts: &mut HashMap, bridge_transfer_id: &BridgeTransferId, pre_image: HashLockPreImage, - ) -> SCCResult { + ) -> SCCResult { let transfer = self .locked_transfers .remove(bridge_transfer_id) @@ -102,11 +108,11 @@ where // TODO: fix this let account = A::from(transfer.recipient_address.clone()); - let balance = accounts.entry(account).or_insert(Amount(0)); + let balance = accounts.entry(account).or_insert(Amount(AssetType::EthAndWeth((0, 0)))); **balance += *transfer.amount; Ok(SmartContractCounterpartyEvent::CompletedBridgeTransfer( - CompletedDetails::from_lock_details(transfer, pre_image), + CounterpartyCompletedDetails::from_lock_details(transfer, pre_image), )) } } diff --git a/protocol-units/bridge/shared/tests/shared/testing/blockchain/initiator_contract.rs b/protocol-units/bridge/shared/tests/shared/testing/blockchain/initiator_contract.rs index 4f711b4c7..ddfb38495 100644 --- a/protocol-units/bridge/shared/tests/shared/testing/blockchain/initiator_contract.rs +++ b/protocol-units/bridge/shared/tests/shared/testing/blockchain/initiator_contract.rs @@ -5,7 +5,7 @@ use thiserror::Error; use bridge_shared::types::{ Amount, BridgeAddressType, BridgeHashType, BridgeTransferDetails, BridgeTransferId, - GenUniqueHash, HashLock, HashLockPreImage, InitiatorAddress, RecipientAddress, TimeLock, + GenUniqueHash, HashLock, HashLockPreImage, InitiatorAddress, RecipientAddress, }; #[derive(Debug, Clone, PartialEq, Eq)] @@ -16,7 +16,7 @@ pub enum SmartContractInitiatorEvent { #[derive(Debug)] pub enum InitiatorCall { - InitiateBridgeTransfer(InitiatorAddress, RecipientAddress, Amount, TimeLock, HashLock), + InitiateBridgeTransfer(InitiatorAddress, RecipientAddress>, Amount, HashLock), CompleteBridgeTransfer(BridgeTransferId, HashLockPreImage), } @@ -53,9 +53,8 @@ where pub fn initiate_bridge_transfer( &mut self, initiator: InitiatorAddress, - recipient: RecipientAddress, + recipient: RecipientAddress>, amount: Amount, - time_lock: TimeLock, hash_lock: HashLock, ) -> SCIResult { let bridge_transfer_id = BridgeTransferId::::gen_unique_hash(&mut self.rng); @@ -77,8 +76,8 @@ where initiator_address: initiator.clone(), recipient_address: recipient.clone(), hash_lock: hash_lock.clone(), - time_lock: time_lock.clone(), amount, + state: 1, }, ); @@ -87,14 +86,14 @@ where initiator_address: initiator, recipient_address: recipient, hash_lock, - time_lock, amount, + state: 1, })) } pub fn complete_bridge_transfer( &mut self, - accounts: &mut HashMap, + _accounts: &mut HashMap, transfer_id: BridgeTransferId, pre_image: HashLockPreImage, ) -> SCIResult { diff --git a/protocol-units/bridge/shared/tests/shared/testing/mocks.rs b/protocol-units/bridge/shared/tests/shared/testing/mocks.rs index aefde1f82..525eede20 100644 --- a/protocol-units/bridge/shared/tests/shared/testing/mocks.rs +++ b/protocol-units/bridge/shared/tests/shared/testing/mocks.rs @@ -9,7 +9,7 @@ use std::{ use bridge_shared::{ blockchain_service::{BlockchainService, ContractEvent}, bridge_contracts::BridgeContractCounterpartyResult, - types::{HashLock, InitiatorAddress, RecipientAddress, TimeLock}, + types::{HashLock, InitiatorAddress, RecipientAddress}, }; use bridge_shared::{ bridge_contracts::BridgeContractInitiatorResult, @@ -137,7 +137,7 @@ where } pub struct MockCounterpartyMonitoring { - pub events: Vec>, + pub events: Vec>, pub _phantom: std::marker::PhantomData, } @@ -161,7 +161,7 @@ where A: std::fmt::Debug + Unpin, H: std::fmt::Debug + Unpin, { - type Item = BridgeContractCounterpartyEvent; + type Item = BridgeContractCounterpartyEvent; fn poll_next(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll> { let this = self.get_mut(); @@ -250,9 +250,8 @@ where async fn initiate_bridge_transfer( &mut self, initiator_address: InitiatorAddress, - recipient_address: RecipientAddress, + recipient_address: RecipientAddress>, hash_lock: HashLock, - time_lock: TimeLock, amount: Amount, ) -> BridgeContractInitiatorResult<()> { let mut state = self.state.lock().expect("lock poisoned"); @@ -265,8 +264,8 @@ where initiator_address, recipient_address, hash_lock, - time_lock, amount, + state: 1, })); Ok(()) } @@ -289,7 +288,7 @@ where async fn get_bridge_transfer_details( &mut self, _bridge_transfer_id: BridgeTransferId, - ) -> BridgeContractInitiatorResult>> { + ) -> BridgeContractInitiatorResult>> { Ok(None) } } @@ -314,12 +313,12 @@ where type Address = A; type Hash = H; - async fn lock_bridge_transfer_assets( + async fn lock_bridge_transfer( &mut self, _bridge_transfer_id: BridgeTransferId, _hash_lock: HashLock, - _time_lock: TimeLock, - _recipient: RecipientAddress, + _initiator: InitiatorAddress>, + _recipient: RecipientAddress, _amount: Amount, ) -> BridgeContractCounterpartyResult<()> { Ok(()) @@ -343,7 +342,7 @@ where async fn get_bridge_transfer_details( &mut self, _bridge_transfer_id: BridgeTransferId, - ) -> BridgeContractCounterpartyResult>> + ) -> BridgeContractCounterpartyResult>> { Ok(None) } diff --git a/protocol-units/da/m1/light-node-client/src/test/e2e/raw/sequencer.rs b/protocol-units/da/m1/light-node-client/src/test/e2e/raw/sequencer.rs index 021345d3e..00af5aeb4 100644 --- a/protocol-units/da/m1/light-node-client/src/test/e2e/raw/sequencer.rs +++ b/protocol-units/da/m1/light-node-client/src/test/e2e/raw/sequencer.rs @@ -1,5 +1,5 @@ use crate::*; -use movement_types::Block; +use movement_types::block::Block; use tokio_stream::StreamExt; #[tokio::test] @@ -28,7 +28,13 @@ async fn test_light_node_submits_blob_over_stream() -> Result<(), anyhow::Error> match blob.blob_type.ok_or(anyhow::anyhow!("No blob type in response"))? { blob_response::BlobType::SequencedBlobBlock(blob) => { let block = serde_json::from_slice::(&blob.data)?; - assert_eq!(block.transactions[0].data, data); + // get 0th transaction from BTreeSet + let transaction_0th = block + .transactions() + .into_iter() + .next() + .ok_or(anyhow::anyhow!("No transactions in block"))?; + assert_eq!(transaction_0th.data(), &data); return Ok(()); } _ => { diff --git a/protocol-units/da/m1/light-node/src/v1/sequencer.rs b/protocol-units/da/m1/light-node/src/v1/sequencer.rs index fcfd7b332..ac0e8b58c 100644 --- a/protocol-units/da/m1/light-node/src/v1/sequencer.rs +++ b/protocol-units/da/m1/light-node/src/v1/sequencer.rs @@ -16,7 +16,7 @@ use movement_algs::grouping_heuristic::{ apply::ToApply, binpacking::FirstFitBinpacking, drop_success::DropSuccess, skip::SkipFor, splitting::Splitting, GroupingHeuristicStack, GroupingOutcome, }; -use movement_types::Block; +use movement_types::block::Block; use std::boxed::Box; use tokio::{ sync::mpsc::{Receiver, Sender}, @@ -82,7 +82,7 @@ impl LightNodeV1 { let block = memseq.wait_for_next_block().await?; match block { Some(block) => { - info!(target: "movement_timing", block_id = %block.id(), uid = %uid, transaction_count = block.transactions.len(), "received_block"); + info!(target: "movement_timing", block_id = %block.id(), uid = %uid, transaction_count = block.transactions().len(), "received_block"); sender.send(block).await?; Ok(()) } @@ -420,7 +420,7 @@ mod block { use celestia_types::{nmt::Namespace, Blob}; use movement_algs::grouping_heuristic::{binpacking::BinpackingWeighted, splitting::Splitable}; - use movement_types::Block; + use movement_types::block::Block; #[derive(Debug)] pub struct WrappedBlock { diff --git a/protocol-units/da/m1/util/src/config/mod.rs b/protocol-units/da/m1/util/src/config/mod.rs index 5a72be098..753acd252 100644 --- a/protocol-units/da/m1/util/src/config/mod.rs +++ b/protocol-units/da/m1/util/src/config/mod.rs @@ -178,12 +178,17 @@ impl Config { pub fn try_block_building_parameters(&self) -> Result<(u32, u64), anyhow::Error> { match self { - Config::Local(local) => Ok((local.memseq.memseq_max_block_size, local.memseq.memseq_build_time)), - Config::Arabica(local) => Ok((local.memseq.memseq_max_block_size, local.memseq.memseq_build_time)), - Config::Mocha(local) => Ok((local.memseq.memseq_max_block_size, local.memseq.memseq_build_time)), + Config::Local(local) => { + Ok((local.memseq.memseq_max_block_size, local.memseq.memseq_build_time)) + } + Config::Arabica(local) => { + Ok((local.memseq.memseq_max_block_size, local.memseq.memseq_build_time)) + } + Config::Mocha(local) => { + Ok((local.memseq.memseq_max_block_size, local.memseq.memseq_build_time)) + } } } - } /// The M1 DA Light Node configuration as should be read from file. diff --git a/protocol-units/dispute/lib/forge-std b/protocol-units/dispute/lib/forge-std new file mode 160000 index 000000000..5a802d7c1 --- /dev/null +++ b/protocol-units/dispute/lib/forge-std @@ -0,0 +1 @@ +Subproject commit 5a802d7c10abb4bbfb3e7214c75052ef9e6a06f8 diff --git a/protocol-units/dispute/lib/murky b/protocol-units/dispute/lib/murky new file mode 160000 index 000000000..5feccd125 --- /dev/null +++ b/protocol-units/dispute/lib/murky @@ -0,0 +1 @@ +Subproject commit 5feccd1253d7da820f7cccccdedf64471025455d diff --git a/protocol-units/dispute/lib/openzeppelin-contracts b/protocol-units/dispute/lib/openzeppelin-contracts new file mode 160000 index 000000000..530179a71 --- /dev/null +++ b/protocol-units/dispute/lib/openzeppelin-contracts @@ -0,0 +1 @@ +Subproject commit 530179a71f435e85ae9df9b9f12b5637cf229e5c diff --git a/protocol-units/execution/dof/Cargo.toml b/protocol-units/execution/dof/Cargo.toml index 2e75b6815..4b23db289 100644 --- a/protocol-units/execution/dof/Cargo.toml +++ b/protocol-units/execution/dof/Cargo.toml @@ -14,9 +14,10 @@ path = "src/lib.rs" [dependencies] anyhow = { workspace = true } -async-channel = { workspace = true } async-trait = { workspace = true } +futures = { workspace = true } tokio = { workspace = true } +tokio-stream = { workspace = true } tracing = { workspace = true } aptos-crypto = { workspace = true } @@ -30,8 +31,6 @@ maptos-opt-executor = { workspace = true } maptos-fin-view = { workspace = true } maptos-execution-util = { workspace = true } movement-types = { workspace = true } -futures = { workspace = true } -tokio-stream = { workspace = true } [dev-dependencies] chrono = { workspace = true } diff --git a/protocol-units/execution/dof/src/lib.rs b/protocol-units/execution/dof/src/lib.rs index 7cc1e922f..a1beebf16 100644 --- a/protocol-units/execution/dof/src/lib.rs +++ b/protocol-units/execution/dof/src/lib.rs @@ -1,6 +1,8 @@ +mod services; pub mod v1; -use aptos_api::runtime::Apis; +use services::Services; + pub use aptos_crypto::hash::HashValue; pub use aptos_types::{ block_executor::partitioner::ExecutableBlock, @@ -9,19 +11,33 @@ pub use aptos_types::{ transaction::signature_verified_transaction::SignatureVerifiedTransaction, transaction::{SignedTransaction, Transaction}, }; +use maptos_execution_util::config::Config; +use movement_types::block::BlockCommitment; -use movement_types::BlockCommitment; - -use async_channel::Sender; use async_trait::async_trait; +use tokio::sync::mpsc::Sender; + +use std::future::Future; #[async_trait] pub trait DynOptFinExecutor { - /// Runs the service - async fn run_service(&self) -> Result<(), anyhow::Error>; + type Context: MakeOptFinServices; - /// Runs the necessary background tasks. - async fn run_background_tasks(&self) -> Result<(), anyhow::Error>; + /// Initialize the background task responsible for transaction processing. + fn background( + &self, + transaction_sender: Sender, + config: &Config, + ) -> Result< + (Self::Context, impl Future> + Send + 'static), + anyhow::Error, + >; + + /// Checks whether the transaction had already been executed by opt + fn has_executed_transaction_opt( + &self, + transaction_hash: HashValue, + ) -> Result; /// Executes a block optimistically async fn execute_block_opt( @@ -33,22 +49,13 @@ pub trait DynOptFinExecutor { fn set_finalized_block_height(&self, block_height: u64) -> Result<(), anyhow::Error>; /// Revert the chain to the specified height - fn revert_block_head_to(&self, block_height: u64) -> Result<(), anyhow::Error>; - - /// Sets the transaction channel. - fn set_tx_channel(&mut self, tx_channel: Sender); - - /// Gets the API for the opt (optimistic) state. - fn get_opt_apis(&self) -> Apis; - - /// Gets the API for the fin (finalized) state. - fn get_fin_apis(&self) -> Apis; + async fn revert_block_head_to(&self, block_height: u64) -> Result<(), anyhow::Error>; /// Get block head height. - async fn get_block_head_height(&self) -> Result; + fn get_block_head_height(&self) -> Result; /// Build block metadata for a timestamp - async fn build_block_metadata( + fn build_block_metadata( &self, block_id: HashValue, timestamp: u64, @@ -59,4 +66,11 @@ pub trait DynOptFinExecutor { /// Decrements transactions in flight on the transaction channel. fn decrement_transactions_in_flight(&self, count: u64); + + /// Gets the config + fn config(&self) -> &Config; +} + +pub trait MakeOptFinServices { + fn services(&self) -> Services; } diff --git a/protocol-units/execution/dof/src/services.rs b/protocol-units/execution/dof/src/services.rs new file mode 100644 index 000000000..babc8f48b --- /dev/null +++ b/protocol-units/execution/dof/src/services.rs @@ -0,0 +1,33 @@ +use aptos_api::{runtime::Apis, Context}; +use tokio::try_join; + +use std::sync::Arc; + +pub struct Services { + opt: maptos_opt_executor::Service, + fin: maptos_fin_view::Service, +} + +impl Services { + pub(crate) fn new(opt: maptos_opt_executor::Service, fin: maptos_fin_view::Service) -> Self { + Services { opt, fin } + } + + pub fn opt_api_context(&self) -> Arc { + self.opt.api_context() + } + + pub fn get_opt_apis(&self) -> Apis { + self.opt.get_apis() + } + + pub fn get_fin_apis(&self) -> Apis { + self.fin.get_apis() + } + + pub async fn run(self) -> anyhow::Result<()> { + let (opt_res, fin_res) = + try_join!(tokio::spawn(self.opt.run()), tokio::spawn(self.fin.run()))?; + opt_res.and(fin_res) + } +} diff --git a/protocol-units/execution/dof/src/v1.rs b/protocol-units/execution/dof/src/v1.rs index 034e11d9f..affb78b78 100644 --- a/protocol-units/execution/dof/src/v1.rs +++ b/protocol-units/execution/dof/src/v1.rs @@ -1,89 +1,85 @@ -use crate::{BlockMetadata, DynOptFinExecutor, ExecutableBlock, HashValue, SignedTransaction}; -use aptos_api::runtime::Apis; -use aptos_mempool::core_mempool::CoreMempool; +use crate::{ + BlockMetadata, DynOptFinExecutor, ExecutableBlock, HashValue, MakeOptFinServices, Services, + SignedTransaction, +}; +use maptos_execution_util::config::Config; use maptos_fin_view::FinalityView; -use maptos_opt_executor::transaction_pipe::TransactionPipeError; -use maptos_opt_executor::Executor as OptExecutor; -use movement_types::BlockCommitment; +use maptos_opt_executor::{Context as OptContext, Executor as OptExecutor}; +use movement_types::block::BlockCommitment; use anyhow::format_err; -use async_channel::Sender; use async_trait::async_trait; -use tokio::time::interval; -use tokio::time::Duration; -use tokio_stream::wrappers::IntervalStream; -use tokio_stream::StreamExt; -use tracing::{debug, info}; +use tokio::sync::mpsc::Sender; +use tracing::debug; -use std::sync::atomic::Ordering; +use std::future::Future; -#[derive(Clone)] pub struct Executor { - pub executor: OptExecutor, + executor: OptExecutor, finality_view: FinalityView, - pub transaction_channel: Sender, +} + +pub struct Context { + opt_context: OptContext, + fin_service: maptos_fin_view::Service, } impl Executor { - pub fn new( - executor: OptExecutor, - finality_view: FinalityView, - transaction_channel: Sender, - ) -> Self { - Self { executor, finality_view, transaction_channel } + /// Creates the execution state with the optimistic executor + /// and the finality view, joined at the hip by shared storage. + pub fn new(executor: OptExecutor) -> Self { + let finality_view = FinalityView::new(executor.db_reader()); + Self { executor, finality_view } } - pub fn try_from_config( - transaction_channel: Sender, - config: maptos_execution_util::config::Config, - ) -> Result { - let executor = OptExecutor::try_from_config(&config.clone())?; - let finality_view = FinalityView::try_from_config( - executor.db.reader.clone(), - executor.mempool_client_sender.clone(), - config, - )?; - Ok(Self::new(executor, finality_view, transaction_channel)) + pub fn try_from_config(config: &Config) -> Result { + let executor = OptExecutor::try_from_config(config)?; + Ok(Self::new(executor)) + } +} + +impl MakeOptFinServices for Context { + fn services(&self) -> Services { + let opt = maptos_opt_executor::Service::new(&self.opt_context); + let fin = self.fin_service.clone(); + Services::new(opt, fin) } } #[async_trait] impl DynOptFinExecutor for Executor { - /// Runs the service. - async fn run_service(&self) -> Result<(), anyhow::Error> { - tokio::try_join!( - self.executor.run_service(), - self.executor.run_indexer_grpc_service(), - self.finality_view.run_service(), - )?; - Ok(()) + type Context = Context; + + fn background( + &self, + transaction_sender: Sender, + config: &Config, + ) -> Result< + (Context, impl Future> + Send + 'static), + anyhow::Error, + > { + let (opt_context, transaction_pipe) = + self.executor.background(transaction_sender, config)?; + let fin_service = self.finality_view.service( + opt_context.mempool_client_sender(), + config, + opt_context.node_config().clone(), + ); + let indexer_runtime = opt_context.run_indexer_grpc_service()?; + let background = async move { + // The indexer runtime should live as long as the Tx pipe. + let _indexer_runtime = indexer_runtime; + transaction_pipe.run().await?; + Ok(()) + }; + Ok((Context { opt_context, fin_service }, background)) } - async fn run_background_tasks(&self) -> Result<(), anyhow::Error> { - let mut core_mempool = CoreMempool::new(&self.executor.node_config); - let mut last_gc = std::time::Instant::now(); - loop { - // readers should be able to run concurrently - match self - .executor - .tick_transaction_pipe( - &mut core_mempool, - self.transaction_channel.clone(), - &mut last_gc, - ) - .await - { - Ok(_) => {} - Err(e) => match e { - TransactionPipeError::TransactionNotAccepted(e) => { - // allow the transaction not to be accepted by the mempool - // because the client may have sent a bad sequence number - tracing::warn!("Transaction not accepted: {:?}", e); - } - _ => anyhow::bail!("Server error: {:?}", e), - }, - } - } + fn has_executed_transaction_opt( + &self, + transaction_hash: HashValue, + ) -> Result { + self.executor.has_executed_transaction(transaction_hash) } async fn execute_block_opt( @@ -98,7 +94,7 @@ impl DynOptFinExecutor for Executor { self.finality_view.set_finalized_block_height(height) } - fn revert_block_head_to(&self, block_height: u64) -> Result<(), anyhow::Error> { + async fn revert_block_head_to(&self, block_height: u64) -> Result<(), anyhow::Error> { if let Some(final_height) = self.finality_view.finalized_block_height() { if block_height < final_height { return Err(format_err!( @@ -106,34 +102,21 @@ impl DynOptFinExecutor for Executor { )); } } - self.executor.revert_block_head_to(block_height) - } - - /// Sets the transaction channel. - fn set_tx_channel(&mut self, tx_channel: Sender) { - self.transaction_channel = tx_channel; - } - - fn get_opt_apis(&self) -> Apis { - self.executor.get_apis() - } - - fn get_fin_apis(&self) -> Apis { - self.finality_view.get_apis() + self.executor.revert_block_head_to(block_height).await } /// Get block head height. - async fn get_block_head_height(&self) -> Result { + fn get_block_head_height(&self) -> Result { self.executor.get_block_head_height() } /// Build block metadata for a timestamp - async fn build_block_metadata( + fn build_block_metadata( &self, block_id: HashValue, timestamp: u64, ) -> Result { - let (epoch, round) = self.executor.get_next_epoch_and_round().await?; + let (epoch, round) = self.executor.get_next_epoch_and_round()?; let signer = &self.executor.signer; // Create a block metadata transaction. @@ -146,20 +129,11 @@ impl DynOptFinExecutor for Executor { } fn decrement_transactions_in_flight(&self, count: u64) { - // fetch sub mind the underflow - // a semaphore might be better here as this will rerun until the value does not change during the operation - self.executor - .transactions_in_flight - .fetch_update(Ordering::SeqCst, Ordering::SeqCst, |current| { - info!( - target: "movement_timing", - count, - current, - "decrementing_transactions_in_flight", - ); - Some(current.saturating_sub(count)) - }) - .unwrap_or_else(|_| 0); + self.executor.decrement_transactions_in_flight(count) + } + + fn config(&self) -> &Config { + self.executor.config() } } @@ -181,7 +155,6 @@ mod tests { account_config::aptos_test_root_address, block_executor::partitioner::ExecutableTransactions, chain_id::ChainId, - ledger_info::LedgerInfoWithSignatures, transaction::{ signature_verified_transaction::SignatureVerifiedTransaction, RawTransaction, Script, SignedTransaction, Transaction, TransactionPayload, Version, @@ -190,6 +163,7 @@ mod tests { use maptos_execution_util::config::Config; use rand::SeedableRng; + use tokio::sync::mpsc; use std::collections::HashMap; @@ -212,12 +186,10 @@ mod tests { #[tokio::test] async fn test_execute_opt_block() -> Result<(), anyhow::Error> { let config = Config::default(); - let (tx, _rx) = async_channel::unbounded(); - let executor = Executor::try_from_config(tx, config)?; + let executor = Executor::try_from_config(&config)?; let block_id = HashValue::random(); let block_metadata = executor .build_block_metadata(block_id.clone(), chrono::Utc::now().timestamp_micros() as u64) - .await .unwrap(); let txs = ExecutableTransactions::Unsharded( [ @@ -236,20 +208,14 @@ mod tests { #[tokio::test] async fn test_pipe_transactions_from_api() -> Result<(), anyhow::Error> { let config = Config::default(); - let (tx, rx) = async_channel::unbounded(); - let executor = Executor::try_from_config(tx, config)?; - let services_executor = executor.clone(); - let background_executor = executor.clone(); + let (tx_sender, mut tx_receiver) = mpsc::channel(16); + let executor = Executor::try_from_config(&config)?; + let (context, background) = executor.background(tx_sender, &config)?; + let services = context.services(); + let api = services.get_opt_apis(); - let services_handle = tokio::spawn(async move { - services_executor.run_service().await?; - Ok(()) as Result<(), anyhow::Error> - }); - - let background_handle = tokio::spawn(async move { - background_executor.run_background_tasks().await?; - Ok(()) as Result<(), anyhow::Error> - }); + let services_handle = tokio::spawn(services.run()); + let background_handle = tokio::spawn(background); // Start the background tasks let user_transaction = create_signed_transaction(0); @@ -257,12 +223,11 @@ mod tests { let bcs_user_transaction = bcs::to_bytes(&user_transaction)?; let request = SubmitTransactionPost::Bcs(aptos_api::bcs_payload::Bcs(bcs_user_transaction)); - let api = executor.get_opt_apis(); api.transactions.submit_transaction(AcceptType::Bcs, request).await?; services_handle.abort(); background_handle.abort(); - let received_transaction = rx.recv().await?; + let received_transaction = tx_receiver.recv().await.unwrap(); assert_eq!(received_transaction, comparison_user_transaction); Ok(()) @@ -271,20 +236,14 @@ mod tests { #[tokio::test] async fn test_pipe_transactions_from_api_and_execute() -> Result<(), anyhow::Error> { let config = Config::default(); - let (tx, rx) = async_channel::unbounded(); - let executor = Executor::try_from_config(tx, config)?; - let services_executor = executor.clone(); - let background_executor = executor.clone(); - - let services_handle = tokio::spawn(async move { - services_executor.run_service().await?; - Ok(()) as Result<(), anyhow::Error> - }); + let (tx_sender, mut tx_receiver) = mpsc::channel(16); + let executor = Executor::try_from_config(&config)?; + let (context, background) = executor.background(tx_sender, &config)?; + let services = context.services(); + let api = services.get_opt_apis(); - let background_handle = tokio::spawn(async move { - background_executor.run_background_tasks().await?; - Ok(()) as Result<(), anyhow::Error> - }); + let services_handle = tokio::spawn(services.run()); + let background_handle = tokio::spawn(background); // Start the background tasks let user_transaction = create_signed_transaction(0); @@ -292,17 +251,15 @@ mod tests { let bcs_user_transaction = bcs::to_bytes(&user_transaction)?; let request = SubmitTransactionPost::Bcs(aptos_api::bcs_payload::Bcs(bcs_user_transaction)); - let api = executor.get_opt_apis(); api.transactions.submit_transaction(AcceptType::Bcs, request).await?; - let received_transaction = rx.recv().await?; + let received_transaction = tx_receiver.recv().await.unwrap(); assert_eq!(received_transaction, comparison_user_transaction); // Now execute the block let block_id = HashValue::random(); let block_metadata = executor .build_block_metadata(block_id.clone(), chrono::Utc::now().timestamp_micros() as u64) - .await .unwrap(); let txs = ExecutableTransactions::Unsharded( [ @@ -316,8 +273,8 @@ mod tests { let block = ExecutableBlock::new(block_id.clone(), txs); let commitment = executor.execute_block_opt(block).await?; - assert_eq!(commitment.block_id.to_vec(), block_id.to_vec()); - assert_eq!(commitment.height, 1); + assert_eq!(commitment.block_id().to_vec(), block_id.to_vec()); + assert_eq!(commitment.height(), 1); services_handle.abort(); background_handle.abort(); @@ -332,24 +289,19 @@ mod tests { #[derive(Debug)] struct Commit { - info: LedgerInfoWithSignatures, current_version: Version, } let config = Config::default(); - let (tx, rx) = async_channel::unbounded::(); - let executor = Executor::try_from_config(tx, config)?; - let services_executor = executor.clone(); - let background_executor = executor.clone(); - let services_handle = tokio::spawn(async move { - services_executor.run_service().await?; - Ok(()) as Result<(), anyhow::Error> - }); - - let background_handle = tokio::spawn(async move { - background_executor.run_background_tasks().await?; - Ok(()) as Result<(), anyhow::Error> - }); + let (tx_sender, mut tx_receiver) = mpsc::channel(16); + let executor = Executor::try_from_config(&config)?; + let (context, background) = executor.background(tx_sender, &config)?; + let services = context.services(); + let api = services.get_opt_apis(); + + let services_handle = tokio::spawn(services.run()); + let background_handle = tokio::spawn(background); + let mut committed_blocks = HashMap::new(); let mut val_generator = ValueGenerator::new(); @@ -359,17 +311,16 @@ mod tests { let mut current_version: Version = 0; let mut commit_versions = vec![]; - for (txns_to_commit, ledger_info_with_sigs) in &blocks { + for (txns_to_commit, _ledger_info_with_sigs) in &blocks { let user_transaction = create_signed_transaction(0); let comparison_user_transaction = user_transaction.clone(); let bcs_user_transaction = bcs::to_bytes(&user_transaction)?; let request = SubmitTransactionPost::Bcs(aptos_api::bcs_payload::Bcs(bcs_user_transaction)); - let api = executor.get_opt_apis(); api.transactions.submit_transaction(AcceptType::Bcs, request).await?; - let received_transaction = rx.recv().await?; + let received_transaction = tx_receiver.recv().await.unwrap(); assert_eq!(received_transaction, comparison_user_transaction); // Now execute the block @@ -379,7 +330,6 @@ mod tests { block_id.clone(), chrono::Utc::now().timestamp_micros() as u64, ) - .await .unwrap(); let txs = ExecutableTransactions::Unsharded( [ @@ -395,10 +345,7 @@ mod tests { blockheight += 1; current_version += txns_to_commit.len() as u64; - committed_blocks.insert( - blockheight, - Commit { info: ledger_info_with_sigs.clone(), current_version }, - ); + committed_blocks.insert(blockheight, Commit { current_version }); commit_versions.push(current_version); //blockheight += 1; } @@ -410,13 +357,10 @@ mod tests { // Get the version to revert to let version_to_revert_to = revert.current_version; - { - let db_writer = executor.executor.db.writer.clone(); - db_writer.revert_commit(&revert.info)?; - } + executor.revert_block_head_to(version_to_revert_to).await?; let latest_version = { - let db_reader = executor.executor.db.reader.clone(); + let db_reader = executor.executor.db_reader().clone(); db_reader.get_synced_version()? }; assert_eq!(latest_version, version_to_revert_to); @@ -429,15 +373,19 @@ mod tests { #[tokio::test] async fn test_execute_block_state_get_api() -> Result<(), anyhow::Error> { // Create an executor instance from the environment configuration. - let (tx, _rx) = async_channel::unbounded::(); let config = Config::default(); - let chain_config = config.chain.clone(); - let executor = Executor::try_from_config(tx, config)?; + let (tx_sender, _tx_receiver) = mpsc::channel(16); + let executor = Executor::try_from_config(&config)?; + let (context, background) = executor.background(tx_sender, &config)?; + let services = context.services(); + let apis = services.get_opt_apis(); + + let background_handle = tokio::spawn(background); // Initialize a root account using a predefined keypair and the test root address. let root_account = LocalAccount::new( aptos_test_root_address(), - AccountKey::from_private_key(chain_config.maptos_private_key), + AccountKey::from_private_key(config.chain.maptos_private_key), 0, ); @@ -446,7 +394,7 @@ mod tests { let mut rng = ::rand::rngs::StdRng::from_seed(seed); // Create a transaction factory with the chain ID of the executor. - let tx_factory = TransactionFactory::new(chain_config.maptos_chain_id); + let tx_factory = TransactionFactory::new(config.chain.maptos_chain_id); // Simulate the execution of multiple blocks. for _ in 0..10 { @@ -457,7 +405,6 @@ mod tests { block_id.clone(), chrono::Utc::now().timestamp_micros() as u64, ) - .await .unwrap(); // Generate new accounts and create transactions for each block. @@ -483,7 +430,6 @@ mod tests { executor.execute_block_opt(block).await?; // Retrieve the executor's API interface and fetch the transaction by each hash. - let apis = executor.get_opt_apis(); for hash in transaction_hashes { let _ = apis .transactions @@ -492,21 +438,28 @@ mod tests { } } + background_handle.abort(); Ok(()) } #[tokio::test] async fn test_set_finalized_block_height_get_fin_api() -> Result<(), anyhow::Error> { // Create an executor instance from the environment configuration. - let (tx, _rx) = async_channel::unbounded::(); let config = Config::default(); - let chain_config = config.chain.clone(); - let executor = Executor::try_from_config(tx, config)?; + let (tx_sender, _tx_receiver) = mpsc::channel(16); + let executor = Executor::try_from_config(&config)?; + let (context, background) = executor.background(tx_sender, &config)?; + let services = context.services(); + + // Retrieve the executor's fin API instance + let apis = services.get_fin_apis(); + + let background_handle = tokio::spawn(background); // Initialize a root account using a predefined keypair and the test root address. let root_account = LocalAccount::new( aptos_test_root_address(), - AccountKey::from_private_key(chain_config.maptos_private_key), + AccountKey::from_private_key(config.chain.maptos_private_key), 0, ); @@ -515,7 +468,7 @@ mod tests { let mut rng = ::rand::rngs::StdRng::from_seed(seed); // Create a transaction factory with the chain ID of the executor. - let tx_factory = TransactionFactory::new(chain_config.maptos_chain_id); + let tx_factory = TransactionFactory::new(config.chain.maptos_chain_id); let mut transaction_hashes = Vec::new(); // Simulate the execution of multiple blocks. @@ -526,7 +479,6 @@ mod tests { block_id.clone(), chrono::Utc::now().timestamp_micros() as u64, ) - .await .unwrap(); // Generate new accounts and create a transaction for each block. @@ -551,9 +503,6 @@ mod tests { // Set the fin height executor.set_finalized_block_height(2)?; - // Retrieve the executor's fin API instance - let apis = executor.get_fin_apis(); - // Fetch the transaction in block 2 let _ = apis .transactions @@ -569,6 +518,8 @@ mod tests { context.get_transaction_by_hash(transaction_hashes[2].into(), ledger_info.version())?; assert!(opt.is_none(), "transaction from opt block is found in the fin view"); + background_handle.abort(); + Ok(()) } } diff --git a/protocol-units/execution/fin-view/Cargo.toml b/protocol-units/execution/fin-view/Cargo.toml index 0597365aa..6ece27c3b 100644 --- a/protocol-units/execution/fin-view/Cargo.toml +++ b/protocol-units/execution/fin-view/Cargo.toml @@ -21,6 +21,7 @@ aptos-types = { workspace = true } maptos-execution-util = { workspace = true } anyhow = { workspace = true } +futures = { workspace = true } poem = { workspace = true } tracing = { workspace = true } diff --git a/protocol-units/execution/fin-view/src/fin_view.rs b/protocol-units/execution/fin-view/src/fin_view.rs index eb139e7ab..f44a4b2ec 100644 --- a/protocol-units/execution/fin-view/src/fin_view.rs +++ b/protocol-units/execution/fin-view/src/fin_view.rs @@ -1,48 +1,43 @@ -use aptos_api::{ - runtime::{get_api_service, get_apis, Apis}, - Context, -}; +use crate::Service; +use aptos_api::Context; use aptos_config::config::NodeConfig; use aptos_mempool::MempoolClientSender; use aptos_storage_interface::{finality_view::FinalityView as AptosFinalityView, DbReader}; use maptos_execution_util::config::Config; -use poem::{http::Method, listener::TcpListener, middleware::Cors, EndpointExt, Route, Server}; -use tracing::info; - use std::sync::Arc; -#[derive(Clone)] /// The API view into the finalized state of the chain. pub struct FinalityView { inner: Arc, - context: Arc, - listen_url: String, } impl FinalityView { /// Create a new `FinalityView` instance. - pub fn new(inner: Arc, context: Arc, listen_url: String) -> Self { - Self { inner, context, listen_url } + pub fn new(db_reader: Arc) -> Self { + let inner = Arc::new(AptosFinalityView::new(db_reader)); + Self { inner } } - pub fn try_from_config( - db_reader: Arc, + /// Instantiate the API service for this finality view. + pub fn service( + &self, mempool_client_sender: MempoolClientSender, - config: Config, - ) -> Result { - let node_config = NodeConfig::default(); - let inner = Arc::new(AptosFinalityView::new(db_reader)); + maptos_config: &Config, + node_config: NodeConfig, + ) -> Service { let context = Arc::new(Context::new( - config.chain.maptos_chain_id, - inner.clone(), + maptos_config.chain.maptos_chain_id, + self.inner.clone(), mempool_client_sender, node_config, None, )); - let listen_url = - format!("{}:{}", config.fin.fin_rest_listen_hostname, config.fin.fin_rest_listen_port,); - Ok(Self::new(inner, context, listen_url)) + let listen_url = format!( + "{}:{}", + maptos_config.fin.fin_rest_listen_hostname, maptos_config.fin.fin_rest_listen_port, + ); + Service::new(context, listen_url) } /// Retrieve the finalized block height. @@ -60,31 +55,6 @@ impl FinalityView { self.inner.set_finalized_block_height(height)?; Ok(()) } - - pub fn get_apis(&self) -> Apis { - get_apis(self.context.clone()) - } - - pub async fn run_service(&self) -> Result<(), anyhow::Error> { - info!("Starting maptos-fin-view services at: {:?}", self.listen_url); - - let api_service = - get_api_service(self.context.clone()).server(format!("http://{:?}", self.listen_url)); - - let ui = api_service.swagger_ui(); - - let cors = Cors::new() - .allow_methods(vec![Method::GET, Method::POST]) - .allow_credentials(true); - let app = Route::new().nest("/v1", api_service).nest("/spec", ui).with(cors); - - Server::new(TcpListener::bind(self.listen_url.clone())) - .run(app) - .await - .map_err(|e| anyhow::anyhow!("Server error: {:?}", e))?; - - Ok(()) - } } #[cfg(test)] @@ -100,22 +70,26 @@ mod tests { use aptos_types::transaction::Transaction; use maptos_opt_executor::Executor; use rand::prelude::*; + use tokio::sync::mpsc; #[tokio::test] async fn test_set_finalized_block_height_get_api() -> Result<(), anyhow::Error> { // Create an Executor and a FinalityView instance from the environment configuration. let config = Config::default(); + let (tx_sender, _tx_receiver) = mpsc::channel(16); let executor = Executor::try_from_config(&config)?; - let finality_view = FinalityView::try_from_config( - executor.db.reader.clone(), - executor.mempool_client_sender.clone(), - config.clone(), - )?; + let (context, _transaction_pipe) = executor.background(tx_sender, &config)?; + let finality_view = FinalityView::new(context.db_reader()); + let service = finality_view.service( + context.mempool_client_sender(), + &context.config(), + context.node_config().clone(), + ); // Initialize a root account using a predefined keypair and the test root address. let root_account = LocalAccount::new( aptos_test_root_address(), - AccountKey::from_private_key(config.chain.maptos_private_key.clone()), + AccountKey::from_private_key(context.config().chain.maptos_private_key.clone()), 0, ); @@ -124,13 +98,13 @@ mod tests { let mut rng = ::rand::rngs::StdRng::from_seed(seed); // Create a transaction factory with the chain ID of the executor. - let tx_factory = TransactionFactory::new(config.chain.maptos_chain_id.clone()); + let tx_factory = TransactionFactory::new(context.config().chain.maptos_chain_id.clone()); let mut account_addrs = Vec::new(); // Simulate the execution of multiple blocks. for _ in 0..3 { - let (epoch, round) = executor.get_next_epoch_and_round().await?; + let (epoch, round) = executor.get_next_epoch_and_round()?; let block_id = HashValue::random(); // Generate a random block ID for each block. @@ -174,7 +148,7 @@ mod tests { finality_view.set_finalized_block_height(2)?; // Retrieve the executor's API interface and fetch the accounts - let apis = finality_view.get_apis(); + let apis = service.get_apis(); apis.accounts .get_account_inner(AcceptType::Bcs, account_addrs[1].into(), None) diff --git a/protocol-units/execution/fin-view/src/lib.rs b/protocol-units/execution/fin-view/src/lib.rs index b9ead93e0..8abc011c8 100644 --- a/protocol-units/execution/fin-view/src/lib.rs +++ b/protocol-units/execution/fin-view/src/lib.rs @@ -1,3 +1,5 @@ mod fin_view; +mod service; pub use fin_view::FinalityView; +pub use service::Service; diff --git a/protocol-units/execution/fin-view/src/service.rs b/protocol-units/execution/fin-view/src/service.rs new file mode 100644 index 000000000..4c22f2b67 --- /dev/null +++ b/protocol-units/execution/fin-view/src/service.rs @@ -0,0 +1,46 @@ +use aptos_api::{ + runtime::{get_api_service, get_apis, Apis}, + Context, +}; + +use futures::prelude::*; +use poem::{http::Method, listener::TcpListener, middleware::Cors, EndpointExt, Route, Server}; +use tracing::info; + +use std::future::Future; +use std::sync::Arc; + +#[derive(Clone)] +/// The API service for the finality view. +pub struct Service { + context: Arc, + listen_url: String, +} + +impl Service { + pub(crate) fn new(context: Arc, listen_url: String) -> Self { + Service { context, listen_url } + } + + pub fn get_apis(&self) -> Apis { + get_apis(self.context.clone()) + } + + pub fn run(&self) -> impl Future> + Send { + info!("Starting maptos-fin-view services at: {:?}", self.listen_url); + + let api_service = + get_api_service(self.context.clone()).server(format!("http://{:?}", self.listen_url)); + + let ui = api_service.swagger_ui(); + + let cors = Cors::new() + .allow_methods(vec![Method::GET, Method::POST]) + .allow_credentials(true); + let app = Route::new().nest("/v1", api_service).nest("/spec", ui).with(cors); + + Server::new(TcpListener::bind(self.listen_url.clone())) + .run(app) + .map_err(|e| anyhow::anyhow!("Server error: {:?}", e)) + } +} diff --git a/protocol-units/execution/opt-executor/Cargo.toml b/protocol-units/execution/opt-executor/Cargo.toml index 9313a183d..e084c33a2 100644 --- a/protocol-units/execution/opt-executor/Cargo.toml +++ b/protocol-units/execution/opt-executor/Cargo.toml @@ -34,12 +34,10 @@ rand = { workspace = true } rand_core = { workspace = true } bcs = { workspace = true } futures = { workspace = true } -async-channel = { workspace = true } aptos-vm = { workspace = true } aptos-vm-validator = { workspace = true } aptos-config = { workspace = true } -aptos-sdk = { workspace = true } aptos-crypto = { workspace = true } aptos-consensus-types = { workspace = true } aptos-db = { workspace = true } @@ -76,3 +74,4 @@ dirs = { workspace = true } tempfile = { workspace = true } tracing-test = { workspace = true } async-trait = { workspace = true } +aptos-sdk = { workspace = true } diff --git a/protocol-units/execution/opt-executor/src/bootstrap.rs b/protocol-units/execution/opt-executor/src/bootstrap.rs new file mode 100644 index 000000000..ee8958668 --- /dev/null +++ b/protocol-units/execution/opt-executor/src/bootstrap.rs @@ -0,0 +1,96 @@ +use aptos_crypto::ed25519::Ed25519PublicKey; +use aptos_db::AptosDB; +use aptos_executor::db_bootstrapper; +use aptos_storage_interface::DbReaderWriter; +use aptos_types::{ + chain_id::ChainId, + on_chain_config::{OnChainConsensusConfig, OnChainExecutionConfig}, + transaction::{ChangeSet, Transaction, WriteSetPayload}, + validator_signer::ValidatorSigner, +}; +use aptos_vm::AptosVM; +use aptos_vm_genesis::{ + default_gas_schedule, encode_genesis_change_set, GenesisConfiguration, TestValidator, Validator, +}; + +use std::path::Path; + +fn genesis_change_set_and_validators( + chain_id: ChainId, + count: Option, + public_key: &Ed25519PublicKey, +) -> (ChangeSet, Vec) { + let framework = aptos_cached_packages::head_release_bundle(); + let test_validators = TestValidator::new_test_set(count, Some(100_000_000)); + let validators_: Vec = test_validators.iter().map(|t| t.data.clone()).collect(); + let validators = &validators_; + + // This number should not exceed u64::MAX / 1_000_000_000 + // to avoid overflowing calculations in aptos-vm-genesis. + // This will last several centuries. + const EPOCH_DURATION_SECS: u64 = 60 * 60 * 24 * 1024 * 128; + + let genesis = encode_genesis_change_set( + &public_key, + validators, + framework, + chain_id, + // todo: get this config from somewhere + &GenesisConfiguration { + allow_new_validators: true, + epoch_duration_secs: EPOCH_DURATION_SECS, + is_test: true, + min_stake: 0, + min_voting_threshold: 0, + // 1M APTOS coins (with 8 decimals). + max_stake: 100_000_000_000_000, + recurring_lockup_duration_secs: EPOCH_DURATION_SECS * 2, + required_proposer_stake: 0, + rewards_apy_percentage: 0, + voting_duration_secs: EPOCH_DURATION_SECS, + voting_power_increase_limit: 50, + employee_vesting_start: 1663456089, + employee_vesting_period_duration: 5 * 60, // 5 minutes + initial_features_override: None, + randomness_config_override: None, + jwk_consensus_config_override: None, + }, + &OnChainConsensusConfig::default_for_genesis(), + &OnChainExecutionConfig::default_for_genesis(), + &default_gas_schedule(), + ); + (genesis, test_validators) +} + +/// Bootstrap a database with a genesis transaction if it is empty. +pub fn maybe_bootstrap_empty_db( + db_dir: impl AsRef + Clone, + chain_id: ChainId, + public_key: &Ed25519PublicKey, +) -> Result<(DbReaderWriter, ValidatorSigner), anyhow::Error> { + let db_rw = DbReaderWriter::new(AptosDB::new_for_test(db_dir)); + let (genesis, validators) = genesis_change_set_and_validators(chain_id, Some(1), public_key); + let genesis_txn = Transaction::GenesisTransaction(WriteSetPayload::Direct(genesis)); + let validator_signer = + ValidatorSigner::new(validators[0].data.owner_address, validators[0].consensus_key.clone()); + + // check for context + + match db_rw.reader.get_latest_ledger_info_option()? { + Some(ledger_info) => { + // context exists + tracing::info!("Ledger info found, not bootstrapping DB: {:?}", ledger_info); + } + None => { + // context does not exist + // simply continue + tracing::info!("No ledger info found, bootstrapping DB."); + let waypoint = db_bootstrapper::generate_waypoint::(&db_rw, &genesis_txn)?; + db_bootstrapper::maybe_bootstrap::(&db_rw, &genesis_txn, waypoint)? + .ok_or(anyhow::anyhow!("Failed to bootstrap DB"))?; + assert!(db_rw.reader.get_latest_ledger_info_option()?.is_some()); + } + } + + Ok((db_rw, validator_signer)) +} diff --git a/protocol-units/execution/opt-executor/src/context.rs b/protocol-units/execution/opt-executor/src/context.rs new file mode 100644 index 000000000..d78faf1d7 --- /dev/null +++ b/protocol-units/execution/opt-executor/src/context.rs @@ -0,0 +1,43 @@ +use aptos_config::config::NodeConfig; +use aptos_mempool::MempoolClientSender; +use aptos_storage_interface::{DbReader, DbReaderWriter}; +use maptos_execution_util::config::Config; + +use std::sync::Arc; + +/// Infrastructure shared by services using the storage and the mempool. +pub struct Context { + pub(crate) db: DbReaderWriter, + pub(crate) mempool_client_sender: MempoolClientSender, + pub(crate) maptos_config: Config, + pub(crate) node_config: NodeConfig, +} + +impl Context { + pub(crate) fn new( + db: DbReaderWriter, + mempool_client_sender: MempoolClientSender, + maptos_config: Config, + node_config: NodeConfig, + ) -> Self { + Context { db, mempool_client_sender, maptos_config, node_config } + } + + /// Returns a reference on the data store reader. + pub fn db_reader(&self) -> Arc { + Arc::clone(&self.db.reader) + } + + /// Returns a clone of the mempool client channel's sender. + pub fn mempool_client_sender(&self) -> MempoolClientSender { + self.mempool_client_sender.clone() + } + + pub fn config(&self) -> &Config { + &self.maptos_config + } + + pub fn node_config(&self) -> &NodeConfig { + &self.node_config + } +} diff --git a/protocol-units/execution/opt-executor/src/executor/execution.rs b/protocol-units/execution/opt-executor/src/executor/execution.rs index 58b3c09b0..974b36ac2 100644 --- a/protocol-units/execution/opt-executor/src/executor/execution.rs +++ b/protocol-units/execution/opt-executor/src/executor/execution.rs @@ -1,10 +1,8 @@ use super::Executor; -use aptos_api::Context; use aptos_crypto::HashValue; use aptos_executor_types::BlockExecutorTrait; use aptos_types::transaction::signature_verified_transaction::into_signature_verified_block; use aptos_types::{ - account_address::AccountAddress, aggregate_signature::AggregateSignature, block_executor::{ config::BlockExecutorConfigFromOnchain, @@ -17,16 +15,15 @@ use aptos_types::{ transaction::{Transaction, Version}, validator_verifier::{ValidatorConsensusInfo, ValidatorVerifier}, }; -use movement_types::{BlockCommitment, Commitment, Id}; -use std::sync::Arc; -use tracing::{debug, debug_span, info}; +use movement_types::block::{BlockCommitment, Commitment, Id}; +use tracing::{debug, info}; impl Executor { pub async fn execute_block( &self, block: ExecutableBlock, ) -> Result { - let (block_metadata, block, senders_and_sequence_numbers) = { + let (block_metadata, block) = { // get the block metadata transaction let metadata_access_block = block.transactions.clone(); let metadata_access_transactions = metadata_access_block.into_txns(); @@ -41,24 +38,13 @@ impl Executor { } }; - // senders and sequence numbers - let senders_and_sequence_numbers = metadata_access_transactions - .iter() - .map(|transaction| match transaction.clone().into_inner() { - Transaction::UserTransaction(transaction) => { - (transaction.sender(), transaction.sequence_number()) - } - _ => (AccountAddress::ZERO, 0), - }) - .collect::>(); - // reconstruct the block let block = ExecutableBlock::new( block.block_id.clone(), ExecutableTransactions::Unsharded(metadata_access_transactions), ); - (block_metadata, block, senders_and_sequence_numbers) + (block_metadata, block) }; let block_id = block.block_id.clone(); @@ -94,10 +80,7 @@ impl Executor { }) .await??; - let proof = { - let reader = self.db.reader.clone(); - reader.get_state_proof(version)? - }; + let proof = self.db().reader.get_state_proof(version)?; // Context has a reach-around to the db so the block height should // have been updated to the most recently committed block. @@ -105,26 +88,26 @@ impl Executor { let block_height = self.get_block_head_height()?; let commitment = Commitment::digest_state_proof(&proof); - Ok(BlockCommitment { - block_id: Id(*block_id.clone()), - commitment, - height: block_height.into(), - }) + Ok(BlockCommitment::new(block_height.into(), Id::new(*block_id.clone()), commitment)) } pub fn get_block_head_height(&self) -> Result { - let ledger_info = self.context.get_latest_ledger_info_wrapped()?; - Ok(ledger_info.block_height.into()) + let ledger_info = self.db().reader.get_latest_ledger_info()?; + let (_, _, new_block_event) = self + .db() + .reader + .get_block_info_by_version(ledger_info.ledger_info().version())?; + Ok(new_block_event.height) } - pub fn revert_block_head_to(&self, block_height: u64) -> Result<(), anyhow::Error> { + pub async fn revert_block_head_to(&self, block_height: u64) -> Result<(), anyhow::Error> { let (_start_ver, end_ver, block_event) = - self.db.reader.get_block_info_by_height(block_height)?; + self.db().reader.get_block_info_by_height(block_height)?; let block_info = BlockInfo::new( block_event.epoch(), block_event.round(), block_event.hash()?, - self.db.reader.get_accumulator_root_hash(end_ver)?, + self.db().reader.get_accumulator_root_hash(end_ver)?, end_ver, block_event.proposed_time(), None, @@ -132,35 +115,32 @@ impl Executor { let ledger_info = LedgerInfo::new(block_info, HashValue::zero()); let aggregate_signature = AggregateSignature::empty(); let ledger_info = LedgerInfoWithSignatures::new(ledger_info, aggregate_signature); - self.db.writer.revert_commit(&ledger_info)?; + let db_writer = self.db().writer.clone(); + tokio::task::spawn_blocking(move || db_writer.revert_commit(&ledger_info)).await??; // Reset the executor state to the reverted storage self.block_executor.reset()?; Ok(()) } - pub fn context(&self) -> Arc { - self.context.clone() - } - /// Gets the next epoch and round. - pub async fn get_next_epoch_and_round(&self) -> Result<(u64, u64), anyhow::Error> { - let epoch = self.db.reader.get_latest_ledger_info()?.ledger_info().next_block_epoch(); - let round = self.db.reader.get_latest_ledger_info()?.ledger_info().round(); + pub fn get_next_epoch_and_round(&self) -> Result<(u64, u64), anyhow::Error> { + let epoch = self.db().reader.get_latest_ledger_info()?.ledger_info().next_block_epoch(); + let round = self.db().reader.get_latest_ledger_info()?.ledger_info().round(); Ok((epoch, round)) } /// Gets the timestamp of the last state. - pub async fn get_last_state_timestamp_micros(&self) -> Result { - let ledger_info = self.db.reader.get_latest_ledger_info()?; + pub fn get_last_state_timestamp_micros(&self) -> Result { + let ledger_info = self.db().reader.get_latest_ledger_info()?; Ok(ledger_info.ledger_info().timestamp_usecs()) } pub async fn rollover_genesis(&self, timestamp: u64) -> Result<(), anyhow::Error> { - let (epoch, round) = self.get_next_epoch_and_round().await?; + let (epoch, round) = self.get_next_epoch_and_round()?; let block_id = HashValue::random(); // genesis timestamp should always be 0 - let genesis_timestamp = self.get_last_state_timestamp_micros().await?; + let genesis_timestamp = self.get_last_state_timestamp_micros()?; info!( "Rollover genesis: epoch: {}, round: {}, block_id: {}, genesis timestamp {}", epoch, round, block_id, genesis_timestamp @@ -233,8 +213,8 @@ impl Executor { #[cfg(test)] mod tests { - use super::*; + use crate::Service; use aptos_api::accept_type::AcceptType; use aptos_crypto::{ ed25519::{Ed25519PrivateKey, Ed25519Signature}, @@ -258,17 +238,18 @@ mod tests { transaction::{RawTransaction, Script, SignedTransaction, Transaction, TransactionPayload}, }; use rand::SeedableRng; + use tokio::sync::mpsc; - fn create_signed_transaction(gas_unit_price: u64, chain_id: ChainId) -> SignedTransaction { + fn create_signed_transaction(sequence_number: u64, chain_id: ChainId) -> SignedTransaction { let private_key = Ed25519PrivateKey::generate_for_testing(); let public_key = private_key.public_key(); let transaction_payload = TransactionPayload::Script(Script::new(vec![0], vec![], vec![])); let raw_transaction = RawTransaction::new( AccountAddress::random(), - 0, + sequence_number, transaction_payload, 0, - gas_unit_price, + 0, 0, chain_id, // This is the value used in aptos testing code. ); @@ -278,7 +259,9 @@ mod tests { #[tokio::test] async fn test_execute_block() -> Result<(), anyhow::Error> { let private_key = Ed25519PrivateKey::generate_for_testing(); - let (executor, _tempdir) = Executor::try_test_default(private_key.clone())?; + let (tx_sender, _tx_receiver) = mpsc::channel(1); + let (executor, config, _tempdir) = Executor::try_test_default(private_key)?; + let (context, _transaction_pipe) = executor.background(tx_sender, &config)?; let block_id = HashValue::random(); let block_metadata = Transaction::BlockMetadata(BlockMetadata::new( block_id, @@ -290,7 +273,7 @@ mod tests { chrono::Utc::now().timestamp_micros() as u64, )); let tx = SignatureVerifiedTransaction::Valid(Transaction::UserTransaction( - create_signed_transaction(0, executor.maptos_config.chain.maptos_chain_id.clone()), + create_signed_transaction(0, context.config().chain.maptos_chain_id.clone()), )); let txs = ExecutableTransactions::Unsharded(vec![ SignatureVerifiedTransaction::Valid(block_metadata), @@ -310,13 +293,15 @@ mod tests { // Create an executor instance from the environment configuration. let private_key = Ed25519PrivateKey::generate_for_testing(); - let (executor, _tempdir) = Executor::try_test_default(private_key.clone())?; + let (tx_sender, _tx_receiver) = mpsc::channel(1); + let (executor, config, _tempdir) = Executor::try_test_default(private_key)?; + let (context, _transaction_pipe) = executor.background(tx_sender, &config)?; executor.rollover_genesis_now().await?; // Initialize a root account using a predefined keypair and the test root address. let root_account = LocalAccount::new( aptos_test_root_address(), - AccountKey::from_private_key(executor.maptos_config.chain.maptos_private_key.clone()), + AccountKey::from_private_key(context.config().chain.maptos_private_key.clone()), 0, ); @@ -326,7 +311,7 @@ mod tests { // Loop to simulate the execution of multiple blocks. for i in 0..10 { - let (epoch, round) = executor.get_next_epoch_and_round().await?; + let (epoch, round) = executor.get_next_epoch_and_round()?; // Generate a random block ID. let block_id = HashValue::random(); @@ -337,7 +322,7 @@ mod tests { // Create a transaction factory with the chain ID of the executor, used for creating transactions. let tx_factory = - TransactionFactory::new(executor.maptos_config.chain.maptos_chain_id.clone()) + TransactionFactory::new(context.config().chain.maptos_chain_id.clone()) .with_transaction_expiration_time( current_time_microseconds, // current_time_microseconds + (i * 1000 * 1000 * 60 * 30) + 30, ); @@ -382,7 +367,7 @@ mod tests { let block_commitment = executor.execute_block(block).await?; // Access the database reader to verify state after execution. - let db_reader = executor.db.reader.clone(); + let db_reader = executor.db_reader(); // Get the latest version of the blockchain state from the database. let latest_version = db_reader.get_synced_version()?; // Verify the transaction by its hash to ensure it was committed. @@ -399,8 +384,8 @@ mod tests { // Check the commitment against state proof let state_proof = db_reader.get_state_proof(latest_version)?; let expected_commitment = Commitment::digest_state_proof(&state_proof); - assert_eq!(block_commitment.height, i + 2); - assert_eq!(block_commitment.commitment, expected_commitment); + assert_eq!(block_commitment.height(), i + 2); + assert_eq!(block_commitment.commitment(), expected_commitment); } Ok(()) @@ -410,13 +395,16 @@ mod tests { async fn test_execute_block_state_get_api() -> Result<(), anyhow::Error> { // Create an executor instance from the environment configuration. let private_key = Ed25519PrivateKey::generate_for_testing(); - let (executor, _tempdir) = Executor::try_test_default(private_key.clone())?; + let (tx_sender, _tx_receiver) = mpsc::channel(16); + let (executor, config, _tempdir) = Executor::try_test_default(private_key)?; + let (context, _transaction_pipe) = executor.background(tx_sender, &config)?; + let service = Service::new(&context); executor.rollover_genesis_now().await?; // Initialize a root account using a predefined keypair and the test root address. let root_account = LocalAccount::new( aptos_test_root_address(), - AccountKey::from_private_key(executor.maptos_config.chain.maptos_private_key.clone()), + AccountKey::from_private_key(context.config().chain.maptos_private_key.clone()), 0, ); @@ -425,13 +413,12 @@ mod tests { let mut rng = ::rand::rngs::StdRng::from_seed(seed); // Create a transaction factory with the chain ID of the executor. - let tx_factory = - TransactionFactory::new(executor.maptos_config.chain.maptos_chain_id.clone()); + let tx_factory = TransactionFactory::new(context.config().chain.maptos_chain_id.clone()); // Simulate the execution of multiple blocks. for _ in 0..10 { // For example, create and execute 3 blocks. - let (epoch, round) = executor.get_next_epoch_and_round().await?; + let (epoch, round) = executor.get_next_epoch_and_round()?; let block_id = HashValue::random(); // Generate a random block ID for each block. @@ -474,7 +461,7 @@ mod tests { executor.execute_block(block).await?; // Retrieve the executor's API interface and fetch the transaction by each hash. - let apis = executor.get_apis(); + let apis = service.get_apis(); for hash in transaction_hashes { let _ = apis .transactions diff --git a/protocol-units/execution/opt-executor/src/executor/indexer.rs b/protocol-units/execution/opt-executor/src/executor/indexer.rs deleted file mode 100644 index 1ebd01ff8..000000000 --- a/protocol-units/execution/opt-executor/src/executor/indexer.rs +++ /dev/null @@ -1,47 +0,0 @@ -use super::Executor; -/*use aptos_indexer_grpc_fullnode::{fullnode_data_service::FullnodeDataService, ServiceContext}; -use aptos_protos::internal::fullnode::v1::fullnode_data_server::FullnodeDataServer; -use std::net::ToSocketAddrs; -use aptos_indexer::runtime::run_forever;*/ -use aptos_indexer::runtime::bootstrap as bootstrap_indexer_stream; -use aptos_indexer_grpc_fullnode::runtime::bootstrap as bootstrap_indexer_grpc; -use aptos_indexer_grpc_table_info::runtime::bootstrap as bootstrap_table_info; - -impl Executor { - // https://github.com/movementlabsxyz/aptos-core/blob/ea91067b81f9673547417bff9c70d5a2fe1b0e7b/aptos-node/src/services.rs#L40 - pub async fn run_indexer_grpc_service(&self) -> Result<(), anyhow::Error> { - tracing::info!("Starting indexer gRPC with node config {:?}", self.node_config); - - // bootstrap table info - let (_table_info_runtime, _async_indexer_v2) = bootstrap_table_info( - &self.node_config, - self.maptos_config.chain.maptos_chain_id.clone(), - self.db.clone(), - self.mempool_client_sender.clone(), - ) - .ok_or(anyhow::anyhow!("Failed to bootstrap table info runtime"))?; - - // bootstrap indexer grpc - // this one actually serves the gRPC service - let _indexer_grpc = bootstrap_indexer_grpc( - &self.node_config, - self.maptos_config.chain.maptos_chain_id.clone(), - self.db.reader.clone(), - self.mempool_client_sender.clone(), - None, - ) - .ok_or(anyhow::anyhow!("Failed to bootstrap indexer grpc runtime"))?; - - // bootstrap indexer stream - let _indexer_stream = bootstrap_indexer_stream( - &self.node_config, - self.maptos_config.chain.maptos_chain_id.clone(), - self.db.reader.clone(), - self.mempool_client_sender.clone(), - ) - .ok_or(anyhow::anyhow!("Failed to bootstrap indexer stream runtime"))?; - - // sleep forever - Ok(futures::future::pending::<()>().await) - } -} diff --git a/protocol-units/execution/opt-executor/src/executor/initialization.rs b/protocol-units/execution/opt-executor/src/executor/initialization.rs index 0d01c9f59..b2e054737 100644 --- a/protocol-units/execution/opt-executor/src/executor/initialization.rs +++ b/protocol-units/execution/opt-executor/src/executor/initialization.rs @@ -1,168 +1,72 @@ use super::Executor; -use aptos_api::Context; +use crate::{bootstrap, Context, TransactionPipe}; + use aptos_config::config::NodeConfig; #[cfg(test)] use aptos_crypto::ed25519::Ed25519PrivateKey; -use aptos_crypto::{ed25519::Ed25519PublicKey, PrivateKey}; -use aptos_db::AptosDB; -use aptos_executor::{ - block_executor::BlockExecutor, - db_bootstrapper::{generate_waypoint, maybe_bootstrap}, -}; -use aptos_mempool::{MempoolClientRequest, MempoolClientSender}; -use aptos_sdk::types::on_chain_config::{OnChainConsensusConfig, OnChainExecutionConfig}; -use aptos_storage_interface::DbReaderWriter; -use aptos_types::{ - chain_id::ChainId, - transaction::{ChangeSet, Transaction, WriteSetPayload}, - validator_signer::ValidatorSigner, -}; -use aptos_vm::AptosVM; -use aptos_vm_genesis::{ - default_gas_schedule, encode_genesis_change_set, GenesisConfiguration, TestValidator, Validator, -}; +use aptos_crypto::PrivateKey; +use aptos_executor::block_executor::BlockExecutor; +use aptos_mempool::MempoolClientRequest; +use aptos_types::transaction::SignedTransaction; use maptos_execution_util::config::Config; use anyhow::Context as _; use futures::channel::mpsc as futures_mpsc; -use tokio::sync::RwLock; +use tokio::sync::mpsc; #[cfg(test)] use tempfile::TempDir; -use std::{ - path::PathBuf, - sync::{atomic::AtomicU64, Arc}, -}; +use std::net::ToSocketAddrs; +use std::sync::{atomic::AtomicU64, Arc}; -// executor channel size +// Executor channel size. +// Allow 2^16 transactions before appling backpressure given theoretical maximum TPS of 170k. const EXECUTOR_CHANNEL_SIZE: usize = 2_usize.pow(16); impl Executor { - pub fn genesis_change_set_and_validators( - chain_id: ChainId, - count: Option, - public_key: &Ed25519PublicKey, - ) -> (ChangeSet, Vec) { - let framework = aptos_cached_packages::head_release_bundle(); - let test_validators = TestValidator::new_test_set(count, Some(100_000_000)); - let validators_: Vec = test_validators.iter().map(|t| t.data.clone()).collect(); - let validators = &validators_; - - // This number should not exceed u64::MAX / 1_000_000_000 - // to avoid overflowing calculations in aptos-vm-genesis. - // This will last several centuries. - const EPOCH_DURATION_SECS: u64 = 60 * 60 * 24 * 1024 * 128; - - let genesis = encode_genesis_change_set( - &public_key, - validators, - framework, - chain_id, - // todo: get this config from somewhere - &GenesisConfiguration { - allow_new_validators: true, - epoch_duration_secs: EPOCH_DURATION_SECS, - is_test: true, - min_stake: 0, - min_voting_threshold: 0, - // 1M APTOS coins (with 8 decimals). - max_stake: 100_000_000_000_000, - recurring_lockup_duration_secs: EPOCH_DURATION_SECS * 2, - required_proposer_stake: 0, - rewards_apy_percentage: 0, - voting_duration_secs: EPOCH_DURATION_SECS, - voting_power_increase_limit: 50, - employee_vesting_start: 1663456089, - employee_vesting_period_duration: 5 * 60, // 5 minutes - initial_features_override: None, - randomness_config_override: None, - jwk_consensus_config_override: None, - }, - &OnChainConsensusConfig::default_for_genesis(), - &OnChainExecutionConfig::default_for_genesis(), - &default_gas_schedule(), - ); - (genesis, test_validators) - } - - /// Bootstrap a database with a genesis transaction if it is empty. - pub fn maybe_bootstrap_empty_db( - db_dir: &PathBuf, - chain_id: ChainId, - public_key: &Ed25519PublicKey, - ) -> Result<(DbReaderWriter, ValidatorSigner), anyhow::Error> { - let db_rw = DbReaderWriter::new(AptosDB::new_for_test(db_dir)); - let (genesis, validators) = - Self::genesis_change_set_and_validators(chain_id, Some(1), public_key); - let genesis_txn = Transaction::GenesisTransaction(WriteSetPayload::Direct(genesis)); - let validator_signer = ValidatorSigner::new( - validators[0].data.owner_address, - validators[0].consensus_key.clone(), - ); - - // check for context - - match db_rw.reader.get_latest_ledger_info_option()? { - Some(ledger_info) => { - // context exists - tracing::info!("Ledger info found, not bootstrapping DB: {:?}", ledger_info); - } - None => { - // context does not exist - // simply continue - tracing::info!("No ledger info found, bootstrapping DB."); - let waypoint = generate_waypoint::(&db_rw, &genesis_txn)?; - maybe_bootstrap::(&db_rw, &genesis_txn, waypoint)? - .ok_or(anyhow::anyhow!("Failed to bootstrap DB"))?; - assert!(db_rw.reader.get_latest_ledger_info_option()?.is_some()); - } - } - - Ok((db_rw, validator_signer)) - } - - pub fn bootstrap( - mempool_client_sender: MempoolClientSender, - mempool_client_receiver: futures_mpsc::Receiver, - node_config: NodeConfig, - maptos_config: Config, - ) -> Result { - let (db, signer) = Self::maybe_bootstrap_empty_db( + pub fn bootstrap(maptos_config: &Config) -> Result { + let (db, signer) = bootstrap::maybe_bootstrap_empty_db( maptos_config.chain.maptos_db_path.as_ref().context("No db path provided.")?, maptos_config.chain.maptos_chain_id.clone(), &maptos_config.chain.maptos_private_key.public_key(), )?; - let reader = db.reader.clone(); - Ok(Self { block_executor: Arc::new(BlockExecutor::new(db.clone())), - db, signer, - mempool_client_sender: mempool_client_sender.clone(), - mempool_client_receiver: Arc::new(RwLock::new(mempool_client_receiver)), - node_config: node_config.clone(), - context: Arc::new(Context::new( - maptos_config.chain.maptos_chain_id.clone(), - reader, - mempool_client_sender, - node_config, - None, - )), - listen_url: format!( - "{}:{}", - maptos_config.chain.maptos_rest_listen_hostname, - maptos_config.chain.maptos_rest_listen_port - ), - maptos_config, transactions_in_flight: Arc::new(AtomicU64::new(0)), + config: maptos_config.clone(), }) } pub fn try_from_config(maptos_config: &Config) -> Result { - // use the default signer, block executor, and mempool - let (mempool_client_sender, mempool_client_receiver) = - futures_mpsc::channel::(EXECUTOR_CHANNEL_SIZE); // allow 2^16 transactions before apply backpressure given theoretical maximum TPS of 170k + Self::bootstrap(maptos_config) + } + + #[cfg(test)] + pub fn try_test_default( + private_key: Ed25519PrivateKey, + ) -> Result<(Self, Config, TempDir), anyhow::Error> { + let tempdir = tempfile::tempdir()?; + + let mut maptos_config = Config::default(); + maptos_config.chain.maptos_private_key = private_key; + + // replace the db path with the temporary directory + maptos_config.chain.maptos_db_path.replace(tempdir.path().to_path_buf()); + let executor = Self::try_from_config(&maptos_config)?; + Ok((executor, maptos_config, tempdir)) + } + + /// Creates an instance of [`Context`] and the background [`TransactionPipe`] + /// task to process transactions. + /// The `Context` must be kept around for as long as the `TransactionPipe` + /// task needs to be running. + pub fn background( + &self, + transaction_sender: mpsc::Sender, + maptos_config: &Config, + ) -> anyhow::Result<(Context, TransactionPipe)> { let mut node_config = NodeConfig::default(); node_config.indexer.enabled = true; @@ -184,12 +88,13 @@ impl Executor { node_config.indexer_grpc.processor_batch_size = 4; node_config.indexer_grpc.processor_task_count = 4; node_config.indexer_grpc.output_batch_size = 4; - node_config.indexer_grpc.address = format!( - "{}:{}", - maptos_config.indexer.maptos_indexer_grpc_listen_hostname, - maptos_config.indexer.maptos_indexer_grpc_listen_port + node_config.indexer_grpc.address = ( + maptos_config.indexer.maptos_indexer_grpc_listen_hostname.as_str(), + maptos_config.indexer.maptos_indexer_grpc_listen_port, ) - .parse()?; + .to_socket_addrs()? + .next() + .context("failed to resolve the value of maptos_indexer_grpc_listen_hostname")?; node_config.indexer_grpc.use_data_service_interface = true; // indexer table info config @@ -197,26 +102,25 @@ impl Executor { node_config.storage.dir = "./.movement/maptos-storage".to_string().into(); node_config.storage.set_data_dir(node_config.storage.dir.clone()); - Self::bootstrap( - mempool_client_sender, + // use the default signer, block executor, and mempool + let (mempool_client_sender, mempool_client_receiver) = + futures_mpsc::channel::(EXECUTOR_CHANNEL_SIZE); + let transaction_pipe = TransactionPipe::new( mempool_client_receiver, - node_config, - maptos_config.clone(), - ) - } - - #[cfg(test)] - pub fn try_test_default( - private_key: Ed25519PrivateKey, - ) -> Result<(Self, TempDir), anyhow::Error> { - let tempdir = tempfile::tempdir()?; + transaction_sender, + self.db().reader.clone(), + &node_config, + Arc::clone(&self.transactions_in_flight), + maptos_config.load_shedding.max_transactions_in_flight, + ); - let mut maptos_config = Config::default(); - maptos_config.chain.maptos_private_key = private_key; + let cx = Context::new( + self.db().clone(), + mempool_client_sender, + maptos_config.clone(), + node_config, + ); - // replace the db path with the temporary directory - maptos_config.chain.maptos_db_path.replace(tempdir.path().to_path_buf()); - let executor = Self::try_from_config(&maptos_config)?; - Ok((executor, tempdir)) + Ok((cx, transaction_pipe)) } } diff --git a/protocol-units/execution/opt-executor/src/executor/mod.rs b/protocol-units/execution/opt-executor/src/executor/mod.rs index 404d42a35..7464bb9c1 100644 --- a/protocol-units/execution/opt-executor/src/executor/mod.rs +++ b/protocol-units/execution/opt-executor/src/executor/mod.rs @@ -1,84 +1,71 @@ -//! Implementation is split over multiple files to make the code more manageable. +// Implementation is split over multiple files to make the code more manageable. +// TODO: code smell, refactor the god object. pub mod execution; pub mod initialization; -pub mod services; -pub mod transaction_pipe; -use anyhow::Context as _; -use aptos_api::context::Context; -use aptos_config::config::NodeConfig; -use aptos_db::AptosDB; + +use aptos_crypto::HashValue; use aptos_executor::block_executor::BlockExecutor; -use aptos_mempool::{MempoolClientRequest, MempoolClientSender}; -use aptos_storage_interface::DbReaderWriter; +use aptos_storage_interface::{DbReader, DbReaderWriter}; use aptos_types::validator_signer::ValidatorSigner; use aptos_vm::AptosVM; -use futures::channel::mpsc as futures_mpsc; -use std::sync::{atomic::AtomicU64, Arc}; -use tokio::sync::RwLock; -pub mod indexer; + +use tracing::info; + +use maptos_execution_util::config::Config; +use std::sync::atomic::{AtomicU64, Ordering}; +use std::sync::Arc; /// The `Executor` is responsible for executing blocks and managing the state of the execution /// against the `AptosVM`. -#[derive(Clone)] pub struct Executor { /// The executing type. pub block_executor: Arc>, - /// The access to db. - pub db: DbReaderWriter, /// The signer of the executor's transactions. pub signer: ValidatorSigner, - /// The sender for the mempool client. - pub mempool_client_sender: MempoolClientSender, - /// The receiver for the mempool client. - pub mempool_client_receiver: Arc>>, - /// The configuration of the node. - pub node_config: NodeConfig, - /// Context - pub context: Arc, - /// URL for the API endpoint - pub listen_url: String, - /// Maptos config - pub maptos_config: maptos_execution_util::config::Config, - /// Transactions in flight counter. - pub transactions_in_flight: Arc, + // Shared reference on the counter of transactions in flight. + transactions_in_flight: Arc, + // The config for the executor. + pub(crate) config: Config, } impl Executor { - /// Create a new `Executor` instance. - pub fn try_new( - block_executor: BlockExecutor, - signer: ValidatorSigner, - mempool_client_sender: MempoolClientSender, - mempool_client_receiver: futures_mpsc::Receiver, - node_config: NodeConfig, - maptos_config: maptos_execution_util::config::Config, - ) -> Result { - let (_aptos_db, reader_writer) = DbReaderWriter::wrap(AptosDB::new_for_test( - &maptos_config.chain.maptos_db_path.clone().context("No db path provided.")?, - )); + fn db(&self) -> &DbReaderWriter { + &self.block_executor.db + } + + pub fn db_reader(&self) -> Arc { + Arc::clone(&self.db().reader) + } + + pub fn decrement_transactions_in_flight(&self, count: u64) { + // fetch sub mind the underflow + // a semaphore might be better here as this will rerun until the value does not change during the operation + self.transactions_in_flight + .fetch_update(Ordering::Relaxed, Ordering::Relaxed, |current| { + info!( + target: "movement_timing", + count, + current, + "decrementing_transactions_in_flight", + ); + Some(current.saturating_sub(count)) + }) + .unwrap_or_else(|_| 0); + } + + pub fn config(&self) -> &Config { + &self.config + } - let reader = reader_writer.reader.clone(); - Ok(Self { - block_executor: Arc::new(block_executor), - db: reader_writer, - signer, - mempool_client_sender: mempool_client_sender.clone(), - node_config: node_config.clone(), - mempool_client_receiver: Arc::new(RwLock::new(mempool_client_receiver)), - context: Arc::new(Context::new( - maptos_config.chain.maptos_chain_id.clone(), - reader, - mempool_client_sender, - node_config, - None, - )), - listen_url: format!( - "{}:{}", - maptos_config.chain.maptos_rest_listen_hostname, - maptos_config.chain.maptos_rest_listen_port - ), - maptos_config, - transactions_in_flight: Arc::new(AtomicU64::new(0)), - }) + pub fn has_executed_transaction( + &self, + transaction_hash: HashValue, + ) -> Result { + let reader = self.db_reader(); + let ledger_version = reader.get_latest_ledger_info_version()?; + match reader.get_transaction_by_hash(transaction_hash, ledger_version, false)? { + Some(_) => Ok(true), + None => Ok(false), + } } } diff --git a/protocol-units/execution/opt-executor/src/executor/services.rs b/protocol-units/execution/opt-executor/src/executor/services.rs deleted file mode 100644 index 024e34f05..000000000 --- a/protocol-units/execution/opt-executor/src/executor/services.rs +++ /dev/null @@ -1,117 +0,0 @@ -use super::Executor; -use aptos_api::{ - get_api_service, - runtime::{get_apis, root_handler, Apis}, - set_failpoints, -}; -use poem::{http::Method, listener::TcpListener, middleware::Cors, EndpointExt, Route, Server}; -use tracing::info; - -impl Executor { - pub fn get_apis(&self) -> Apis { - get_apis(self.context()) - } - - pub async fn run_service(&self) -> Result<(), anyhow::Error> { - info!("Starting maptos-opt-executor services at: {:?}", self.listen_url); - - let api_service = - get_api_service(self.context()).server(format!("http://{:?}", self.listen_url)); - - let spec_json = api_service.spec_endpoint(); - let spec_yaml = api_service.spec_endpoint_yaml(); - - let ui = api_service.swagger_ui(); - - let cors = Cors::new() - .allow_methods(vec![Method::GET, Method::POST]) - .allow_credentials(true); - let app = Route::new() - .at("/", poem::get(root_handler)) - .nest("/v1", api_service) - .nest("/spec", ui) - .at("/spec.json", poem::get(spec_json)) - .at("/spec.yaml", poem::get(spec_yaml)) - .at( - "/set_failpoint", - poem::get(set_failpoints::set_failpoint_poem).data(self.context()), - ) - .with(cors); - - Server::new(TcpListener::bind(self.listen_url.clone())) - .run(app) - .await - .map_err(|e| anyhow::anyhow!("Server error: {:?}", e))?; - - Ok(()) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use aptos_mempool::core_mempool::CoreMempool; - use aptos_mempool::MempoolClientRequest; - use aptos_types::{ - account_config, mempool_status::MempoolStatusCode, test_helpers::transaction_test_helpers, - transaction::SignedTransaction, - }; - use aptos_vm_genesis::GENESIS_KEYPAIR; - use futures::channel::oneshot; - use futures::SinkExt; - use maptos_execution_util::config::Config; - - fn create_signed_transaction( - sequence_number: u64, - maptos_config: &Config, - ) -> SignedTransaction { - let address = account_config::aptos_test_root_address(); - transaction_test_helpers::get_test_txn_with_chain_id( - address, - sequence_number, - &GENESIS_KEYPAIR.0, - GENESIS_KEYPAIR.1.clone(), - maptos_config.chain.maptos_chain_id.clone(), // This is the value used in aptos testing code. - ) - } - - #[tokio::test] - async fn test_pipe_mempool_while_server_running() -> Result<(), anyhow::Error> { - let (mut executor, _tempdir) = Executor::try_test_default(GENESIS_KEYPAIR.0.clone())?; - let server_executor = executor.clone(); - - let handle = tokio::spawn(async move { - server_executor.run_service().await?; - Ok(()) as Result<(), anyhow::Error> - }); - - let user_transaction = create_signed_transaction(0, &executor.maptos_config); - - // send transaction to mempool - let (req_sender, callback) = oneshot::channel(); - executor - .mempool_client_sender - .send(MempoolClientRequest::SubmitTransaction(user_transaction.clone(), req_sender)) - .await?; - - // tick the transaction pipe - let (tx, rx) = async_channel::unbounded(); - let mut core_mempool = CoreMempool::new(&executor.node_config.clone()); - executor - .tick_transaction_pipe(&mut core_mempool, tx, &mut std::time::Instant::now()) - .await?; - - // receive the callback - let (status, _vm_status_code) = callback.await??; - // dbg!(_vm_status_code); - assert_eq!(status.code, MempoolStatusCode::Accepted); - - // receive the transaction - let received_transaction = rx.recv().await?; - assert_eq!(received_transaction, user_transaction); - - handle.abort(); - - Ok(()) - } -} diff --git a/protocol-units/execution/opt-executor/src/executor/transaction_pipe.rs b/protocol-units/execution/opt-executor/src/executor/transaction_pipe.rs deleted file mode 100644 index 88516431e..000000000 --- a/protocol-units/execution/opt-executor/src/executor/transaction_pipe.rs +++ /dev/null @@ -1,386 +0,0 @@ -use super::Executor; -use aptos_mempool::core_mempool::CoreMempool; -use aptos_mempool::SubmissionStatus; -use aptos_mempool::{core_mempool::TimelineState, MempoolClientRequest}; -use aptos_sdk::types::mempool_status::{MempoolStatus, MempoolStatusCode}; -use aptos_types::transaction::{SignedTransaction, VMValidatorResult}; -use aptos_vm_validator::vm_validator::TransactionValidation; -use aptos_vm_validator::vm_validator::VMValidator; -use async_channel::Sender; -use futures::StreamExt; -use std::sync::Arc; -use thiserror::Error; -use tracing::{debug, info, info_span, warn, Instrument}; - -const IN_FLIGHT_LIMIT: u64 = 2u64.pow(16); - -#[derive(Debug, Clone, Error)] -pub enum TransactionPipeError { - #[error("Transaction Pipe InternalError: {0}")] - InternalError(String), - #[error("Transaction not accepted: {0}")] - TransactionNotAccepted(MempoolStatus), - #[error("Transaction stream closed")] - InputClosed, -} - -impl From for TransactionPipeError { - fn from(e: anyhow::Error) -> Self { - TransactionPipeError::InternalError(e.to_string()) - } -} - -impl Executor { - /// Pipes a batch of transactions from the mempool to the transaction channel. - /// todo: it may be wise to move the batching logic up a level to the consuming structs. - pub async fn tick_transaction_pipe( - &self, - core_mempool: &mut CoreMempool, - transaction_channel: async_channel::Sender, - last_gc: &mut std::time::Instant, - ) -> Result<(), TransactionPipeError> { - // Drop the receiver RwLock as soon as possible. - let next = { - let mut mempool_client_receiver = self.mempool_client_receiver.write().await; - mempool_client_receiver.next().await - }; - if let Some(request) = next { - match request { - MempoolClientRequest::SubmitTransaction(transaction, callback) => { - // For now, we are going to consider a transaction in flight until it exits the mempool and is sent to the DA as is indicated by WriteBatch. - let in_flight = - self.transactions_in_flight.load(std::sync::atomic::Ordering::SeqCst); - info!( - target: "movement_timing", - in_flight = %in_flight, - "transactions_in_flight" - ); - if in_flight > IN_FLIGHT_LIMIT { - info!( - target: "movement_timing", - "shedding_load" - ); - let status = MempoolStatus::new(MempoolStatusCode::MempoolIsFull); - callback.send(Ok((status.clone(), None))).map_err(|e| { - TransactionPipeError::InternalError(format!( - "Error sending transaction: {:?}", - e - )) - })?; - return Ok(()); - } - - let span = info_span!( - target: "movement_timing", - "submit_transaction", - tx_hash = %transaction.committed_hash(), - sender = %transaction.sender(), - sequence_number = transaction.sequence_number(), - ); - let status = self - .submit_transaction(core_mempool, transaction, transaction_channel) - .instrument(span) - .await?; - - callback.send(Ok(status)).map_err(|e| { - TransactionPipeError::InternalError(format!( - "Error sending transaction: {:?}", - e - )) - })?; - } - MempoolClientRequest::GetTransactionByHash(hash, sender) => { - let mempool_result = core_mempool.get_by_hash(hash); - sender.send(mempool_result).map_err(|e| { - TransactionPipeError::InternalError(format!( - "Error sending transaction: {:?}", - e - )) - })?; - } - } - } - - if last_gc.elapsed().as_secs() > 120 { - core_mempool.gc(); - *last_gc = std::time::Instant::now(); - } - - Ok(()) - } - - async fn submit_transaction( - &self, - core_mempool: &mut CoreMempool, - transaction: SignedTransaction, - transaction_channel: Sender, - ) -> Result { - // Pre-execute Tx to validate its content. - // Re-create the validator for each Tx because it uses a frozen version of the ledger. - let vm_validator = VMValidator::new(Arc::clone(&self.db.reader)); - let tx_result = vm_validator.validate_transaction(transaction.clone())?; - match tx_result.status() { - Some(_) => { - let ms = MempoolStatus::new(MempoolStatusCode::VmError); - return Ok((ms, tx_result.status())); - } - None => {} - } - - debug!( - "Adding transaction to mempool: {:?} {:?}", - transaction, - transaction.sequence_number() - ); - let status = core_mempool.add_txn( - transaction.clone(), - 0, - transaction.sequence_number(), - TimelineState::NonQualified, - true, - ); - - match status.code { - MempoolStatusCode::Accepted => { - debug!("Transaction accepted: {:?}", transaction); - transaction_channel - .send(transaction.clone()) - .await - .map_err(|e| anyhow::anyhow!("Error sending transaction: {:?}", e))?; - // increment transactions in flight - self.transactions_in_flight.fetch_add(1, std::sync::atomic::Ordering::SeqCst); - core_mempool - .commit_transaction(&transaction.sender(), transaction.sequence_number()); - } - _ => { - warn!("Transaction not accepted: {:?}", status); - } - } - - // report status - let ms = MempoolStatus::new(MempoolStatusCode::Accepted); - Ok((ms, None)) - } -} - -#[cfg(test)] -mod tests { - - use std::collections::BTreeSet; - - use super::*; - use aptos_api::{accept_type::AcceptType, transactions::SubmitTransactionPost}; - use aptos_types::{ - account_config, test_helpers::transaction_test_helpers, transaction::SignedTransaction, - }; - use aptos_vm_genesis::GENESIS_KEYPAIR; - use futures::channel::oneshot; - use futures::SinkExt; - use maptos_execution_util::config::Config; - - fn create_signed_transaction( - sequence_number: u64, - maptos_config: &Config, - ) -> SignedTransaction { - let address = account_config::aptos_test_root_address(); - transaction_test_helpers::get_test_txn_with_chain_id( - address, - sequence_number, - &GENESIS_KEYPAIR.0, - GENESIS_KEYPAIR.1.clone(), - maptos_config.chain.maptos_chain_id.clone(), // This is the value used in aptos testing code. - ) - } - - #[tokio::test] - async fn test_pipe_mempool() -> Result<(), anyhow::Error> { - // header - let (mut executor, _tempdir) = Executor::try_test_default(GENESIS_KEYPAIR.0.clone())?; - let user_transaction = create_signed_transaction(1, &executor.maptos_config); - - // send transaction to mempool - let (req_sender, callback) = oneshot::channel(); - executor - .mempool_client_sender - .send(MempoolClientRequest::SubmitTransaction(user_transaction.clone(), req_sender)) - .await?; - - // tick the transaction pipe - let (tx, rx) = async_channel::unbounded(); - let mut core_mempool = CoreMempool::new(&executor.node_config.clone()); - executor - .tick_transaction_pipe(&mut core_mempool, tx, &mut std::time::Instant::now()) - .await?; - - // receive the callback - let (status, _vm_status_code) = callback.await??; - assert_eq!(status.code, MempoolStatusCode::Accepted); - - // receive the transaction - let received_transaction = rx.recv().await?; - assert_eq!(received_transaction, user_transaction); - - Ok(()) - } - - #[tokio::test] - async fn test_pipe_mempool_cancellation() -> Result<(), anyhow::Error> { - // header - let (mut executor, _tempdir) = Executor::try_test_default(GENESIS_KEYPAIR.0.clone())?; - let user_transaction = create_signed_transaction(1, &executor.maptos_config); - - // send transaction to mempool - let (req_sender, callback) = oneshot::channel(); - executor - .mempool_client_sender - .send(MempoolClientRequest::SubmitTransaction(user_transaction.clone(), req_sender)) - .await?; - - // drop the callback to simulate cancellation of the request - drop(callback); - - // tick the transaction pipe, should succeed - let (tx, rx) = async_channel::unbounded(); - let mut core_mempool = CoreMempool::new(&executor.node_config.clone()); - executor - .tick_transaction_pipe(&mut core_mempool, tx, &mut std::time::Instant::now()) - .await?; - - Ok(()) - } - - #[tokio::test] - async fn test_pipe_mempool_with_malformed_transaction() -> Result<(), anyhow::Error> { - // header - let (mut executor, _tempdir) = Executor::try_test_default(GENESIS_KEYPAIR.0.clone())?; - let user_transaction = create_signed_transaction(1, &executor.maptos_config); - - // send transaction to mempool - let (req_sender, callback) = oneshot::channel(); - executor - .mempool_client_sender - .send(MempoolClientRequest::SubmitTransaction(user_transaction.clone(), req_sender)) - .await?; - - // tick the transaction pipe - let (tx, rx) = async_channel::unbounded(); - let mut core_mempool = CoreMempool::new(&executor.node_config.clone()); - executor - .tick_transaction_pipe(&mut core_mempool, tx.clone(), &mut std::time::Instant::now()) - .await?; - - // receive the callback - let (status, _vm_status_code) = callback.await??; - // dbg!(_vm_status_code); - assert_eq!(status.code, MempoolStatusCode::Accepted); - - // receive the transaction - let received_transaction = rx.recv().await?; - assert_eq!(received_transaction, user_transaction); - - // send the same transaction again - let (req_sender, callback) = oneshot::channel(); - executor - .mempool_client_sender - .send(MempoolClientRequest::SubmitTransaction(user_transaction.clone(), req_sender)) - .await?; - - // tick the transaction pipe - let (tx, rx) = async_channel::unbounded(); - let mut core_mempool = CoreMempool::new(&executor.node_config.clone()); - executor - .tick_transaction_pipe(&mut core_mempool, tx, &mut std::time::Instant::now()) - .await?; - /*match executor.tick_transaction_pipe(tx).await { - Err(TransactionPipeError::TransactionNotAccepted(_)) => {} - Err(e) => return Err(anyhow::anyhow!("Unexpected error: {:?}", e)), - Ok(_) => return Err(anyhow::anyhow!("Expected error")), - }*/ - - callback.await??; - - let received_transaction = rx.recv().await?; - assert_eq!(received_transaction, user_transaction); - - Ok(()) - } - - #[tokio::test] - async fn test_pipe_mempool_from_api() -> Result<(), anyhow::Error> { - let (executor, _tempdir) = Executor::try_test_default(GENESIS_KEYPAIR.0.clone())?; - let mempool_executor = executor.clone(); - - let (tx, rx) = async_channel::unbounded(); - let mempool_handle = tokio::spawn(async move { - let mut core_mempool = CoreMempool::new(&mempool_executor.node_config.clone()); - loop { - mempool_executor - .tick_transaction_pipe( - &mut core_mempool, - tx.clone(), - &mut std::time::Instant::now(), - ) - .await?; - } - Ok(()) as Result<(), anyhow::Error> - }); - - let api = executor.get_apis(); - let user_transaction = create_signed_transaction(1, &executor.maptos_config); - let comparison_user_transaction = user_transaction.clone(); - let bcs_user_transaction = bcs::to_bytes(&user_transaction)?; - let request = SubmitTransactionPost::Bcs(aptos_api::bcs_payload::Bcs(bcs_user_transaction)); - api.transactions.submit_transaction(AcceptType::Bcs, request).await?; - let received_transaction = rx.recv().await?; - assert_eq!(received_transaction, comparison_user_transaction); - - mempool_handle.abort(); - - Ok(()) - } - - #[tokio::test] - async fn test_repeated_pipe_mempool_from_api() -> Result<(), anyhow::Error> { - let (executor, _tempdir) = Executor::try_test_default(GENESIS_KEYPAIR.0.clone())?; - let mempool_executor = executor.clone(); - - let (tx, rx) = async_channel::unbounded(); - let mempool_handle = tokio::spawn(async move { - let mut core_mempool = CoreMempool::new(&mempool_executor.node_config.clone()); - loop { - mempool_executor - .tick_transaction_pipe( - &mut core_mempool, - tx.clone(), - &mut std::time::Instant::now(), - ) - .await?; - } - Ok(()) as Result<(), anyhow::Error> - }); - - let api = executor.get_apis(); - let mut user_transactions = BTreeSet::new(); - let mut comparison_user_transactions = BTreeSet::new(); - for i in 1..25 { - let user_transaction = create_signed_transaction(i, &executor.maptos_config); - let bcs_user_transaction = bcs::to_bytes(&user_transaction)?; - user_transactions.insert(bcs_user_transaction.clone()); - - let request = - SubmitTransactionPost::Bcs(aptos_api::bcs_payload::Bcs(bcs_user_transaction)); - api.transactions.submit_transaction(AcceptType::Bcs, request).await?; - - let received_transaction = rx.recv().await?; - let bcs_received_transaction = bcs::to_bytes(&received_transaction)?; - comparison_user_transactions.insert(bcs_received_transaction.clone()); - } - - assert_eq!(user_transactions.len(), comparison_user_transactions.len()); - assert_eq!(user_transactions, comparison_user_transactions); - - mempool_handle.abort(); - - Ok(()) - } -} diff --git a/protocol-units/execution/opt-executor/src/indexer.rs b/protocol-units/execution/opt-executor/src/indexer.rs new file mode 100644 index 000000000..b352f7599 --- /dev/null +++ b/protocol-units/execution/opt-executor/src/indexer.rs @@ -0,0 +1,56 @@ +use super::Context; +use aptos_indexer_grpc_fullnode::runtime::bootstrap as bootstrap_indexer_grpc; +use aptos_indexer_grpc_table_info::runtime::bootstrap as bootstrap_table_info; + +use tokio::runtime::Runtime; + +/// Runtime handle for indexer services. This object should be kept alive +/// while services are running. +pub struct IndexerRuntime { + // We only keep the runtimes around to drop them + _table_info_runtime: Runtime, + _indexer_grpc: Runtime, + // _indexer_stream: Runtime, +} + +impl Context { + // https://github.com/movementlabsxyz/aptos-core/blob/ea91067b81f9673547417bff9c70d5a2fe1b0e7b/aptos-node/src/services.rs#L40 + pub fn run_indexer_grpc_service(&self) -> Result { + tracing::info!("Starting indexer gRPC with node config {:?}", self.node_config); + + // bootstrap table info + let (_table_info_runtime, _async_indexer) = bootstrap_table_info( + &self.node_config, + self.maptos_config.chain.maptos_chain_id.clone(), + self.db.clone(), + self.mempool_client_sender.clone(), + ) + .ok_or(anyhow::anyhow!("Failed to bootstrap table info runtime"))?; + + // Bootstrap indexer grpc. + // this one actually serves the gRPC service + let _indexer_grpc = bootstrap_indexer_grpc( + &self.node_config, + self.maptos_config.chain.maptos_chain_id.clone(), + self.db.reader.clone(), + self.mempool_client_sender.clone(), + None, + ) + .ok_or(anyhow::anyhow!("Failed to bootstrap indexer grpc runtime"))?; + + // Grpc stream works without the indexer. + // By default indexer is not started on Suzuka node. + // bootstrap indexer stream + // Commented because not needed. Done the external indexer. + // let _indexer_stream = bootstrap_indexer_stream( + // &self.node_config, + // self.maptos_config.chain.maptos_chain_id.clone(), + // self.db.reader.clone(), + // self.mempool_client_sender.clone(), + // ).ok_or( + // anyhow::anyhow!("Failed to bootstrap indexer stream runtime"), + // )?; + + Ok(IndexerRuntime { _table_info_runtime, _indexer_grpc }) //, _indexer_stream + } +} diff --git a/protocol-units/execution/opt-executor/src/lib.rs b/protocol-units/execution/opt-executor/src/lib.rs index eae0d6a82..d1b3ea37c 100644 --- a/protocol-units/execution/opt-executor/src/lib.rs +++ b/protocol-units/execution/opt-executor/src/lib.rs @@ -1,3 +1,12 @@ +pub mod bootstrap; +pub mod context; #[warn(unused_imports)] pub mod executor; -pub use executor::*; +pub mod indexer; +pub mod service; +pub mod transaction_pipe; + +pub use context::Context; +pub use executor::Executor; +pub use service::Service; +pub use transaction_pipe::TransactionPipe; diff --git a/protocol-units/execution/opt-executor/src/service.rs b/protocol-units/execution/opt-executor/src/service.rs new file mode 100644 index 000000000..0c55d88c3 --- /dev/null +++ b/protocol-units/execution/opt-executor/src/service.rs @@ -0,0 +1,148 @@ +use crate::Context; + +use aptos_api::{ + get_api_service, + runtime::{get_apis, root_handler, Apis}, + set_failpoints, +}; +use aptos_storage_interface::DbReaderWriter; + +use futures::prelude::*; +use poem::{http::Method, listener::TcpListener, middleware::Cors, EndpointExt, Route, Server}; +use tracing::info; + +use std::future::Future; +use std::sync::Arc; + +#[derive(Clone)] +pub struct Service { + // API context + context: Arc, + // URL for the API endpoint + listen_url: String, +} + +impl Service { + pub fn new(cx: &Context) -> Self { + let Context { + db: DbReaderWriter { reader, .. }, + mempool_client_sender, + maptos_config, + node_config, + } = cx; + let context = Arc::new(aptos_api::Context::new( + maptos_config.chain.maptos_chain_id.clone(), + reader.clone(), + mempool_client_sender.clone(), + node_config.clone(), + None, + )); + let listen_url = format!( + "{}:{}", + maptos_config.chain.maptos_rest_listen_hostname, + maptos_config.chain.maptos_rest_listen_port + ); + Service { context, listen_url } + } + + pub fn api_context(&self) -> Arc { + Arc::clone(&self.context) + } + + pub fn get_apis(&self) -> Apis { + get_apis(self.api_context()) + } + + pub fn run(&self) -> impl Future> + Send { + info!("Starting maptos-opt-executor services at: {:?}", self.listen_url); + + let api_service = + get_api_service(self.api_context()).server(format!("http://{:?}", self.listen_url)); + + let spec_json = api_service.spec_endpoint(); + let spec_yaml = api_service.spec_endpoint_yaml(); + + let ui = api_service.swagger_ui(); + + let cors = Cors::new() + .allow_methods(vec![Method::GET, Method::POST]) + .allow_credentials(true); + let listener = TcpListener::bind(self.listen_url.clone()); + let app = Route::new() + .at("/", poem::get(root_handler)) + .nest("/v1", api_service) + .nest("/spec", ui) + .at("/spec.json", poem::get(spec_json)) + .at("/spec.yaml", poem::get(spec_yaml)) + .at( + "/set_failpoint", + poem::get(set_failpoints::set_failpoint_poem).data(self.api_context()), + ) + .with(cors); + + Server::new(listener) + .run(app) + .map_err(|e| anyhow::anyhow!("Server error: {:?}", e)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::Executor; + use aptos_mempool::MempoolClientRequest; + use aptos_types::{ + account_config, mempool_status::MempoolStatusCode, test_helpers::transaction_test_helpers, + transaction::SignedTransaction, + }; + use aptos_vm_genesis::GENESIS_KEYPAIR; + use futures::channel::oneshot; + use futures::SinkExt; + use maptos_execution_util::config::chain::Config; + use tokio::sync::mpsc; + + fn create_signed_transaction(sequence_number: u64, chain_config: &Config) -> SignedTransaction { + let address = account_config::aptos_test_root_address(); + transaction_test_helpers::get_test_txn_with_chain_id( + address, + sequence_number, + &GENESIS_KEYPAIR.0, + GENESIS_KEYPAIR.1.clone(), + chain_config.maptos_chain_id.clone(), // This is the value used in aptos testing code. + ) + } + + #[tokio::test] + async fn test_pipe_mempool_while_server_running() -> Result<(), anyhow::Error> { + let (tx_sender, mut tx_receiver) = mpsc::channel(16); + let (executor, config, _tempdir) = Executor::try_test_default(GENESIS_KEYPAIR.0.clone())?; + let (context, mut transaction_pipe) = executor.background(tx_sender, &config)?; + let service = Service::new(&context); + let handle = tokio::spawn(async move { service.run().await }); + + let user_transaction = create_signed_transaction(0, &context.config().chain); + + // send transaction to mempool + let (req_sender, callback) = oneshot::channel(); + context + .mempool_client_sender() + .send(MempoolClientRequest::SubmitTransaction(user_transaction.clone(), req_sender)) + .await?; + + // tick the transaction pipe + transaction_pipe.tick().await?; + + // receive the callback + let (status, _vm_status_code) = callback.await??; + // dbg!(_vm_status_code); + assert_eq!(status.code, MempoolStatusCode::Accepted); + + // receive the transaction + let received_transaction = tx_receiver.recv().await.unwrap(); + assert_eq!(received_transaction, user_transaction); + + handle.abort(); + + Ok(()) + } +} diff --git a/protocol-units/execution/opt-executor/src/transaction_pipe.rs b/protocol-units/execution/opt-executor/src/transaction_pipe.rs new file mode 100644 index 000000000..d3680994c --- /dev/null +++ b/protocol-units/execution/opt-executor/src/transaction_pipe.rs @@ -0,0 +1,465 @@ +//! Task processing incoming transactions for the opt API. + +use aptos_config::config::NodeConfig; +use aptos_mempool::core_mempool::CoreMempool; +use aptos_mempool::SubmissionStatus; +use aptos_mempool::{core_mempool::TimelineState, MempoolClientRequest}; +use aptos_storage_interface::{state_view::LatestDbStateCheckpointView as _, DbReader}; +use aptos_types::mempool_status::{MempoolStatus, MempoolStatusCode}; +use aptos_types::transaction::SignedTransaction; +use aptos_types::vm_status::DiscardedVMStatus; +use aptos_vm_validator::vm_validator::{self, TransactionValidation, VMValidator}; + +use futures::channel::mpsc as futures_mpsc; +use futures::StreamExt; +use thiserror::Error; +use tokio::sync::mpsc; +use tracing::{debug, info, info_span, warn, Instrument}; + +use std::sync::{atomic::AtomicU64, Arc}; +use std::time::{Duration, Instant}; + +const GC_INTERVAL: Duration = Duration::from_secs(30); + +/// Domain error for the transaction pipe task +#[derive(Debug, Clone, Error)] +pub enum Error { + #[error("Transaction Pipe InternalError: {0}")] + InternalError(String), + #[error("Transaction not accepted: {0}")] + TransactionNotAccepted(MempoolStatus), + #[error("Transaction stream closed")] + InputClosed, +} + +impl From for Error { + fn from(e: anyhow::Error) -> Self { + Error::InternalError(e.to_string()) + } +} + +pub struct TransactionPipe { + // The receiver for the mempool client. + mempool_client_receiver: futures_mpsc::Receiver, + // Sender for the channel with accepted transactions. + transaction_sender: mpsc::Sender, + // Access to the ledger DB. TODO: reuse an instance of VMValidator + db_reader: Arc, + // State of the Aptos mempool + core_mempool: CoreMempool, + // Shared reference on the counter of transactions in flight. + transactions_in_flight: Arc, + // The configured limit on transactions in flight + in_flight_limit: u64, + // Timestamp of the last garbage collection + last_gc: Instant, +} + +impl TransactionPipe { + pub(crate) fn new( + mempool_client_receiver: futures_mpsc::Receiver, + transaction_sender: mpsc::Sender, + db_reader: Arc, + node_config: &NodeConfig, + transactions_in_flight: Arc, + transactions_in_flight_limit: u64, + ) -> Self { + TransactionPipe { + mempool_client_receiver, + transaction_sender, + db_reader, + core_mempool: CoreMempool::new(node_config), + transactions_in_flight, + in_flight_limit: transactions_in_flight_limit, + last_gc: Instant::now(), + } + } + + pub async fn run(mut self) -> Result<(), Error> { + loop { + self.tick().await?; + } + } + + /// Pipes a batch of transactions from the mempool to the transaction channel. + /// todo: it may be wise to move the batching logic up a level to the consuming structs. + pub(crate) async fn tick(&mut self) -> Result<(), Error> { + let next = self.mempool_client_receiver.next().await; + if let Some(request) = next { + match request { + MempoolClientRequest::SubmitTransaction(transaction, callback) => { + let span = info_span!( + target: "movement_timing", + "submit_transaction", + tx_hash = %transaction.committed_hash(), + sender = %transaction.sender(), + sequence_number = transaction.sequence_number(), + ); + let status = self.submit_transaction(transaction).instrument(span).await?; + callback.send(Ok(status)).unwrap_or_else(|_| { + debug!("SubmitTransaction request canceled"); + }); + } + MempoolClientRequest::GetTransactionByHash(hash, sender) => { + let mempool_result = self.core_mempool.get_by_hash(hash); + sender.send(mempool_result).unwrap_or_else(|_| { + debug!("GetTransactionByHash request canceled"); + }); + } + } + } + + if self.last_gc.elapsed() >= GC_INTERVAL { + self.core_mempool.gc(); + self.last_gc = Instant::now(); + } + + Ok(()) + } + + async fn submit_transaction( + &mut self, + transaction: SignedTransaction, + ) -> Result { + // For now, we are going to consider a transaction in flight until it exits the mempool and is sent to the DA as is indicated by WriteBatch. + let in_flight = self.transactions_in_flight.load(std::sync::atomic::Ordering::Relaxed); + info!( + target: "movement_timing", + in_flight = %in_flight, + "transactions_in_flight" + ); + if in_flight > self.in_flight_limit { + info!( + target: "movement_timing", + "shedding_load" + ); + let status = MempoolStatus::new(MempoolStatusCode::MempoolIsFull); + return Ok((status, None)); + } + + // Pre-execute Tx to validate its content. + // Re-create the validator for each Tx because it uses a frozen version of the ledger. + let vm_validator = VMValidator::new(Arc::clone(&self.db_reader)); + let tx_result = vm_validator.validate_transaction(transaction.clone())?; + match tx_result.status() { + Some(_) => { + let ms = MempoolStatus::new(MempoolStatusCode::VmError); + return Ok((ms, tx_result.status())); + } + None => {} + } + + // Retrieve the current sequence number for the account from the db + let state_view = self + .db_reader + .latest_state_checkpoint_view() + .expect("Failed to get latest state checkpoint view."); + let sequence_number = + vm_validator::get_account_sequence_number(&state_view, transaction.sender())?; + if transaction.sequence_number() < sequence_number { + let status = MempoolStatus::new(MempoolStatusCode::VmError); + return Ok((status, Some(DiscardedVMStatus::SEQUENCE_NUMBER_TOO_OLD))); + } + + debug!(%sequence_number, "adding transaction to mempool: {:?}", transaction); + let status = self.core_mempool.add_txn( + transaction.clone(), + 0, + sequence_number, + TimelineState::NonQualified, + true, + ); + + match status.code { + MempoolStatusCode::Accepted => { + debug!("Transaction accepted: {:?}", transaction); + let sender = transaction.sender(); + self.transaction_sender + .send(transaction) + .await + .map_err(|e| anyhow::anyhow!("Error sending transaction: {:?}", e))?; + // increment transactions in flight + self.transactions_in_flight.fetch_add(1, std::sync::atomic::Ordering::Relaxed); + self.core_mempool.commit_transaction(&sender, sequence_number); + } + _ => { + warn!("Transaction not accepted: {:?}", status); + } + } + + // report status + Ok((status, None)) + } +} + +#[cfg(test)] +mod tests { + + use std::collections::BTreeSet; + + use super::*; + use crate::{Executor, Service}; + use aptos_api::{accept_type::AcceptType, transactions::SubmitTransactionPost}; + use aptos_crypto::HashValue; + use aptos_mempool::MempoolClientSender; + use aptos_types::{ + account_config, + block_executor::partitioner::{ExecutableBlock, ExecutableTransactions}, + block_metadata::BlockMetadata, + test_helpers::transaction_test_helpers, + transaction::{ + signature_verified_transaction::SignatureVerifiedTransaction, SignedTransaction, + Transaction, + }, + }; + use aptos_vm_genesis::GENESIS_KEYPAIR; + use futures::channel::oneshot; + use futures::SinkExt; + use maptos_execution_util::config::chain::Config; + + fn setup() -> (TransactionPipe, MempoolClientSender, mpsc::Receiver) { + let (tx_sender, tx_receiver) = mpsc::channel(16); + let (executor, config, _tempdir) = + Executor::try_test_default(GENESIS_KEYPAIR.0.clone()).unwrap(); + let (context, transaction_pipe) = executor.background(tx_sender, &config).unwrap(); + (transaction_pipe, context.mempool_client_sender(), tx_receiver) + } + + fn create_signed_transaction(sequence_number: u64, chain_config: &Config) -> SignedTransaction { + let address = account_config::aptos_test_root_address(); + transaction_test_helpers::get_test_txn_with_chain_id( + address, + sequence_number, + &GENESIS_KEYPAIR.0, + GENESIS_KEYPAIR.1.clone(), + chain_config.maptos_chain_id.clone(), // This is the value used in aptos testing code. + ) + } + + #[tokio::test] + async fn test_pipe_mempool() -> Result<(), anyhow::Error> { + // set up + let maptos_config = Config::default(); + let (mut transaction_pipe, mut mempool_client_sender, mut tx_receiver) = setup(); + let user_transaction = create_signed_transaction(1, &maptos_config); + + // send transaction to mempool + let (req_sender, callback) = oneshot::channel(); + mempool_client_sender + .send(MempoolClientRequest::SubmitTransaction(user_transaction.clone(), req_sender)) + .await?; + + // tick the transaction pipe + transaction_pipe.tick().await?; + + // receive the callback + let (status, _vm_status_code) = callback.await??; + assert_eq!(status.code, MempoolStatusCode::Accepted); + + // receive the transaction + let received_transaction = tx_receiver.recv().await.unwrap(); + assert_eq!(received_transaction, user_transaction); + + Ok(()) + } + + #[tokio::test] + async fn test_pipe_mempool_cancellation() -> Result<(), anyhow::Error> { + // set up + let maptos_config = Config::default(); + let (mut transaction_pipe, mut mempool_client_sender, _tx_receiver) = setup(); + let user_transaction = create_signed_transaction(1, &maptos_config); + + // send transaction to mempool + let (req_sender, callback) = oneshot::channel(); + mempool_client_sender + .send(MempoolClientRequest::SubmitTransaction(user_transaction.clone(), req_sender)) + .await?; + + // drop the callback to simulate cancellation of the request + drop(callback); + + // tick the transaction pipe, should succeed + transaction_pipe.tick().await?; + + Ok(()) + } + + #[tokio::test] + async fn test_pipe_mempool_with_duplicate_transaction() -> Result<(), anyhow::Error> { + // set up + let maptos_config = Config::default(); + let (mut transaction_pipe, mut mempool_client_sender, mut tx_receiver) = setup(); + let user_transaction = create_signed_transaction(1, &maptos_config); + + // send transaction to mempool + let (req_sender, callback) = oneshot::channel(); + mempool_client_sender + .send(MempoolClientRequest::SubmitTransaction(user_transaction.clone(), req_sender)) + .await?; + + // tick the transaction pipe + transaction_pipe.tick().await?; + + // receive the callback + let (status, _vm_status_code) = callback.await??; + assert_eq!(status.code, MempoolStatusCode::Accepted); + + // receive the transaction + let received_transaction = tx_receiver.recv().await.unwrap(); + assert_eq!(received_transaction, user_transaction); + + // send the same transaction again + let (req_sender, callback) = oneshot::channel(); + mempool_client_sender + .send(MempoolClientRequest::SubmitTransaction(user_transaction.clone(), req_sender)) + .await?; + + // tick the transaction pipe + transaction_pipe.tick().await?; + + callback.await??; + + let received_transaction = tx_receiver.recv().await.unwrap(); + assert_eq!(received_transaction, user_transaction); + + Ok(()) + } + + #[tokio::test] + async fn test_pipe_mempool_from_api() -> Result<(), anyhow::Error> { + let (tx_sender, mut tx_receiver) = mpsc::channel(16); + let (executor, config, _tempdir) = Executor::try_test_default(GENESIS_KEYPAIR.0.clone())?; + let (context, mut transaction_pipe) = executor.background(tx_sender, &config)?; + let service = Service::new(&context); + + #[allow(unreachable_code)] + let mempool_handle = tokio::spawn(async move { + loop { + transaction_pipe.tick().await?; + } + Ok(()) as Result<(), anyhow::Error> + }); + + let api = service.get_apis(); + let user_transaction = create_signed_transaction(1, &context.config().chain); + let comparison_user_transaction = user_transaction.clone(); + let bcs_user_transaction = bcs::to_bytes(&user_transaction)?; + let request = SubmitTransactionPost::Bcs(aptos_api::bcs_payload::Bcs(bcs_user_transaction)); + api.transactions.submit_transaction(AcceptType::Bcs, request).await?; + let received_transaction = tx_receiver.recv().await.unwrap(); + assert_eq!(received_transaction, comparison_user_transaction); + + mempool_handle.abort(); + + Ok(()) + } + + #[tokio::test] + async fn test_repeated_pipe_mempool_from_api() -> Result<(), anyhow::Error> { + let (tx_sender, mut tx_receiver) = mpsc::channel(16); + let (executor, config, _tempdir) = Executor::try_test_default(GENESIS_KEYPAIR.0.clone())?; + let (context, mut transaction_pipe) = executor.background(tx_sender, &config)?; + let service = Service::new(&context); + + #[allow(unreachable_code)] + let mempool_handle = tokio::spawn(async move { + loop { + transaction_pipe.tick().await?; + } + Ok(()) as Result<(), anyhow::Error> + }); + + let api = service.get_apis(); + let mut user_transactions = BTreeSet::new(); + let mut comparison_user_transactions = BTreeSet::new(); + for i in 1..25 { + let user_transaction = create_signed_transaction(i, &context.config().chain); + let bcs_user_transaction = bcs::to_bytes(&user_transaction)?; + user_transactions.insert(bcs_user_transaction.clone()); + + let request = + SubmitTransactionPost::Bcs(aptos_api::bcs_payload::Bcs(bcs_user_transaction)); + api.transactions.submit_transaction(AcceptType::Bcs, request).await?; + + let received_transaction = tx_receiver.recv().await.unwrap(); + let bcs_received_transaction = bcs::to_bytes(&received_transaction)?; + comparison_user_transactions.insert(bcs_received_transaction.clone()); + } + + assert_eq!(user_transactions.len(), comparison_user_transactions.len()); + assert_eq!(user_transactions, comparison_user_transactions); + + mempool_handle.abort(); + + Ok(()) + } + + #[tokio::test] + async fn test_sequence_number_too_old() -> Result<(), anyhow::Error> { + let (tx_sender, _tx_receiver) = mpsc::channel(16); + let (executor, config, _tempdir) = Executor::try_test_default(GENESIS_KEYPAIR.0.clone())?; + let (context, mut transaction_pipe) = executor.background(tx_sender, &config)?; + + #[allow(unreachable_code)] + let mempool_handle = tokio::spawn(async move { + loop { + transaction_pipe.tick().await?; + } + Ok(()) as Result<(), anyhow::Error> + }); + + let tx = create_signed_transaction(0, &context.config().chain); + + // Commit the first transaction to a block + let block_id = HashValue::random(); + let block_metadata = Transaction::BlockMetadata(BlockMetadata::new( + block_id, + 0, + 0, + executor.signer.author(), + vec![], + vec![], + chrono::Utc::now().timestamp_micros() as u64, + )); + let txs = ExecutableTransactions::Unsharded( + [block_metadata, Transaction::UserTransaction(tx)] + .into_iter() + .map(SignatureVerifiedTransaction::Valid) + .collect(), + ); + let block = ExecutableBlock::new(block_id.clone(), txs); + executor.execute_block(block).await?; + + { + let state_view = executor + .db_reader() + .latest_state_checkpoint_view() + .expect("Failed to get latest state checkpoint view."); + let account_address = account_config::aptos_test_root_address(); + let sequence_number = + vm_validator::get_account_sequence_number(&state_view, account_address)?; + assert_eq!(sequence_number, 1); + } + + // Create another transaction using the already used sequence number + let tx = create_signed_transaction(0, &context.config().chain); + + // send the transaction to mempool + let (req_sender, callback) = oneshot::channel(); + context + .mempool_client_sender() + .send(MempoolClientRequest::SubmitTransaction(tx, req_sender)) + .await?; + + let status = callback.await??; + + assert_eq!(status.0.code, MempoolStatusCode::VmError); + let vm_status = status.1.unwrap(); + assert_eq!(vm_status, DiscardedVMStatus::SEQUENCE_NUMBER_TOO_OLD); + + mempool_handle.abort(); + + Ok(()) + } +} diff --git a/protocol-units/execution/util/src/config/common.rs b/protocol-units/execution/util/src/config/common.rs index 7bac72def..1cb241a5f 100644 --- a/protocol-units/execution/util/src/config/common.rs +++ b/protocol-units/execution/util/src/config/common.rs @@ -106,6 +106,20 @@ env_default!( 30734 ); +env_default!( + default_maptos_indexer_grpc_inactivity_timeout, + "MAPTOS_INDEXER_GRPC_INACTIVITY_TIMEOUT_SEC", + u64, + 60 +); + +env_default!( + default_maptos_indexer_grpc_ping_interval, + "MAPTOS_INDEXER_GRPC_PING_INTERVAL_SEC", + u64, + 10 +); + env_default!( default_postgres_connection_string, "INDEXER_PROCESSOR_POSTGRES_CONNECTION_STRING", @@ -119,3 +133,5 @@ env_default!( String, "auth_token".to_string() ); + +env_default!(default_max_transactions_in_flight, "MAPTOS_MAX_TRANSACTIONS_IN_FLIGHT", u64, 12000); diff --git a/protocol-units/execution/util/src/config/indexer.rs b/protocol-units/execution/util/src/config/indexer.rs index 3514e652f..6384a59ea 100644 --- a/protocol-units/execution/util/src/config/indexer.rs +++ b/protocol-units/execution/util/src/config/indexer.rs @@ -1,17 +1,26 @@ use super::common::{ - default_maptos_indexer_grpc_listen_hostname, default_maptos_indexer_grpc_listen_port, + default_maptos_indexer_grpc_inactivity_timeout, default_maptos_indexer_grpc_listen_hostname, + default_maptos_indexer_grpc_listen_port, default_maptos_indexer_grpc_ping_interval, }; use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct Config { - /// The URL of the Aptos gRPC indexer server + /// The URL of the Suzuka node gRPC indexer server #[serde(default = "default_maptos_indexer_grpc_listen_hostname")] pub maptos_indexer_grpc_listen_hostname: String, - /// The port of the Aptos gRPC indexer server + /// The port of the Suzuka node gRPC indexer server #[serde(default = "default_maptos_indexer_grpc_listen_port")] pub maptos_indexer_grpc_listen_port: u16, + + /// Inactivity timeout of the gRpc connection + #[serde(default = "default_maptos_indexer_grpc_inactivity_timeout")] + pub maptos_indexer_grpc_inactivity_timeout: u64, + + /// Ping interval of the gRpc connection + #[serde(default = "default_maptos_indexer_grpc_ping_interval")] + pub maptos_indexer_grpc_inactivity_ping_interval: u64, } impl Default for Config { @@ -19,6 +28,10 @@ impl Default for Config { Self { maptos_indexer_grpc_listen_hostname: default_maptos_indexer_grpc_listen_hostname(), maptos_indexer_grpc_listen_port: default_maptos_indexer_grpc_listen_port(), + maptos_indexer_grpc_inactivity_timeout: default_maptos_indexer_grpc_inactivity_timeout( + ), + maptos_indexer_grpc_inactivity_ping_interval: default_maptos_indexer_grpc_ping_interval( + ), } } } diff --git a/protocol-units/execution/util/src/config/load_shedding.rs b/protocol-units/execution/util/src/config/load_shedding.rs new file mode 100644 index 000000000..4df6e7252 --- /dev/null +++ b/protocol-units/execution/util/src/config/load_shedding.rs @@ -0,0 +1,19 @@ +//! Configuration for load-sheding limits. + +use super::common::default_max_transactions_in_flight; + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct Config { + /// The maximum number of transactions permitted to be in flight + /// before new transactions are rejected. + #[serde(default = "default_max_transactions_in_flight")] + pub max_transactions_in_flight: u64, +} + +impl Default for Config { + fn default() -> Self { + Self { max_transactions_in_flight: default_max_transactions_in_flight() } + } +} diff --git a/protocol-units/execution/util/src/config/mod.rs b/protocol-units/execution/util/src/config/mod.rs index 84a58f85b..9763d4a6d 100644 --- a/protocol-units/execution/util/src/config/mod.rs +++ b/protocol-units/execution/util/src/config/mod.rs @@ -5,6 +5,8 @@ pub mod faucet; pub mod fin; pub mod indexer; pub mod indexer_processor; +pub mod load_shedding; + use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] @@ -32,6 +34,10 @@ pub struct Config { /// The fin configuration #[serde(default)] pub fin: fin::Config, + + /// The load shedding parameters + #[serde(default)] + pub load_shedding: load_shedding::Config, } impl Default for Config { @@ -43,6 +49,7 @@ impl Default for Config { client: client::Config::default(), faucet: faucet::Config::default(), fin: fin::Config::default(), + load_shedding: load_shedding::Config::default(), } } } diff --git a/protocol-units/mempool/move-rocks/src/lib.rs b/protocol-units/mempool/move-rocks/src/lib.rs index d28cc57a0..96315085e 100644 --- a/protocol-units/mempool/move-rocks/src/lib.rs +++ b/protocol-units/mempool/move-rocks/src/lib.rs @@ -1,7 +1,10 @@ use anyhow::Error; use bcs; use mempool_util::{MempoolBlockOperations, MempoolTransaction, MempoolTransactionOperations}; -use movement_types::{Block, Id}; +use movement_types::{ + block::{self, Block}, + transaction, +}; use rocksdb::{ColumnFamilyDescriptor, Options, DB}; use std::fmt::Write; use std::sync::Arc; @@ -41,7 +44,7 @@ impl RocksdbMempool { key.write_fmt(format_args!( "{:032}:{:032}:{}", transaction.timestamp, - transaction.transaction.sequence_number, + transaction.transaction.sequence_number(), transaction.transaction.id(), )) .unwrap(); @@ -50,7 +53,7 @@ impl RocksdbMempool { fn internal_get_mempool_transaction_key( db: Arc, - transaction_id: &Id, + transaction_id: transaction::Id, ) -> Result>, Error> { let cf_handle = db .cf_handle("transaction_lookups") @@ -61,17 +64,20 @@ impl RocksdbMempool { /// Helper function to retrieve the key for mempool transaction from the lookup table. async fn get_mempool_transaction_key( &self, - transaction_id: &Id, + transaction_id: transaction::Id, ) -> Result>, Error> { let db = self.db.clone(); let transaction_id = transaction_id.clone(); tokio::task::spawn_blocking(move || { - Self::internal_get_mempool_transaction_key(db, &transaction_id) + Self::internal_get_mempool_transaction_key(db, transaction_id) }) .await? } - fn internal_has_mempool_transaction(db: Arc, transaction_id: &Id) -> Result { + fn internal_has_mempool_transaction( + db: Arc, + transaction_id: transaction::Id, + ) -> Result { let key = Self::internal_get_mempool_transaction_key(db.clone(), transaction_id)?; match key { Some(k) => { @@ -86,10 +92,13 @@ impl RocksdbMempool { } impl MempoolTransactionOperations for RocksdbMempool { - async fn has_mempool_transaction(&self, transaction_id: Id) -> Result { + async fn has_mempool_transaction( + &self, + transaction_id: transaction::Id, + ) -> Result { let db = self.db.clone(); tokio::task::spawn_blocking(move || { - Self::internal_has_mempool_transaction(db, &transaction_id) + Self::internal_has_mempool_transaction(db, transaction_id) }) .await? } @@ -107,15 +116,20 @@ impl MempoolTransactionOperations for RocksdbMempool { .cf_handle("transaction_lookups") .ok_or_else(|| Error::msg("CF handle not found"))?; - for tx in transactions { - if Self::internal_has_mempool_transaction(db.clone(), &tx.transaction.id())? { + for transaction in transactions { + if Self::internal_has_mempool_transaction(db.clone(), transaction.transaction.id())? + { continue; } - let serialized_tx = bcs::to_bytes(&tx)?; - let key = Self::construct_mempool_transaction_key(&tx); - db.put_cf(&mempool_transactions_cf_handle, &key, &serialized_tx)?; - db.put_cf(&transaction_lookups_cf_handle, tx.transaction.id().to_vec(), &key)?; + let serialized_transaction = bcs::to_bytes(&transaction)?; + let key = Self::construct_mempool_transaction_key(&transaction); + db.put_cf(&mempool_transactions_cf_handle, &key, &serialized_transaction)?; + db.put_cf( + &transaction_lookups_cf_handle, + transaction.transaction.id().to_vec(), + &key, + )?; } Ok::<(), Error>(()) }) @@ -123,8 +137,8 @@ impl MempoolTransactionOperations for RocksdbMempool { Ok(()) } - async fn add_mempool_transaction(&self, tx: MempoolTransaction) -> Result<(), Error> { - let serialized_tx = bcs::to_bytes(&tx)?; + async fn add_mempool_transaction(&self, transaction: MempoolTransaction) -> Result<(), Error> { + let serialized_transaction = bcs::to_bytes(&transaction)?; let db = self.db.clone(); tokio::task::spawn_blocking(move || { @@ -135,9 +149,9 @@ impl MempoolTransactionOperations for RocksdbMempool { .cf_handle("transaction_lookups") .ok_or_else(|| Error::msg("CF handle not found"))?; - let key = Self::construct_mempool_transaction_key(&tx); - db.put_cf(&mempool_transactions_cf_handle, &key, &serialized_tx)?; - db.put_cf(&transaction_lookups_cf_handle, tx.transaction.id().to_vec(), &key)?; + let key = Self::construct_mempool_transaction_key(&transaction); + db.put_cf(&mempool_transactions_cf_handle, &key, &serialized_transaction)?; + db.put_cf(&transaction_lookups_cf_handle, transaction.transaction.id().to_vec(), &key)?; Ok::<(), Error>(()) }) .await??; @@ -145,8 +159,11 @@ impl MempoolTransactionOperations for RocksdbMempool { Ok(()) } - async fn remove_mempool_transaction(&self, transaction_id: Id) -> Result<(), Error> { - let key = self.get_mempool_transaction_key(&transaction_id).await?; + async fn remove_mempool_transaction( + &self, + transaction_id: transaction::Id, + ) -> Result<(), Error> { + let key = self.get_mempool_transaction_key(transaction_id).await?; let db = self.db.clone(); tokio::task::spawn_blocking(move || { match key { @@ -171,9 +188,9 @@ impl MempoolTransactionOperations for RocksdbMempool { // Updated method signatures and implementations go here async fn get_mempool_transaction( &self, - transaction_id: Id, + transaction_id: transaction::Id, ) -> Result, Error> { - let key = match self.get_mempool_transaction_key(&transaction_id).await? { + let key = match self.get_mempool_transaction_key(transaction_id).await? { Some(k) => k, None => return Ok(None), // If no key found in lookup, return None }; @@ -183,9 +200,9 @@ impl MempoolTransactionOperations for RocksdbMempool { .cf_handle("mempool_transactions") .ok_or_else(|| Error::msg("CF handle not found"))?; match db.get_cf(&cf_handle, &key)? { - Some(serialized_tx) => { - let tx: MempoolTransaction = bcs::from_bytes(&serialized_tx)?; - Ok(Some(tx)) + Some(serialized_transaction) => { + let transaction: MempoolTransaction = bcs::from_bytes(&serialized_transaction)?; + Ok(Some(transaction)) } None => Ok(None), } @@ -205,16 +222,16 @@ impl MempoolTransactionOperations for RocksdbMempool { None => return Ok(None), // No transactions to pop Some(res) => { let (key, value) = res?; - let tx: MempoolTransaction = bcs::from_bytes(&value)?; + let transaction: MempoolTransaction = bcs::from_bytes(&value)?; db.delete_cf(&cf_handle, &key)?; // Optionally, remove from the lookup table as well let lookups_cf_handle = db .cf_handle("transaction_lookups") .ok_or_else(|| Error::msg("CF handle not found"))?; - db.delete_cf(&lookups_cf_handle, tx.transaction.id().to_vec())?; + db.delete_cf(&lookups_cf_handle, transaction.transaction.id().to_vec())?; - Ok(Some(tx)) + Ok(Some(transaction)) } } }) @@ -235,16 +252,16 @@ impl MempoolTransactionOperations for RocksdbMempool { let mut mempool_transactions = Vec::with_capacity(n as usize); while let Some(res) = iter.next() { let (key, value) = res?; - let tx: MempoolTransaction = bcs::from_bytes(&value)?; + let transaction: MempoolTransaction = bcs::from_bytes(&value)?; db.delete_cf(&cf_handle, &key)?; // Optionally, remove from the lookup table as well let lookups_cf_handle = db .cf_handle("transaction_lookups") .ok_or_else(|| Error::msg("CF handle not found"))?; - db.delete_cf(&lookups_cf_handle, tx.transaction.id().to_vec())?; + db.delete_cf(&lookups_cf_handle, transaction.transaction.id().to_vec())?; - mempool_transactions.push(tx); + mempool_transactions.push(transaction); if mempool_transactions.len() > n - 1 { break; } @@ -256,7 +273,7 @@ impl MempoolTransactionOperations for RocksdbMempool { } impl MempoolBlockOperations for RocksdbMempool { - async fn has_block(&self, block_id: Id) -> Result { + async fn has_block(&self, block_id: block::Id) -> Result { let db = self.db.clone(); tokio::task::spawn_blocking(move || { let cf_handle = @@ -278,7 +295,7 @@ impl MempoolBlockOperations for RocksdbMempool { .await? } - async fn remove_block(&self, block_id: Id) -> Result<(), Error> { + async fn remove_block(&self, block_id: block::Id) -> Result<(), Error> { let db = self.db.clone(); tokio::task::spawn_blocking(move || { let cf_handle = @@ -289,7 +306,7 @@ impl MempoolBlockOperations for RocksdbMempool { .await? } - async fn get_block(&self, block_id: Id) -> Result, Error> { + async fn get_block(&self, block_id: block::Id) -> Result, Error> { let db = self.db.clone(); tokio::task::spawn_blocking(move || { let cf_handle = @@ -311,7 +328,7 @@ impl MempoolBlockOperations for RocksdbMempool { pub mod test { use super::*; - use movement_types::Transaction; + use movement_types::transaction::Transaction; use tempfile::tempdir; #[tokio::test] @@ -320,21 +337,21 @@ pub mod test { let path = temp_dir.path().to_str().unwrap(); let mempool = RocksdbMempool::try_new(path)?; - let tx = MempoolTransaction::test(); - let tx_id = tx.id(); - mempool.add_mempool_transaction(tx.clone()).await?; - assert!(mempool.has_mempool_transaction(tx_id.clone()).await?); - let tx2 = mempool.get_mempool_transaction(tx_id.clone()).await?; - assert_eq!(Some(tx), tx2); - mempool.remove_mempool_transaction(tx_id.clone()).await?; - assert!(!mempool.has_mempool_transaction(tx_id.clone()).await?); + let transaction = MempoolTransaction::test(); + let transaction_id = transaction.id(); + mempool.add_mempool_transaction(transaction.clone()).await?; + assert!(mempool.has_mempool_transaction(transaction_id.clone()).await?); + let transaction2 = mempool.get_mempool_transaction(transaction_id.clone()).await?; + assert_eq!(Some(transaction.clone()), transaction2); + mempool.remove_mempool_transaction(transaction_id.clone()).await?; + assert!(!mempool.has_mempool_transaction(transaction_id.clone()).await?); let block = Block::test(); let block_id = block.id(); mempool.add_block(block.clone()).await?; assert!(mempool.has_block(block_id.clone()).await?); let block2 = mempool.get_block(block_id.clone()).await?; - assert_eq!(Some(block), block2); + assert_eq!(Some(block.clone()), block2); mempool.remove_block(block_id.clone()).await?; assert!(!mempool.has_block(block_id.clone()).await?); @@ -347,14 +364,14 @@ pub mod test { let path = temp_dir.path().to_str().unwrap(); let mempool = RocksdbMempool::try_new(path)?; - let tx = Transaction::test(); - let tx_id = tx.id(); - mempool.add_transaction(tx.clone()).await?; - assert!(mempool.has_transaction(tx_id.clone()).await?); - let tx2 = mempool.get_transaction(tx_id.clone()).await?; - assert_eq!(Some(tx), tx2); - mempool.remove_transaction(tx_id.clone()).await?; - assert!(!mempool.has_transaction(tx_id.clone()).await?); + let transaction = Transaction::test(); + let transaction_id = transaction.id(); + mempool.add_transaction(transaction.clone()).await?; + assert!(mempool.has_transaction(transaction_id.clone()).await?); + let transaction2 = mempool.get_transaction(transaction_id.clone()).await?; + assert_eq!(Some(transaction.clone()), transaction2); + mempool.remove_transaction(transaction_id.clone()).await?; + assert!(!mempool.has_transaction(transaction_id.clone()).await?); Ok(()) } @@ -365,18 +382,18 @@ pub mod test { let path = temp_dir.path().to_str().unwrap(); let mempool = RocksdbMempool::try_new(path)?; - let tx1 = MempoolTransaction::at_time(Transaction::new(vec![1], 0), 2); - let tx2 = MempoolTransaction::at_time(Transaction::new(vec![2], 0), 64); - let tx3 = MempoolTransaction::at_time(Transaction::new(vec![3], 0), 128); + let transaction1 = MempoolTransaction::at_time(Transaction::new(vec![1], 0), 2); + let transaction2 = MempoolTransaction::at_time(Transaction::new(vec![2], 0), 64); + let transaction3 = MempoolTransaction::at_time(Transaction::new(vec![3], 0), 128); - mempool.add_mempool_transaction(tx2.clone()).await?; - mempool.add_mempool_transaction(tx1.clone()).await?; - mempool.add_mempool_transaction(tx3.clone()).await?; + mempool.add_mempool_transaction(transaction2.clone()).await?; + mempool.add_mempool_transaction(transaction1.clone()).await?; + mempool.add_mempool_transaction(transaction3.clone()).await?; - let txs = mempool.pop_mempool_transactions(3).await?; - assert_eq!(txs[0], tx1); - assert_eq!(txs[1], tx2); - assert_eq!(txs[2], tx3); + let transactions = mempool.pop_mempool_transactions(3).await?; + assert_eq!(transactions[0], transaction1); + assert_eq!(transactions[1], transaction2); + assert_eq!(transactions[2], transaction3); Ok(()) } @@ -387,18 +404,18 @@ pub mod test { let path = temp_dir.path().to_str().unwrap(); let mempool = RocksdbMempool::try_new(path)?; - let tx1 = MempoolTransaction::at_time(Transaction::new(vec![1], 0), 2); - let tx2 = MempoolTransaction::at_time(Transaction::new(vec![2], 1), 2); - let tx3 = MempoolTransaction::at_time(Transaction::new(vec![3], 0), 64); + let transaction1 = MempoolTransaction::at_time(Transaction::new(vec![1], 0), 2); + let transaction2 = MempoolTransaction::at_time(Transaction::new(vec![2], 1), 2); + let transaction3 = MempoolTransaction::at_time(Transaction::new(vec![3], 0), 64); - mempool.add_mempool_transaction(tx2.clone()).await?; - mempool.add_mempool_transaction(tx1.clone()).await?; - mempool.add_mempool_transaction(tx3.clone()).await?; + mempool.add_mempool_transaction(transaction2.clone()).await?; + mempool.add_mempool_transaction(transaction1.clone()).await?; + mempool.add_mempool_transaction(transaction3.clone()).await?; - let txs = mempool.pop_mempool_transactions(3).await?; - assert_eq!(txs[0], tx1); - assert_eq!(txs[1], tx2); - assert_eq!(txs[2], tx3); + let transactions = mempool.pop_mempool_transactions(3).await?; + assert_eq!(transactions[0], transaction1); + assert_eq!(transactions[1], transaction2); + assert_eq!(transactions[2], transaction3); Ok(()) } @@ -409,18 +426,18 @@ pub mod test { let path = temp_dir.path().to_str().unwrap(); let mempool = RocksdbMempool::try_new(path)?; - let tx1 = MempoolTransaction::at_time(Transaction::new(vec![1], 0), 0); - let tx2 = MempoolTransaction::at_time(Transaction::new(vec![2], 1), 0); - let tx3 = MempoolTransaction::at_time(Transaction::new(vec![3], 2), 0); + let transaction1 = MempoolTransaction::at_time(Transaction::new(vec![1], 0), 0); + let transaction2 = MempoolTransaction::at_time(Transaction::new(vec![2], 1), 0); + let transaction3 = MempoolTransaction::at_time(Transaction::new(vec![3], 2), 0); - mempool.add_mempool_transaction(tx2.clone()).await?; - mempool.add_mempool_transaction(tx1.clone()).await?; - mempool.add_mempool_transaction(tx3.clone()).await?; + mempool.add_mempool_transaction(transaction2.clone()).await?; + mempool.add_mempool_transaction(transaction1.clone()).await?; + mempool.add_mempool_transaction(transaction3.clone()).await?; - let txs = mempool.pop_mempool_transactions(3).await?; - assert_eq!(txs[0], tx1); - assert_eq!(txs[1], tx2); - assert_eq!(txs[2], tx3); + let transactions = mempool.pop_mempool_transactions(3).await?; + assert_eq!(transactions[0], transaction1); + assert_eq!(transactions[1], transaction2); + assert_eq!(transactions[2], transaction3); Ok(()) } diff --git a/protocol-units/mempool/util/src/lib.rs b/protocol-units/mempool/util/src/lib.rs index 6d407ff45..81f24b210 100644 --- a/protocol-units/mempool/util/src/lib.rs +++ b/protocol-units/mempool/util/src/lib.rs @@ -1,6 +1,9 @@ use serde::{Deserialize, Serialize}; -use movement_types::{Block, Id, Transaction}; +use movement_types::{ + block::{self, Block}, + transaction::{self, Transaction}, +}; use std::cmp::Ordering; pub trait MempoolTransactionOperations { @@ -12,13 +15,22 @@ pub trait MempoolTransactionOperations { ) -> Result<(), anyhow::Error>; /// Checks whether a mempool transaction exists in the mempool. - async fn has_mempool_transaction(&self, transaction_id: Id) -> Result; + async fn has_mempool_transaction( + &self, + transaction_id: transaction::Id, + ) -> Result; /// Adds a mempool transaction to the mempool. - async fn add_mempool_transaction(&self, tx: MempoolTransaction) -> Result<(), anyhow::Error>; + async fn add_mempool_transaction( + &self, + transaction: MempoolTransaction, + ) -> Result<(), anyhow::Error>; /// Removes a mempool transaction from the mempool. - async fn remove_mempool_transaction(&self, transaction_id: Id) -> Result<(), anyhow::Error>; + async fn remove_mempool_transaction( + &self, + transaction_id: transaction::Id, + ) -> Result<(), anyhow::Error>; /// Pops mempool transaction from the mempool. async fn pop_mempool_transaction(&self) -> Result, anyhow::Error>; @@ -26,7 +38,7 @@ pub trait MempoolTransactionOperations { /// Gets a mempool transaction from the mempool. async fn get_mempool_transaction( &self, - transaction_id: Id, + transaction_id: transaction::Id, ) -> Result, anyhow::Error>; /// Pops the next n mempool transactions from the mempool. @@ -46,7 +58,10 @@ pub trait MempoolTransactionOperations { } /// Checks whether the mempool has the transaction. - async fn has_transaction(&self, transaction_id: Id) -> Result { + async fn has_transaction( + &self, + transaction_id: transaction::Id, + ) -> Result { self.has_mempool_transaction(transaction_id).await } @@ -57,17 +72,20 @@ pub trait MempoolTransactionOperations { } /// Adds a transaction to the mempool. - async fn add_transaction(&self, tx: Transaction) -> Result<(), anyhow::Error> { - if self.has_transaction(tx.id()).await? { + async fn add_transaction(&self, transaction: Transaction) -> Result<(), anyhow::Error> { + if self.has_transaction(transaction.id()).await? { return Ok(()); } - let mempool_transaction = MempoolTransaction::slot_now(tx); + let mempool_transaction = MempoolTransaction::slot_now(transaction); self.add_mempool_transaction(mempool_transaction).await } /// Removes a transaction from the mempool. - async fn remove_transaction(&self, transaction_id: Id) -> Result<(), anyhow::Error> { + async fn remove_transaction( + &self, + transaction_id: transaction::Id, + ) -> Result<(), anyhow::Error> { self.remove_mempool_transaction(transaction_id).await } @@ -80,7 +98,7 @@ pub trait MempoolTransactionOperations { /// Gets a transaction from the mempool. async fn get_transaction( &self, - transaction_id: Id, + transaction_id: transaction::Id, ) -> Result, anyhow::Error> { let mempool_transaction = self.get_mempool_transaction(transaction_id).await?; Ok(mempool_transaction.map(|mempool_transaction| mempool_transaction.transaction)) @@ -98,16 +116,16 @@ pub trait MempoolTransactionOperations { pub trait MempoolBlockOperations { /// Checks whether a block exists in the mempool. - async fn has_block(&self, block_id: Id) -> Result; + async fn has_block(&self, block_id: block::Id) -> Result; /// Adds a block to the mempool. async fn add_block(&self, block: Block) -> Result<(), anyhow::Error>; /// Removes a block from the mempool. - async fn remove_block(&self, block_id: Id) -> Result<(), anyhow::Error>; + async fn remove_block(&self, block_id: block::Id) -> Result<(), anyhow::Error>; /// Gets a block from the mempool. - async fn get_block(&self, block_id: Id) -> Result, anyhow::Error>; + async fn get_block(&self, block_id: block::Id) -> Result, anyhow::Error>; } /// Wraps a transaction with a timestamp for help ordering. @@ -134,13 +152,7 @@ impl Ord for MempoolTransaction { non_equal => return non_equal, } - // If slot_seconds are equal, then compare by sequence number - match self.transaction.sequence_number.cmp(&other.transaction.sequence_number) { - Ordering::Equal => {} - non_equal => return non_equal, - } - - // If sequence number is equal, then compare by transaction on the whole + // If slots seconds are equal, then compare by transaction on the whole self.transaction.cmp(&other.transaction) } } @@ -173,7 +185,7 @@ impl MempoolTransaction { Self::at_time(transaction, timestamp) } - pub fn id(&self) -> Id { + pub fn id(&self) -> transaction::Id { self.transaction.id() } } diff --git a/protocol-units/movement-rest/Cargo.toml b/protocol-units/movement-rest/Cargo.toml index 5835000cd..cf8c4eadc 100644 --- a/protocol-units/movement-rest/Cargo.toml +++ b/protocol-units/movement-rest/Cargo.toml @@ -14,6 +14,7 @@ path = "src/lib.rs" [dependencies] anyhow = { workspace = true } +futures = { workspace = true } poem = { workspace = true } tokio = { workspace = true } tracing = { workspace = true } diff --git a/protocol-units/movement-rest/src/lib.rs b/protocol-units/movement-rest/src/lib.rs index c941c3584..f2e5ee4cf 100644 --- a/protocol-units/movement-rest/src/lib.rs +++ b/protocol-units/movement-rest/src/lib.rs @@ -1,5 +1,7 @@ -use anyhow::Error; use aptos_api::Context; + +use anyhow::Error; +use futures::prelude::*; use poem::listener::TcpListener; use poem::{ get, handler, @@ -7,9 +9,11 @@ use poem::{ web::{Data, Path}, EndpointExt, IntoResponse, Response, Route, Server, }; +use tracing::info; + use std::env; +use std::future::Future; use std::sync::Arc; -use tracing::info; #[derive(Debug)] pub struct MovementRest { @@ -22,17 +26,22 @@ pub struct MovementRest { impl MovementRest { pub const MOVEMENT_REST_ENV_VAR: &'static str = "MOVEMENT_REST_URL"; - pub fn try_from_env(context: Option>) -> Result { + pub fn try_from_env() -> Result { let url = env::var(Self::MOVEMENT_REST_ENV_VAR) .unwrap_or_else(|_| "http://0.0.0.0:30832".to_string()); - Ok(Self { url, context }) + Ok(Self { url, context: None }) + } + + pub fn set_context(&mut self, context: Arc) { + self.context = Some(context); } - pub async fn run_service(&self) -> Result<(), Error> { + pub fn run_service(&self) -> impl Future> + Send { info!("Starting movement rest service at {}", self.url); let movement_rest = self.create_routes(); - Server::new(TcpListener::bind(&self.url)).run(movement_rest).await?; - Ok(()) + Server::new(TcpListener::bind(self.url.clone())) + .run(movement_rest) + .map_err(Into::into) } pub fn create_routes(&self) -> impl EndpointExt { @@ -84,7 +93,7 @@ mod tests { #[tokio::test] async fn test_health_endpoint() { - let rest_service = MovementRest::try_from_env(None).expect("Failed to create MovementRest"); + let rest_service = MovementRest::try_from_env().expect("Failed to create MovementRest"); assert_eq!(rest_service.url, "http://0.0.0.0:30832"); // Create a test client let client = TestClient::new(rest_service.create_routes()); diff --git a/protocol-units/sequencing/memseq/sequencer/src/lib.rs b/protocol-units/sequencing/memseq/sequencer/src/lib.rs index 89cdfcadb..3116a1bf4 100644 --- a/protocol-units/sequencing/memseq/sequencer/src/lib.rs +++ b/protocol-units/sequencing/memseq/sequencer/src/lib.rs @@ -1,7 +1,11 @@ use mempool_util::{MempoolBlockOperations, MempoolTransactionOperations}; pub use move_rocks::RocksdbMempool; -pub use movement_types::{Block, Id, Transaction}; +pub use movement_types::{ + block::{self, Block}, + transaction::{self, Transaction}, +}; pub use sequencing_util::Sequencer; +use std::collections::BTreeSet; use std::{path::PathBuf, sync::Arc}; use tokio::sync::RwLock; @@ -10,7 +14,7 @@ pub struct Memseq { mempool: T, // this value should not be changed after initialization block_size: u32, - pub parent_block: Arc>, + pub parent_block: Arc>, // this value should not be changed after initialization building_time_ms: u64, } @@ -19,7 +23,7 @@ impl Memseq { pub fn new( mempool: T, block_size: u32, - parent_block: Arc>, + parent_block: Arc>, building_time_ms: u64, ) -> Self { Self { mempool, block_size, parent_block, building_time_ms } @@ -44,12 +48,12 @@ impl Memseq { pub fn try_move_rocks( path: PathBuf, block_size: u32, - building_time_ms: u64, + building_time_ms: u64, ) -> Result { let mempool = RocksdbMempool::try_new( path.to_str().ok_or(anyhow::anyhow!("PathBuf to str failed"))?, )?; - let parent_block = Arc::new(RwLock::new(Id::default())); + let parent_block = Arc::new(RwLock::new(block::Id::default())); Ok(Self::new(mempool, block_size, parent_block, building_time_ms)) } @@ -72,7 +76,7 @@ impl Sequencer for Mem async fn wait_for_next_block(&self) -> Result, anyhow::Error> { let mut transactions = Vec::with_capacity(self.block_size as usize); - let mut now = std::time::Instant::now(); + let now = std::time::Instant::now(); loop { let current_block_size = transactions.len() as u32; @@ -97,7 +101,7 @@ impl Sequencer for Mem } else { let new_block = { let parent_block = self.parent_block.read().await.clone(); - Block::new(Default::default(), parent_block.to_vec(), transactions) + Block::new(Default::default(), parent_block, BTreeSet::from_iter(transactions)) }; // update the parent block @@ -124,7 +128,9 @@ pub mod test { async fn test_wait_for_next_block_building_time_expires() -> Result<(), anyhow::Error> { let dir = tempdir()?; let path = dir.path().to_path_buf(); - let memseq = Memseq::try_move_rocks(path, 128, 250)?.with_block_size(10).with_building_time_ms(500); + let memseq = Memseq::try_move_rocks(path, 128, 250)? + .with_block_size(10) + .with_building_time_ms(500); // Add some transactions for i in 0..5 { @@ -138,7 +144,7 @@ pub mod test { assert!(block.is_some()); let block = block.ok_or(anyhow::anyhow!("Block not found"))?; - assert_eq!(block.transactions.len(), 5); + assert_eq!(block.transactions().len(), 5); Ok(()) } @@ -146,7 +152,7 @@ pub mod test { #[tokio::test] async fn test_publish_error_propagation() -> Result<(), anyhow::Error> { let mempool = MockMempool; - let parent_block = Arc::new(RwLock::new(Id::default())); + let parent_block = Arc::new(RwLock::new(block::Id::default())); let memseq = Memseq::new(mempool, 10, parent_block, 1000); let transaction = Transaction::new(vec![1, 2, 3], 0); @@ -156,7 +162,7 @@ pub mod test { let result = memseq.wait_for_next_block().await; assert!(result.is_err()); - assert_eq!(result.unwrap_err().to_string(), "Mock pop_transaction"); + assert_eq!(result.unwrap_err().to_string(), "Mock pop_mempool_transaction"); Ok(()) } @@ -215,22 +221,14 @@ pub mod test { async fn test_try_move_rocks() -> Result<(), anyhow::Error> { let dir = tempdir()?; let path = dir.path().to_path_buf(); - let memseq = Memseq::try_move_rocks( - path.clone(), - 1024, - 500, - )?; + let memseq = Memseq::try_move_rocks(path.clone(), 1024, 500)?; assert_eq!(memseq.block_size, 1024); assert_eq!(memseq.building_time_ms, 500); // Test invalid path let invalid_path = PathBuf::from(""); - let result = Memseq::try_move_rocks( - invalid_path, - 1024, - 500, - ); + let result = Memseq::try_move_rocks(invalid_path, 1024, 500); assert!(result.is_err()); Ok(()) @@ -246,7 +244,7 @@ pub mod test { )?; let block_size = 50; let building_time_ms = 2000; - let parent_block = Arc::new(RwLock::new(Id::default())); + let parent_block = Arc::new(RwLock::new(block::Id::default())); let memseq = Memseq::new(mem_pool, block_size, Arc::clone(&parent_block), building_time_ms); @@ -267,7 +265,7 @@ pub mod test { )?; let block_size = 50; let building_time_ms = 2000; - let parent_block = Arc::new(RwLock::new(Id::default())); + let parent_block = Arc::new(RwLock::new(block::Id::default())); let memseq = Memseq::new(mem_pool, block_size, Arc::clone(&parent_block), building_time_ms); @@ -288,7 +286,9 @@ pub mod test { async fn test_wait_for_next_block_no_transactions() -> Result<(), anyhow::Error> { let dir = tempdir()?; let path = dir.path().to_path_buf(); - let memseq = Memseq::try_move_rocks(path, 128, 250)?.with_block_size(10).with_building_time_ms(500); + let memseq = Memseq::try_move_rocks(path, 128, 250)? + .with_block_size(10) + .with_building_time_ms(500); let block = memseq.wait_for_next_block().await?; assert!(block.is_none()); @@ -306,8 +306,13 @@ pub mod test { memseq.publish(transaction.clone()).await?; let block = memseq.wait_for_next_block().await?; - - assert_eq!(block.ok_or(anyhow::anyhow!("Block not found"))?.transactions[0], transaction); + let block = block.ok_or(anyhow::anyhow!("Block not found"))?; + let transaction_0th = block + .transactions() + .into_iter() + .next() + .ok_or(anyhow::anyhow!("No transactions in block"))?; + assert_eq!(transaction_0th, &transaction); Ok(()) } @@ -332,7 +337,7 @@ pub mod test { let block = block.ok_or(anyhow::anyhow!("Block not found"))?; - assert_eq!(block.transactions.len(), block_size as usize); + assert_eq!(block.transactions().len(), block_size as usize); let second_block = memseq.wait_for_next_block().await?; @@ -340,7 +345,7 @@ pub mod test { let second_block = second_block.ok_or(anyhow::anyhow!("Second block not found"))?; - assert_eq!(second_block.transactions.len(), block_size as usize); + assert_eq!(second_block.transactions().len(), block_size as usize); Ok(()) } @@ -384,7 +389,7 @@ pub mod test { let block = memseq.wait_for_next_block().await?; assert!(block.is_some()); let block = block.ok_or(anyhow::anyhow!("Block not found"))?; - assert_eq!(block.transactions.len(), (block_size / 2) as usize); + assert_eq!(block.transactions().len(), (block_size / 2) as usize); tokio::time::sleep(std::time::Duration::from_millis(200)).await; @@ -392,7 +397,7 @@ pub mod test { let block = memseq.wait_for_next_block().await?; assert!(block.is_some()); let block = block.ok_or(anyhow::anyhow!("Block not found"))?; - assert_eq!(block.transactions.len(), ((block_size / 2) - 2) as usize); + assert_eq!(block.transactions().len(), ((block_size / 2) - 2) as usize); Ok::<_, anyhow::Error>(()) }; @@ -407,7 +412,7 @@ pub mod test { impl MempoolTransactionOperations for MockMempool { async fn has_mempool_transaction( &self, - _transaction_id: Id, + _transaction_id: transaction::Id, ) -> Result { Err(anyhow::anyhow!("Mock has_mempool_transaction")) } @@ -428,7 +433,7 @@ pub mod test { async fn remove_mempool_transaction( &self, - _transaction_id: Id, + _transaction_id: transaction::Id, ) -> Result<(), anyhow::Error> { Err(anyhow::anyhow!("Mock remove_mempool_transaction")) } @@ -441,7 +446,7 @@ pub mod test { async fn get_mempool_transaction( &self, - _transaction_id: Id, + _transaction_id: transaction::Id, ) -> Result, anyhow::Error> { Err(anyhow::anyhow!("Mock get_mempool_transaction")) } @@ -456,7 +461,7 @@ pub mod test { } impl MempoolBlockOperations for MockMempool { - async fn has_block(&self, _block_id: Id) -> Result { + async fn has_block(&self, _block_id: block::Id) -> Result { todo!() } @@ -464,11 +469,11 @@ pub mod test { todo!() } - async fn remove_block(&self, _block_id: Id) -> Result<(), anyhow::Error> { + async fn remove_block(&self, _block_id: block::Id) -> Result<(), anyhow::Error> { todo!() } - async fn get_block(&self, _block_id: Id) -> Result, anyhow::Error> { + async fn get_block(&self, _block_id: block::Id) -> Result, anyhow::Error> { todo!() } } diff --git a/protocol-units/sequencing/util/src/lib.rs b/protocol-units/sequencing/util/src/lib.rs index c86b76d5c..0ef29f7e1 100644 --- a/protocol-units/sequencing/util/src/lib.rs +++ b/protocol-units/sequencing/util/src/lib.rs @@ -1,4 +1,6 @@ -use movement_types::{AtomicTransactionBundle, Block, Transaction}; +use movement_types::{ + atomic_transaction_bundle::AtomicTransactionBundle, block::Block, transaction::Transaction, +}; pub trait Sequencer { async fn publish_many(&self, atbs: Vec) -> Result<(), anyhow::Error>; diff --git a/protocol-units/settlement/mcr/client/abis/MCRLegacy.json b/protocol-units/settlement/mcr/client/abis/MCRLegacy.json deleted file mode 100644 index 324d3d157..000000000 --- a/protocol-units/settlement/mcr/client/abis/MCRLegacy.json +++ /dev/null @@ -1 +0,0 @@ -{"abi":[{"type":"constructor","inputs":[{"name":"epochDurationSecs","type":"uint256","internalType":"uint256"},{"name":"_leadingBlockTolerance","type":"uint256","internalType":"uint256"},{"name":"_genesisStakeRequired","type":"uint256","internalType":"uint256"},{"name":"_lastAcceptedBlockHeight","type":"uint256","internalType":"uint256"}],"stateMutability":"nonpayable"},{"type":"function","name":"acceptedBlocks","inputs":[{"name":"","type":"uint256","internalType":"uint256"}],"outputs":[{"name":"height","type":"uint256","internalType":"uint256"},{"name":"commitment","type":"bytes32","internalType":"bytes32"},{"name":"blockId","type":"bytes32","internalType":"bytes32"}],"stateMutability":"view"},{"type":"function","name":"blockHeightEpochAssignments","inputs":[{"name":"","type":"uint256","internalType":"uint256"}],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"type":"function","name":"commitmentStakes","inputs":[{"name":"","type":"uint256","internalType":"uint256"},{"name":"","type":"bytes32","internalType":"bytes32"}],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"type":"function","name":"commitments","inputs":[{"name":"","type":"uint256","internalType":"uint256"},{"name":"","type":"address","internalType":"address"}],"outputs":[{"name":"height","type":"uint256","internalType":"uint256"},{"name":"commitment","type":"bytes32","internalType":"bytes32"},{"name":"blockId","type":"bytes32","internalType":"bytes32"}],"stateMutability":"view"},{"type":"function","name":"createBlockCommitment","inputs":[{"name":"height","type":"uint256","internalType":"uint256"},{"name":"commitment","type":"bytes32","internalType":"bytes32"},{"name":"blockId","type":"bytes32","internalType":"bytes32"}],"outputs":[{"name":"","type":"tuple","internalType":"struct MCRLegacy.BlockCommitment","components":[{"name":"height","type":"uint256","internalType":"uint256"},{"name":"commitment","type":"bytes32","internalType":"bytes32"},{"name":"blockId","type":"bytes32","internalType":"bytes32"}]}],"stateMutability":"pure"},{"type":"function","name":"currentEpoch","inputs":[],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"type":"function","name":"epochDuration","inputs":[],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"type":"function","name":"epochStakes","inputs":[{"name":"","type":"uint256","internalType":"uint256"},{"name":"","type":"address","internalType":"address"}],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"type":"function","name":"epochUnstakes","inputs":[{"name":"","type":"uint256","internalType":"uint256"},{"name":"","type":"address","internalType":"address"}],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"type":"function","name":"genesisStakeAccumulated","inputs":[],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"type":"function","name":"genesisStakeRequired","inputs":[],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"type":"function","name":"getAcceptedCommitmentAtBlockHeight","inputs":[{"name":"blockHeight","type":"uint256","internalType":"uint256"}],"outputs":[{"name":"","type":"tuple","internalType":"struct MCRLegacy.BlockCommitment","components":[{"name":"height","type":"uint256","internalType":"uint256"},{"name":"commitment","type":"bytes32","internalType":"bytes32"},{"name":"blockId","type":"bytes32","internalType":"bytes32"}]}],"stateMutability":"view"},{"type":"function","name":"getCurrentEpoch","inputs":[],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"type":"function","name":"getCurrentEpochStake","inputs":[{"name":"validatorAddress","type":"address","internalType":"address"}],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"type":"function","name":"getCurrentEpochUnstake","inputs":[{"name":"validatorAddress","type":"address","internalType":"address"}],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"type":"function","name":"getEpochByBlockTime","inputs":[],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"type":"function","name":"getEpochDuration","inputs":[],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"type":"function","name":"getGenesisStakeAccumulated","inputs":[],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"type":"function","name":"getGenesisStakeRequired","inputs":[],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"type":"function","name":"getLeadingBlockTolerance","inputs":[],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"type":"function","name":"getMaxTolerableBlockHeight","inputs":[],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"type":"function","name":"getNextEpoch","inputs":[],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"type":"function","name":"getStakeAtEpoch","inputs":[{"name":"validatorAddress","type":"address","internalType":"address"},{"name":"epoch","type":"uint256","internalType":"uint256"}],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"type":"function","name":"getTotalStakeForCurrentEpoch","inputs":[],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"type":"function","name":"getTotalStakeForEpoch","inputs":[{"name":"epoch","type":"uint256","internalType":"uint256"}],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"type":"function","name":"getUnstakeAtEpoch","inputs":[{"name":"validatorAddress","type":"address","internalType":"address"},{"name":"epoch","type":"uint256","internalType":"uint256"}],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"type":"function","name":"getValidatorCommitmentAtBlockHeight","inputs":[{"name":"blockHeight","type":"uint256","internalType":"uint256"},{"name":"validatorAddress","type":"address","internalType":"address"}],"outputs":[{"name":"","type":"tuple","internalType":"struct MCRLegacy.BlockCommitment","components":[{"name":"height","type":"uint256","internalType":"uint256"},{"name":"commitment","type":"bytes32","internalType":"bytes32"},{"name":"blockId","type":"bytes32","internalType":"bytes32"}]}],"stateMutability":"view"},{"type":"function","name":"hasGenesisCeremonyEnded","inputs":[],"outputs":[{"name":"","type":"bool","internalType":"bool"}],"stateMutability":"view"},{"type":"function","name":"lastAcceptedBlockHeight","inputs":[],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"type":"function","name":"leadingBlockTolerance","inputs":[],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"type":"function","name":"stake","inputs":[],"outputs":[],"stateMutability":"payable"},{"type":"function","name":"stakeGenesis","inputs":[],"outputs":[],"stateMutability":"payable"},{"type":"function","name":"submitBatchBlockCommitment","inputs":[{"name":"blockCommitments","type":"tuple[]","internalType":"struct MCRLegacy.BlockCommitment[]","components":[{"name":"height","type":"uint256","internalType":"uint256"},{"name":"commitment","type":"bytes32","internalType":"bytes32"},{"name":"blockId","type":"bytes32","internalType":"bytes32"}]}],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"submitBlockCommitment","inputs":[{"name":"blockCommitment","type":"tuple","internalType":"struct MCRLegacy.BlockCommitment","components":[{"name":"height","type":"uint256","internalType":"uint256"},{"name":"commitment","type":"bytes32","internalType":"bytes32"},{"name":"blockId","type":"bytes32","internalType":"bytes32"}]}],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"unstake","inputs":[{"name":"amount","type":"uint256","internalType":"uint256"}],"outputs":[],"stateMutability":"nonpayable"},{"type":"event","name":"BlockAccepted","inputs":[{"name":"blockHash","type":"bytes32","indexed":true,"internalType":"bytes32"},{"name":"stateCommitment","type":"bytes32","indexed":false,"internalType":"bytes32"},{"name":"height","type":"uint256","indexed":false,"internalType":"uint256"}],"anonymous":false},{"type":"event","name":"BlockCommitmentSubmitted","inputs":[{"name":"blockHash","type":"bytes32","indexed":true,"internalType":"bytes32"},{"name":"stateCommitment","type":"bytes32","indexed":false,"internalType":"bytes32"},{"name":"validatorStake","type":"uint256","indexed":false,"internalType":"uint256"}],"anonymous":false},{"type":"event","name":"ValidatorStaked","inputs":[{"name":"validator","type":"address","indexed":true,"internalType":"address"},{"name":"stake","type":"uint256","indexed":false,"internalType":"uint256"},{"name":"epoch","type":"uint256","indexed":false,"internalType":"uint256"}],"anonymous":false},{"type":"event","name":"ValidatorUnstaked","inputs":[{"name":"validator","type":"address","indexed":true,"internalType":"address"},{"name":"stake","type":"uint256","indexed":false,"internalType":"uint256"},{"name":"epoch","type":"uint256","indexed":false,"internalType":"uint256"}],"anonymous":false}],"bytecode":{"object":"0x6080604052348015600f57600080fd5b50604051611607380380611607833981016040819052602c916047565b6002939093556003919091556000908155600155600455607c565b60008060008060808587031215605c57600080fd5b505082516020840151604085015160609095015191969095509092509050565b61157c8061008b6000396000f3fe6080604052600436106102045760003560e01c80636b588ac711610118578063e1ffa44c116100a0578063ee96da051161006f578063ee96da0514610661578063efe97d05146106af578063f89fe60b146106c4578063f934ed75146106e4578063fc2788ad1461070757600080fd5b8063e1ffa44c1461058a578063e6a386e4146105f0578063e90d4c0614610605578063ee377d901461064c57600080fd5b806384df6455116100e757806384df6455146104bb578063b97dd9e2146104fd578063c76c27bf14610512578063ce2a96b41461054a578063e1b2b2221461056a57600080fd5b80636b588ac71461046557806372710d181461047b57806374e8e5641461049057806376671808146104a557600080fd5b806320e0e4451161019b5780633a4b66f11161016a5780633a4b66f1146103fd57806342e0aeb2146104055780634ff0876a1461041a5780635d3ea8f11461043057806367d144ab1461044557600080fd5b806320e0e445146103915780632a3fa95c146103be5780632e17de78146103d3578063365f490b146103f557600080fd5b8063108cf69b116101d7578063108cf69b146102cc57806311fe3345146103045780631c4e4e27146103245780631cfa25161461037b57600080fd5b8063048317c814610209578063056f4aa6146102325780630a0b81cc146102485780630dd563661461028a575b600080fd5b34801561021557600080fd5b5061021f60005481565b6040519081526020015b60405180910390f35b34801561023e57600080fd5b5061021f60015481565b34801561025457600080fd5b50610268610263366004611274565b61073f565b6040805182518152602080840151908201529181015190820152606001610229565b34801561029657600080fd5b5061021f6102a53660046112a0565b60009081526008602090815260408083206001600160a01b03949094168352929052205490565b3480156102d857600080fd5b5061021f6102e7366004611274565b600860209081526000928352604080842090915290825290205481565b34801561031057600080fd5b5061021f61031f3660046112ca565b6107a3565b34801561033057600080fd5b5061036061033f3660046112e5565b600d6020526000908152604090208054600182015460029092015490919083565b60408051938452602084019290925290820152606001610229565b34801561038757600080fd5b5061021f60035481565b34801561039d57600080fd5b5061021f6103ac3660046112e5565b600a6020526000908152604090205481565b3480156103ca57600080fd5b5060035461021f565b3480156103df57600080fd5b506103f36103ee3660046112e5565b6107b2565b005b6103f3610914565b6103f3610abe565b34801561041157600080fd5b5060005461021f565b34801561042657600080fd5b5061021f60025481565b34801561043c57600080fd5b5060025461021f565b34801561045157600080fd5b5061021f6104603660046112ca565b610bb6565b34801561047157600080fd5b5061021f60045481565b34801561048757600080fd5b5061021f610bc5565b34801561049c57600080fd5b5061021f610bd8565b3480156104b157600080fd5b5061021f60055481565b3480156104c757600080fd5b5061021f6104d63660046112a0565b60009081526009602090815260408083206001600160a01b03949094168352929052205490565b34801561050957600080fd5b5060055461021f565b34801561051e57600080fd5b5061021f61052d366004611274565b600960209081526000928352604080842090915290825290205481565b34801561055657600080fd5b5061021f6105653660046112e5565b610bea565b34801561057657600080fd5b506103f36105853660046113a1565b610c50565b34801561059657600080fd5b506102686105a53660046112e5565b604080516060808201835260008083526020808401829052928401819052938452600d8252928290208251938401835280548452600181015491840191909152600201549082015290565b3480156105fc57600080fd5b5060015461021f565b34801561061157600080fd5b50610360610620366004611274565b600b60209081526000928352604080842090915290825290208054600182015460029092015490919083565b34801561065857600080fd5b5061021f610c8a565b34801561066d57600080fd5b5061026861067c366004611451565b60408051606080820183526000808352602080840182905292840152825190810183529485528401929092529082015290565b3480156106bb57600080fd5b5061021f610c9a565b3480156106d057600080fd5b506103f36106df36600461147d565b610cab565b3480156106f057600080fd5b506000546001546040519111158152602001610229565b34801561071357600080fd5b5061021f610722366004611499565b600c60209081526000928352604080842090915290825290205481565b604080516060808201835260008083526020808401829052928401819052858152600b83528381206001600160a01b038616825283528390208351918201845280548252600181015492820192909252600290910154918101919091525b92915050565b600061079d826102a560055490565b600054600154101561080b5760405162461bcd60e51b815260206004820152601f60248201527f47656e6573697320636572656d6f6e7920686173206e6f7420656e6465642e0060448201526064015b60405180910390fd5b806008600061081960055490565b815260208082019290925260409081016000908120338252909252902054101561087b5760405162461bcd60e51b815260206004820152601360248201527224b739bab33334b1b4b2b73a1039ba30b5b29760691b6044820152606401610802565b8060096000610888610c9a565b81526020019081526020016000206000336001600160a01b03166001600160a01b0316815260200190815260200160002060008282546108c891906114d1565b909155503390507f04fba62a80e1849b9aee6f029587f10dc735742856c310f041883b34f4107ab9826108f9610c9a565b6040805192835260208301919091520160405180910390a250565b600054600154106109675760405162461bcd60e51b815260206004820152601b60248201527f47656e6573697320636572656d6f6e792068617320656e6465642e00000000006044820152606401610802565b610972600633610cb5565b503360009081527f5eff886ea0ce6ca488a3d6e336d6c0f75f46d19b42c06ce5ee98e42c96d256c76020526040812080543492906109b19084906114d1565b9250508190555034600160008282546109ca91906114d1565b9091555050604080513481526000602082015233917f7df7881b496facd9ed493d6f1b0fc5092ad3a605083889c92e64ef36602dbb6a910160405180910390a260005460015410610abc57610a1d610c8a565b60055560005b610a2d6006610cd1565b811015610aba576000610a41600683610cdb565b6001600160a01b03811660009081527f5eff886ea0ce6ca488a3d6e336d6c0f75f46d19b42c06ce5ee98e42c96d256c76020526040812054919250600890610a8860055490565b8152602080820192909252604090810160009081206001600160a01b0390951681529390915290912055600101610a23565b505b565b6000546001541015610b125760405162461bcd60e51b815260206004820152601f60248201527f47656e6573697320636572656d6f6e7920686173206e6f7420656e6465642e006044820152606401610802565b610b1d600633610cb5565b503460086000610b2b610c9a565b81526020019081526020016000206000336001600160a01b03166001600160a01b031681526020019081526020016000206000828254610b6b91906114d1565b909155503390507f7df7881b496facd9ed493d6f1b0fc5092ad3a605083889c92e64ef36602dbb6a34610b9c610c9a565b6040805192835260208301919091520160405180910390a2565b600061079d826104d660055490565b6000610bd361056560055490565b905090565b6000600354600454610bd391906114d1565b600080805b610bf96006610cd1565b811015610c4957610c35610c0e600683610cdb565b60008681526008602090815260408083206001600160a01b03949094168352929052205490565b610c3f90836114d1565b9150600101610bef565b5092915050565b60005b8151811015610c8657610c7e828281518110610c7157610c716114e4565b6020026020010151610cab565b600101610c53565b5050565b600060025442610bd391906114fa565b60006005546001610bd391906114d1565b610aba3382610ce7565b6000610cca836001600160a01b038416610ee1565b9392505050565b600061079d825490565b6000610cca8383610f30565b80516000908152600b602090815260408083206001600160a01b038616845290915290205415610d7f5760405162461bcd60e51b815260206004820152603960248201527f56616c696461746f722068617320616c726561647920636f6d6d69747465642060448201527f746f206120626c6f636b206174207468697320686569676874000000000000006064820152608401610802565b600354600454610d8f91906114d1565b815110610e185760405162461bcd60e51b815260206004820152604b60248201527f56616c696461746f722068617320636f6d6d697474656420746f206120626c6f60448201527f636b20746f6f20666172206168656164206f6620746865206c6173742061636360648201526a657074656420626c6f636b60a81b608482015260a401610802565b80516000908152600a60205260408120549003610e4957610e37610c8a565b81516000908152600a60205260409020555b80516000908152600b602090815260408083206001600160a01b03861684528252918290208351815590830151600182015590820151600290910155610e8e826107a3565b81516000908152600c6020908152604080832082860151845290915281208054909190610ebc9084906114d1565b90915550505b610ed96004546001610ed491906114d1565b610f5a565b610ec2575050565b6000818152600183016020526040812054610f285750815460018181018455600084815260208082209093018490558454848252828601909352604090209190915561079d565b50600061079d565b6000826000018281548110610f4757610f476114e4565b9060005260206000200154905092915050565b6000818152600a60205260408120545b80610f7460055490565b1015610f9057610f8b610f8660055490565b61105f565b610f6a565b60005b610f9d6006610cd1565b811015611055576000610fb1600683610cdb565b6000868152600b602090815260408083206001600160a01b03851684528252808320815160608101835281548082526001830154828601908152600290930154828501528552600c84528285209151855292529091205491925090600361101786610bea565b61102290600261151c565b61102c91906114fa565b81111561104a5761103d82866110b1565b5060019695505050505050565b505050600101610f93565b5060009392505050565b60005b61106c6006610cd1565b811015611095576000611080600683610cdb565b905061108c8184611133565b50600101611062565b506001600560008282546110a991906114d1565b909155505050565b81516000908152600d6020908152604091829020845180825582860151600183018190558487015160029093018390556004829055845190815292830152917f5b0d1f14085ff88796df21c2944d34c115838925a7bee30850fcacaaf902a379910160405180910390a280611124610c8a565b1115610c8657610c868161105f565b600960006111428360016114d1565b8152602080820192909252604090810160009081206001600160a01b0386168083529084528282205485835260088552838320918352935220546111869190611533565b600860006111958460016114d1565b81526020019081526020016000206000846001600160a01b03166001600160a01b0316815260200190815260200160002060008282546111d591906114d1565b90915550506001600160a01b0382166108fc600960006111f68560016114d1565b81526020019081526020016000206000856001600160a01b03166001600160a01b03168152602001908152602001600020549081150290604051600060405180830381858888f19350505050158015611253573d6000803e3d6000fd5b505050565b80356001600160a01b038116811461126f57600080fd5b919050565b6000806040838503121561128757600080fd5b8235915061129760208401611258565b90509250929050565b600080604083850312156112b357600080fd5b6112bc83611258565b946020939093013593505050565b6000602082840312156112dc57600080fd5b610cca82611258565b6000602082840312156112f757600080fd5b5035919050565b634e487b7160e01b600052604160045260246000fd5b604051601f8201601f1916810167ffffffffffffffff8111828210171561133d5761133d6112fe565b604052919050565b60006060828403121561135757600080fd5b6040516060810181811067ffffffffffffffff8211171561137a5761137a6112fe565b80604052508091508235815260208301356020820152604083013560408201525092915050565b600060208083850312156113b457600080fd5b823567ffffffffffffffff808211156113cc57600080fd5b818501915085601f8301126113e057600080fd5b8135818111156113f2576113f26112fe565b611400848260051b01611314565b8181528481019250606091820284018501918883111561141f57600080fd5b938501935b82851015611445576114368986611345565b84529384019392850192611424565b50979650505050505050565b60008060006060848603121561146657600080fd5b505081359360208301359350604090920135919050565b60006060828403121561148f57600080fd5b610cca8383611345565b600080604083850312156114ac57600080fd5b50508035926020909101359150565b634e487b7160e01b600052601160045260246000fd5b8082018082111561079d5761079d6114bb565b634e487b7160e01b600052603260045260246000fd5b60008261151757634e487b7160e01b600052601260045260246000fd5b500490565b808202811582820484141761079d5761079d6114bb565b8181038181111561079d5761079d6114bb56fea2646970667358221220c9542e0185c9b0ec6eb1d431d6380d142087b991f911e0e830486ae7143e052a64736f6c63430008190033","sourceMap":"163:15268:26:-:0;;;2927:455;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;3135:13;:33;;;;3178:21;:46;;;;-1:-1:-1;3234:44:26;;;3288:23;:27;3325:23;:50;163:15268;;14:368:28;111:6;119;127;135;188:3;176:9;167:7;163:23;159:33;156:53;;;205:1;202;195:12;156:53;-1:-1:-1;;228:16:28;;284:2;269:18;;263:25;328:2;313:18;;307:25;372:2;357:18;;;351:25;228:16;;263:25;;-1:-1:-1;351:25:28;;-1:-1:-1;14:368:28;-1:-1:-1;14:368:28:o;:::-;163:15268:26;;;;;;","linkReferences":{}},"deployedBytecode":{"object":"0x6080604052600436106102045760003560e01c80636b588ac711610118578063e1ffa44c116100a0578063ee96da051161006f578063ee96da0514610661578063efe97d05146106af578063f89fe60b146106c4578063f934ed75146106e4578063fc2788ad1461070757600080fd5b8063e1ffa44c1461058a578063e6a386e4146105f0578063e90d4c0614610605578063ee377d901461064c57600080fd5b806384df6455116100e757806384df6455146104bb578063b97dd9e2146104fd578063c76c27bf14610512578063ce2a96b41461054a578063e1b2b2221461056a57600080fd5b80636b588ac71461046557806372710d181461047b57806374e8e5641461049057806376671808146104a557600080fd5b806320e0e4451161019b5780633a4b66f11161016a5780633a4b66f1146103fd57806342e0aeb2146104055780634ff0876a1461041a5780635d3ea8f11461043057806367d144ab1461044557600080fd5b806320e0e445146103915780632a3fa95c146103be5780632e17de78146103d3578063365f490b146103f557600080fd5b8063108cf69b116101d7578063108cf69b146102cc57806311fe3345146103045780631c4e4e27146103245780631cfa25161461037b57600080fd5b8063048317c814610209578063056f4aa6146102325780630a0b81cc146102485780630dd563661461028a575b600080fd5b34801561021557600080fd5b5061021f60005481565b6040519081526020015b60405180910390f35b34801561023e57600080fd5b5061021f60015481565b34801561025457600080fd5b50610268610263366004611274565b61073f565b6040805182518152602080840151908201529181015190820152606001610229565b34801561029657600080fd5b5061021f6102a53660046112a0565b60009081526008602090815260408083206001600160a01b03949094168352929052205490565b3480156102d857600080fd5b5061021f6102e7366004611274565b600860209081526000928352604080842090915290825290205481565b34801561031057600080fd5b5061021f61031f3660046112ca565b6107a3565b34801561033057600080fd5b5061036061033f3660046112e5565b600d6020526000908152604090208054600182015460029092015490919083565b60408051938452602084019290925290820152606001610229565b34801561038757600080fd5b5061021f60035481565b34801561039d57600080fd5b5061021f6103ac3660046112e5565b600a6020526000908152604090205481565b3480156103ca57600080fd5b5060035461021f565b3480156103df57600080fd5b506103f36103ee3660046112e5565b6107b2565b005b6103f3610914565b6103f3610abe565b34801561041157600080fd5b5060005461021f565b34801561042657600080fd5b5061021f60025481565b34801561043c57600080fd5b5060025461021f565b34801561045157600080fd5b5061021f6104603660046112ca565b610bb6565b34801561047157600080fd5b5061021f60045481565b34801561048757600080fd5b5061021f610bc5565b34801561049c57600080fd5b5061021f610bd8565b3480156104b157600080fd5b5061021f60055481565b3480156104c757600080fd5b5061021f6104d63660046112a0565b60009081526009602090815260408083206001600160a01b03949094168352929052205490565b34801561050957600080fd5b5060055461021f565b34801561051e57600080fd5b5061021f61052d366004611274565b600960209081526000928352604080842090915290825290205481565b34801561055657600080fd5b5061021f6105653660046112e5565b610bea565b34801561057657600080fd5b506103f36105853660046113a1565b610c50565b34801561059657600080fd5b506102686105a53660046112e5565b604080516060808201835260008083526020808401829052928401819052938452600d8252928290208251938401835280548452600181015491840191909152600201549082015290565b3480156105fc57600080fd5b5060015461021f565b34801561061157600080fd5b50610360610620366004611274565b600b60209081526000928352604080842090915290825290208054600182015460029092015490919083565b34801561065857600080fd5b5061021f610c8a565b34801561066d57600080fd5b5061026861067c366004611451565b60408051606080820183526000808352602080840182905292840152825190810183529485528401929092529082015290565b3480156106bb57600080fd5b5061021f610c9a565b3480156106d057600080fd5b506103f36106df36600461147d565b610cab565b3480156106f057600080fd5b506000546001546040519111158152602001610229565b34801561071357600080fd5b5061021f610722366004611499565b600c60209081526000928352604080842090915290825290205481565b604080516060808201835260008083526020808401829052928401819052858152600b83528381206001600160a01b038616825283528390208351918201845280548252600181015492820192909252600290910154918101919091525b92915050565b600061079d826102a560055490565b600054600154101561080b5760405162461bcd60e51b815260206004820152601f60248201527f47656e6573697320636572656d6f6e7920686173206e6f7420656e6465642e0060448201526064015b60405180910390fd5b806008600061081960055490565b815260208082019290925260409081016000908120338252909252902054101561087b5760405162461bcd60e51b815260206004820152601360248201527224b739bab33334b1b4b2b73a1039ba30b5b29760691b6044820152606401610802565b8060096000610888610c9a565b81526020019081526020016000206000336001600160a01b03166001600160a01b0316815260200190815260200160002060008282546108c891906114d1565b909155503390507f04fba62a80e1849b9aee6f029587f10dc735742856c310f041883b34f4107ab9826108f9610c9a565b6040805192835260208301919091520160405180910390a250565b600054600154106109675760405162461bcd60e51b815260206004820152601b60248201527f47656e6573697320636572656d6f6e792068617320656e6465642e00000000006044820152606401610802565b610972600633610cb5565b503360009081527f5eff886ea0ce6ca488a3d6e336d6c0f75f46d19b42c06ce5ee98e42c96d256c76020526040812080543492906109b19084906114d1565b9250508190555034600160008282546109ca91906114d1565b9091555050604080513481526000602082015233917f7df7881b496facd9ed493d6f1b0fc5092ad3a605083889c92e64ef36602dbb6a910160405180910390a260005460015410610abc57610a1d610c8a565b60055560005b610a2d6006610cd1565b811015610aba576000610a41600683610cdb565b6001600160a01b03811660009081527f5eff886ea0ce6ca488a3d6e336d6c0f75f46d19b42c06ce5ee98e42c96d256c76020526040812054919250600890610a8860055490565b8152602080820192909252604090810160009081206001600160a01b0390951681529390915290912055600101610a23565b505b565b6000546001541015610b125760405162461bcd60e51b815260206004820152601f60248201527f47656e6573697320636572656d6f6e7920686173206e6f7420656e6465642e006044820152606401610802565b610b1d600633610cb5565b503460086000610b2b610c9a565b81526020019081526020016000206000336001600160a01b03166001600160a01b031681526020019081526020016000206000828254610b6b91906114d1565b909155503390507f7df7881b496facd9ed493d6f1b0fc5092ad3a605083889c92e64ef36602dbb6a34610b9c610c9a565b6040805192835260208301919091520160405180910390a2565b600061079d826104d660055490565b6000610bd361056560055490565b905090565b6000600354600454610bd391906114d1565b600080805b610bf96006610cd1565b811015610c4957610c35610c0e600683610cdb565b60008681526008602090815260408083206001600160a01b03949094168352929052205490565b610c3f90836114d1565b9150600101610bef565b5092915050565b60005b8151811015610c8657610c7e828281518110610c7157610c716114e4565b6020026020010151610cab565b600101610c53565b5050565b600060025442610bd391906114fa565b60006005546001610bd391906114d1565b610aba3382610ce7565b6000610cca836001600160a01b038416610ee1565b9392505050565b600061079d825490565b6000610cca8383610f30565b80516000908152600b602090815260408083206001600160a01b038616845290915290205415610d7f5760405162461bcd60e51b815260206004820152603960248201527f56616c696461746f722068617320616c726561647920636f6d6d69747465642060448201527f746f206120626c6f636b206174207468697320686569676874000000000000006064820152608401610802565b600354600454610d8f91906114d1565b815110610e185760405162461bcd60e51b815260206004820152604b60248201527f56616c696461746f722068617320636f6d6d697474656420746f206120626c6f60448201527f636b20746f6f20666172206168656164206f6620746865206c6173742061636360648201526a657074656420626c6f636b60a81b608482015260a401610802565b80516000908152600a60205260408120549003610e4957610e37610c8a565b81516000908152600a60205260409020555b80516000908152600b602090815260408083206001600160a01b03861684528252918290208351815590830151600182015590820151600290910155610e8e826107a3565b81516000908152600c6020908152604080832082860151845290915281208054909190610ebc9084906114d1565b90915550505b610ed96004546001610ed491906114d1565b610f5a565b610ec2575050565b6000818152600183016020526040812054610f285750815460018181018455600084815260208082209093018490558454848252828601909352604090209190915561079d565b50600061079d565b6000826000018281548110610f4757610f476114e4565b9060005260206000200154905092915050565b6000818152600a60205260408120545b80610f7460055490565b1015610f9057610f8b610f8660055490565b61105f565b610f6a565b60005b610f9d6006610cd1565b811015611055576000610fb1600683610cdb565b6000868152600b602090815260408083206001600160a01b03851684528252808320815160608101835281548082526001830154828601908152600290930154828501528552600c84528285209151855292529091205491925090600361101786610bea565b61102290600261151c565b61102c91906114fa565b81111561104a5761103d82866110b1565b5060019695505050505050565b505050600101610f93565b5060009392505050565b60005b61106c6006610cd1565b811015611095576000611080600683610cdb565b905061108c8184611133565b50600101611062565b506001600560008282546110a991906114d1565b909155505050565b81516000908152600d6020908152604091829020845180825582860151600183018190558487015160029093018390556004829055845190815292830152917f5b0d1f14085ff88796df21c2944d34c115838925a7bee30850fcacaaf902a379910160405180910390a280611124610c8a565b1115610c8657610c868161105f565b600960006111428360016114d1565b8152602080820192909252604090810160009081206001600160a01b0386168083529084528282205485835260088552838320918352935220546111869190611533565b600860006111958460016114d1565b81526020019081526020016000206000846001600160a01b03166001600160a01b0316815260200190815260200160002060008282546111d591906114d1565b90915550506001600160a01b0382166108fc600960006111f68560016114d1565b81526020019081526020016000206000856001600160a01b03166001600160a01b03168152602001908152602001600020549081150290604051600060405180830381858888f19350505050158015611253573d6000803e3d6000fd5b505050565b80356001600160a01b038116811461126f57600080fd5b919050565b6000806040838503121561128757600080fd5b8235915061129760208401611258565b90509250929050565b600080604083850312156112b357600080fd5b6112bc83611258565b946020939093013593505050565b6000602082840312156112dc57600080fd5b610cca82611258565b6000602082840312156112f757600080fd5b5035919050565b634e487b7160e01b600052604160045260246000fd5b604051601f8201601f1916810167ffffffffffffffff8111828210171561133d5761133d6112fe565b604052919050565b60006060828403121561135757600080fd5b6040516060810181811067ffffffffffffffff8211171561137a5761137a6112fe565b80604052508091508235815260208301356020820152604083013560408201525092915050565b600060208083850312156113b457600080fd5b823567ffffffffffffffff808211156113cc57600080fd5b818501915085601f8301126113e057600080fd5b8135818111156113f2576113f26112fe565b611400848260051b01611314565b8181528481019250606091820284018501918883111561141f57600080fd5b938501935b82851015611445576114368986611345565b84529384019392850192611424565b50979650505050505050565b60008060006060848603121561146657600080fd5b505081359360208301359350604090920135919050565b60006060828403121561148f57600080fd5b610cca8383611345565b600080604083850312156114ac57600080fd5b50508035926020909101359150565b634e487b7160e01b600052601160045260246000fd5b8082018082111561079d5761079d6114bb565b634e487b7160e01b600052603260045260246000fd5b60008261151757634e487b7160e01b600052601260045260246000fd5b500490565b808202811582820484141761079d5761079d6114bb565b8181038181111561079d5761079d6114bb56fea2646970667358221220c9542e0185c9b0ec6eb1d431d6380d142087b991f911e0e830486ae7143e052a64736f6c63430008190033","sourceMap":"163:15268:26:-:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;269:35;;;;;;;;;;;;;;;;;;;160:25:28;;;148:2;133:18;269:35:26;;;;;;;;310:38;;;;;;;;;;;;;;;;6621:203;;;;;;;;;;-1:-1:-1;6621:203:26;;;;;:::i;:::-;;:::i;:::-;;;;865:13:28;;847:32;;935:4;923:17;;;917:24;895:20;;;888:54;986:17;;;980:24;958:20;;;951:54;835:2;820:18;6621:203:26;633:378:28;5177:156:26;;;;;;;;;;-1:-1:-1;5177:156:26;;;;;:::i;:::-;5264:7;5290:18;;;:11;:18;;;;;;;;-1:-1:-1;;;;;5290:36:26;;;;;;;;;;;;5177:156;1812:67;;;;;;;;;;-1:-1:-1;1812:67:26;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;5404:162;;;;;;;;;;-1:-1:-1;5404:162:26;;;;;:::i;:::-;;:::i;2487:57::-;;;;;;;;;;-1:-1:-1;2487:57:26;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;1853:25:28;;;1909:2;1894:18;;1887:34;;;;1937:18;;;1930:34;1841:2;1826:18;2487:57:26;1651:319:28;1133:36:26;;;;;;;;;;;;;;;;2059:62;;;;;;;;;;-1:-1:-1;2059:62:26;;;;;:::i;:::-;;;;;;;;;;;;;;4147:111;;;;;;;;;;-1:-1:-1;4230:21:26;;4147:111;;8471:813;;;;;;;;;;-1:-1:-1;8471:813:26;;;;;:::i;:::-;;:::i;:::-;;7478:942;;;:::i;7090:346::-;;;:::i;3693:109::-;;;;;;;;;;-1:-1:-1;3749:7:26;3775:20;3693:109;;355:28;;;;;;;;;;;;;;;;4006:95;;;;;;;;;;-1:-1:-1;4081:13:26;;4006:95;;5868:166;;;;;;;;;;-1:-1:-1;5868:166:26;;;;;:::i;:::-;;:::i;1305:38::-;;;;;;;;;;;;;;;;6430:134;;;;;;;;;;;;;:::i;4497:139::-;;;;;;;;;;;;;:::i;1407:27::-;;;;;;;;;;;;;;;;5635:160;;;;;;;;;;-1:-1:-1;5635:160:26;;;;;:::i;:::-;5724:7;5750:20;;;:13;:20;;;;;;;;-1:-1:-1;;;;;5750:38:26;;;;;;;;;;;;5635:160;4890:93;;;;;;;;;;-1:-1:-1;4964:12:26;;4890:93;;1942:69;;;;;;;;;;-1:-1:-1;1942:69:26;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;6086:288;;;;;;;;;;-1:-1:-1;6086:288:26;;;;;:::i;:::-;;:::i;13859:238::-;;;;;;;;;;-1:-1:-1;13859:238:26;;;;;:::i;:::-;;:::i;6890:161::-;;;;;;;;;;-1:-1:-1;6890:161:26;;;;;:::i;:::-;-1:-1:-1;;;;;;;;;;;;;;;;;;;;;;;;;;7017:27:26;;;:14;:27;;;;;;7010:34;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;6890:161;3854:115;;;;;;;;;;-1:-1:-1;3939:23:26;;3854:115;;2199:74;;;;;;;;;;-1:-1:-1;2199:74:26;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;4700:116;;;;;;;;;;;;;:::i;3417:227::-;;;;;;;;;;-1:-1:-1;3417:227:26;;;;;:::i;:::-;-1:-1:-1;;;;;;;;;;;;;;;;;;;;;;;;3593:44:26;;;;;;;;;;;;;;;;;;;;;3417:227;5016:94;;;;;;;;;;;;;:::i;13680:173::-;;;;;;;;;;-1:-1:-1;13680:173:26;;;;;:::i;:::-;;:::i;4315:133::-;;;;;;;;;;-1:-1:-1;4371:4:26;4421:20;4394:23;;4315:133;;4394:47;-1:-1:-1;4394:47:26;4676:41:28;;4664:2;4649:18;4315:133:26;4536:187:28;2362:70:26;;;;;;;;;;-1:-1:-1;2362:70:26;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;6621:203;-1:-1:-1;;;;;;;;;;;;;;;;;;;;;;;;;;6775:24:26;;;:11;:24;;;;;-1:-1:-1;;;;;6775:42:26;;;;;;;;;6768:49;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;6621:203;;;;;:::o;5404:162::-;5481:7;5507:52;5523:16;5541:17;4964:12;;;4890:93;8471:813;8572:20;;8545:23;;:47;;8524:125;;;;-1:-1:-1;;;8524:125:26;;5183:2:28;8524:125:26;;;5165:21:28;5222:2;5202:18;;;5195:30;5261:33;5241:18;;;5234:61;5312:18;;8524:125:26;;;;;;;;;8727:6;8681:11;:30;8693:17;4964:12;;;4890:93;8693:17;8681:30;;;;;;;;;;;;;;-1:-1:-1;8681:30:26;;;8712:10;8681:42;;;;;;;;:52;;8660:118;;;;-1:-1:-1;;;8660:118:26;;5543:2:28;8660:118:26;;;5525:21:28;5582:2;5562:18;;;5555:30;-1:-1:-1;;;5601:18:28;;;5594:49;5660:18;;8660:118:26;5341:343:28;8660:118:26;9155:6;9110:13;:29;9124:14;:12;:14::i;:::-;9110:29;;;;;;;;;;;:41;9140:10;-1:-1:-1;;;;;9110:41:26;-1:-1:-1;;;;;9110:41:26;;;;;;;;;;;;;:51;;;;;;;:::i;:::-;;;;-1:-1:-1;9208:10:26;;-1:-1:-1;9177:99:26;9232:6;9252:14;:12;:14::i;:::-;9177:99;;;6125:25:28;;;6181:2;6166:18;;6159:34;;;;6098:18;9177:99:26;;;;;;;8471:813;:::o;7478:942::-;7589:20;;7563:23;;:46;7542:120;;;;-1:-1:-1;;;7542:120:26;;6406:2:28;7542:120:26;;;6388:21:28;6445:2;6425:18;;;6418:30;6484:29;6464:18;;;6457:57;6531:18;;7542:120:26;6204:351:28;7542:120:26;7673:26;:10;7688;7673:14;:26::i;:::-;-1:-1:-1;7724:10:26;7709:14;:26;;;:14;;:26;:14;:26;;:39;;7739:9;;7709:14;:39;;7739:9;;7709:39;:::i;:::-;;;;;;;;7785:9;7758:23;;:36;;;;;;;:::i;:::-;;;;-1:-1:-1;;7809:41:26;;;7837:9;6125:25:28;;7848:1:26;6181:2:28;6166:18;;6159:34;7825:10:26;;7809:41;;6098:18:28;7809:41:26;;;;;;;7892:20;;7865:23;;:47;7861:552;;8063:21;:19;:21::i;:::-;8048:12;:36;8180:9;8175:226;8199:19;:10;:17;:19::i;:::-;8195:1;:23;8175:226;;;8242:24;8269:16;:10;8283:1;8269:13;:16::i;:::-;-1:-1:-1;;;;;8354:32:26;;:14;:32;;;:14;;:32;:14;:32;;;8242:43;;-1:-1:-1;8354:11:26;;8315:17;4964:12;;;4890:93;8315:17;8303:30;;;;;;;;;;;;;;-1:-1:-1;8303:30:26;;;-1:-1:-1;;;;;8303:48:26;;;;;;;;;;;;:83;8220:3;;8175:226;;;;7861:552;7478:942::o;7090:346::-;7183:20;;7156:23;;:47;;7135:125;;;;-1:-1:-1;;;7135:125:26;;5183:2:28;7135:125:26;;;5165:21:28;5222:2;5202:18;;;5195:30;5261:33;5241:18;;;5234:61;5312:18;;7135:125:26;4981:355:28;7135:125:26;7271:26;:10;7286;7271:14;:26::i;:::-;;7350:9;7307:11;:27;7319:14;:12;:14::i;:::-;7307:27;;;;;;;;;;;:39;7335:10;-1:-1:-1;;;;;7307:39:26;-1:-1:-1;;;;;7307:39:26;;;;;;;;;;;;;:52;;;;;;;:::i;:::-;;;;-1:-1:-1;7390:10:26;;-1:-1:-1;7374:54:26;7402:9;7413:14;:12;:14::i;:::-;7374:54;;;6125:25:28;;;6181:2;6166:18;;6159:34;;;;6098:18;7374:54:26;;;;;;;7090:346::o;5868:166::-;5947:7;5973:54;5991:16;6009:17;4964:12;;;4890:93;6430:134;6491:7;6517:40;6539:17;4964:12;;;4890:93;6517:40;6510:47;;6430:134;:::o;4497:139::-;4556:7;4608:21;;4582:23;;:47;;;;:::i;6086:288::-;6153:7;;;6213:128;6237:19;:10;:17;:19::i;:::-;6233:1;:23;6213:128;;;6290:40;6306:16;:10;6320:1;6306:13;:16::i;:::-;5264:7;5290:18;;;:11;:18;;;;;;;;-1:-1:-1;;;;;5290:36:26;;;;;;;;;;;;5177:156;6290:40;6276:54;;;;:::i;:::-;;-1:-1:-1;6258:3:26;;6213:128;;;-1:-1:-1;6357:10:26;6086:288;-1:-1:-1;;6086:288:26:o;13859:238::-;13975:9;13970:120;13994:16;:23;13990:1;:27;13970:120;;;14037:42;14059:16;14076:1;14059:19;;;;;;;;:::i;:::-;;;;;;;14037:21;:42::i;:::-;14019:3;;13970:120;;;;13859:238;:::o;4700:116::-;4752:7;4796:13;;4778:15;:31;;;;:::i;5016:94::-;5061:7;5087:12;;5102:1;5087:16;;;;:::i;13680:173::-;13783:62;13817:10;13829:15;13783:33;:62::i;8316:150:24:-;8386:4;8409:50;8414:3;-1:-1:-1;;;;;8434:23:24;;8409:4;:50::i;:::-;8402:57;8316:150;-1:-1:-1;;;8316:150:24:o;9117:115::-;9180:7;9206:19;9214:3;4556:18;;4474:107;9574:156;9648:7;9698:22;9702:3;9714:5;9698:3;:22::i;10184:1807:26:-;10356:22;;10344:35;;;;:11;:35;;;;;;;;-1:-1:-1;;;;;10344:53:26;;;;;;;;;:60;:65;10336:135;;;;-1:-1:-1;;;10336:135:26;;7377:2:28;10336:135:26;;;7359:21:28;7416:2;7396:18;;;7389:30;7455:34;7435:18;;;7428:62;7526:27;7506:18;;;7499:55;7571:19;;10336:135:26;7175:421:28;10336:135:26;10769:21;;10743:23;;:47;;;;:::i;:::-;10718:22;;:72;10710:160;;;;-1:-1:-1;;;10710:160:26;;7803:2:28;10710:160:26;;;7785:21:28;7842:2;7822:18;;;7815:30;7881:34;7861:18;;;7854:62;7952:34;7932:18;;;7925:62;-1:-1:-1;;;8003:19:28;;;7996:42;8055:19;;10710:160:26;7601:479:28;10710:160:26;11000:22;;10972:51;;;;:27;:51;;;;;;:56;;10968:261;;11197:21;:19;:21::i;:::-;11171:22;;11143:51;;;;:27;:51;;;;;:75;10968:261;11298:22;;11286:35;;;;:11;:35;;;;;;;;-1:-1:-1;;;;;11286:53:26;;;;;;;;;;:71;;;;;;;;;;;;;;;;;;;;;11491:38;11322:16;11491:20;:38::i;:::-;11436:22;;11419:40;;;;:16;:40;;;;;;;;11460:26;;;;11419:68;;;;;;;:110;;:68;;:40;:110;;;;;:::i;:::-;;;;-1:-1:-1;;11921:57:26;11928:46;11946:23;;11972:1;11946:27;;;;:::i;:::-;11928:17;:46::i;:::-;11921:57;;10184:1807;;:::o;2241:406:24:-;2304:4;4360:21;;;:14;;;:21;;;;;;2320:321;;-1:-1:-1;2362:23:24;;;;;;;;:11;:23;;;;;;;;;;;;;2544:18;;2520:21;;;:14;;;:21;;;;;;:42;;;;2576:11;;2320:321;-1:-1:-1;2625:5:24;2618:12;;4923:118;4990:7;5016:3;:11;;5028:5;5016:18;;;;;;;;:::i;:::-;;;;;;;;;5009:25;;4923:118;;;;:::o;11997:1677:26:-;12063:4;12155:40;;;:27;:40;;;;;;12524:96;12551:10;12531:17;4964:12;;;4890:93;12531:17;:30;12524:96;;;12577:32;12591:17;4964:12;;;4890:93;12591:17;12577:13;:32::i;:::-;12524:96;;;12839:9;12834:810;12858:19;:10;:17;:19::i;:::-;12854:1;:23;12834:810;;;12898:24;12925:16;:10;12939:1;12925:13;:16::i;:::-;13026:38;13067:24;;;:11;:24;;;;;;;;-1:-1:-1;;;;;13067:42:26;;;;;;;;;13026:83;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;13212:40;;:16;:40;;;;;13253:26;;13212:68;;;;;;;;12898:43;;-1:-1:-1;13026:83:26;13364:1;13329:33;13351:10;13329:21;:33::i;:::-;13325:37;;:1;:37;:::i;:::-;13324:41;;;;:::i;:::-;13299:22;:66;13295:338;;;13478:50;13500:15;13517:10;13478:21;:50::i;:::-;-1:-1:-1;13613:4:26;;11997:1677;-1:-1:-1;;;;;;11997:1677:26:o;13295:338::-;-1:-1:-1;;;12879:3:26;;12834:810;;;-1:-1:-1;13661:5:26;;11997:1677;-1:-1:-1;;;11997:1677:26:o;15060:368::-;15171:9;15166:179;15190:19;:10;:17;:19::i;:::-;15186:1;:23;15166:179;;;15229:24;15256:16;:10;15270:1;15256:13;:16::i;:::-;15229:43;;15286:48;15304:16;15322:11;15286:17;:48::i;:::-;-1:-1:-1;15211:3:26;;15166:179;;;;15410:1;15394:12;;:17;;;;;;;:::i;:::-;;;;-1:-1:-1;;;15060:368:26:o;14103:822::-;14299:22;;14284:38;;;;:14;:38;;;;;;;;;:56;;;;;;;;;;;;;;;;;;;;;;;;;;14393:23;:48;;;14629:90;;6125:25:28;;;6166:18;;;6159:34;14284:56:26;14629:90;;6098:18:28;14629:90:26;;;;;;;14847:11;14823:21;:19;:21::i;:::-;:35;14819:92;;;14874:26;14888:11;14874:13;:26::i;9356:773::-;9654:13;:30;9668:15;:11;9682:1;9668:15;:::i;:::-;9654:30;;;;;;;;;;;;;;-1:-1:-1;9654:30:26;;;-1:-1:-1;;;;;9654:48:26;;;;;;;;;;;;9609:24;;;:11;:24;;;;;:42;;;;;;;:93;;9654:48;9609:93;:::i;:::-;9559:11;:28;9571:15;:11;9585:1;9571:15;:::i;:::-;9559:28;;;;;;;;;;;:46;9588:16;-1:-1:-1;;;;;9559:46:26;-1:-1:-1;;;;;9559:46:26;;;;;;;;;;;;;:143;;;;;;;:::i;:::-;;;;-1:-1:-1;;;;;;;10037:34:26;;:84;10072:13;:30;10086:15;:11;10100:1;10086:15;:::i;:::-;10072:30;;;;;;;;;;;:48;10103:16;-1:-1:-1;;;;;10072:48:26;-1:-1:-1;;;;;10072:48:26;;;;;;;;;;;;;10037:84;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;9356:773;;:::o;196:173:28:-;264:20;;-1:-1:-1;;;;;313:31:28;;303:42;;293:70;;359:1;356;349:12;293:70;196:173;;;:::o;374:254::-;442:6;450;503:2;491:9;482:7;478:23;474:32;471:52;;;519:1;516;509:12;471:52;555:9;542:23;532:33;;584:38;618:2;607:9;603:18;584:38;:::i;:::-;574:48;;374:254;;;;;:::o;1016:::-;1084:6;1092;1145:2;1133:9;1124:7;1120:23;1116:32;1113:52;;;1161:1;1158;1151:12;1113:52;1184:29;1203:9;1184:29;:::i;:::-;1174:39;1260:2;1245:18;;;;1232:32;;-1:-1:-1;;;1016:254:28:o;1275:186::-;1334:6;1387:2;1375:9;1366:7;1362:23;1358:32;1355:52;;;1403:1;1400;1393:12;1355:52;1426:29;1445:9;1426:29;:::i;1466:180::-;1525:6;1578:2;1566:9;1557:7;1553:23;1549:32;1546:52;;;1594:1;1591;1584:12;1546:52;-1:-1:-1;1617:23:28;;1466:180;-1:-1:-1;1466:180:28:o;1975:127::-;2036:10;2031:3;2027:20;2024:1;2017:31;2067:4;2064:1;2057:15;2091:4;2088:1;2081:15;2107:275;2178:2;2172:9;2243:2;2224:13;;-1:-1:-1;;2220:27:28;2208:40;;2278:18;2263:34;;2299:22;;;2260:62;2257:88;;;2325:18;;:::i;:::-;2361:2;2354:22;2107:275;;-1:-1:-1;2107:275:28:o;2387:546::-;2449:5;2497:4;2485:9;2480:3;2476:19;2472:30;2469:50;;;2515:1;2512;2505:12;2469:50;2548:2;2542:9;2590:4;2582:6;2578:17;2661:6;2649:10;2646:22;2625:18;2613:10;2610:34;2607:62;2604:88;;;2672:18;;:::i;:::-;2712:10;2708:2;2701:22;;2741:6;2732:15;;2784:9;2771:23;2763:6;2756:39;2856:2;2845:9;2841:18;2828:32;2823:2;2815:6;2811:15;2804:57;2922:2;2911:9;2907:18;2894:32;2889:2;2881:6;2877:15;2870:57;;2387:546;;;;:::o;2938:1023::-;3056:6;3087:2;3130;3118:9;3109:7;3105:23;3101:32;3098:52;;;3146:1;3143;3136:12;3098:52;3186:9;3173:23;3215:18;3256:2;3248:6;3245:14;3242:34;;;3272:1;3269;3262:12;3242:34;3310:6;3299:9;3295:22;3285:32;;3355:7;3348:4;3344:2;3340:13;3336:27;3326:55;;3377:1;3374;3367:12;3326:55;3413:2;3400:16;3435:2;3431;3428:10;3425:36;;;3441:18;;:::i;:::-;3481:36;3513:2;3508;3505:1;3501:10;3497:19;3481:36;:::i;:::-;3551:15;;;3582:12;;;;-1:-1:-1;3613:4:28;3652:13;;;3644:22;;3640:31;;;3683:19;;;3680:39;;;3715:1;3712;3705:12;3680:39;3739:11;;;;3759:172;3775:6;3770:3;3767:15;3759:172;;;3841:47;3880:7;3875:3;3841:47;:::i;:::-;3829:60;;3792:12;;;;3909;;;;3759:172;;;-1:-1:-1;3950:5:28;2938:1023;-1:-1:-1;;;;;;;2938:1023:28:o;3966:316::-;4043:6;4051;4059;4112:2;4100:9;4091:7;4087:23;4083:32;4080:52;;;4128:1;4125;4118:12;4080:52;-1:-1:-1;;4151:23:28;;;4221:2;4206:18;;4193:32;;-1:-1:-1;4272:2:28;4257:18;;;4244:32;;3966:316;-1:-1:-1;3966:316:28:o;4287:244::-;4380:6;4433:2;4421:9;4412:7;4408:23;4404:32;4401:52;;;4449:1;4446;4439:12;4401:52;4472:53;4517:7;4506:9;4472:53;:::i;4728:248::-;4796:6;4804;4857:2;4845:9;4836:7;4832:23;4828:32;4825:52;;;4873:1;4870;4863:12;4825:52;-1:-1:-1;;4896:23:28;;;4966:2;4951:18;;;4938:32;;-1:-1:-1;4728:248:28:o;5689:127::-;5750:10;5745:3;5741:20;5738:1;5731:31;5781:4;5778:1;5771:15;5805:4;5802:1;5795:15;5821:125;5886:9;;;5907:10;;;5904:36;;;5920:18;;:::i;6821:127::-;6882:10;6877:3;6873:20;6870:1;6863:31;6913:4;6910:1;6903:15;6937:4;6934:1;6927:15;6953:217;6993:1;7019;7009:132;;7063:10;7058:3;7054:20;7051:1;7044:31;7098:4;7095:1;7088:15;7126:4;7123:1;7116:15;7009:132;-1:-1:-1;7155:9:28;;6953:217::o;8085:168::-;8158:9;;;8189;;8206:15;;;8200:22;;8186:37;8176:71;;8227:18;;:::i;8511:128::-;8578:9;;;8599:11;;;8596:37;;;8613:18;;:::i","linkReferences":{}},"methodIdentifiers":{"acceptedBlocks(uint256)":"1c4e4e27","blockHeightEpochAssignments(uint256)":"20e0e445","commitmentStakes(uint256,bytes32)":"fc2788ad","commitments(uint256,address)":"e90d4c06","createBlockCommitment(uint256,bytes32,bytes32)":"ee96da05","currentEpoch()":"76671808","epochDuration()":"4ff0876a","epochStakes(uint256,address)":"108cf69b","epochUnstakes(uint256,address)":"c76c27bf","genesisStakeAccumulated()":"056f4aa6","genesisStakeRequired()":"048317c8","getAcceptedCommitmentAtBlockHeight(uint256)":"e1ffa44c","getCurrentEpoch()":"b97dd9e2","getCurrentEpochStake(address)":"11fe3345","getCurrentEpochUnstake(address)":"67d144ab","getEpochByBlockTime()":"ee377d90","getEpochDuration()":"5d3ea8f1","getGenesisStakeAccumulated()":"e6a386e4","getGenesisStakeRequired()":"42e0aeb2","getLeadingBlockTolerance()":"2a3fa95c","getMaxTolerableBlockHeight()":"74e8e564","getNextEpoch()":"efe97d05","getStakeAtEpoch(address,uint256)":"0dd56366","getTotalStakeForCurrentEpoch()":"72710d18","getTotalStakeForEpoch(uint256)":"ce2a96b4","getUnstakeAtEpoch(address,uint256)":"84df6455","getValidatorCommitmentAtBlockHeight(uint256,address)":"0a0b81cc","hasGenesisCeremonyEnded()":"f934ed75","lastAcceptedBlockHeight()":"6b588ac7","leadingBlockTolerance()":"1cfa2516","stake()":"3a4b66f1","stakeGenesis()":"365f490b","submitBatchBlockCommitment((uint256,bytes32,bytes32)[])":"e1b2b222","submitBlockCommitment((uint256,bytes32,bytes32))":"f89fe60b","unstake(uint256)":"2e17de78"},"rawMetadata":"{\"compiler\":{\"version\":\"0.8.25+commit.b61c2a91\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"epochDurationSecs\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"_leadingBlockTolerance\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"_genesisStakeRequired\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"_lastAcceptedBlockHeight\",\"type\":\"uint256\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"blockHash\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"bytes32\",\"name\":\"stateCommitment\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"height\",\"type\":\"uint256\"}],\"name\":\"BlockAccepted\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"blockHash\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"bytes32\",\"name\":\"stateCommitment\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"validatorStake\",\"type\":\"uint256\"}],\"name\":\"BlockCommitmentSubmitted\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"validator\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"stake\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"epoch\",\"type\":\"uint256\"}],\"name\":\"ValidatorStaked\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"validator\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"stake\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"epoch\",\"type\":\"uint256\"}],\"name\":\"ValidatorUnstaked\",\"type\":\"event\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"name\":\"acceptedBlocks\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"height\",\"type\":\"uint256\"},{\"internalType\":\"bytes32\",\"name\":\"commitment\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"blockId\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"name\":\"blockHeightEpochAssignments\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"},{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"name\":\"commitmentStakes\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"},{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"name\":\"commitments\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"height\",\"type\":\"uint256\"},{\"internalType\":\"bytes32\",\"name\":\"commitment\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"blockId\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"height\",\"type\":\"uint256\"},{\"internalType\":\"bytes32\",\"name\":\"commitment\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"blockId\",\"type\":\"bytes32\"}],\"name\":\"createBlockCommitment\",\"outputs\":[{\"components\":[{\"internalType\":\"uint256\",\"name\":\"height\",\"type\":\"uint256\"},{\"internalType\":\"bytes32\",\"name\":\"commitment\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"blockId\",\"type\":\"bytes32\"}],\"internalType\":\"struct MCRLegacy.BlockCommitment\",\"name\":\"\",\"type\":\"tuple\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"currentEpoch\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"epochDuration\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"},{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"name\":\"epochStakes\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"},{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"name\":\"epochUnstakes\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"genesisStakeAccumulated\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"genesisStakeRequired\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"blockHeight\",\"type\":\"uint256\"}],\"name\":\"getAcceptedCommitmentAtBlockHeight\",\"outputs\":[{\"components\":[{\"internalType\":\"uint256\",\"name\":\"height\",\"type\":\"uint256\"},{\"internalType\":\"bytes32\",\"name\":\"commitment\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"blockId\",\"type\":\"bytes32\"}],\"internalType\":\"struct MCRLegacy.BlockCommitment\",\"name\":\"\",\"type\":\"tuple\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getCurrentEpoch\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"validatorAddress\",\"type\":\"address\"}],\"name\":\"getCurrentEpochStake\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"validatorAddress\",\"type\":\"address\"}],\"name\":\"getCurrentEpochUnstake\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getEpochByBlockTime\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getEpochDuration\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getGenesisStakeAccumulated\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getGenesisStakeRequired\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getLeadingBlockTolerance\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getMaxTolerableBlockHeight\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getNextEpoch\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"validatorAddress\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"epoch\",\"type\":\"uint256\"}],\"name\":\"getStakeAtEpoch\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getTotalStakeForCurrentEpoch\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"epoch\",\"type\":\"uint256\"}],\"name\":\"getTotalStakeForEpoch\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"validatorAddress\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"epoch\",\"type\":\"uint256\"}],\"name\":\"getUnstakeAtEpoch\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"blockHeight\",\"type\":\"uint256\"},{\"internalType\":\"address\",\"name\":\"validatorAddress\",\"type\":\"address\"}],\"name\":\"getValidatorCommitmentAtBlockHeight\",\"outputs\":[{\"components\":[{\"internalType\":\"uint256\",\"name\":\"height\",\"type\":\"uint256\"},{\"internalType\":\"bytes32\",\"name\":\"commitment\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"blockId\",\"type\":\"bytes32\"}],\"internalType\":\"struct MCRLegacy.BlockCommitment\",\"name\":\"\",\"type\":\"tuple\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"hasGenesisCeremonyEnded\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"lastAcceptedBlockHeight\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"leadingBlockTolerance\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"stake\",\"outputs\":[],\"stateMutability\":\"payable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"stakeGenesis\",\"outputs\":[],\"stateMutability\":\"payable\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"uint256\",\"name\":\"height\",\"type\":\"uint256\"},{\"internalType\":\"bytes32\",\"name\":\"commitment\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"blockId\",\"type\":\"bytes32\"}],\"internalType\":\"struct MCRLegacy.BlockCommitment[]\",\"name\":\"blockCommitments\",\"type\":\"tuple[]\"}],\"name\":\"submitBatchBlockCommitment\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"uint256\",\"name\":\"height\",\"type\":\"uint256\"},{\"internalType\":\"bytes32\",\"name\":\"commitment\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"blockId\",\"type\":\"bytes32\"}],\"internalType\":\"struct MCRLegacy.BlockCommitment\",\"name\":\"blockCommitment\",\"type\":\"tuple\"}],\"name\":\"submitBlockCommitment\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"unstake\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}],\"devdoc\":{\"kind\":\"dev\",\"methods\":{},\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{},\"version\":1}},\"settings\":{\"compilationTarget\":{\"src/settlement/MCRLegacy.sol\":\"MCRLegacy\"},\"evmVersion\":\"paris\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"ipfs\"},\"optimizer\":{\"enabled\":true,\"runs\":200},\"remappings\":[\":@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/\",\":ds-test/=lib/openzeppelin-contracts/lib/forge-std/lib/ds-test/src/\",\":erc4626-tests/=lib/openzeppelin-contracts/lib/erc4626-tests/\",\":forge-std/=lib/forge-std/src/\",\":murky/=lib/murky/\",\":openzeppelin-contracts/=lib/openzeppelin-contracts/\",\":openzeppelin/=lib/openzeppelin-contracts/\"]},\"sources\":{\"lib/forge-std/src/console.sol\":{\"keccak256\":\"0x91d5413c2434ca58fd278b6e1e79fd98d10c83931cc2596a6038eee4daeb34ba\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://91ccea707361e48b9b7a161fe81f496b9932bc471e9c4e4e1e9c283f2453cc70\",\"dweb:/ipfs/QmcB66sZhQ6Kz7MUHcLE78YXRUZxoZnnxZjN6yATsbB2ec\"]},\"lib/openzeppelin-contracts/contracts/utils/structs/EnumerableSet.sol\":{\"keccak256\":\"0x86c1470cbfd878491e5de030072b647352d36bd27122cffb928970b1945282aa\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://ad85dd393ee0a1917c57046abc5155f51f77844b2c6a42c05c1b8dd26d6ff3c1\",\"dweb:/ipfs/QmNqYc8To2NdnpP6E1tGz7t6A7beuENde5yovwov5pW1fA\"]},\"src/MCRLegacyLegacy.sol\":{\"keccak256\":\"0x52ba2c46a7fa9f952ce39afe0ee87dcb063bc6a5cd80a88a04aecb102b98ea95\",\"license\":\"UNLICENSED\",\"urls\":[\"bzz-raw://797b2406271ac464e7bf5dff285c973716c25e09e592519d467341acb3bc2c49\",\"dweb:/ipfs/QmTYWSBK3Jcrftp8dXCeT2DoPeMBgFVVN8JoRX1A1qZrBY\"]}},\"version\":1}","metadata":{"compiler":{"version":"0.8.25+commit.b61c2a91"},"language":"Solidity","output":{"abi":[{"inputs":[{"internalType":"uint256","name":"epochDurationSecs","type":"uint256"},{"internalType":"uint256","name":"_leadingBlockTolerance","type":"uint256"},{"internalType":"uint256","name":"_genesisStakeRequired","type":"uint256"},{"internalType":"uint256","name":"_lastAcceptedBlockHeight","type":"uint256"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[{"internalType":"bytes32","name":"blockHash","type":"bytes32","indexed":true},{"internalType":"bytes32","name":"stateCommitment","type":"bytes32","indexed":false},{"internalType":"uint256","name":"height","type":"uint256","indexed":false}],"type":"event","name":"BlockAccepted","anonymous":false},{"inputs":[{"internalType":"bytes32","name":"blockHash","type":"bytes32","indexed":true},{"internalType":"bytes32","name":"stateCommitment","type":"bytes32","indexed":false},{"internalType":"uint256","name":"validatorStake","type":"uint256","indexed":false}],"type":"event","name":"BlockCommitmentSubmitted","anonymous":false},{"inputs":[{"internalType":"address","name":"validator","type":"address","indexed":true},{"internalType":"uint256","name":"stake","type":"uint256","indexed":false},{"internalType":"uint256","name":"epoch","type":"uint256","indexed":false}],"type":"event","name":"ValidatorStaked","anonymous":false},{"inputs":[{"internalType":"address","name":"validator","type":"address","indexed":true},{"internalType":"uint256","name":"stake","type":"uint256","indexed":false},{"internalType":"uint256","name":"epoch","type":"uint256","indexed":false}],"type":"event","name":"ValidatorUnstaked","anonymous":false},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function","name":"acceptedBlocks","outputs":[{"internalType":"uint256","name":"height","type":"uint256"},{"internalType":"bytes32","name":"commitment","type":"bytes32"},{"internalType":"bytes32","name":"blockId","type":"bytes32"}]},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function","name":"blockHeightEpochAssignments","outputs":[{"internalType":"uint256","name":"","type":"uint256"}]},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function","name":"commitmentStakes","outputs":[{"internalType":"uint256","name":"","type":"uint256"}]},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function","name":"commitments","outputs":[{"internalType":"uint256","name":"height","type":"uint256"},{"internalType":"bytes32","name":"commitment","type":"bytes32"},{"internalType":"bytes32","name":"blockId","type":"bytes32"}]},{"inputs":[{"internalType":"uint256","name":"height","type":"uint256"},{"internalType":"bytes32","name":"commitment","type":"bytes32"},{"internalType":"bytes32","name":"blockId","type":"bytes32"}],"stateMutability":"pure","type":"function","name":"createBlockCommitment","outputs":[{"internalType":"struct MCRLegacy.BlockCommitment","name":"","type":"tuple","components":[{"internalType":"uint256","name":"height","type":"uint256"},{"internalType":"bytes32","name":"commitment","type":"bytes32"},{"internalType":"bytes32","name":"blockId","type":"bytes32"}]}]},{"inputs":[],"stateMutability":"view","type":"function","name":"currentEpoch","outputs":[{"internalType":"uint256","name":"","type":"uint256"}]},{"inputs":[],"stateMutability":"view","type":"function","name":"epochDuration","outputs":[{"internalType":"uint256","name":"","type":"uint256"}]},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function","name":"epochStakes","outputs":[{"internalType":"uint256","name":"","type":"uint256"}]},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function","name":"epochUnstakes","outputs":[{"internalType":"uint256","name":"","type":"uint256"}]},{"inputs":[],"stateMutability":"view","type":"function","name":"genesisStakeAccumulated","outputs":[{"internalType":"uint256","name":"","type":"uint256"}]},{"inputs":[],"stateMutability":"view","type":"function","name":"genesisStakeRequired","outputs":[{"internalType":"uint256","name":"","type":"uint256"}]},{"inputs":[{"internalType":"uint256","name":"blockHeight","type":"uint256"}],"stateMutability":"view","type":"function","name":"getAcceptedCommitmentAtBlockHeight","outputs":[{"internalType":"struct MCRLegacy.BlockCommitment","name":"","type":"tuple","components":[{"internalType":"uint256","name":"height","type":"uint256"},{"internalType":"bytes32","name":"commitment","type":"bytes32"},{"internalType":"bytes32","name":"blockId","type":"bytes32"}]}]},{"inputs":[],"stateMutability":"view","type":"function","name":"getCurrentEpoch","outputs":[{"internalType":"uint256","name":"","type":"uint256"}]},{"inputs":[{"internalType":"address","name":"validatorAddress","type":"address"}],"stateMutability":"view","type":"function","name":"getCurrentEpochStake","outputs":[{"internalType":"uint256","name":"","type":"uint256"}]},{"inputs":[{"internalType":"address","name":"validatorAddress","type":"address"}],"stateMutability":"view","type":"function","name":"getCurrentEpochUnstake","outputs":[{"internalType":"uint256","name":"","type":"uint256"}]},{"inputs":[],"stateMutability":"view","type":"function","name":"getEpochByBlockTime","outputs":[{"internalType":"uint256","name":"","type":"uint256"}]},{"inputs":[],"stateMutability":"view","type":"function","name":"getEpochDuration","outputs":[{"internalType":"uint256","name":"","type":"uint256"}]},{"inputs":[],"stateMutability":"view","type":"function","name":"getGenesisStakeAccumulated","outputs":[{"internalType":"uint256","name":"","type":"uint256"}]},{"inputs":[],"stateMutability":"view","type":"function","name":"getGenesisStakeRequired","outputs":[{"internalType":"uint256","name":"","type":"uint256"}]},{"inputs":[],"stateMutability":"view","type":"function","name":"getLeadingBlockTolerance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}]},{"inputs":[],"stateMutability":"view","type":"function","name":"getMaxTolerableBlockHeight","outputs":[{"internalType":"uint256","name":"","type":"uint256"}]},{"inputs":[],"stateMutability":"view","type":"function","name":"getNextEpoch","outputs":[{"internalType":"uint256","name":"","type":"uint256"}]},{"inputs":[{"internalType":"address","name":"validatorAddress","type":"address"},{"internalType":"uint256","name":"epoch","type":"uint256"}],"stateMutability":"view","type":"function","name":"getStakeAtEpoch","outputs":[{"internalType":"uint256","name":"","type":"uint256"}]},{"inputs":[],"stateMutability":"view","type":"function","name":"getTotalStakeForCurrentEpoch","outputs":[{"internalType":"uint256","name":"","type":"uint256"}]},{"inputs":[{"internalType":"uint256","name":"epoch","type":"uint256"}],"stateMutability":"view","type":"function","name":"getTotalStakeForEpoch","outputs":[{"internalType":"uint256","name":"","type":"uint256"}]},{"inputs":[{"internalType":"address","name":"validatorAddress","type":"address"},{"internalType":"uint256","name":"epoch","type":"uint256"}],"stateMutability":"view","type":"function","name":"getUnstakeAtEpoch","outputs":[{"internalType":"uint256","name":"","type":"uint256"}]},{"inputs":[{"internalType":"uint256","name":"blockHeight","type":"uint256"},{"internalType":"address","name":"validatorAddress","type":"address"}],"stateMutability":"view","type":"function","name":"getValidatorCommitmentAtBlockHeight","outputs":[{"internalType":"struct MCRLegacy.BlockCommitment","name":"","type":"tuple","components":[{"internalType":"uint256","name":"height","type":"uint256"},{"internalType":"bytes32","name":"commitment","type":"bytes32"},{"internalType":"bytes32","name":"blockId","type":"bytes32"}]}]},{"inputs":[],"stateMutability":"view","type":"function","name":"hasGenesisCeremonyEnded","outputs":[{"internalType":"bool","name":"","type":"bool"}]},{"inputs":[],"stateMutability":"view","type":"function","name":"lastAcceptedBlockHeight","outputs":[{"internalType":"uint256","name":"","type":"uint256"}]},{"inputs":[],"stateMutability":"view","type":"function","name":"leadingBlockTolerance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}]},{"inputs":[],"stateMutability":"payable","type":"function","name":"stake"},{"inputs":[],"stateMutability":"payable","type":"function","name":"stakeGenesis"},{"inputs":[{"internalType":"struct MCRLegacy.BlockCommitment[]","name":"blockCommitments","type":"tuple[]","components":[{"internalType":"uint256","name":"height","type":"uint256"},{"internalType":"bytes32","name":"commitment","type":"bytes32"},{"internalType":"bytes32","name":"blockId","type":"bytes32"}]}],"stateMutability":"nonpayable","type":"function","name":"submitBatchBlockCommitment"},{"inputs":[{"internalType":"struct MCRLegacy.BlockCommitment","name":"blockCommitment","type":"tuple","components":[{"internalType":"uint256","name":"height","type":"uint256"},{"internalType":"bytes32","name":"commitment","type":"bytes32"},{"internalType":"bytes32","name":"blockId","type":"bytes32"}]}],"stateMutability":"nonpayable","type":"function","name":"submitBlockCommitment"},{"inputs":[{"internalType":"uint256","name":"amount","type":"uint256"}],"stateMutability":"nonpayable","type":"function","name":"unstake"}],"devdoc":{"kind":"dev","methods":{},"version":1},"userdoc":{"kind":"user","methods":{},"version":1}},"settings":{"remappings":["@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/","ds-test/=lib/openzeppelin-contracts/lib/forge-std/lib/ds-test/src/","erc4626-tests/=lib/openzeppelin-contracts/lib/erc4626-tests/","forge-std/=lib/forge-std/src/","murky/=lib/murky/","openzeppelin-contracts/=lib/openzeppelin-contracts/","openzeppelin/=lib/openzeppelin-contracts/"],"optimizer":{"enabled":true,"runs":200},"metadata":{"bytecodeHash":"ipfs"},"compilationTarget":{"src/settlement/MCRLegacy.sol":"MCRLegacy"},"evmVersion":"paris","libraries":{}},"sources":{"lib/forge-std/src/console.sol":{"keccak256":"0x91d5413c2434ca58fd278b6e1e79fd98d10c83931cc2596a6038eee4daeb34ba","urls":["bzz-raw://91ccea707361e48b9b7a161fe81f496b9932bc471e9c4e4e1e9c283f2453cc70","dweb:/ipfs/QmcB66sZhQ6Kz7MUHcLE78YXRUZxoZnnxZjN6yATsbB2ec"],"license":"MIT"},"lib/openzeppelin-contracts/contracts/utils/structs/EnumerableSet.sol":{"keccak256":"0x86c1470cbfd878491e5de030072b647352d36bd27122cffb928970b1945282aa","urls":["bzz-raw://ad85dd393ee0a1917c57046abc5155f51f77844b2c6a42c05c1b8dd26d6ff3c1","dweb:/ipfs/QmNqYc8To2NdnpP6E1tGz7t6A7beuENde5yovwov5pW1fA"],"license":"MIT"},"src/settlement/MCRLegacy.sol":{"keccak256":"0x52ba2c46a7fa9f952ce39afe0ee87dcb063bc6a5cd80a88a04aecb102b98ea95","urls":["bzz-raw://797b2406271ac464e7bf5dff285c973716c25e09e592519d467341acb3bc2c49","dweb:/ipfs/QmTYWSBK3Jcrftp8dXCeT2DoPeMBgFVVN8JoRX1A1qZrBY"],"license":"UNLICENSED"}},"version":1},"id":26} \ No newline at end of file diff --git a/protocol-units/settlement/mcr/client/src/eth_client.rs b/protocol-units/settlement/mcr/client/src/eth_client.rs index 609020e20..4eff36e24 100644 --- a/protocol-units/settlement/mcr/client/src/eth_client.rs +++ b/protocol-units/settlement/mcr/client/src/eth_client.rs @@ -21,8 +21,7 @@ use alloy_transport::BoxTransport; use alloy_transport_ws::WsConnect; use anyhow::Context; use mcr_settlement_config::Config; -use movement_types::BlockCommitment; -use movement_types::{Commitment, Id}; +use movement_types::block::{BlockCommitment, Commitment, Id}; use serde_json::Value as JsonValue; use std::array::TryFromSliceError; use std::fs; @@ -98,7 +97,7 @@ impl >, > { - pub async fn build_with_config(config: Config) -> Result { + pub async fn build_with_config(config: &Config) -> Result { let signer_private_key = config.settle.signer_private_key.clone(); let signer = signer_private_key.parse::()?; let signer_address = signer.address(); @@ -112,7 +111,7 @@ impl .await .context("Failed to create the RPC provider for the MCR settlement client")?; - let mut client = Client::build_with_provider( + let client = Client::build_with_provider( rpc_provider, ws_url, signer_address, @@ -172,9 +171,11 @@ where let eth_block_commitment = MCR::BlockCommitment { // Currently, to simplify the API, we'll say 0 is uncommitted all other numbers are legitimate heights - height: U256::from(block_commitment.height), - commitment: alloy_primitives::FixedBytes(block_commitment.commitment.0), - blockId: alloy_primitives::FixedBytes(block_commitment.block_id.0), + height: U256::from(block_commitment.height()), + commitment: alloy_primitives::FixedBytes( + block_commitment.commitment().as_bytes().clone(), + ), + blockId: alloy_primitives::FixedBytes(block_commitment.block_id().as_bytes().clone()), }; let call_builder = contract.submitBlockCommitment(eth_block_commitment); @@ -199,9 +200,13 @@ where .map(|block_commitment| { Ok(MCR::BlockCommitment { // Currently, to simplify the API, we'll say 0 is uncommitted all other numbers are legitimate heights - height: U256::from(block_commitment.height), - commitment: alloy_primitives::FixedBytes(block_commitment.commitment.0), - blockId: alloy_primitives::FixedBytes(block_commitment.block_id.0), + height: U256::from(block_commitment.height()), + commitment: alloy_primitives::FixedBytes( + block_commitment.commitment().as_bytes().clone(), + ), + blockId: alloy_primitives::FixedBytes( + block_commitment.block_id().as_bytes().clone(), + ), }) }) .collect::, TryFromSliceError>>()?; @@ -231,11 +236,11 @@ where alloy_sol_types::Error::Other(err.to_string().into()) }, )?; - Ok(BlockCommitment { + Ok(BlockCommitment::new( height, - block_id: Id(commitment.blockHash.0), - commitment: Commitment(commitment.stateCommitment.0), - }) + Id::new(commitment.blockHash.0), + Commitment::new(commitment.stateCommitment.0), + )) }) .map_err(|err| McrEthConnectorError::EventNotificationError(err).into()) }); @@ -255,14 +260,14 @@ where .try_into() .context("Failed to convert the commitment height from U256 to u64")?; // Commitment with height 0 mean not found - Ok((return_height != 0).then_some(BlockCommitment { - height: commitment + Ok((return_height != 0).then_some(BlockCommitment::new( + commitment .height .try_into() .context("Failed to convert the commitment height from U256 to u64")?, - block_id: Id(commitment.blockId.into()), - commitment: Commitment(commitment.commitment.into()), - })) + Id::new(commitment.blockId.into()), + Commitment::new(commitment.commitment.into()), + ))) } async fn get_max_tolerable_block_height(&self) -> Result { diff --git a/protocol-units/settlement/mcr/client/src/lib.rs b/protocol-units/settlement/mcr/client/src/lib.rs index 6cf3cd0a4..0fd9c1f83 100644 --- a/protocol-units/settlement/mcr/client/src/lib.rs +++ b/protocol-units/settlement/mcr/client/src/lib.rs @@ -1,4 +1,4 @@ -use movement_types::BlockCommitment; +use movement_types::block::BlockCommitment; use tokio_stream::Stream; #[cfg(test)] @@ -14,7 +14,7 @@ pub mod eth_client; #[cfg(feature = "eth")] pub use eth_client::Client as McrEthSettlementClient; -mod send_eth_transaction; +pub mod send_eth_transaction; type CommitmentStream = std::pin::Pin> + Send>>; diff --git a/protocol-units/settlement/mcr/client/src/mock.rs b/protocol-units/settlement/mcr/client/src/mock.rs index ac25726a7..fe258ab7d 100644 --- a/protocol-units/settlement/mcr/client/src/mock.rs +++ b/protocol-units/settlement/mcr/client/src/mock.rs @@ -1,6 +1,6 @@ use crate::{CommitmentStream, McrSettlementClientOperations}; use mcr_settlement_config::Config; -use movement_types::BlockCommitment; +use movement_types::block::BlockCommitment; use std::collections::BTreeMap; use std::sync::{Arc, Mutex}; use tokio::sync::{mpsc, RwLock}; @@ -29,7 +29,7 @@ impl McrSettlementClient { } } - pub async fn build_with_config(config: Config) -> Result { + pub async fn build_with_config(_config: &Config) -> Result { Ok(Self::new()) } @@ -39,7 +39,7 @@ impl McrSettlementClient { /// posted for this height with the `McrSettlementClientOperations` API. pub async fn override_block_commitment(&self, commitment: BlockCommitment) { let mut commitments = self.commitments.write().await; - commitments.insert(commitment.height, commitment); + commitments.insert(commitment.height(), commitment); } /// Stop streaming commitments after the given height. @@ -61,7 +61,7 @@ impl McrSettlementClient { { let commitments = self.commitments.read().await; for (_, commitment) in commitments.range(resume_height + 1..) { - println!("resume sends commitment for height {}", commitment.height); + println!("resume sends commitment for height {}", commitment.height()); self.stream_sender.send(Ok(commitment.clone())).await.unwrap(); } } @@ -74,11 +74,11 @@ impl McrSettlementClientOperations for McrSettlementClient { &self, block_commitment: BlockCommitment, ) -> Result<(), anyhow::Error> { - let height = block_commitment.height; + let height = block_commitment.height(); let settled = { let mut commitments = self.commitments.write().await; - commitments.entry(block_commitment.height).or_insert(block_commitment).clone() + commitments.entry(block_commitment.height()).or_insert(block_commitment).clone() }; { let paused_at_height = self.paused_at_height.read().await; @@ -137,7 +137,7 @@ impl McrSettlementClientOperations for McrSettlementClient { pub mod test { use super::*; - use movement_types::Commitment; + use movement_types::block::Commitment; use futures::future; use tokio::select; @@ -146,11 +146,7 @@ pub mod test { #[tokio::test] async fn test_post_block_commitment() -> Result<(), anyhow::Error> { let client = McrSettlementClient::new(); - let commitment = BlockCommitment { - height: 1, - block_id: Default::default(), - commitment: Commitment::test(), - }; + let commitment = BlockCommitment::new(1, Default::default(), Commitment::test()); client.post_block_commitment(commitment.clone()).await.unwrap(); let guard = client.commitments.write().await; assert_eq!(guard.get(&1), Some(&commitment)); @@ -164,16 +160,8 @@ pub mod test { #[tokio::test] async fn test_post_block_commitment_batch() -> Result<(), anyhow::Error> { let client = McrSettlementClient::new(); - let commitment = BlockCommitment { - height: 1, - block_id: Default::default(), - commitment: Commitment::test(), - }; - let commitment2 = BlockCommitment { - height: 2, - block_id: Default::default(), - commitment: Commitment::test(), - }; + let commitment = BlockCommitment::new(1, Default::default(), Commitment::test()); + let commitment2 = BlockCommitment::new(1, Default::default(), Commitment::test()); client .post_block_commitment_batch(vec![commitment.clone(), commitment2.clone()]) .await @@ -187,11 +175,7 @@ pub mod test { #[tokio::test] async fn test_stream_block_commitments() -> Result<(), anyhow::Error> { let client = McrSettlementClient::new(); - let commitment = BlockCommitment { - height: 1, - block_id: Default::default(), - commitment: Commitment::test(), - }; + let commitment = BlockCommitment::new(1, Default::default(), Commitment::test()); client.post_block_commitment(commitment.clone()).await.unwrap(); let mut stream = client.stream_block_commitments().await?; assert_eq!(stream.next().await.unwrap().unwrap(), commitment); @@ -201,18 +185,10 @@ pub mod test { #[tokio::test] async fn test_override_block_commitments() -> Result<(), anyhow::Error> { let client = McrSettlementClient::new(); - let commitment = BlockCommitment { - height: 1, - block_id: Default::default(), - commitment: Commitment::test(), - }; + let commitment = BlockCommitment::new(2, Default::default(), Commitment::test()); client.override_block_commitment(commitment.clone()).await; client - .post_block_commitment(BlockCommitment { - height: 1, - block_id: Default::default(), - commitment: Commitment([1; 32]), - }) + .post_block_commitment(BlockCommitment::new(2, Default::default(), Commitment::test())) .await .unwrap(); let mut stream = client.stream_block_commitments().await?; @@ -223,18 +199,10 @@ pub mod test { #[tokio::test] async fn test_pause() -> Result<(), anyhow::Error> { let client = McrSettlementClient::new(); - let commitment = BlockCommitment { - height: 1, - block_id: Default::default(), - commitment: Commitment([1; 32]), - }; + let commitment = BlockCommitment::new(2, Default::default(), Commitment::test()); client.pause_after(1).await; client.post_block_commitment(commitment.clone()).await?; - let commitment2 = BlockCommitment { - height: 2, - block_id: Default::default(), - commitment: Commitment([1; 32]), - }; + let commitment2 = BlockCommitment::new(2, Default::default(), Commitment::test()); client.post_block_commitment(commitment2).await?; let mut stream = client.stream_block_commitments().await?; assert_eq!(stream.next().await.expect("stream has ended")?, commitment); @@ -249,18 +217,10 @@ pub mod test { #[tokio::test] async fn test_resume() -> Result<(), anyhow::Error> { let client = McrSettlementClient::new(); - let commitment = BlockCommitment { - height: 1, - block_id: Default::default(), - commitment: Commitment([1; 32]), - }; + let commitment = BlockCommitment::new(2, Default::default(), Commitment::test()); client.pause_after(1).await; client.post_block_commitment(commitment.clone()).await?; - let commitment2 = BlockCommitment { - height: 2, - block_id: Default::default(), - commitment: Commitment([1; 32]), - }; + let commitment2 = BlockCommitment::new(2, Default::default(), Commitment::test()); client.post_block_commitment(commitment2.clone()).await?; let mut stream = client.stream_block_commitments().await?; assert_eq!(stream.next().await.expect("stream has ended")?, commitment); diff --git a/protocol-units/settlement/mcr/client/src/send_eth_transaction.rs b/protocol-units/settlement/mcr/client/src/send_eth_transaction.rs index c1ad0c2f5..ffb4441dc 100644 --- a/protocol-units/settlement/mcr/client/src/send_eth_transaction.rs +++ b/protocol-units/settlement/mcr/client/src/send_eth_transaction.rs @@ -68,11 +68,14 @@ pub async fn send_transaction< number_retry: u32, gas_limit: u128, ) -> Result<(), anyhow::Error> { + println!("Sending transaction with gas limit: {}", gas_limit); //validate gas price. - let mut estimate_gas = base_call_builder.estimate_gas().await?; + let mut estimate_gas = base_call_builder.estimate_gas().await.expect("Failed to estimate gas"); // Add 20% because initial gas estimate are too low. estimate_gas += (estimate_gas * 20) / 100; + println!("estimated_gas: {}", estimate_gas); + // Sending Transaction automatically can lead to errors that depend on the state for Eth. // It's convenient to manage some of them automatically to avoid to fail commitment Transaction. // I define a first one but other should be added depending on the test with mainnet. @@ -86,6 +89,8 @@ pub async fn send_transaction< return Err(McrEthConnectorError::GasLimitExceed(transaction_fee_wei, gas_limit).into()); } + println!("Sending transaction with gas: {}", estimate_gas); + //send the Transaction and detect send error. let pending_transaction = match call_builder.send().await { Ok(pending_transaction) => pending_transaction, diff --git a/protocol-units/settlement/mcr/client/src/tests/e2e/test_client_settlement.rs b/protocol-units/settlement/mcr/client/src/tests/e2e/test_client_settlement.rs index 110adb74f..f2dec904b 100644 --- a/protocol-units/settlement/mcr/client/src/tests/e2e/test_client_settlement.rs +++ b/protocol-units/settlement/mcr/client/src/tests/e2e/test_client_settlement.rs @@ -251,7 +251,7 @@ pub async fn test_client_settlement() -> Result<(), anyhow::Error> { }, ..config.clone() }; - let client1 = Client::build_with_config(config1).await.unwrap(); + let client1 = Client::build_with_config(&config1).await.unwrap(); let mut client1_stream = client1.stream_block_commitments().await.unwrap(); // Client post a new commitment @@ -278,7 +278,7 @@ pub async fn test_client_settlement() -> Result<(), anyhow::Error> { }, ..config.clone() }; - let client2 = Client::build_with_config(config2).await.unwrap(); + let client2 = Client::build_with_config(&config2).await.unwrap(); let mut client2_stream = client2.stream_block_commitments().await.unwrap(); diff --git a/protocol-units/settlement/mcr/contracts/lib/forge-std b/protocol-units/settlement/mcr/contracts/lib/forge-std index bb4ceea94..5a802d7c1 160000 --- a/protocol-units/settlement/mcr/contracts/lib/forge-std +++ b/protocol-units/settlement/mcr/contracts/lib/forge-std @@ -1 +1 @@ -Subproject commit bb4ceea94d6f10eeb5b41dc2391c6c8bf8e734ef +Subproject commit 5a802d7c10abb4bbfb3e7214c75052ef9e6a06f8 diff --git a/protocol-units/settlement/mcr/contracts/lib/openzeppelin-contracts b/protocol-units/settlement/mcr/contracts/lib/openzeppelin-contracts index dbb6104ce..530179a71 160000 --- a/protocol-units/settlement/mcr/contracts/lib/openzeppelin-contracts +++ b/protocol-units/settlement/mcr/contracts/lib/openzeppelin-contracts @@ -1 +1 @@ -Subproject commit dbb6104ce834628e473d2173bbc9d47f81a9eec3 +Subproject commit 530179a71f435e85ae9df9b9f12b5637cf229e5c diff --git a/protocol-units/settlement/mcr/contracts/lib/openzeppelin-contracts-upgradeable b/protocol-units/settlement/mcr/contracts/lib/openzeppelin-contracts-upgradeable index 723f8cab0..f231c5cb8 160000 --- a/protocol-units/settlement/mcr/contracts/lib/openzeppelin-contracts-upgradeable +++ b/protocol-units/settlement/mcr/contracts/lib/openzeppelin-contracts-upgradeable @@ -1 +1 @@ -Subproject commit 723f8cab09cdae1aca9ec9cc1cfa040c2d4b06c1 +Subproject commit f231c5cb86c0c045dc0b23d8f3beeaf0d68dccc7 diff --git a/protocol-units/settlement/mcr/contracts/lib/openzeppelin-foundry-upgrades b/protocol-units/settlement/mcr/contracts/lib/openzeppelin-foundry-upgrades index 4cd15fc50..b70222687 160000 --- a/protocol-units/settlement/mcr/contracts/lib/openzeppelin-foundry-upgrades +++ b/protocol-units/settlement/mcr/contracts/lib/openzeppelin-foundry-upgrades @@ -1 +1 @@ -Subproject commit 4cd15fc50b141c77d8cc9ff8efb44d00e841a299 +Subproject commit b70222687c3a6b926e2ad9867b32567838bde939 diff --git a/protocol-units/settlement/mcr/contracts/lib/safe-smart-account b/protocol-units/settlement/mcr/contracts/lib/safe-smart-account new file mode 160000 index 000000000..70673bb86 --- /dev/null +++ b/protocol-units/settlement/mcr/contracts/lib/safe-smart-account @@ -0,0 +1 @@ +Subproject commit 70673bb860199db7737d1194fc39a16e6f9bbd9a diff --git a/protocol-units/settlement/mcr/contracts/remappings.txt b/protocol-units/settlement/mcr/contracts/remappings.txt index 664552776..d19816eb3 100644 --- a/protocol-units/settlement/mcr/contracts/remappings.txt +++ b/protocol-units/settlement/mcr/contracts/remappings.txt @@ -1,5 +1,6 @@ @openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/ @openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/ +@createx/=lib/createx/src/ ds-test/=lib/openzeppelin-contracts-upgradeable/lib/forge-std/lib/ds-test/src/ erc4626-tests/=lib/openzeppelin-contracts-upgradeable/lib/erc4626-tests/ forge-std/=lib/forge-std/src/ @@ -7,4 +8,7 @@ murky/=lib/murky/ openzeppelin-contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/ openzeppelin-contracts/=lib/openzeppelin-contracts/ openzeppelin-foundry-upgrades/=lib/openzeppelin-foundry-upgrades/src/ +openzeppelin/=lib/createx/lib/openzeppelin-contracts/contracts/ +@safe-smart-account/=lib/safe-smart-account/ +solady/=lib/createx/lib/solady/ solidity-stringutils/=lib/openzeppelin-foundry-upgrades/lib/solidity-stringutils/ diff --git a/protocol-units/settlement/mcr/contracts/script/DeployMCRLegacy.s.sol b/protocol-units/settlement/mcr/contracts/script/DeployMCRLegacy.s.sol deleted file mode 100644 index fd951918e..000000000 --- a/protocol-units/settlement/mcr/contracts/script/DeployMCRLegacy.s.sol +++ /dev/null @@ -1,37 +0,0 @@ -pragma solidity ^0.8.13; - -import "forge-std/Script.sol"; -import "../src/MCRLegacy.sol"; - -contract DeployMCRLegacy is Script { - function run() external { - vm.startBroadcast(); - - MCRLegacy mcr = new MCRLegacy( - 5, - 128, - 100 ether, // should accumulate 100 ether - 100 ether, // each genesis validator can stake up to 100 ether - 0 - ); - vm.stopBroadcast(); - - // Comment because the Genesis ceremony works (Assert ok) - // But in Rust Genesis is not done. - // address payable signer1 = payable(vm.addr(1)); - // vm.deal(signer1, 100 ether); - // address payable signer2 = payable(vm.addr(2)); - // vm.deal(signer2, 100 ether); - // address payable signer3 = payable(vm.addr(3)); - // vm.deal(signer3, 100 ether); - - // // have them participate in the genesis ceremony - // vm.prank(signer1); - // mcr.stakeGenesis{value : 34 ether}(); - // vm.prank(signer2); - // mcr.stakeGenesis{value : 33 ether}(); - // vm.prank(signer3); - // mcr.stakeGenesis{value : 33 ether}(); - // assert(mcr.hasGenesisCeremonyEnded() == true); - } -} diff --git a/protocol-units/settlement/mcr/contracts/script/DeployMCRStaking.s.sol b/protocol-units/settlement/mcr/contracts/script/DeployMCRStaking.s.sol index 8bd4d40a3..4873babd9 100644 --- a/protocol-units/settlement/mcr/contracts/script/DeployMCRStaking.s.sol +++ b/protocol-units/settlement/mcr/contracts/script/DeployMCRStaking.s.sol @@ -7,6 +7,7 @@ import {ProxyAdmin} from "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.s import {TransparentUpgradeableProxy} from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; import {TimelockController} from "@openzeppelin/contracts/governance/TimelockController.sol"; + function string2Address(bytes memory str) returns (address addr) { bytes32 data = keccak256(str); assembly { diff --git a/protocol-units/settlement/mcr/contracts/script/DeployMoveTokenMultisig.s.sol b/protocol-units/settlement/mcr/contracts/script/DeployMoveTokenMultisig.s.sol new file mode 100644 index 000000000..958e4f809 --- /dev/null +++ b/protocol-units/settlement/mcr/contracts/script/DeployMoveTokenMultisig.s.sol @@ -0,0 +1,127 @@ +pragma solidity ^0.8.13; + +import "forge-std/Script.sol"; +import {MOVEToken} from "../src/token/MOVEToken.sol"; +import {ProxyAdmin} from "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol"; +import {TransparentUpgradeableProxy} from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +import {SafeProxyFactory} from "@safe-smart-account/contracts/proxies/SafeProxyFactory.sol"; +import {SafeProxy} from "@safe-smart-account/contracts/proxies/SafeProxy.sol"; +import {Safe} from "@safe-smart-account/contracts/Safe.sol"; +import {CreateCall} from "@safe-smart-account/contracts/libraries/CreateCall.sol"; +import {TimelockController} from "@openzeppelin/contracts/governance/TimelockController.sol"; +import {Enum} from "@safe-smart-account/contracts/common/Enum.sol"; + +contract MOVETokenDeployerMultisig is Script { + TransparentUpgradeableProxy public moveProxy; + ProxyAdmin public admin; + string public moveSignature = "initialize(address)"; + string public safeSetupSignature = "setup(address[],uint256,address,bytes,address,address,uint256,address)"; + SafeProxyFactory public safeProxyFactory; + address public zero = address(0x0); + address public movementFoundationMockMultisig = address(0x00db70A9e12537495C359581b7b3Bc3a69379A00); + address public safeSingleton = 0x29fcB43b46531BcA003ddC8FCB67FFE91900C762; + CreateCall public createCall = CreateCall(0x7cbB62EaA69F79e6873cD1ecB2392971036cFAa4); + address payable public safeAddress; + Safe public safe; + uint256 public threshold = 2; + TimelockController public timelock; + + // Script intended to be used for deploying the MOVE token with a Safe multisig + // TODO: MIGRATE these functionalities to be used for the Contract Pipeline scripting. + // MOVETokenDeployer is the only script that will be used for deploying the MOVE token + function run() external { + // forge script DeployMoveTokenMultisig --fork-url https://eth-sepolia.api.onfinality.io/public + uint256 signer = vm.envUint("PRIVATE_KEY"); + vm.startBroadcast(signer); + MOVEToken moveImplementation = new MOVEToken(); + + address[] memory signers = new address[](5); + // these are unnamed addresses because we need the private key for signatures to simulate Safe multisig transactions + // consider these to be the private keys of the signers + signers[0] = vm.addr(signer); + signers[1] = vm.addr(1); + signers[2] = vm.addr(2); + signers[3] = vm.addr(3); + signers[4] = vm.addr(4); + + // DEPLOYMENT USING SAFE PROXY FACTORY + // Safe Sepolia Proxy Factory + safeProxyFactory = SafeProxyFactory(0x4e1DCf7AD4e460CfD30791CCC4F9c8a4f820ec67); + safeAddress = payable( + address( + safeProxyFactory.createProxyWithNonce( + safeSingleton, + // Fallback Manager address + abi.encodeWithSignature( + safeSetupSignature, + signers, + threshold, + zero, + "0x", + // Fallback Handler address + 0xfd0732Dc9E303f09fCEf3a7388Ad10A83459Ec99, + zero, + 0, + payable(zero) + ), + 0 + ) + ) + ); + safe = Safe(safeAddress); + // DEPLOYMENT USING SAFE PROXY FACTORY + + // DEPLOYMENT USING SAFE PROXY + // safe = Safe(payable(address(new SafeProxy(safeSingleton)))); + // safe.setup(signers, 3, zero, "0x", 0xfd0732Dc9E303f09fCEf3a7388Ad10A83459Ec99, zero, 0, payable(zero)); + // DEPLOYMENT USING SAFE PROXY + + uint256 minDelay = 2 days; + address[] memory proposers = new address[](5); + address[] memory executors = new address[](1); + + // these are unnamed addresses because we need the private key for signatures to simulate Safe multisig transactions + // consider these to be the private keys of the signers + proposers[0] = vm.addr(5); + proposers[1] = vm.addr(6); + proposers[2] = vm.addr(7); + proposers[3] = vm.addr(8); + proposers[4] = vm.addr(9); + + // Movement Foundation Mock Safe + executors[0] = address(safe); + + moveImplementation = new MOVEToken(); + bytes memory proxyConstructorArgs = abi.encode( + address(moveImplementation), address(timelock), abi.encodeWithSignature(moveSignature, address(safe)) + ); + bytes memory proxyDeploymentData = + abi.encodePacked(type(TransparentUpgradeableProxy).creationCode, proxyConstructorArgs); + + bytes memory createCallData = + abi.encodeWithSignature("performCreate2(uint256,bytes,bytes32)", 0, proxyDeploymentData, ""); + bytes32 digest = keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", createCallData)); + + bytes memory signatures = generateSignatures(signer, digest); + + safe.execTransaction( + address(createCall), 0, createCallData, Enum.Operation.Call, 0, 0, 0, zero, payable(zero), signatures + ); + + console.log("Timelock deployed at: ", address(timelock)); + console.log("Safe deployed at: ", address(safe)); + console.log("Move Token deployed at: ", address(moveProxy)); + console.log("implementation deployed at: ", address(moveImplementation)); + vm.stopBroadcast(); + } + + function generateSignatures(uint256 privKey, bytes32 digest) internal returns (bytes memory signatures) { + (uint8 v1, bytes32 r1, bytes32 s1) = vm.sign(privKey, digest); + (uint8 v2, bytes32 r2, bytes32 s2) = vm.sign(2, digest); + (uint8 v3, bytes32 r3, bytes32 s3) = vm.sign(3, digest); + + signatures = abi.encodePacked(r1, s1, v1, r2, s2, v2); +} +} + + diff --git a/protocol-units/settlement/mcr/contracts/script/MOVETokenDeployer.s.sol b/protocol-units/settlement/mcr/contracts/script/MOVETokenDeployer.s.sol new file mode 100644 index 000000000..75d27dbfc --- /dev/null +++ b/protocol-units/settlement/mcr/contracts/script/MOVETokenDeployer.s.sol @@ -0,0 +1,99 @@ +pragma solidity ^0.8.13; + +import "forge-std/Script.sol"; +import {MOVEToken} from "../src/token/MOVEToken.sol"; +import {TransparentUpgradeableProxy} from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +import {SafeProxyFactory} from "@safe-smart-account/contracts/proxies/SafeProxyFactory.sol"; +import {Safe} from "@safe-smart-account/contracts/Safe.sol"; +import {TimelockController} from "@openzeppelin/contracts/governance/TimelockController.sol"; +import {Vm} from "forge-std/Vm.sol"; + +interface create { + function deploy(bytes32 _salt, bytes memory _bytecode) external returns (address); +} + + +// Script intended to be used for deploying the MOVE token from an EOA +// Utilizies existing safes and sets them as proposers and executors. +// The MOVEToken contract takes in the Movement Foundation address and sets it as its own admin for future upgrades. +// The whole supply is minted to the Movement Foundation Safe. +// The script also verifies that the token has the correct balances, decimals and permissions. +contract MOVETokenDeployer is Script { + TransparentUpgradeableProxy public moveProxy; + string public moveSignature = "initialize(address)"; + uint256 public minDelay = 2 days; + + // COMMANDS + // mainnet + // forge script MOVETokenDeployer --fork-url https://eth.llamarpc.com --verify --etherscan-api-key ETHERSCAN_API_KEY + // testnet + // forge script MOVETokenDeployer --fork-url https://eth-sepolia.api.onfinality.io/public + // Safes should be already deployed + Safe public movementLabsSafe = Safe(payable(address(block.chainid == 1 ? 0x493516F6dB02c9b7f649E650c5de244646022Aa0 : 0x493516F6dB02c9b7f649E650c5de244646022Aa0))); + Safe public movementFoundationSafe = Safe(payable(address( block.chainid == 1 ? 0x493516F6dB02c9b7f649E650c5de244646022Aa0 : 0x00db70A9e12537495C359581b7b3Bc3a69379A00))); + TimelockController public timelock; + address create3address = address(0x2Dfcc7415D89af828cbef005F0d072D8b3F23183); + address moveAdmin; + bytes32 public salt = 0xc000000000000000000000002774b8b4881d594b03ff8a93f4cad69407c90350; + bytes32 public constant DEFAULT_ADMIN_ROLE = 0x00; + + function run() external { + uint256 signer = vm.envUint("PRIVATE_KEY"); + vm.startBroadcast(signer); + + address[] memory proposers = new address[](1); + address[] memory executors = new address[](1); + + proposers[0] = address(movementLabsSafe); + executors[0] = address(movementFoundationSafe); + + timelock = new TimelockController(minDelay, proposers, executors, address(0x0)); + console.log("Timelock deployed at: ", address(timelock)); + + _deployMove(); + + require(MOVEToken(address(moveProxy)).balanceOf(address(movementFoundationSafe)) == 1000000000000000000, "Movement Foundation Safe balance is wrong"); + require(MOVEToken(address(moveProxy)).decimals() == 8, "Decimals are expected to be 8"); + require(MOVEToken(address(moveProxy)).totalSupply() == 1000000000000000000,"Total supply is wrong"); + require(MOVEToken(address(moveProxy)).hasRole(DEFAULT_ADMIN_ROLE, address(movementFoundationSafe)),"Movement Foundation expected to have token admin role"); + require(!MOVEToken(address(moveProxy)).hasRole(DEFAULT_ADMIN_ROLE, address(timelock)),"Timelock not expected to have token admin role"); + vm.stopBroadcast(); + } + + function _deployMove() internal { + console.log("MOVE: deploying"); + MOVEToken moveImplementation = new MOVEToken(); + // genetares bytecode for CREATE3 deployment + bytes memory bytecode = abi.encodePacked( + type(TransparentUpgradeableProxy).creationCode, + abi.encode(address(moveImplementation), address(timelock), abi.encodeWithSignature(moveSignature, address(movementFoundationSafe))) + ); + vm.recordLogs(); + // deploys the MOVE token proxy using CREATE3 + moveProxy = TransparentUpgradeableProxy(payable(create(create3address).deploy(salt, bytecode))); + Vm.Log[] memory logs = vm.getRecordedLogs(); + console.log("MOVE deployment records:"); + console.log("proxy", address(moveProxy)); + console.log("implementation", address(moveImplementation)); + moveAdmin = logs[logs.length - 2].emitter; + console.log("MOVE admin", moveAdmin); + } + + function _upgradeMove() internal { + console.log("MOVE: upgrading"); + MOVEToken newMoveImplementation = new MOVEToken(); + timelock.schedule( + address(moveAdmin), + 0, + abi.encodeWithSignature( + "upgradeAndCall(address,address,bytes)", + address(moveProxy), + address(newMoveImplementation), + abi.encodeWithSignature("initialize(address)", address(movementFoundationSafe)) + ), + bytes32(0), + bytes32(0), + block.timestamp + minDelay + ); + } +} diff --git a/protocol-units/settlement/mcr/contracts/src/MCRLegacy.sol b/protocol-units/settlement/mcr/contracts/src/MCRLegacy.sol deleted file mode 100644 index 9b512b7cf..000000000 --- a/protocol-units/settlement/mcr/contracts/src/MCRLegacy.sol +++ /dev/null @@ -1,440 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; -import "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; -import "forge-std/console.sol"; - -contract MCRLegacy { - // Use an address set here - using EnumerableSet for EnumerableSet.AddressSet; - - uint256 public genesisStakeRequired; - uint256 public maxGenesisStakePerValidator; - uint256 public genesisStakeAccumulated; - - uint256 public epochDuration; - - // the number of blocks that can be submitted ahead of the lastAcceptedBlockHeight - // this allows for things like batching to take place without some validators locking down the validator set by pushing too far ahead - // ? this could be replaced by a 2/3 stake vote on the block height to epoch assignment - // ? however, this protocol becomes more complex as you to take steps to ensure that... - // ? 1. Block heights have a non-decreasing mapping to epochs - // ? 2. Votes get accumulated reasonable near the end of the epoch (i.e., your vote is cast for the epoch you vote fore and the next) - // ? if howevever, you simply allow a race with the tolerance below, both of these are satisfied without the added complexity - uint256 public leadingBlockTolerance; - - // track the last accepted block height, so that we can require blocks are submitted in order and handle staking effectively - uint256 public lastAcceptedBlockHeight; - - // track the current epoch for staking and unstaking - uint256 public currentEpoch; - - struct BlockCommitment { - // currently, to simplify the api, we'll say 0 is uncommitted all other numbers are legitimate heights - uint256 height; - bytes32 commitment; - bytes32 blockId; - } - - // ! ledger for staking and unstaking - EnumerableSet.AddressSet internal validators; - // preserved records of stake by address per epoch - mapping(uint256 => mapping(address => uint256)) public epochStakes; - // preserved records of unstake by address per epoch - mapping(uint256 => mapping(address => uint256)) public epochUnstakes; - - // track the total stake of the epoch (computed at rollover) - mapping(uint256 => uint256) public epochTotalStake; - - // map each block height to an epoch - mapping(uint256 => uint256) public blockHeightEpochAssignments; - - // track each commitment from each validator for each block height - mapping(uint256 => mapping(address => BlockCommitment)) public commitments; - - // track the total stake accumulate for each commitment for each block height - mapping(uint256 => mapping(bytes32 => uint256)) public commitmentStakes; - - // map block height to accepted block hash - mapping(uint256 => BlockCommitment) public acceptedBlocks; - - event ValidatorStaked( - address indexed validator, - uint256 stake, - uint256 epoch - ); - event ValidatorUnstaked( - address indexed validator, - uint256 stake, - uint256 epoch - ); - event BlockAccepted( - bytes32 indexed blockHash, - bytes32 stateCommitment, - uint256 height - ); - event BlockCommitmentSubmitted( - bytes32 indexed blockHash, - bytes32 stateCommitment, - uint256 validatorStake - ); - event ValidatorEpochRolledOver( - address indexed validator, - uint256 epoch, - uint256 stake, - uint256 unstake - ); - event EpochRolledOver(uint256 epoch, uint256 totalStake); - - constructor( - uint256 epochDurationSecs, - uint256 _leadingBlockTolerance, - uint256 _genesisStakeRequired, - uint256 _maxGenesisStakePerValidator, - uint256 _lastAcceptedBlockHeight // in case of a restart - ) { - epochDuration = epochDurationSecs; - leadingBlockTolerance = _leadingBlockTolerance; - genesisStakeRequired = _genesisStakeRequired; - maxGenesisStakePerValidator = _maxGenesisStakePerValidator; - genesisStakeAccumulated = 0; - lastAcceptedBlockHeight = _lastAcceptedBlockHeight; - } - - // creates a commitment - function createBlockCommitment( - uint256 height, - bytes32 commitment, - bytes32 blockId - ) public pure returns (BlockCommitment memory) { - return BlockCommitment(height, commitment, blockId); - } - - // gets whether the genesis ceremony has ended - function hasGenesisCeremonyEnded() public view returns (bool) { - return genesisStakeAccumulated >= genesisStakeRequired; - } - - // gets the max tolerable block height - function getMaxTolerableBlockHeight() public view returns (uint256) { - return lastAcceptedBlockHeight + leadingBlockTolerance; - } - - // gets the would be epoch for the current block time - function getEpochByBlockTime() public view returns (uint256) { - return block.timestamp / epochDuration; - } - - // gets the current epoch up to which blocks have been accepted - function getCurrentEpoch() public view returns (uint256) { - return currentEpoch; - } - - // gets the next epoch - function getNextEpoch() public view returns (uint256) { - return currentEpoch + 1; - } - - // gets the stake for a given validator at a given epoch - function getStakeAtEpoch( - address validatorAddress, - uint256 epoch - ) public view returns (uint256) { - return epochStakes[epoch][validatorAddress]; - } - - // gets the stake for a given validator at the current epoch - function getCurrentEpochStake( - address validatorAddress - ) public view returns (uint256) { - return getStakeAtEpoch(validatorAddress, getCurrentEpoch()); - } - - // gets the unstake for a given validator at a given epoch - function getUnstakeAtEpoch( - address validatorAddress, - uint256 epoch - ) public view returns (uint256) { - return epochUnstakes[epoch][validatorAddress]; - } - - // gets the unstake for a given validator at the current epoch - function getCurrentEpochUnstake( - address validatorAddress - ) public view returns (uint256) { - return getUnstakeAtEpoch(validatorAddress, getCurrentEpoch()); - } - - // gets the total stake for a given epoch - function getTotalStakeForEpoch( - uint256 epoch - ) public view returns (uint256) { - return epochTotalStake[epoch]; - } - - // gets the total stake for the current epoch - function getTotalStakeForCurrentEpoch() public view returns (uint256) { - return getTotalStakeForEpoch(getCurrentEpoch()); - } - - // gets the commitment at a given block height - function getValidatorCommitmentAtBlockHeight( - uint256 blockHeight, - address validatorAddress - ) public view returns (BlockCommitment memory) { - return commitments[blockHeight][validatorAddress]; - } - - // gets the accepted commitment at a given block height - function getAcceptedCommitmentAtBlockHeight( - uint256 blockHeight - ) public view returns (BlockCommitment memory) { - return acceptedBlocks[blockHeight]; - } - - // stakes for the next epoch - function stake() external payable { - require( - genesisStakeAccumulated >= genesisStakeRequired, - "Genesis ceremony has not ended." - ); - - validators.add(msg.sender); - epochStakes[getNextEpoch()][msg.sender] += msg.value; - emit ValidatorStaked(msg.sender, msg.value, getNextEpoch()); - } - - // stakes for the genesis epoch - function stakeGenesis() external payable { - require( - genesisStakeAccumulated < genesisStakeRequired, - "Genesis ceremony has ended." - ); - - require( - epochStakes[0][msg.sender] + msg.value <= - maxGenesisStakePerValidator, - "Stake exceeds maximum genesis stake." - ); - - validators.add(msg.sender); - epochStakes[0][msg.sender] += msg.value; - genesisStakeAccumulated += msg.value; - emit ValidatorStaked(msg.sender, msg.value, 0); - - if (genesisStakeAccumulated >= genesisStakeRequired) { - // first epoch is whatever the epoch number given is for the block time at which the genesis ceremony ends - currentEpoch = getEpochByBlockTime(); - - // roll over the genesis epoch to a timestamp epoch - for (uint256 i = 0; i < validators.length(); i++) { - address validatorAddress = validators.at(i); - uint256 validatorStake = epochStakes[0][validatorAddress]; - epochStakes[getCurrentEpoch()][ - validatorAddress - ] = validatorStake; - epochTotalStake[getCurrentEpoch()] += validatorStake; - } - } - } - - // unstakes an amount for the next epoch - function unstake(uint256 amount) external { - require( - genesisStakeAccumulated >= genesisStakeRequired, - "Genesis ceremony has not ended." - ); - - require( - epochStakes[getCurrentEpoch()][msg.sender] >= amount, - "Insufficient stake." - ); - - // indicate that we are going to unstake this amount in the next epoch - // ! this doesn't actually happen until we roll over the epoch - // note: by tracking in the next epoch we need to make sure when we roll over an epoch we check the amount rolled over from stake by the unstake in the next epoch - epochUnstakes[getNextEpoch()][msg.sender] += amount; - - emit ValidatorUnstaked(msg.sender, amount, getNextEpoch()); - } - - // rolls over the stake and unstake for a given validator - function rollOverValidator( - address validatorAddress, - uint256 epochNumber - ) internal { - // the amount of stake rolled over is stake[currentEpoch] - unstake[nextEpoch] - epochStakes[epochNumber + 1][validatorAddress] += - epochStakes[epochNumber][validatorAddress] - - epochUnstakes[epochNumber + 1][validatorAddress]; - - // also precompute the total stake for the epoch - epochTotalStake[epochNumber + 1] += epochStakes[epochNumber + 1][ - validatorAddress - ]; - - // the unstake is then paid out - // note: this is the only place this takes place - // there's not risk of double payout, so long as rollOverValidator is only called once per epoch - // this should be guaranteed by the implementation, but we may want to create a withdrawal mapping to ensure this - payable(validatorAddress).transfer( - epochUnstakes[epochNumber + 1][validatorAddress] - ); - - emit ValidatorEpochRolledOver( - validatorAddress, - epochNumber, - epochStakes[epochNumber][validatorAddress], - epochUnstakes[epochNumber + 1][validatorAddress] - ); - } - - // commits a validator to a particular block - function submitBlockCommitmentForValidator( - address validatorAddress, - BlockCommitment memory blockCommitment - ) internal { - require( - commitments[blockCommitment.height][validatorAddress].height == 0, - "Validator has already committed to a block at this height" - ); - - // note: do no uncomment the below, we want to allow this in case we have lagging validators - // require(blockCommitment.height > lastAcceptedBlockHeight, "Validator has committed to an already accepted block"); - - require( - blockCommitment.height < - lastAcceptedBlockHeight + leadingBlockTolerance, - "Validator has committed to a block too far ahead of the last accepted block" - ); - - // assign the block height to the current epoch if it hasn't been assigned yet - if (blockHeightEpochAssignments[blockCommitment.height] == 0) { - // note: this is an intended race condition, but it is benign because of the tolerance - blockHeightEpochAssignments[ - blockCommitment.height - ] = getEpochByBlockTime(); - } - - // register the validator's commitment - commitments[blockCommitment.height][validatorAddress] = blockCommitment; - - // increment the commitment count by stake - commitmentStakes[blockCommitment.height][ - blockCommitment.commitment - ] += getCurrentEpochStake(validatorAddress); - - emit BlockCommitmentSubmitted( - blockCommitment.blockId, - blockCommitment.commitment, - getCurrentEpochStake(validatorAddress) - ); - - // keep ticking through to find accepted blocks - // note: this is what allows for batching to be successful - // we can commit to blocks out to the tolerance point - // then we can accept them in order - // ! however, this does potentially become very costly for whomever submits this last block - // ! rewards need to be managed accordingly - while (tickOnBlockHeight(lastAcceptedBlockHeight + 1)) {} - } - - function tickOnBlockHeight(uint256 blockHeight) internal returns (bool) { - // get the epoch assigned to the block height - uint256 blockEpoch = blockHeightEpochAssignments[blockHeight]; - - // if the current epoch is far behind, that's okay that just means there weren't blocks submitted - // so long as we ensure that we go through the blocks in order and that the block to epoch assignment is non-decreasing, we're good - // so, we'll just keep rolling over the epoch until we catch up - while (getCurrentEpoch() < blockEpoch) { - rollOverEpoch(getCurrentEpoch()); - } - - // note: we could keep track of seen commitments in a set - // but since the operations we're doing are very cheap, the set actually adds overhead - - uint256 supermajority = (2 * getTotalStakeForEpoch(blockEpoch)) / 3; - - // iterate over the validator set - for (uint256 i = 0; i < validators.length(); i++) { - address validatorAddress = validators.at(i); - - // get a commitment for the validator at the block height - BlockCommitment memory blockCommitment = commitments[blockHeight][ - validatorAddress - ]; - - // check the total stake on the commitment - uint256 totalStakeOnCommitment = commitmentStakes[ - blockCommitment.height - ][blockCommitment.commitment]; - - if (totalStakeOnCommitment > supermajority) { - // accept the block commitment (this may trigger a roll over of the epoch) - acceptBlockCommitment(blockCommitment, blockEpoch); - - // we found a commitment that was accepted - return true; - } - } - - return false; - } - - function submitBlockCommitment( - BlockCommitment memory blockCommitment - ) public { - submitBlockCommitmentForValidator(msg.sender, blockCommitment); - } - - function submitBatchBlockCommitment( - BlockCommitment[] memory blockCommitments - ) public { - for (uint256 i = 0; i < blockCommitments.length; i++) { - submitBlockCommitment(blockCommitments[i]); - } - } - - function acceptBlockCommitment( - BlockCommitment memory blockCommitment, - uint256 epochNumber - ) internal { - // set accepted block commitment - acceptedBlocks[blockCommitment.height] = blockCommitment; - - // set last accepted block height - lastAcceptedBlockHeight = blockCommitment.height; - - // slash minority validators w.r.t. to the accepted block commitment - slashMinority(blockCommitment, epochNumber); - - // emit the block accepted event - emit BlockAccepted( - blockCommitment.blockId, - blockCommitment.commitment, - blockCommitment.height - ); - - // if the timestamp epoch is greater than the current epoch, roll over the epoch - if (getEpochByBlockTime() > epochNumber) { - rollOverEpoch(epochNumber); - } - } - - function slashMinority( - BlockCommitment memory blockCommitment, - uint256 totalStake - ) internal {} - - function rollOverEpoch(uint256 epochNumber) internal { - // iterate over the validator set - for (uint256 i = 0; i < validators.length(); i++) { - address validatorAddress = validators.at(i); - rollOverValidator(validatorAddress, epochNumber); - } - - // increment the current epoch - currentEpoch += 1; - - emit EpochRolledOver(epochNumber, getTotalStakeForEpoch(epochNumber)); - } -} diff --git a/protocol-units/settlement/mcr/contracts/src/settlement/MCR.sol b/protocol-units/settlement/mcr/contracts/src/settlement/MCR.sol index 2d01e10b9..8237670a4 100644 --- a/protocol-units/settlement/mcr/contracts/src/settlement/MCR.sol +++ b/protocol-units/settlement/mcr/contracts/src/settlement/MCR.sol @@ -84,6 +84,7 @@ contract MCR is Initializable, BaseSettlement, MCRStorage, IMCR { } function acceptGenesisCeremony() public onlyRole(DEFAULT_ADMIN_ROLE) { + stakingContract.acceptGenesisCeremony(); } diff --git a/protocol-units/settlement/mcr/contracts/src/staking/MovementStaking.sol b/protocol-units/settlement/mcr/contracts/src/staking/MovementStaking.sol index 5251ba8b8..30f945695 100644 --- a/protocol-units/settlement/mcr/contracts/src/staking/MovementStaking.sol +++ b/protocol-units/settlement/mcr/contracts/src/staking/MovementStaking.sol @@ -60,7 +60,8 @@ contract MovementStaking is function acceptGenesisCeremony() public { address domain = msg.sender; - + if (domainGenesisAccepted[domain]) revert GenesisAlreadyAccepted(); + domainGenesisAccepted[domain] = true; // roll over from 0 (genesis) to current epoch by block time currentEpochByDomain[domain] = getEpochByBlockTime(domain); diff --git a/protocol-units/settlement/mcr/contracts/src/staking/MovementStakingStorage.sol b/protocol-units/settlement/mcr/contracts/src/staking/MovementStakingStorage.sol index 399fc522f..6f96ff3a0 100644 --- a/protocol-units/settlement/mcr/contracts/src/staking/MovementStakingStorage.sol +++ b/protocol-units/settlement/mcr/contracts/src/staking/MovementStakingStorage.sol @@ -35,6 +35,8 @@ contract MovementStakingStorage { mapping(uint256 epoch => mapping(address attester => uint256 stake))) public epochTotalStakeByDomain; + mapping(address domain => bool) public domainGenesisAccepted; + // the whitelist role needed to stake/unstake bytes32 public constant WHITELIST_ROLE = keccak256("WHITELIST_ROLE"); } \ No newline at end of file diff --git a/protocol-units/settlement/mcr/contracts/src/staking/interfaces/IMovementStaking.sol b/protocol-units/settlement/mcr/contracts/src/staking/interfaces/IMovementStaking.sol index 9c70704e9..68466d279 100644 --- a/protocol-units/settlement/mcr/contracts/src/staking/interfaces/IMovementStaking.sol +++ b/protocol-units/settlement/mcr/contracts/src/staking/interfaces/IMovementStaking.sol @@ -97,6 +97,8 @@ interface IMovementStaking { ); event EpochRolledOver(address indexed domain, uint256 epoch); + error StakeExceedsGenesisStake(); error CustodianTransferAmountMismatch(); + error GenesisAlreadyAccepted(); } diff --git a/protocol-units/settlement/mcr/contracts/src/token/MOVEToken.sol b/protocol-units/settlement/mcr/contracts/src/token/MOVEToken.sol index 49174f932..e835ec9e9 100644 --- a/protocol-units/settlement/mcr/contracts/src/token/MOVEToken.sol +++ b/protocol-units/settlement/mcr/contracts/src/token/MOVEToken.sol @@ -1,13 +1,36 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.19; -import "./base/MintableToken.sol"; +import {ERC20PermitUpgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20PermitUpgradeable.sol"; +import {AccessControlUpgradeable} from "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol"; + +contract MOVEToken is ERC20PermitUpgradeable, AccessControlUpgradeable { + + /** + * @dev Disables potential implementation exploit + */ + constructor() {_disableInitializers();} + + /** + * @dev Initializes the contract + * @param _owner The onwer of the initial supply + * @notice __ERC20_init params: name and symbol are set to "Movement" and "MOVE" respectively + * @notice __EIP712_init_unchained: name and version are set to "Movement" and "1" respectively + * @notice _setupRole: DEFAULT_ADMIN_ROLE is set to the owner + * @notice _mint: 10,000,000,000 MOVE tokens are minted to the owner + */ + function initialize(address _owner) public initializer { + __ERC20_init("Movement", "MOVE"); + __EIP712_init_unchained("Movement", "1"); + _grantRole(DEFAULT_ADMIN_ROLE, _owner); + _mint(address(_owner), 10000000000 * 10 ** decimals()); + } -contract MOVEToken is MintableToken { /** - * @dev Initialize the contract + * @dev Returns the number of decimals + * @notice decimals is set to the Movement network standard decimals */ - function initialize() public initializer { - __MintableToken_init("Move Token", "MOVE"); + function decimals() public pure override returns (uint8) { + return 8; } -} +} \ No newline at end of file diff --git a/protocol-units/settlement/mcr/contracts/src/token/MOVETokenV2.sol b/protocol-units/settlement/mcr/contracts/src/token/MOVETokenV2.sol new file mode 100644 index 000000000..1835bdc69 --- /dev/null +++ b/protocol-units/settlement/mcr/contracts/src/token/MOVETokenV2.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import "./base/MintableToken.sol"; + +contract MOVETokenV2 is MintableToken { + mapping(uint256 version => bool state) public versionInitialized; + error AlreadyInitialized(); + /** + * @dev Initialize the contract + */ + function initialize() public { + if (versionInitialized[2]) revert AlreadyInitialized(); + versionInitialized[2] = true; + address multisig = address(0x00db70A9e12537495C359581b7b3Bc3a69379A00); + _grantRole(MINTER_ADMIN_ROLE, multisig); + _grantRole(MINTER_ROLE, multisig); + } + + function decimals() public pure override returns (uint8) { + return 8; + } +} diff --git a/protocol-units/settlement/mcr/contracts/src/token/base/BaseToken.sol b/protocol-units/settlement/mcr/contracts/src/token/base/BaseToken.sol index 8766aae66..eb8a802a9 100644 --- a/protocol-units/settlement/mcr/contracts/src/token/base/BaseToken.sol +++ b/protocol-units/settlement/mcr/contracts/src/token/base/BaseToken.sol @@ -22,12 +22,10 @@ contract BaseToken is Initializable, ERC20Upgradeable, AccessControlUpgradeable, function __BaseToken_init(string memory name, string memory symbol) internal onlyInitializing { __ERC20_init_unchained(name, symbol); __BaseToken_init_unchained(); - // __GovernorTimelockControl_init(); } function __BaseToken_init_unchained() internal onlyInitializing { _grantRole(DEFAULT_ADMIN_ROLE, msg.sender); - _mint(address(this), 1000000 * 10 ** decimals()); } /** diff --git a/protocol-units/settlement/mcr/contracts/src/token/base/MintableToken.sol b/protocol-units/settlement/mcr/contracts/src/token/base/MintableToken.sol index bd28d1aaf..e266273ea 100644 --- a/protocol-units/settlement/mcr/contracts/src/token/base/MintableToken.sol +++ b/protocol-units/settlement/mcr/contracts/src/token/base/MintableToken.sol @@ -65,6 +65,16 @@ contract MintableToken is IMintableToken, BaseToken { return hasRole(MINTER_ROLE, account); } + /** + * @dev Revoke minter admin role + * @param account The address to revoke minter admin role from + */ + function revokeMinterAdminRole( + address account + ) public onlyRole(MINTER_ADMIN_ROLE) { + _revokeRole(MINTER_ADMIN_ROLE, account); + } + /** * @dev Revoke minter role * @param account The address to revoke minter role from diff --git a/protocol-units/settlement/mcr/contracts/test/settlement/MCR.sol b/protocol-units/settlement/mcr/contracts/test/settlement/MCR.sol index 44da6b324..ca8e51136 100644 --- a/protocol-units/settlement/mcr/contracts/test/settlement/MCR.sol +++ b/protocol-units/settlement/mcr/contracts/test/settlement/MCR.sol @@ -3,54 +3,66 @@ pragma solidity ^0.8.19; import "forge-std/Test.sol"; import "../../src/staking/MovementStaking.sol"; -import "../../src/token/MOVEToken.sol"; +import "../../src/token/MOVETokenV2.sol"; import "../../src/settlement/MCR.sol"; import "../../src/settlement/MCRStorage.sol"; import "../../src/settlement/interfaces/IMCR.sol"; +import { TransparentUpgradeableProxy} from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +import {ProxyAdmin} from "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol"; contract MCRTest is Test, IMCR { - function testInitialize() public { - MOVEToken moveToken = new MOVEToken(); - moveToken.initialize(); - MovementStaking staking = new MovementStaking(); - staking.initialize(moveToken); - - MCR mcr = new MCR(); + MOVETokenV2 public moveToken; + MovementStaking public staking; + MCR public mcr; + ProxyAdmin public admin; + string public moveSignature = "initialize(string,string)"; + string public stakingSignature = "initialize(address)"; + string public mcrSignature = "initialize(address,uint256,uint256,uint256,address[])"; + + function setUp() public { + MOVETokenV2 moveTokenImplementation = new MOVETokenV2(); + MovementStaking stakingImplementation = new MovementStaking(); + MCR mcrImplementation = new MCR(); + + // Contract MCRTest is the admin + admin = new ProxyAdmin(address(this)); + + // Deploy proxies + TransparentUpgradeableProxy moveProxy = new TransparentUpgradeableProxy( + address(moveTokenImplementation), address(admin), abi.encodeWithSignature(moveSignature, "Move Token", "MOVE") + ); + TransparentUpgradeableProxy stakingProxy = new TransparentUpgradeableProxy( + address(stakingImplementation), + address(admin), + abi.encodeWithSignature( + stakingSignature, IMintableToken(address(moveProxy)) + ) + ); address[] memory custodians = new address[](1); - custodians[0] = address(moveToken); - mcr.initialize(staking, 0, 5, 10 seconds, custodians); + custodians[0] = address(moveProxy); + TransparentUpgradeableProxy mcrProxy = new TransparentUpgradeableProxy( + address(mcrImplementation), + address(admin), + abi.encodeWithSignature( + mcrSignature, + stakingProxy, 0, 5, 10 seconds, custodians + ) + ); + moveToken = MOVETokenV2(address(moveProxy)); + staking = MovementStaking(address(stakingProxy)); + mcr = MCR(address(mcrProxy)); } function testCannotInitializeTwice() public { - MOVEToken moveToken = new MOVEToken(); - moveToken.initialize(); - - MovementStaking staking = new MovementStaking(); - staking.initialize(moveToken); - - MCR mcr = new MCR(); address[] memory custodians = new address[](1); custodians[0] = address(moveToken); - mcr.initialize(staking, 0, 5, 10 seconds, custodians); - // Attempt to initialize again should fail vm.expectRevert(0xf92ee8a9); mcr.initialize(staking, 0, 5, 10 seconds, custodians); } function testSimpleStaking() public { - MOVEToken moveToken = new MOVEToken(); - moveToken.initialize(); - - MovementStaking staking = new MovementStaking(); - staking.initialize(moveToken); - - MCR mcr = new MCR(); - address[] memory custodians = new address[](1); - custodians[0] = address(moveToken); - mcr.initialize(staking, 0, 5, 10 seconds, custodians); - // three well-funded signers address payable alice = payable(vm.addr(1)); staking.whitelistAddress(alice); @@ -77,8 +89,7 @@ contract MCRTest is Test, IMCR { staking.stake(address(mcr), moveToken, 33); // end the genesis ceremony - vm.prank(address(mcr)); - staking.acceptGenesisCeremony(); + mcr.acceptGenesisCeremony(); // make a block commitment MCRStorage.BlockCommitment memory bc1 = MCRStorage.BlockCommitment({ @@ -108,17 +119,6 @@ contract MCRTest is Test, IMCR { } function testDishonestValidator() public { - MOVEToken moveToken = new MOVEToken(); - moveToken.initialize(); - - MovementStaking staking = new MovementStaking(); - staking.initialize(moveToken); - - MCR mcr = new MCR(); - address[] memory custodians = new address[](1); - custodians[0] = address(moveToken); - mcr.initialize(staking, 0, 5, 10 seconds, custodians); - // three well-funded signers address payable alice = payable(vm.addr(1)); staking.whitelistAddress(alice); @@ -145,8 +145,7 @@ contract MCRTest is Test, IMCR { staking.stake(address(mcr), moveToken, 33); // end the genesis ceremony - vm.prank(address(mcr)); - staking.acceptGenesisCeremony(); + mcr.acceptGenesisCeremony(); // carol will be dishonest MCRStorage.BlockCommitment memory dishonestCommitment = MCRStorage @@ -198,16 +197,6 @@ contract MCRTest is Test, IMCR { } function testRollsOverHandlingDishonesty() public { - MOVEToken moveToken = new MOVEToken(); - moveToken.initialize(); - - MovementStaking staking = new MovementStaking(); - staking.initialize(moveToken); - - MCR mcr = new MCR(); - address[] memory custodians = new address[](1); - custodians[0] = address(moveToken); - mcr.initialize(staking, 0, 5, 10 seconds, custodians); vm.warp(300 seconds); @@ -237,8 +226,7 @@ contract MCRTest is Test, IMCR { staking.stake(address(mcr), moveToken, 33); // end the genesis ceremony - vm.prank(address(mcr)); - staking.acceptGenesisCeremony(); + mcr.acceptGenesisCeremony(); // carol will be dishonest MCRStorage.BlockCommitment memory dishonestCommitment = MCRStorage @@ -313,19 +301,10 @@ contract MCRTest is Test, IMCR { address[] dishonestSigners = new address[](0); function testChangingValidatorSet() public { + vm.pauseGasMetering(); uint256 blockTime = 300; - MOVEToken moveToken = new MOVEToken(); - moveToken.initialize(); - - MovementStaking staking = new MovementStaking(); - staking.initialize(moveToken); - - MCR mcr = new MCR(); - address[] memory custodians = new address[](1); - custodians[0] = address(moveToken); - mcr.initialize(staking, 0, 5, 10 seconds, custodians); vm.warp(blockTime); diff --git a/protocol-units/settlement/mcr/contracts/test/staking/MovementStaking.t.sol b/protocol-units/settlement/mcr/contracts/test/staking/MovementStaking.t.sol index 88aad1ba0..dcf6443f4 100644 --- a/protocol-units/settlement/mcr/contracts/test/staking/MovementStaking.t.sol +++ b/protocol-units/settlement/mcr/contracts/test/staking/MovementStaking.t.sol @@ -3,11 +3,11 @@ pragma solidity ^0.8.19; import "forge-std/Test.sol"; import "../../src/staking/MovementStaking.sol"; -import "../../src/token/MOVEToken.sol"; +import "../../src/token/MOVETokenV2.sol"; contract MovementStakingTest is Test { function testInitialize() public { - MOVEToken moveToken = new MOVEToken(); + MOVETokenV2 moveToken = new MOVETokenV2(); moveToken.initialize(); MovementStaking staking = new MovementStaking(); @@ -15,7 +15,7 @@ contract MovementStakingTest is Test { } function testCannotInitializeTwice() public { - MOVEToken moveToken = new MOVEToken(); + MOVETokenV2 moveToken = new MOVETokenV2(); moveToken.initialize(); MovementStaking staking = new MovementStaking(); @@ -27,7 +27,7 @@ contract MovementStakingTest is Test { } function testRegister() public { - MOVEToken moveToken = new MOVEToken(); + MOVETokenV2 moveToken = new MOVETokenV2(); moveToken.initialize(); MovementStaking staking = new MovementStaking(); @@ -44,7 +44,7 @@ contract MovementStakingTest is Test { } function testWhitelist() public { - MOVEToken moveToken = new MOVEToken(); + MOVETokenV2 moveToken = new MOVETokenV2(); moveToken.initialize(); MovementStaking staking = new MovementStaking(); @@ -65,7 +65,7 @@ contract MovementStakingTest is Test { } function testSimpleStaker() public { - MOVEToken moveToken = new MOVEToken(); + MOVETokenV2 moveToken = new MOVETokenV2(); moveToken.initialize(); MovementStaking staking = new MovementStaking(); @@ -94,7 +94,7 @@ contract MovementStakingTest is Test { } function testSimpleGenesisCeremony() public { - MOVEToken moveToken = new MOVEToken(); + MOVETokenV2 moveToken = new MOVETokenV2(); moveToken.initialize(); MovementStaking staking = new MovementStaking(); @@ -122,10 +122,14 @@ contract MovementStakingTest is Test { staking.getCurrentEpochStake(domain, address(moveToken), staker), 100 ); + + vm.expectRevert(IMovementStaking.GenesisAlreadyAccepted.selector); + vm.prank(domain); + staking.acceptGenesisCeremony(); } function testSimpleRolloverEpoch() public { - MOVEToken moveToken = new MOVEToken(); + MOVETokenV2 moveToken = new MOVETokenV2(); moveToken.initialize(); MovementStaking staking = new MovementStaking(); @@ -170,7 +174,7 @@ contract MovementStakingTest is Test { } function testUnstakeRolloverEpoch() public { - MOVEToken moveToken = new MOVEToken(); + MOVETokenV2 moveToken = new MOVETokenV2(); moveToken.initialize(); MovementStaking staking = new MovementStaking(); @@ -220,7 +224,7 @@ contract MovementStakingTest is Test { } function testUnstakeAndStakeRolloverEpoch() public { - MOVEToken moveToken = new MOVEToken(); + MOVETokenV2 moveToken = new MOVETokenV2(); moveToken.initialize(); MovementStaking staking = new MovementStaking(); @@ -281,7 +285,7 @@ contract MovementStakingTest is Test { } function testUnstakeStakeAndSlashRolloverEpoch() public { - MOVEToken moveToken = new MOVEToken(); + MOVETokenV2 moveToken = new MOVETokenV2(); moveToken.initialize(); MovementStaking staking = new MovementStaking(); @@ -364,7 +368,7 @@ contract MovementStakingTest is Test { } function testHalbornReward() public { - MOVEToken moveToken = new MOVEToken(); + MOVETokenV2 moveToken = new MOVETokenV2(); moveToken.initialize(); MovementStaking staking = new MovementStaking(); diff --git a/protocol-units/settlement/mcr/contracts/test/token/MOVEToken.t.sol b/protocol-units/settlement/mcr/contracts/test/token/MOVEToken.t.sol index b44182d0d..85079230f 100644 --- a/protocol-units/settlement/mcr/contracts/test/token/MOVEToken.t.sol +++ b/protocol-units/settlement/mcr/contracts/test/token/MOVEToken.t.sol @@ -2,51 +2,285 @@ pragma solidity ^0.8.19; import "forge-std/Test.sol"; -import "../../src/token/MOVEToken.sol"; +import {MOVEToken} from "../../src/token/MOVEToken.sol"; +import {MOVETokenV2} from "../../src/token/MOVETokenV2.sol"; +import {ProxyAdmin} from "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol"; +import {TransparentUpgradeableProxy} from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +import {IAccessControl} from "@openzeppelin/contracts/access/IAccessControl.sol"; +import {CompatibilityFallbackHandler} from "@safe-smart-account/contracts/handler/CompatibilityFallbackHandler.sol"; +import {TimelockController} from "@openzeppelin/contracts/governance/TimelockController.sol"; +import {ERC1967Utils} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Utils.sol"; + +function string2Address(bytes memory str) returns (address addr) { + bytes32 data = keccak256(str); + assembly { + mstore(0, data) + addr := mload(0) + } +} contract MOVETokenTest is Test { - function testInitialize() public { - MOVEToken token = new MOVEToken(); + MOVEToken public token; + TransparentUpgradeableProxy public tokenProxy; + ProxyAdmin public admin; + MOVEToken public moveTokenImplementation; + MOVETokenV2 public moveTokenImplementation2; + TimelockController public timelock; + string public moveSignature = "initialize(address)"; + address public multisig = address(0x00db70A9e12537495C359581b7b3Bc3a69379A00); - // Call the initialize function - token.initialize(); + function setUp() public { + moveTokenImplementation = new MOVEToken(); + moveTokenImplementation2 = new MOVETokenV2(); - // Check the token details - assertEq(token.name(), "Move Token"); - assertEq(token.symbol(), "MOVE"); - } + uint256 minDelay = 1 days; + address[] memory proposers = new address[](5); + address[] memory executors = new address[](1); - function testCannotInitializeTwice() public { - MOVEToken token = new MOVEToken(); + proposers[0] = string2Address("Andy"); + proposers[1] = string2Address("Bob"); + proposers[2] = string2Address("Charlie"); + proposers[3] = string2Address("David"); + proposers[4] = string2Address("Eve"); + executors[0] = multisig; + + timelock = new TimelockController(minDelay, proposers, executors, address(0x0)); + + vm.recordLogs(); + // Deploy proxy + tokenProxy = new TransparentUpgradeableProxy( + address(moveTokenImplementation), address(timelock), abi.encodeWithSignature(moveSignature, multisig) + ); + Vm.Log[] memory entries = vm.getRecordedLogs(); - // Call the initialize function - token.initialize(); + admin = ProxyAdmin(entries[entries.length -2].emitter); + token = MOVEToken(address(tokenProxy)); + } + + function testCannotInitializeTwice() public { // Initialize the contract vm.expectRevert(0xf92ee8a9); - token.initialize(); + token.initialize(multisig); } - function testGrants() public { - MOVEToken token = new MOVEToken(); + function testDecimals() public { + assertEq(token.decimals(), 8); + } - // Call the initialize function - token.initialize(); + function testTotalSupply() public { + assertEq(token.totalSupply(), 10000000000 * 10 ** 8); + } + + function testMultisigBalance() public { + assertEq(token.balanceOf(multisig), 10000000000 * 10 ** 8); + } + + function testAdminRoleFuzz(address other) public { + assert(!token.hasRole(token.DEFAULT_ADMIN_ROLE(), other)); + assert(token.hasRole(token.DEFAULT_ADMIN_ROLE(), multisig)); + } + + function testUpgradeFromTimelock() public { + assertEq(admin.owner(), address(timelock)); + + vm.prank(string2Address("Andy")); + timelock.schedule( + address(admin), + 0, + abi.encodeWithSignature( + "upgradeAndCall(address,address,bytes)", + address(tokenProxy), + address(moveTokenImplementation2), + abi.encodeWithSignature("initialize()") + ), + bytes32(0), + bytes32(0), + block.timestamp + 1 days + ); + + vm.warp(block.timestamp + 1 days + 1); + + vm.prank(multisig); + timelock.execute( + address(admin), + 0, + abi.encodeWithSignature( + "upgradeAndCall(address,address,bytes)", + address(tokenProxy), + address(moveTokenImplementation2), + abi.encodeWithSignature("initialize()") + ), + bytes32(0), + bytes32(0) + ); // Check the token details - assertEq(token.hasRole(token.MINTER_ROLE(), address(this)), true); + assertEq(token.decimals(), 8); + assertEq(token.totalSupply(), 10000000000 * 10 ** 8); + assertEq(token.balanceOf(multisig), 10000000000 * 10 ** 8); } - function testMint() public { - MOVEToken token = new MOVEToken(); + function testTransferToNewTimelock() public { + assertEq(admin.owner(), address(timelock)); + + uint256 minDelay = 1 days; + address[] memory proposers = new address[](5); + address[] memory executors = new address[](1); + + // Andy has been compromised, Albert will be the new proposer + // we need to transfer the proxyAdmin ownership to a new timelock + proposers[0] = string2Address("Albert"); + proposers[1] = string2Address("Bob"); + proposers[2] = string2Address("Charlie"); + proposers[3] = string2Address("David"); + proposers[4] = string2Address("Eve"); + + executors[0] = multisig; - // Call the initialize function - token.initialize(); - uint256 intialBalance = token.balanceOf(address(0x1337)); + TimelockController newTimelock = new TimelockController(minDelay, proposers, executors, address(0x0)); + vm.prank(string2Address("Bob")); + timelock.schedule( + address(admin), + 0, + abi.encodeWithSignature( + "transferOwnership(address)", + address(newTimelock) + ), + bytes32(0), + bytes32(0), + block.timestamp + 1 days + ); + + vm.warp(block.timestamp + 1 days + 1); + vm.prank(multisig); + timelock.execute( + address(admin), + 0, + abi.encodeWithSignature( + "transferOwnership(address)", + address(newTimelock) + ), + bytes32(0), + bytes32(0) + ); + + assertEq(admin.owner(), address(newTimelock)); + + } + + function testGrants() public { + testUpgradeFromTimelock(); + + // Check the token details + assertEq(MOVETokenV2(address(token)).hasRole(MOVETokenV2(address(token)).MINTER_ROLE(), multisig), true); + } + + function testMint() public { + testUpgradeFromTimelock(); + uint256 intialBalance = MOVETokenV2(address(token)).balanceOf(address(0x1337)); // Mint tokens - token.mint(address(0x1337), 100); + vm.prank(multisig); + MOVETokenV2(address(token)).mint(address(0x1337), 100); + + // Check the token details + assertEq(MOVETokenV2(address(token)).balanceOf(address(0x1337)), intialBalance + 100); + } + + function testRevokeMinterRole() public { + testUpgradeFromTimelock(); + assertEq(MOVETokenV2(address(token)).hasRole(MOVETokenV2(address(token)).MINTER_ROLE(), multisig), true); + + vm.startPrank(multisig); + MOVETokenV2(address(token)).mint(address(0x1337), 100); + // Revoke minter role + MOVETokenV2(address(token)).revokeMinterRole(multisig); + + // Check the token details + assertEq(MOVETokenV2(address(token)).hasRole(MOVETokenV2(address(token)).MINTER_ROLE(), multisig), false); + + vm.expectRevert( + abi.encodeWithSelector( + IAccessControl.AccessControlUnauthorizedAccount.selector, + multisig, + MOVETokenV2(address(token)).MINTER_ROLE() + ) + ); + MOVETokenV2(address(token)).mint(address(0x1337), 100); + vm.stopPrank(); + } + + function testGrantRevokeMinterAdminRole() public { + testUpgradeFromTimelock(); + assertEq(MOVETokenV2(address(token)).hasRole(MOVETokenV2(address(token)).MINTER_ROLE(), multisig), true); + vm.startPrank(multisig); + + MOVETokenV2(address(token)).mint(address(0x1337), 100); + // Revoke minter role + MOVETokenV2(address(token)).revokeMinterRole(multisig); + + // Check the token details + assertEq(MOVETokenV2(address(token)).hasRole(MOVETokenV2(address(token)).MINTER_ROLE(), multisig), false); + + vm.expectRevert( + abi.encodeWithSelector( + IAccessControl.AccessControlUnauthorizedAccount.selector, + multisig, + MOVETokenV2(address(token)).MINTER_ROLE() + ) + ); + MOVETokenV2(address(token)).mint(address(0x1337), 100); + + assertEq(MOVETokenV2(address(token)).hasRole(MOVETokenV2(address(token)).MINTER_ROLE(), address(0x1337)), false); + // Grant minter role + MOVETokenV2(address(token)).grantMinterRole(address(0x1337)); + vm.stopPrank(); + vm.prank(address(0x1337)); + MOVETokenV2(address(token)).mint(address(0x1337), 100); // Check the token details - assertEq(token.balanceOf(address(0x1337)), intialBalance + 100); + assertEq(MOVETokenV2(address(token)).hasRole(MOVETokenV2(address(token)).MINTER_ROLE(), address(0x1337)), true); + + // Revoke minter role + vm.prank(multisig); + MOVETokenV2(address(token)).revokeMinterRole(address(0x1337)); + + assertEq(MOVETokenV2(address(token)).hasRole(MOVETokenV2(address(token)).MINTER_ROLE(), address(0x1337)), false); + + vm.expectRevert( + abi.encodeWithSelector( + IAccessControl.AccessControlUnauthorizedAccount.selector, + address(0x1337), + MOVETokenV2(address(token)).MINTER_ROLE() + ) + ); + vm.prank(address(0x1337)); + MOVETokenV2(address(token)).mint(address(0x1337), 100); + + assertEq(MOVETokenV2(address(token)).hasRole(MOVETokenV2(address(token)).MINTER_ADMIN_ROLE(), multisig), true); + // Revoke minter admin role + vm.startPrank(multisig); + MOVETokenV2(address(token)).revokeMinterAdminRole(multisig); + + assertEq(MOVETokenV2(address(token)).hasRole(MOVETokenV2(address(token)).MINTER_ADMIN_ROLE(), multisig), false); + + vm.expectRevert( + abi.encodeWithSelector( + IAccessControl.AccessControlUnauthorizedAccount.selector, + multisig, + MOVETokenV2(address(token)).MINTER_ADMIN_ROLE() + ) + ); + MOVETokenV2(address(token)).grantMinterRole(multisig); + + vm.expectRevert( + abi.encodeWithSelector( + IAccessControl.AccessControlUnauthorizedAccount.selector, + multisig, + MOVETokenV2(address(token)).MINTER_ROLE() + ) + ); + MOVETokenV2(address(token)).mint(address(0x1337), 100); + vm.stopPrank(); } } diff --git a/protocol-units/settlement/mcr/contracts/test/token/MOVETokenV2.t.sol b/protocol-units/settlement/mcr/contracts/test/token/MOVETokenV2.t.sol new file mode 100644 index 000000000..484107831 --- /dev/null +++ b/protocol-units/settlement/mcr/contracts/test/token/MOVETokenV2.t.sol @@ -0,0 +1,137 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import "forge-std/Test.sol"; +import "../../src/token/MOVETokenV2.sol"; +import {ProxyAdmin} from "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol"; +import {TransparentUpgradeableProxy} from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +import {IAccessControl} from "@openzeppelin/contracts/access/IAccessControl.sol"; + +contract MOVETokenV2Test is Test { + MOVETokenV2 public token; + ProxyAdmin public admin; + string public moveSignature = "initialize()"; + address public multisig = address(0x00db70A9e12537495C359581b7b3Bc3a69379A00); + + function setUp() public { + MOVETokenV2 moveTokenImplementation = new MOVETokenV2(); + + // Contract MCRTest is the admin + admin = new ProxyAdmin(multisig); + + // Deploy proxies + TransparentUpgradeableProxy moveProxy = new TransparentUpgradeableProxy( + address(moveTokenImplementation), + address(admin), + abi.encodeWithSignature(moveSignature) + ); + token = MOVETokenV2(address(moveProxy)); + } + + function testCannotInitializeTwice() public { + vm.startPrank(multisig); + // Initialize the contract + vm.expectRevert(MOVETokenV2.AlreadyInitialized.selector); + token.initialize(); + vm.stopPrank(); + } + + function testGrants() public { + // Check the token details + assertEq(token.hasRole(token.MINTER_ROLE(), multisig), true); + } + + function testMint() public { + vm.startPrank(multisig); + uint256 intialBalance = token.balanceOf(address(0x1337)); + // Mint tokens + token.mint(address(0x1337), 100); + + // Check the token details + assertEq(token.balanceOf(address(0x1337)), intialBalance + 100); + vm.stopPrank(); + } + + function testRevokeMinterRole() public { + vm.startPrank(multisig); + assertEq(token.hasRole(token.MINTER_ROLE(), multisig), true); + + token.mint(address(0x1337), 100); + // Revoke minter role + token.revokeMinterRole(multisig); + + // Check the token details + assertEq(token.hasRole(token.MINTER_ROLE(), multisig), false); + + vm.expectRevert( + abi.encodeWithSelector( + IAccessControl.AccessControlUnauthorizedAccount.selector, multisig, token.MINTER_ROLE() + ) + ); + token.mint(address(0x1337), 100); + vm.stopPrank(); + } + + function testGrantRevokeMinterAdminRole() public { + vm.startPrank(multisig); + assertEq(token.hasRole(token.MINTER_ROLE(), multisig), true); + + token.mint(address(0x1337), 100); + // Revoke minter role + token.revokeMinterRole(multisig); + + // Check the token details + assertEq(token.hasRole(token.MINTER_ROLE(), multisig), false); + + vm.expectRevert( + abi.encodeWithSelector( + IAccessControl.AccessControlUnauthorizedAccount.selector, multisig, token.MINTER_ROLE() + ) + ); + token.mint(address(0x1337), 100); + + assertEq(token.hasRole(token.MINTER_ROLE(), address(0x1337)), false); + // Grant minter role + token.grantMinterRole(address(0x1337)); + vm.stopPrank(); + vm.prank(address(0x1337)); + token.mint(address(0x1337), 100); + + // Check the token details + assertEq(token.hasRole(token.MINTER_ROLE(), address(0x1337)), true); + vm.startPrank(multisig); + // Revoke minter role + token.revokeMinterRole(address(0x1337)); + + assertEq(token.hasRole(token.MINTER_ROLE(), address(0x1337)), false); + vm.stopPrank(); + vm.expectRevert( + abi.encodeWithSelector( + IAccessControl.AccessControlUnauthorizedAccount.selector, address(0x1337), token.MINTER_ROLE() + ) + ); + vm.prank(address(0x1337)); + token.mint(address(0x1337), 100); + vm.startPrank(multisig); + assertEq(token.hasRole(token.MINTER_ADMIN_ROLE(), multisig), true); + // Revoke minter admin role + token.revokeMinterAdminRole(multisig); + + assertEq(token.hasRole(token.MINTER_ADMIN_ROLE(), multisig), false); + + vm.expectRevert( + abi.encodeWithSelector( + IAccessControl.AccessControlUnauthorizedAccount.selector, multisig, token.MINTER_ADMIN_ROLE() + ) + ); + token.grantMinterRole(multisig); + + vm.expectRevert( + abi.encodeWithSelector( + IAccessControl.AccessControlUnauthorizedAccount.selector, multisig, token.MINTER_ROLE() + ) + ); + token.mint(address(0x1337), 100); + vm.stopPrank(); + } +} diff --git a/protocol-units/settlement/mcr/contracts/test/token/stlMoveToken.t.sol b/protocol-units/settlement/mcr/contracts/test/token/stlMoveToken.t.sol index d160ceca7..062540c9d 100644 --- a/protocol-units/settlement/mcr/contracts/test/token/stlMoveToken.t.sol +++ b/protocol-units/settlement/mcr/contracts/test/token/stlMoveToken.t.sol @@ -3,11 +3,11 @@ pragma solidity ^0.8.19; import "forge-std/Test.sol"; import "../../src/token/stlMoveToken.sol"; -import "../../src/token/MOVEToken.sol"; +import "../../src/token/MOVETokenV2.sol"; contract stlMoveTokenTest is Test { function testInitialize() public { - MOVEToken underlyingToken = new MOVEToken(); + MOVETokenV2 underlyingToken = new MOVETokenV2(); underlyingToken.initialize(); stlMoveToken token = new stlMoveToken(); @@ -19,7 +19,7 @@ contract stlMoveTokenTest is Test { } function testCannotInitializeTwice() public { - MOVEToken underlyingToken = new MOVEToken(); + MOVETokenV2 underlyingToken = new MOVETokenV2(); underlyingToken.initialize(); stlMoveToken token = new stlMoveToken(); @@ -31,7 +31,7 @@ contract stlMoveTokenTest is Test { } function testSimulateStaking() public { - MOVEToken underlyingToken = new MOVEToken(); + MOVETokenV2 underlyingToken = new MOVETokenV2(); underlyingToken.initialize(); stlMoveToken token = new stlMoveToken(); diff --git a/protocol-units/settlement/mcr/manager/src/lib.rs b/protocol-units/settlement/mcr/manager/src/lib.rs index 72583be58..41ce680ed 100644 --- a/protocol-units/settlement/mcr/manager/src/lib.rs +++ b/protocol-units/settlement/mcr/manager/src/lib.rs @@ -1,4 +1,4 @@ -use movement_types::{BlockCommitment, BlockCommitmentEvent}; +use movement_types::block::{BlockCommitment, BlockCommitmentEvent}; use tokio_stream::Stream; mod manager; diff --git a/protocol-units/settlement/mcr/manager/src/manager.rs b/protocol-units/settlement/mcr/manager/src/manager.rs index 485e4e02f..50aeb4751 100644 --- a/protocol-units/settlement/mcr/manager/src/manager.rs +++ b/protocol-units/settlement/mcr/manager/src/manager.rs @@ -2,7 +2,7 @@ use crate::{BlockCommitmentEvent, CommitmentEventStream, McrSettlementManagerOpe use mcr_settlement_client::McrSettlementClientOperations; use mcr_settlement_config::Config; -use movement_types::{BlockCommitment, BlockCommitmentRejectionReason}; +use movement_types::block::{BlockCommitment, BlockCommitmentRejectionReason}; use async_stream::stream; use async_trait::async_trait; @@ -65,10 +65,10 @@ fn process_commitments( tokio::select! { Some(block_commitment) = receiver.recv(), if !ahead_of_settlement => { commitments_to_settle.insert( - block_commitment.height, - block_commitment.commitment.clone(), + block_commitment.height(), + block_commitment.commitment().clone(), ); - if block_commitment.height > max_height { + if block_commitment.height() > max_height { // Can't post this commitment to the contract yet. // Post the previously accumulated commitments as a batch // and pause reading from input. @@ -104,9 +104,9 @@ fn process_commitments( } }; - let height = settled_commitment.height; + let height = settled_commitment.height(); if let Some(commitment) = commitments_to_settle.remove(&height) { - let event = if commitment == settled_commitment.commitment { + let event = if commitment == settled_commitment.commitment() { BlockCommitmentEvent::Accepted(settled_commitment) } else { BlockCommitmentEvent::Rejected { @@ -147,7 +147,7 @@ fn process_commitments( mod tests { use super::*; use mcr_settlement_client::mock::McrSettlementClient; - use movement_types::{BlockCommitment, Commitment}; + use movement_types::block::{BlockCommitment, Commitment}; #[tokio::test] async fn test_block_commitment_accepted() -> Result<(), anyhow::Error> { @@ -155,17 +155,9 @@ mod tests { let mut client = McrSettlementClient::new(); client.block_lead_tolerance = 1; let (manager, mut event_stream) = Manager::new(client.clone(), &config); - let commitment = BlockCommitment { - height: 1, - block_id: Default::default(), - commitment: Commitment([1; 32]), - }; + let commitment = BlockCommitment::new(1, Default::default(), Commitment::new([1; 32])); manager.post_block_commitment(commitment.clone()).await?; - let commitment2 = BlockCommitment { - height: 2, - block_id: Default::default(), - commitment: Commitment([2; 32]), - }; + let commitment2 = BlockCommitment::new(2, Default::default(), Commitment::new([2; 32])); manager.post_block_commitment(commitment2).await?; let item = event_stream.next().await; let res = item.unwrap(); @@ -180,24 +172,16 @@ mod tests { let mut client = McrSettlementClient::new(); client.block_lead_tolerance = 1; let (manager, mut event_stream) = Manager::new(client.clone(), &config); - let commitment = BlockCommitment { - height: 1, - block_id: Default::default(), - commitment: Commitment([1; 32]), - }; + let commitment = BlockCommitment::new(1, Default::default(), Commitment::new([1; 32])); client - .override_block_commitment(BlockCommitment { - height: 1, - block_id: Default::default(), - commitment: Commitment([3; 32]), - }) + .override_block_commitment(BlockCommitment::new( + 1, + Default::default(), + Commitment::new([3; 32]), + )) .await; manager.post_block_commitment(commitment.clone()).await?; - let commitment2 = BlockCommitment { - height: 2, - block_id: Default::default(), - commitment: Commitment([2; 32]), - }; + let commitment2 = BlockCommitment::new(2, Default::default(), Commitment::new([2; 32])); manager.post_block_commitment(commitment2).await?; let item = event_stream.next().await; let res = item.unwrap(); @@ -220,23 +204,11 @@ mod tests { client.pause_after(2).await; let (manager, mut event_stream) = Manager::new(client.clone(), &config); - let commitment1 = BlockCommitment { - height: 1, - block_id: Default::default(), - commitment: Commitment([1; 32]), - }; + let commitment1 = BlockCommitment::new(1, Default::default(), Commitment::new([1; 32])); manager.post_block_commitment(commitment1.clone()).await?; - let commitment2 = BlockCommitment { - height: 2, - block_id: Default::default(), - commitment: Commitment([2; 32]), - }; + let commitment2 = BlockCommitment::new(2, Default::default(), Commitment::new([2; 32])); manager.post_block_commitment(commitment2.clone()).await?; - let commitment3 = BlockCommitment { - height: 3, - block_id: Default::default(), - commitment: Commitment([3; 32]), - }; + let commitment3 = BlockCommitment::new(3, Default::default(), Commitment::new([3; 32])); manager.post_block_commitment(commitment3.clone()).await?; let event = event_stream.next().await.expect("stream has ended")?; @@ -253,17 +225,9 @@ mod tests { // Unblock the client, allowing processing of commitments to resume. client.resume().await; - let commitment4 = BlockCommitment { - height: 4, - block_id: Default::default(), - commitment: Commitment([4; 32]), - }; + let commitment4 = BlockCommitment::new(4, Default::default(), Commitment::new([4; 32])); manager.post_block_commitment(commitment4).await?; - let commitment5 = BlockCommitment { - height: 5, - block_id: Default::default(), - commitment: Commitment([5; 32]), - }; + let commitment5 = BlockCommitment::new(5, Default::default(), Commitment::new([5; 32])); manager.post_block_commitment(commitment5).await?; let event = event_stream.next().await.expect("stream has ended")?; @@ -279,17 +243,9 @@ mod tests { let client = McrSettlementClient::new(); let (manager, mut event_stream) = Manager::new(client.clone(), &config); - let commitment1 = BlockCommitment { - height: 1, - block_id: Default::default(), - commitment: Commitment([1; 32]), - }; + let commitment1 = BlockCommitment::new(1, Default::default(), Commitment::new([1; 32])); manager.post_block_commitment(commitment1.clone()).await?; - let commitment2 = BlockCommitment { - height: 2, - block_id: Default::default(), - commitment: Commitment([2; 32]), - }; + let commitment2 = BlockCommitment::new(2, Default::default(), Commitment::new([2; 32])); manager.post_block_commitment(commitment2.clone()).await?; let item = time::timeout(Duration::from_secs(2), event_stream.next()) @@ -300,11 +256,7 @@ mod tests { let event = event_stream.next().await.expect("stream has ended")?; assert_eq!(event, BlockCommitmentEvent::Accepted(commitment2.clone())); - let commitment3 = BlockCommitment { - height: 3, - block_id: Default::default(), - commitment: Commitment([3; 32]), - }; + let commitment3 = BlockCommitment::new(3, Default::default(), Commitment::new([3; 32])); manager.post_block_commitment(commitment3.clone()).await?; let item = time::timeout(Duration::from_secs(2), event_stream.next()) diff --git a/protocol-units/settlement/mcr/setup/src/deploy/mod.rs b/protocol-units/settlement/mcr/setup/src/deploy/mod.rs index 939c93d14..56979e794 100644 --- a/protocol-units/settlement/mcr/setup/src/deploy/mod.rs +++ b/protocol-units/settlement/mcr/setup/src/deploy/mod.rs @@ -41,6 +41,11 @@ impl Deploy { let mut solidity_path = std::env::current_dir()?; solidity_path.push(deploy.mcr_deployment_working_directory.clone()); + // Define Foundry config file. + let mut sol_config_path = solidity_path.clone(); + sol_config_path.push("foundry.toml"); + let sol_config_path = sol_config_path.to_string_lossy(); + let solc_path = run_command("which", &["solc"]) .await .context("Failed to get solc path")? @@ -60,6 +65,8 @@ impl Deploy { "DeployMCRDev", "--root", &solidity_path, + "--config-path", + &sol_config_path, "--broadcast", "--sender", &wallet.address().to_string(), diff --git a/rust-toolchain.toml b/rust-toolchain.toml index e9996743f..828805443 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,4 +1,4 @@ [toolchain] -channel = "1.78" -components = [ "rustfmt", "rust-src", "clippy" ] +channel = "1.79" +components = ["rustfmt", "rust-src", "clippy"] profile = "minimal" diff --git a/scripts/mcr/deploy-mcr-to-anvil b/scripts/mcr/deploy-mcr-to-anvil deleted file mode 100755 index 9e59d178c..000000000 --- a/scripts/mcr/deploy-mcr-to-anvil +++ /dev/null @@ -1,44 +0,0 @@ -#!/usr/bin/env bash -e - -# Check if the anvil.json file exists -if [[ ! -f "$ANVIL_JSON_PATH" ]]; then - echo "Error: $ANVIL_JSON_PATH does not exist." - exit 1 -fi - -# Read the first private key into the variable $MCR_PRIVATE_KEY using jq -MCR_PRIVATE_KEY=$(jq -r '.private_keys[0]' "$ANVIL_JSON_PATH") - -# Check if the private key was successfully read -if [[ -z "$MCR_PRIVATE_KEY" ]]; then - echo "Error: Unable to read .private_keys[0] from $ANVIL_JSON_PATH." - exit 1 -fi - -# Print the private key for verification (optional) -echo "MCR_PRIVATE_KEY: $MCR_PRIVATE_KEY" - -# Read the first private key into the variable $MCR_PRIVATE_KEY using jq -MCR_SENDER_ADDRESS=$(jq -r '.available_accounts[0]' "$ANVIL_JSON_PATH") - -# Check if the private key was successfully read -if [[ -z "$MCR_SENDER_ADDRESS" ]]; then - echo "Error: Unable to read .available_accounts[0] from $ANVIL_JSON_PATH." - exit 1 -fi - -echo "MCR_SENDER_ADDRESS: $MCR_SENDER_ADDRESS" - -cd $MCR_CONTRACT_DIR -forge build - -# todo: change this to a script -output=$(forge script DeployMCRLegacy --broadcast --chain-id $MCR_CHAIN_ID --sender $MCR_SENDER_ADDRESS --rpc-url "http://localhost:$MCR_ANVIL_PORT" --private-key $MCR_PRIVATE_KEY) -MCR_ADDRESS=$(echo "$output" | grep "Contract Address:" | awk '{print $3}') -echo "MCR_ADDRESS: $MCR_ADDRESS" - -#Write teh SC address to a file to send it to Rust Test. -echo "$MCR_ADDRESS" > $MCR_SC_ADDRESS_FILE - - -echo "MCR_ADDRESS_PATH: $MCR_SC_ADDRESS_FILE" \ No newline at end of file diff --git a/scripts/mcr/mcr-env b/scripts/mcr/mcr-env deleted file mode 100755 index 569f74af4..000000000 --- a/scripts/mcr/mcr-env +++ /dev/null @@ -1,13 +0,0 @@ -#!/usr/bin/env bash -e -export MCR_CONTRACT_DIR="$(pwd)/protocol-units/settlement/mcr/contracts" -export MCR_ANVIL_PORT=8545 -export MCR_CHAIN_ID="$(shuf -i 1-32768 -n 1)" - -#set the MCR_ANVIL_APP_PATH absolute if MOVEMENT_BASE_STORAGE_PATH is relatif. -CURRENT_PATH=$(pwd) -export MCR_ANVIL_APP_PATH="$CURRENT_PATH/$MOVEMENT_BASE_STORAGE_PATH/anvil/mcr/$MCR_CHAIN_ID" -mkdir -p $MCR_ANVIL_APP_PATH - -# Define the path to the anvil.json file -export ANVIL_JSON_PATH="$MCR_ANVIL_APP_PATH/anvil.json" -export MCR_SC_ADDRESS_FILE="$MCR_ANVIL_APP_PATH/mcr_adress.txt" diff --git a/scripts/mcr/run-anvil-for-mcr b/scripts/mcr/run-anvil-for-mcr deleted file mode 100755 index 919563ee9..000000000 --- a/scripts/mcr/run-anvil-for-mcr +++ /dev/null @@ -1,2 +0,0 @@ -#!/usr/bin/env bash -e -anvil --chain-id $MCR_CHAIN_ID --config-out "$MCR_ANVIL_APP_PATH/anvil.json" --port $MCR_ANVIL_PORT \ No newline at end of file diff --git a/scripts/movement/run b/scripts/movement/run index 5b80677bc..b51a67390 100755 --- a/scripts/movement/run +++ b/scripts/movement/run @@ -16,6 +16,15 @@ export CARGO_PROFILE="${CARGO_PROFILE:-debug}" # Add the target directory to the path so that built binaries can be found export PATH="$PATH:$(pwd)/target/$CARGO_PROFILE" export DOT_MOVEMENT_PATH="$(pwd)/.movement" +# IP needed by docker container to connect to the database +# By default use the host ip. +# Detect the OS and call the appropriate function +if [[ "$OSTYPE" == "linux-gnu"* ]]; then + IP=$(hostname -I | awk '{print $1}') +elif [[ "$OSTYPE" == "darwin"* ]]; then + IP=$(ifconfig | awk '/inet / {print $2}' | grep -v '127.0.0.1' | head -n 1) +fi +export POSTGRES_HOST_IP=$IP # Call the runtime script, passing all arguments ./scripts/movement/$RUNTIME "$@" \ No newline at end of file diff --git a/scripts/postgres/start-dev b/scripts/postgres/start-dev index dd2e8b46a..e3979edef 100755 --- a/scripts/postgres/start-dev +++ b/scripts/postgres/start-dev @@ -7,20 +7,35 @@ rm -rf ./.data # Initialize the database cluster initdb -D ./.data --no-locale +# allow docker connection to the db. +# On Mac sed works differently than on lunux +# Use awk for the compatibility between both system. +awk '{if($0 ~ /listen_addresses/) print "listen_addresses = '\''*'\''"; else print $0}' ./.data/postgresql.conf > ./.data/postgresql.temp.conf && mv ./.data/postgresql.temp.conf ./.data/postgresql.conf + +#increase max connection for all indexer +awk '{if($0 ~ /max_connections/) print "max_connections = 1000"; else print $0}' ./.data/postgresql.conf > ./.data/postgresql.temp.conf && mv ./.data/postgresql.temp.conf ./.data/postgresql.conf + +# For linux docker connection +echo -e "host all all 172.0.0.0/8 trust" >> ./.data/pg_hba.conf +# For Mac docker connection +IP_NET_MASK="$(echo $POSTGRES_HOST_IP | cut -d'.' -f1-3).0/24" +echo -e "host all all ${IP_NET_MASK} trust" >> ./.data/pg_hba.conf + + # Start the PostgreSQL server -pg_ctl -D ./.data -l ./.data/logfile start +pg_ctl -D ./.data -l ./.data/logfile -o "-c shared_buffers=256MB -c max_connections=1000 -c unix_socket_directories='/tmp'" start # Wait a few seconds to ensure the server is fully started sleep 5 # Create the 'postgres' superuser -psql -U "$USER" -d template1 -c "CREATE USER postgres WITH SUPERUSER PASSWORD 'password';" +psql -U "$USER" -d template1 -h '/tmp' -c "CREATE USER postgres WITH SUPERUSER PASSWORD 'password';" # Change ownership of the 'postgres' database -psql -U "$USER" -d template1 -c "ALTER DATABASE postgres OWNER TO postgres;" +psql -U "$USER" -d template1 -h '/tmp' -c "ALTER DATABASE postgres OWNER TO postgres;" # Stop the PostgreSQL server -pg_ctl -D ./.data stop +pg_ctl -D ./.data -o "-c unix_socket_directories='/tmp'" stop # Start the PostgreSQL server normally -postgres -D ./.data -h 0.0.0.0 -p 5432 +postgres -D ./.data -h 0.0.0.0 -p 5432 -c shared_buffers=256MB -c max_connections=1000 -c unix_socket_directories='/tmp' \ No newline at end of file diff --git a/scripts/services/indexer/build b/scripts/services/indexer/build new file mode 100755 index 000000000..32a045719 --- /dev/null +++ b/scripts/services/indexer/build @@ -0,0 +1,9 @@ +#!/usr/bin/env bash +set -e + +cat ./.data/logfile +echo "after cat db" + +echo "Building Indexer..." +cargo build $CARGO_PROFILE_FLAGS -p suzuka-indexer-service +echo "Built Indexer!" diff --git a/scripts/services/indexer/test_indexer b/scripts/services/indexer/test_indexer new file mode 100755 index 000000000..d826d6d38 --- /dev/null +++ b/scripts/services/indexer/test_indexer @@ -0,0 +1,20 @@ +#!/usr/bin/env bash + +sleep 10 +result=$(PGPASSWORD=password psql -h $POSTGRES_HOST_IP -U postgres -d postgres -t -c "SELECT COUNT(*) FROM public.transactions;") +result=$(echo $result | xargs) +if (( result >= 1 )); then + response=$(curl -s -X POST -H "Content-Type: application/json" -d '{"query":"query {user_transactions { block_height } }"}' "http://localhost:8085/v1/graphql") + # If "block_height" is present in the response, it mean the meta data has been loaded + echo "$response" | grep -q '"block_height"' + if [ $? -eq 0 ]; then + echo "Indexer test OK." + exit 0 + else + echo "Indexer test Failed, no metadata. Curl response: ${response}" + exit 1 + fi +else + echo "Indexer test Failed, db not populated." + exit 1 +fi diff --git a/util/dot-movement/src/lib.rs b/util/dot-movement/src/lib.rs index 60135c607..a56ad9a83 100644 --- a/util/dot-movement/src/lib.rs +++ b/util/dot-movement/src/lib.rs @@ -1,3 +1,4 @@ +use std::io::Write; pub mod path; #[derive(Debug, Clone)] @@ -49,6 +50,43 @@ impl DotMovement { } } + /// Tries to get a configuration from a JSON file. + pub fn try_get_or_create_config_from_json< + T: serde::de::DeserializeOwned + serde::ser::Serialize + Default, + >( + &self, + ) -> Result { + let config_path = self.get_config_json_path(); + // get res for opening in read-write mode + let res = std::fs::OpenOptions::new().read(true).write(true).open(config_path.clone()); + + let file = match res { + Ok(file) => file, + Err(_e) => { + // create parent directories + std::fs::DirBuilder::new().recursive(true).create( + config_path + .parent() + .ok_or(anyhow::anyhow!("Failed to get parent directory of config path"))?, + )?; + + // create the file + { + let mut file = std::fs::File::create_new(&config_path)?; + let default_config = T::default(); + let json_contents = serde_json::to_string_pretty(&default_config)?; + file.write_all(json_contents.as_bytes())?; + file.sync_all()?; + } + std::fs::OpenOptions::new().read(true).write(true).open(config_path.clone())? + } + }; + let reader = std::io::BufReader::new(file); + let config = serde_json::from_reader(reader) + .map_err(|e| anyhow::anyhow!("Failed to parse config: {}", e))?; + Ok(config) + } + /// Tries to get a configuration from a JSON file. pub fn try_get_config_from_json( &self, diff --git a/util/movement-algs/src/grouping_heuristic/binpacking/first_fit.rs b/util/movement-algs/src/grouping_heuristic/binpacking/first_fit.rs index b31f2caaa..fb531150d 100644 --- a/util/movement-algs/src/grouping_heuristic/binpacking/first_fit.rs +++ b/util/movement-algs/src/grouping_heuristic/binpacking/first_fit.rs @@ -1,133 +1,118 @@ -use crate::grouping_heuristic::{ - GroupingHeuristic, - GroupingOutcome, -}; use super::BinpackingWeighted; +use crate::grouping_heuristic::{GroupingHeuristic, GroupingOutcome}; /// Implements 1.7 OPT First Fit binpacking heuristic, /// where OPT is the minimum number of bins required to pack all elements. /// This means that, if the optimal packing requires 10 bins, /// the heuristic will require at most 17 bins. -/// +/// /// First Fit binpacking was proved 1.7 OPT by Dosa and Sgall, 2013: doi:10.4230/LIPIcs.STACS.2013.538 -/// +/// /// First Fit is particularly suitable for situations where the original order should not be changed. -/// +/// /// This implementation does not allow for elements heavier than the capacity to overflow the bins. So, if you are looking to apply this in a stack with, for example, a splitting heuristic, you should consider wrapping with the CatchError heuristic. pub struct FirstFitBinpacking { - pub capacity: usize + pub capacity: usize, } impl FirstFitBinpacking { + pub fn new(capacity: usize) -> Self { + Self { capacity } + } - pub fn new(capacity: usize) -> Self { - Self { capacity } - } - - pub fn boxed(capacity: usize) -> Box { - Box::new(Self::new(capacity)) - } - + pub fn boxed(capacity: usize) -> Box { + Box::new(Self::new(capacity)) + } } -impl GroupingHeuristic for FirstFitBinpacking -where T: BinpackingWeighted { - - fn distribute(&mut self, distribution: Vec>) -> Result>, anyhow::Error> { - - // Flatten all of the elements - let elements: Vec<_> = distribution.into_iter().flat_map(|outcome| outcome.into_inner()).collect(); - - // Prepare the result vector - let mut result: Vec> = Vec::new(); - - for element in elements.into_iter() { - - // if the element is heavier than the capacity, return an error - if element.weight() > self.capacity { - return Err(anyhow::anyhow!("Element is heavier than the capacity")); - } - - // Try to place the current element in the last knapsack - let remaining = if let Some(last_knapsack) = result.last_mut() { - let current_weight: usize = last_knapsack.0.iter().map(|item| item.weight()).sum(); - if current_weight + element.weight() <= self.capacity { - last_knapsack.0.push(element); - None - } else { - Some(element) - } - } else { - Some(element) - }; - - // If the element couldn't be placed in the last knapsack, create a new knapsack - match remaining { - Some(current_element) =>{ - let mut new_knapsack = Vec::new(); - new_knapsack.push(current_element); - result.push(new_knapsack.into()); - }, - None => () - } - } - - Ok(result) - - } - +impl GroupingHeuristic for FirstFitBinpacking +where + T: BinpackingWeighted, +{ + fn distribute( + &mut self, + distribution: Vec>, + ) -> Result>, anyhow::Error> { + // Flatten all of the elements + let elements: Vec<_> = + distribution.into_iter().flat_map(|outcome| outcome.into_inner()).collect(); + + // Prepare the result vector + let mut result: Vec> = Vec::new(); + + for element in elements.into_iter() { + // if the element is heavier than the capacity, return an error + if element.weight() > self.capacity { + return Err(anyhow::anyhow!("Element is heavier than the capacity")); + } + + // Try to place the current element in the last knapsack + let remaining = if let Some(last_knapsack) = result.last_mut() { + let current_weight: usize = last_knapsack.0.iter().map(|item| item.weight()).sum(); + if current_weight + element.weight() <= self.capacity { + last_knapsack.0.push(element); + None + } else { + Some(element) + } + } else { + Some(element) + }; + + // If the element couldn't be placed in the last knapsack, create a new knapsack + match remaining { + Some(current_element) => { + let mut new_knapsack = Vec::new(); + new_knapsack.push(current_element); + result.push(new_knapsack.into()); + } + None => (), + } + } + + Ok(result) + } } #[cfg(test)] pub mod test { - use super::*; - use crate::grouping_heuristic::ElementalOutcome; - - #[test] - fn test_first_fit_binpacking() -> Result<(), anyhow::Error> { - - let mut heuristic = FirstFitBinpacking::new(9); - let distribution = vec![ - GroupingOutcome::new(vec![ - ElementalOutcome::Apply(1), - ElementalOutcome::Apply(2), - ElementalOutcome::Apply(3), - ElementalOutcome::Apply(4) - ]), - GroupingOutcome::new(vec![ - ElementalOutcome::Apply(5), - ElementalOutcome::Apply(6), - ElementalOutcome::Apply(7), - ElementalOutcome::Apply(8) - ]), - ]; - - let distribution = heuristic.distribute(distribution)?; - - let should_be = vec![ - GroupingOutcome::new(vec![ - ElementalOutcome::Apply(1), - ElementalOutcome::Apply(2), - ElementalOutcome::Apply(3), - ]), - GroupingOutcome::new(vec![ - ElementalOutcome::Apply(4), - ElementalOutcome::Apply(5), - ]), - GroupingOutcome::new(vec![ - ElementalOutcome::Apply(6), - ]), - GroupingOutcome::new(vec![ - ElementalOutcome::Apply(7), - ]), - GroupingOutcome::new(vec![ - ElementalOutcome::Apply(8), - ]), - ]; - assert_eq!(distribution, should_be); - - Ok(()) - } - -} \ No newline at end of file + use super::*; + use crate::grouping_heuristic::ElementalOutcome; + + #[test] + fn test_first_fit_binpacking() -> Result<(), anyhow::Error> { + let mut heuristic = FirstFitBinpacking::new(9); + let distribution = vec![ + GroupingOutcome::new(vec![ + ElementalOutcome::Apply(1), + ElementalOutcome::Apply(2), + ElementalOutcome::Apply(3), + ElementalOutcome::Apply(4), + ]), + GroupingOutcome::new(vec![ + ElementalOutcome::Apply(5), + ElementalOutcome::Apply(6), + ElementalOutcome::Apply(7), + ElementalOutcome::Apply(8), + ]), + ]; + + let distribution = heuristic.distribute(distribution)?; + + let should_be = vec![ + GroupingOutcome::new(vec![ + ElementalOutcome::Apply(1), + ElementalOutcome::Apply(2), + ElementalOutcome::Apply(3), + ]), + GroupingOutcome::new(vec![ElementalOutcome::Apply(4), ElementalOutcome::Apply(5)]), + GroupingOutcome::new(vec![ElementalOutcome::Apply(6)]), + GroupingOutcome::new(vec![ElementalOutcome::Apply(7)]), + GroupingOutcome::new(vec![ElementalOutcome::Apply(8)]), + ]; + assert_eq!(distribution, should_be); + + Ok(()) + } +} diff --git a/util/movement-algs/src/grouping_heuristic/binpacking/mod.rs b/util/movement-algs/src/grouping_heuristic/binpacking/mod.rs index 53045d5b0..a04d630e2 100644 --- a/util/movement-algs/src/grouping_heuristic/binpacking/mod.rs +++ b/util/movement-algs/src/grouping_heuristic/binpacking/mod.rs @@ -72,17 +72,26 @@ pub mod numeric { mod block { use super::*; - use movement_types::{Block, Id, Transaction}; + use movement_types::{ + block::{self, Block}, + transaction::{self, Transaction}, + }; - impl BinpackingWeighted for Id { + impl BinpackingWeighted for block::Id { fn weight(&self) -> usize { - self.0.len() + self.as_bytes().len() + } + } + + impl BinpackingWeighted for transaction::Id { + fn weight(&self) -> usize { + self.as_bytes().len() } } impl BinpackingWeighted for Transaction { fn weight(&self) -> usize { - self.data.len() + self.id.weight() + self.data().len() + self.id().weight() } } @@ -90,13 +99,13 @@ mod block { fn weight(&self) -> usize { // sum of the transactions let mut weight = - self.transactions.iter().map(|transaction| transaction.weight()).sum::(); + self.transactions().map(|transaction| transaction.weight()).sum::(); // id - weight += self.id.weight(); + weight += self.id().weight(); // parent - weight += self.parent.len(); + weight += self.parent().as_bytes().len(); // for now metadata is negligible diff --git a/util/movement-algs/src/grouping_heuristic/chunking.rs b/util/movement-algs/src/grouping_heuristic/chunking.rs index 52ebd7f04..ca00d2aef 100644 --- a/util/movement-algs/src/grouping_heuristic/chunking.rs +++ b/util/movement-algs/src/grouping_heuristic/chunking.rs @@ -21,7 +21,7 @@ impl GroupingHeuristic for Chunking { distribution: Vec>, ) -> Result>, anyhow::Error> { // flatten the distribution - let mut distribution = distribution + let distribution = distribution .into_iter() .flat_map(|outcome| outcome.into_inner()) .collect::>(); diff --git a/util/movement-algs/src/grouping_heuristic/drop_success.rs b/util/movement-algs/src/grouping_heuristic/drop_success.rs index 9ff538c1b..2892c9d86 100644 --- a/util/movement-algs/src/grouping_heuristic/drop_success.rs +++ b/util/movement-algs/src/grouping_heuristic/drop_success.rs @@ -1,33 +1,28 @@ -use crate::grouping_heuristic::{ - GroupingHeuristic, - GroupingOutcome -}; +use crate::grouping_heuristic::{GroupingHeuristic, GroupingOutcome}; pub struct DropSuccess; impl DropSuccess { - - pub fn new() -> Self { - DropSuccess - } + pub fn new() -> Self { + DropSuccess + } - pub fn boxed() -> Box { - Box::new(DropSuccess) - } - + pub fn boxed() -> Box { + Box::new(DropSuccess) + } } -impl GroupingHeuristic for DropSuccess { - - fn distribute(&mut self, distribution: Vec>) -> Result>, anyhow::Error> { +impl GroupingHeuristic for DropSuccess { + fn distribute( + &mut self, + distribution: Vec>, + ) -> Result>, anyhow::Error> { + // remove all of the success outcomes + let distribution = distribution + .into_iter() + .filter(|outcome| outcome.0.iter().any(|outcome| !outcome.is_success())) + .collect::>(); - // remove all of the success outcomes - let distribution = distribution.into_iter().filter(|outcome| - outcome.0.iter().any(|outcome| !outcome.is_success()) - ).collect::>(); - - Ok(distribution) - - } - -} \ No newline at end of file + Ok(distribution) + } +} diff --git a/util/movement-algs/src/grouping_heuristic/splitting.rs b/util/movement-algs/src/grouping_heuristic/splitting.rs index 9241997dc..952c41de1 100644 --- a/util/movement-algs/src/grouping_heuristic/splitting.rs +++ b/util/movement-algs/src/grouping_heuristic/splitting.rs @@ -102,21 +102,24 @@ impl Splitable for Vec { mod block { + use std::collections::BTreeSet; + use super::*; - use movement_types::Block; + use movement_types::block::Block; impl Splitable for Block { fn split(self, factor: usize) -> Result, anyhow::Error> { // unpack the transactions - let Block { metadata, transactions, parent, id: _ } = self; + let (metadata, parent, transactions, _id) = self.into_parts(); // split the vector of transactions - let split_transactions = transactions.split(factor)?; + let split_transactions = Vec::from_iter(transactions).split(factor)?; // create a new block for each split transaction let mut blocks = Vec::new(); for split in split_transactions { - let parent = Block::new(metadata.clone(), parent.clone(), split); + let parent = + Block::new(metadata.clone(), parent.clone(), BTreeSet::from_iter(split)); blocks.push(parent); } diff --git a/util/movement-types/src/atomic_transaction_bundle.rs b/util/movement-types/src/atomic_transaction_bundle.rs new file mode 100644 index 000000000..6db92a5d8 --- /dev/null +++ b/util/movement-types/src/atomic_transaction_bundle.rs @@ -0,0 +1,74 @@ +use crate::transaction::Transaction; +use core::fmt; +use serde::{Deserialize, Serialize}; + +#[derive( + Serialize, Deserialize, Clone, Copy, Default, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, +)] +pub struct Id([u8; 32]); + +impl Id { + pub fn new(data: [u8; 32]) -> Self { + Self(data) + } + + pub fn as_bytes(&self) -> &[u8; 32] { + &self.0 + } + + pub fn test() -> Self { + Self([0; 32]) + } + + pub fn to_vec(&self) -> Vec { + self.0.into() + } +} + +impl AsRef<[u8]> for Id { + fn as_ref(&self) -> &[u8] { + &self.0 + } +} + +impl fmt::Display for Id { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + for byte in &self.0 { + write!(f, "{:02x}", byte)?; + } + Ok(()) + } +} + +#[derive(Serialize, Deserialize, Clone, Default, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub struct TransactionEntry { + consumer_id: Id, + data: Transaction, +} + +#[derive(Serialize, Deserialize, Clone, Default, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub struct AtomicTransactionBundle { + sequencer_id: Id, + transactions: Vec, +} + +impl TryFrom for Transaction { + type Error = anyhow::Error; + + fn try_from(value: AtomicTransactionBundle) -> Result { + if value.transactions.len() == 1 { + Ok(value.transactions[0].data.clone()) + } else { + Err(anyhow::anyhow!("AtomicTransactionBundle must contain exactly one transaction")) + } + } +} + +impl From for AtomicTransactionBundle { + fn from(transaction: Transaction) -> Self { + Self { + sequencer_id: Id::default(), + transactions: vec![TransactionEntry { consumer_id: Id::default(), data: transaction }], + } + } +} diff --git a/util/movement-types/src/block.rs b/util/movement-types/src/block.rs new file mode 100644 index 000000000..25b98547a --- /dev/null +++ b/util/movement-types/src/block.rs @@ -0,0 +1,199 @@ +use crate::transaction::Transaction; +use aptos_types::state_proof::StateProof; +use core::fmt; +use serde::{Deserialize, Serialize}; +use std::collections::btree_set; +use std::collections::BTreeSet; + +pub type Transactions<'a> = btree_set::Iter<'a, Transaction>; + +#[derive( + Serialize, Deserialize, Clone, Copy, Default, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, +)] +pub struct Id([u8; 32]); + +impl Id { + pub fn new(data: [u8; 32]) -> Self { + Self(data) + } + + pub fn as_bytes(&self) -> &[u8; 32] { + &self.0 + } + + pub fn test() -> Self { + Self([0; 32]) + } + + pub fn to_vec(&self) -> Vec { + self.0.into() + } + + pub fn genesis_block() -> Self { + Self([0; 32]) + } +} + +impl AsRef<[u8]> for Id { + fn as_ref(&self) -> &[u8] { + &self.0 + } +} + +impl fmt::Display for Id { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + for byte in &self.0 { + write!(f, "{:02x}", byte)?; + } + Ok(()) + } +} + +#[derive(Serialize, Deserialize, Clone, Default, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub enum BlockMetadata { + #[default] + BlockMetadata, +} + +#[derive(Serialize, Deserialize, Clone, Default, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub struct Block { + metadata: BlockMetadata, + parent: Id, + transactions: BTreeSet, + id: Id, +} + +impl Block { + pub fn new(metadata: BlockMetadata, parent: Id, transactions: BTreeSet) -> Self { + let mut hasher = blake3::Hasher::new(); + hasher.update(parent.as_bytes()); + for transaction in &transactions { + hasher.update(&transaction.id().as_ref()); + } + let id = Id(hasher.finalize().into()); + + Self { metadata, parent, transactions, id } + } + + pub fn into_parts(self) -> (BlockMetadata, Id, BTreeSet, Id) { + (self.metadata, self.parent, self.transactions, self.id) + } + + pub fn id(&self) -> Id { + self.id + } + + pub fn parent(&self) -> Id { + self.parent + } + + pub fn transactions(&self) -> Transactions { + self.transactions.iter() + } + + pub fn metadata(&self) -> &BlockMetadata { + &self.metadata + } + + pub fn test() -> Self { + Self::new( + BlockMetadata::BlockMetadata, + Id::test(), + BTreeSet::from_iter(vec![Transaction::test()]), + ) + } + + pub fn add_transaction(&mut self, transaction: Transaction) { + self.transactions.insert(transaction); + } +} + +#[derive( + Serialize, Deserialize, Clone, Copy, Default, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, +)] +pub struct Commitment([u8; 32]); + +impl Commitment { + pub fn new(data: [u8; 32]) -> Self { + Self(data) + } + + pub fn test() -> Self { + Self([0; 32]) + } + + pub fn as_bytes(&self) -> &[u8; 32] { + &self.0 + } + + /// Creates a commitment by making a cryptographic digest of the state proof. + pub fn digest_state_proof(state_proof: &StateProof) -> Self { + let mut hasher = blake3::Hasher::new(); + bcs::serialize_into(&mut hasher, &state_proof).expect("unexpected serialization error"); + Self(hasher.finalize().into()) + } +} + +impl From for [u8; 32] { + fn from(commitment: Commitment) -> [u8; 32] { + commitment.0 + } +} + +impl From for Vec { + fn from(commitment: Commitment) -> Vec { + commitment.0.into() + } +} + +impl fmt::Display for Commitment { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + for byte in &self.0 { + write!(f, "{:02x}", byte)?; + } + Ok(()) + } +} + +#[derive(Serialize, Deserialize, Clone, Default, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub struct BlockCommitment { + height: u64, + block_id: Id, + commitment: Commitment, +} + +impl BlockCommitment { + pub fn new(height: u64, block_id: Id, commitment: Commitment) -> Self { + Self { height, block_id, commitment } + } + + pub fn height(&self) -> u64 { + self.height + } + + pub fn block_id(&self) -> &Id { + &self.block_id + } + + pub fn commitment(&self) -> Commitment { + self.commitment + } + + pub fn test() -> Self { + Self::new(0, Id::test(), Commitment::test()) + } +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub enum BlockCommitmentRejectionReason { + InvalidBlockId, + InvalidCommitment, + InvalidHeight, + ContractError, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub enum BlockCommitmentEvent { + Accepted(BlockCommitment), + Rejected { height: u64, reason: BlockCommitmentRejectionReason }, +} diff --git a/util/movement-types/src/lib.rs b/util/movement-types/src/lib.rs index ea89ce16e..136f85ae5 100644 --- a/util/movement-types/src/lib.rs +++ b/util/movement-types/src/lib.rs @@ -1,204 +1,3 @@ -use aptos_types::state_proof::StateProof; -use core::fmt; -use serde::{Deserialize, Serialize}; - -#[derive(Serialize, Deserialize, Clone, Default, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] -pub struct Id(pub [u8; 32]); - -impl Id { - pub fn test() -> Self { - Self([0; 32]) - } - - pub fn to_vec(&self) -> Vec { - self.0.into() - } - - pub fn genesis_block() -> Self { - Self([0; 32]) - } -} - -impl AsRef<[u8]> for Id { - fn as_ref(&self) -> &[u8] { - &self.0 - } -} - -impl fmt::Display for Id { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - for byte in &self.0 { - write!(f, "{:02x}", byte)?; - } - Ok(()) - } -} - -#[derive(Serialize, Deserialize, Clone, Default, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] -pub struct Transaction { - pub data: Vec, - pub sequence_number: u64, - pub id: Id, -} - -impl Transaction { - pub fn new(data: Vec, sequence_number: u64) -> Self { - let mut hasher = blake3::Hasher::new(); - hasher.update(&data); - hasher.update(&sequence_number.to_le_bytes()); - let id = Id(hasher.finalize().into()); - Self { data, sequence_number, id } - } - - pub fn id(&self) -> Id { - self.id.clone() - } - - pub fn test() -> Self { - Self::new(vec![0], 0) - } -} - -#[derive(Serialize, Deserialize, Clone, Default, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] -pub struct TransactionEntry { - pub consumer_id: Id, - pub data: Transaction, -} - -#[derive(Serialize, Deserialize, Clone, Default, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] -pub struct AtomicTransactionBundle { - pub sequencer_id: Id, - pub transactions: Vec, -} - -impl TryFrom for Transaction { - type Error = anyhow::Error; - - fn try_from(value: AtomicTransactionBundle) -> Result { - if value.transactions.len() == 1 { - Ok(value.transactions[0].data.clone()) - } else { - Err(anyhow::anyhow!("AtomicTransactionBundle must contain exactly one transaction")) - } - } -} - -impl From for AtomicTransactionBundle { - fn from(transaction: Transaction) -> Self { - Self { - sequencer_id: Id::default(), - transactions: vec![TransactionEntry { consumer_id: Id::default(), data: transaction }], - } - } -} - -#[derive(Serialize, Deserialize, Clone, Default, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] -pub enum BlockMetadata { - #[default] - BlockMetadata, -} - -#[derive(Serialize, Deserialize, Clone, Default, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] -pub struct Block { - pub metadata: BlockMetadata, - pub parent: Vec, - pub transactions: Vec, - pub id: Id, -} - -impl Block { - pub fn new(metadata: BlockMetadata, parent: Vec, transactions: Vec) -> Self { - let mut hasher = blake3::Hasher::new(); - hasher.update(&parent); - for transaction in &transactions { - hasher.update(&transaction.id().as_ref()); - } - let id = Id(hasher.finalize().into()); - - Self { metadata, parent, transactions, id } - } - - pub fn id(&self) -> Id { - self.id.clone() - } - - pub fn test() -> Self { - Self::new(BlockMetadata::BlockMetadata, vec![0], vec![Transaction::test()]) - } - - pub fn add_transaction(&mut self, transaction: Transaction) { - self.transactions.push(transaction); - } -} - -#[derive(Serialize, Deserialize, Clone, Default, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] -pub struct Commitment(pub [u8; 32]); - -impl Commitment { - pub fn test() -> Self { - Self([0; 32]) - } - - /// Creates a commitment by making a cryptographic digest of the state proof. - pub fn digest_state_proof(state_proof: &StateProof) -> Self { - let mut hasher = blake3::Hasher::new(); - bcs::serialize_into(&mut hasher, &state_proof).expect("unexpected serialization error"); - Self(hasher.finalize().into()) - } -} - -impl TryFrom> for Commitment { - type Error = std::array::TryFromSliceError; - - fn try_from(data: Vec) -> Result { - Ok(Self(data[..32].try_into()?)) - } -} - -impl From<[u8; 32]> for Commitment { - fn from(data: [u8; 32]) -> Self { - Self(data) - } -} - -impl From for [u8; 32] { - fn from(commitment: Commitment) -> [u8; 32] { - commitment.0 - } -} - -impl From for Vec { - fn from(commitment: Commitment) -> Vec { - commitment.0.into() - } -} - -impl fmt::Display for Commitment { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - for byte in &self.0 { - write!(f, "{:02x}", byte)?; - } - Ok(()) - } -} - -#[derive(Serialize, Deserialize, Clone, Default, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] -pub struct BlockCommitment { - pub height: u64, - pub block_id: Id, - pub commitment: Commitment, -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] -pub enum BlockCommitmentRejectionReason { - InvalidBlockId, - InvalidCommitment, - InvalidHeight, - ContractError, -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] -pub enum BlockCommitmentEvent { - Accepted(BlockCommitment), - Rejected { height: u64, reason: BlockCommitmentRejectionReason }, -} +pub mod atomic_transaction_bundle; +pub mod block; +pub mod transaction; diff --git a/util/movement-types/src/transaction.rs b/util/movement-types/src/transaction.rs new file mode 100644 index 000000000..15f6d1119 --- /dev/null +++ b/util/movement-types/src/transaction.rs @@ -0,0 +1,108 @@ +use core::fmt; +use serde::{Deserialize, Serialize}; +use std::cmp::Ordering; + +#[derive( + Serialize, Deserialize, Clone, Copy, Default, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, +)] +pub struct Id([u8; 32]); + +impl Id { + pub fn new(data: [u8; 32]) -> Self { + Self(data) + } + + pub fn as_bytes(&self) -> &[u8; 32] { + &self.0 + } + + pub fn test() -> Self { + Self([0; 32]) + } + + pub fn to_vec(&self) -> Vec { + self.0.into() + } +} + +impl AsRef<[u8]> for Id { + fn as_ref(&self) -> &[u8] { + &self.0 + } +} + +impl fmt::Display for Id { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + for byte in &self.0 { + write!(f, "{:02x}", byte)?; + } + Ok(()) + } +} + +#[derive(Serialize, Deserialize, Clone, Default, Debug, PartialEq, Eq, Hash)] +pub struct Transaction { + data: Vec, + sequence_number: u64, + id: Id, +} + +impl Transaction { + pub fn new(data: Vec, sequence_number: u64) -> Self { + let mut hasher = blake3::Hasher::new(); + hasher.update(&data); + hasher.update(&sequence_number.to_le_bytes()); + let id = Id(hasher.finalize().into()); + Self { data, sequence_number, id } + } + + pub fn id(&self) -> Id { + self.id + } + + pub fn data(&self) -> &[u8] { + &self.data + } + + pub fn sequence_number(&self) -> u64 { + self.sequence_number + } + + pub fn test() -> Self { + Self::new(vec![0], 0) + } +} + +impl Ord for Transaction { + fn cmp(&self, other: &Self) -> Ordering { + // First, compare by sequence_number + match self.sequence_number().cmp(&other.sequence_number()) { + Ordering::Equal => {} + non_equal => return non_equal, + } + + // If sequence number is equal, then compare by transaction on the whole + self.id().cmp(&other.id()) + } +} + +impl PartialOrd for Transaction { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_transaction_ordering() { + let transaction = Transaction::test(); + let transaction2 = Transaction::new(vec![1], 1); + let transaction3 = Transaction::new(vec![1], 2); + + assert!(transaction < transaction2); + assert!(transaction2 < transaction3); + } +}