diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index d9a3eb6a5..f93704fdd 100755 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -4,6 +4,134 @@ on: push: jobs: + cargo-check: + 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 Cargo Check in nix environment + run: nix develop --command bash -c "cargo check --all-targets" + + suzuka-full-node-local: + strategy: + matrix: + include: + - os: ubuntu-22.04 + arch: x86_64 + runs-on: buildjet-16vcpu-ubuntu-2204 + + runs-on: ${{ matrix.runs-on }} + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + submodules: true + + - name: Install Nix + uses: DeterminateSystems/nix-installer-action@main + + - name: Run Suzuka Full Node Tests Against Local ETH and Local Celestia + env: + CELESTIA_LOG_LEVEL: FATAL # adjust the log level while debugging + run: | + nix develop --command bash -c "just suzuka-full-node native build.setup.eth-local.celestia-local.test -t=false" + nix develop --command bash -c "just suzuka-full-node native build.setup.eth-local.celestia-local.test -t=false" + + suzuka-full-node-remote: + if: false + strategy: + matrix: + include: + - os: ubuntu-22.04 + arch: x86_64 + runs-on: buildjet-8vcpu-ubuntu-2204 + + runs-on: ${{ matrix.runs-on }} + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + submodules: true + + - name: Install Nix + uses: DeterminateSystems/nix-installer-action@main + + - name: Run Suzuka Full Node Tests Against Holesky and Local Celestia + env: + CELESTIA_LOG_LEVEL: FATAL # adjust the log level while debugging + run: | + export MCR_DEPLOYMENT_ACCOUNT_PRIVATE_KEY=${{ secrets.MCR_DEPLOYMENT_ACCOUNT_PRIVATE_KEY }} + nix develop --command bash -c "just suzuka-full-node native build.setup.eth-holesky.celestia-local.test -t=false" + nix develop --command bash -c "just suzuka-full-node native build.setup.eth-holesky.celestia-local.test -t=false" + + m1-da-light-node: + if: false # this is effectively tested by the above + strategy: + matrix: + include: + - os: ubuntu-22.04 + arch: x86_64 + runs-on: buildjet-8vcpu-ubuntu-2204 + + runs-on: ${{ matrix.runs-on }} + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Install Nix + uses: DeterminateSystems/nix-installer-action@main + + - name: Run M1 DA Light Node tests in nix environment + # adjust the log level while debugging + run: CELESTIA_LOG_LEVEL=FATAL nix develop --command bash -c "just m1-da-light-node native build.setup.test.local -t=false" + + - name: Run foundry tests + # Run the foundry solidity contracts using the WETH9 contract on sepolia + run: cd protocol-units/bridge/contracts && forge test --fork-url https://ethereum-sepolia-rpc.publicnode.com -vv + + mcr: +# if: false + strategy: + matrix: + include: + - os: ubuntu-22.04 + arch: x86_64 + runs-on: buildjet-8vcpu-ubuntu-2204 + + runs-on: ${{ matrix.runs-on }} + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Install Nix + uses: DeterminateSystems/nix-installer-action@main + + - name: Run MCR tests in nix environment + # adjust the log level while debugging + run: CELESTIA_LOG_LEVEL=FATAL nix develop --command bash -c "just mcr native test.local -t=false" + + - name: Run MCR Client Tests + run: nix develop --command bash -c "just mcr-client native build.local.test -t=false" + move-modules-test: strategy: matrix: @@ -50,18 +178,14 @@ jobs: - name: Checkout repository uses: actions/checkout@v4 - - name: Install Aptos CLI - run: | - curl -fsSL "https://aptos.dev/scripts/install_cli.py" | python3 - - name: Install Nix uses: DeterminateSystems/nix-installer-action@main - name: Run foundry tests run: | nix develop --command bash -c " - cd protocol-units/bridge/contracts && \ - forge test --fork-url https://ethereum-sepolia-rpc.publicnode.com -vv + cd protocol-units/bridge/contracts && \ + forge test --fork-url https://ethereum-sepolia-rpc.publicnode.com -vv " bridge-eth-movement: @@ -86,3 +210,4 @@ jobs: nix develop --command bash -c " cargo test --test eth_movement -- --nocapture --test-threads=1 " + diff --git a/Cargo.lock b/Cargo.lock index d0b91e91a..2f5a72afd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11,7 +11,7 @@ checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3" [[package]] name = "abstract-domain-derive" version = "0.1.0" -source = "git+https://github.com/movementlabsxyz/aptos-core?rev=51d875c93565e11f4e3f8321d200b406ed55a286#51d875c93565e11f4e3f8321d200b406ed55a286" +source = "git+https://github.com/movementlabsxyz/aptos-core?rev=1c63a73c49efa9c4bb568e05eea2e1dda4e5197a#1c63a73c49efa9c4bb568e05eea2e1dda4e5197a" dependencies = [ "proc-macro2", "quote", @@ -765,7 +765,7 @@ checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" [[package]] name = "aptos-abstract-gas-usage" version = "0.1.0" -source = "git+https://github.com/movementlabsxyz/aptos-core?rev=51d875c93565e11f4e3f8321d200b406ed55a286#51d875c93565e11f4e3f8321d200b406ed55a286" +source = "git+https://github.com/movementlabsxyz/aptos-core?rev=1c63a73c49efa9c4bb568e05eea2e1dda4e5197a#1c63a73c49efa9c4bb568e05eea2e1dda4e5197a" dependencies = [ "anyhow", "aptos-gas-algebra", @@ -778,7 +778,7 @@ dependencies = [ [[package]] name = "aptos-accumulator" version = "0.1.0" -source = "git+https://github.com/movementlabsxyz/aptos-core?rev=51d875c93565e11f4e3f8321d200b406ed55a286#51d875c93565e11f4e3f8321d200b406ed55a286" +source = "git+https://github.com/movementlabsxyz/aptos-core?rev=1c63a73c49efa9c4bb568e05eea2e1dda4e5197a#1c63a73c49efa9c4bb568e05eea2e1dda4e5197a" dependencies = [ "anyhow", "aptos-crypto", @@ -788,7 +788,7 @@ dependencies = [ [[package]] name = "aptos-aggregator" version = "0.1.0" -source = "git+https://github.com/movementlabsxyz/aptos-core?rev=51d875c93565e11f4e3f8321d200b406ed55a286#51d875c93565e11f4e3f8321d200b406ed55a286" +source = "git+https://github.com/movementlabsxyz/aptos-core?rev=1c63a73c49efa9c4bb568e05eea2e1dda4e5197a#1c63a73c49efa9c4bb568e05eea2e1dda4e5197a" dependencies = [ "aptos-logger", "aptos-types", @@ -802,7 +802,7 @@ dependencies = [ [[package]] name = "aptos-api" version = "0.2.0" -source = "git+https://github.com/movementlabsxyz/aptos-core?rev=51d875c93565e11f4e3f8321d200b406ed55a286#51d875c93565e11f4e3f8321d200b406ed55a286" +source = "git+https://github.com/movementlabsxyz/aptos-core?rev=1c63a73c49efa9c4bb568e05eea2e1dda4e5197a#1c63a73c49efa9c4bb568e05eea2e1dda4e5197a" dependencies = [ "anyhow", "aptos-api-types", @@ -844,7 +844,7 @@ dependencies = [ [[package]] name = "aptos-api-types" version = "0.0.1" -source = "git+https://github.com/movementlabsxyz/aptos-core?rev=51d875c93565e11f4e3f8321d200b406ed55a286#51d875c93565e11f4e3f8321d200b406ed55a286" +source = "git+https://github.com/movementlabsxyz/aptos-core?rev=1c63a73c49efa9c4bb568e05eea2e1dda4e5197a#1c63a73c49efa9c4bb568e05eea2e1dda4e5197a" dependencies = [ "anyhow", "aptos-config", @@ -874,7 +874,7 @@ dependencies = [ [[package]] name = "aptos-bcs-utils" version = "0.1.0" -source = "git+https://github.com/movementlabsxyz/aptos-core?rev=51d875c93565e11f4e3f8321d200b406ed55a286#51d875c93565e11f4e3f8321d200b406ed55a286" +source = "git+https://github.com/movementlabsxyz/aptos-core?rev=1c63a73c49efa9c4bb568e05eea2e1dda4e5197a#1c63a73c49efa9c4bb568e05eea2e1dda4e5197a" dependencies = [ "anyhow", "hex", @@ -883,7 +883,7 @@ dependencies = [ [[package]] name = "aptos-bitvec" version = "0.1.0" -source = "git+https://github.com/movementlabsxyz/aptos-core?rev=51d875c93565e11f4e3f8321d200b406ed55a286#51d875c93565e11f4e3f8321d200b406ed55a286" +source = "git+https://github.com/movementlabsxyz/aptos-core?rev=1c63a73c49efa9c4bb568e05eea2e1dda4e5197a#1c63a73c49efa9c4bb568e05eea2e1dda4e5197a" dependencies = [ "serde", "serde_bytes", @@ -892,7 +892,7 @@ dependencies = [ [[package]] name = "aptos-block-executor" version = "0.1.0" -source = "git+https://github.com/movementlabsxyz/aptos-core?rev=51d875c93565e11f4e3f8321d200b406ed55a286#51d875c93565e11f4e3f8321d200b406ed55a286" +source = "git+https://github.com/movementlabsxyz/aptos-core?rev=1c63a73c49efa9c4bb568e05eea2e1dda4e5197a#1c63a73c49efa9c4bb568e05eea2e1dda4e5197a" dependencies = [ "anyhow", "aptos-aggregator", @@ -927,7 +927,7 @@ dependencies = [ [[package]] name = "aptos-block-partitioner" version = "0.1.0" -source = "git+https://github.com/movementlabsxyz/aptos-core?rev=51d875c93565e11f4e3f8321d200b406ed55a286#51d875c93565e11f4e3f8321d200b406ed55a286" +source = "git+https://github.com/movementlabsxyz/aptos-core?rev=1c63a73c49efa9c4bb568e05eea2e1dda4e5197a#1c63a73c49efa9c4bb568e05eea2e1dda4e5197a" dependencies = [ "aptos-crypto", "aptos-logger", @@ -948,7 +948,7 @@ dependencies = [ [[package]] name = "aptos-bounded-executor" version = "0.1.0" -source = "git+https://github.com/movementlabsxyz/aptos-core?rev=51d875c93565e11f4e3f8321d200b406ed55a286#51d875c93565e11f4e3f8321d200b406ed55a286" +source = "git+https://github.com/movementlabsxyz/aptos-core?rev=1c63a73c49efa9c4bb568e05eea2e1dda4e5197a#1c63a73c49efa9c4bb568e05eea2e1dda4e5197a" dependencies = [ "futures", "rustversion", @@ -958,7 +958,7 @@ dependencies = [ [[package]] name = "aptos-build-info" version = "0.1.0" -source = "git+https://github.com/movementlabsxyz/aptos-core?rev=51d875c93565e11f4e3f8321d200b406ed55a286#51d875c93565e11f4e3f8321d200b406ed55a286" +source = "git+https://github.com/movementlabsxyz/aptos-core?rev=1c63a73c49efa9c4bb568e05eea2e1dda4e5197a#1c63a73c49efa9c4bb568e05eea2e1dda4e5197a" dependencies = [ "shadow-rs", ] @@ -966,7 +966,7 @@ dependencies = [ [[package]] name = "aptos-cached-packages" version = "0.1.0" -source = "git+https://github.com/movementlabsxyz/aptos-core?rev=51d875c93565e11f4e3f8321d200b406ed55a286#51d875c93565e11f4e3f8321d200b406ed55a286" +source = "git+https://github.com/movementlabsxyz/aptos-core?rev=1c63a73c49efa9c4bb568e05eea2e1dda4e5197a#1c63a73c49efa9c4bb568e05eea2e1dda4e5197a" dependencies = [ "anyhow", "aptos-framework", @@ -980,7 +980,7 @@ dependencies = [ [[package]] name = "aptos-channels" version = "0.1.0" -source = "git+https://github.com/movementlabsxyz/aptos-core?rev=51d875c93565e11f4e3f8321d200b406ed55a286#51d875c93565e11f4e3f8321d200b406ed55a286" +source = "git+https://github.com/movementlabsxyz/aptos-core?rev=1c63a73c49efa9c4bb568e05eea2e1dda4e5197a#1c63a73c49efa9c4bb568e05eea2e1dda4e5197a" dependencies = [ "anyhow", "aptos-infallible", @@ -991,7 +991,7 @@ dependencies = [ [[package]] name = "aptos-compression" version = "0.1.0" -source = "git+https://github.com/movementlabsxyz/aptos-core?rev=51d875c93565e11f4e3f8321d200b406ed55a286#51d875c93565e11f4e3f8321d200b406ed55a286" +source = "git+https://github.com/movementlabsxyz/aptos-core?rev=1c63a73c49efa9c4bb568e05eea2e1dda4e5197a#1c63a73c49efa9c4bb568e05eea2e1dda4e5197a" dependencies = [ "aptos-logger", "aptos-metrics-core", @@ -1003,7 +1003,7 @@ dependencies = [ [[package]] name = "aptos-config" version = "0.1.0" -source = "git+https://github.com/movementlabsxyz/aptos-core?rev=51d875c93565e11f4e3f8321d200b406ed55a286#51d875c93565e11f4e3f8321d200b406ed55a286" +source = "git+https://github.com/movementlabsxyz/aptos-core?rev=1c63a73c49efa9c4bb568e05eea2e1dda4e5197a#1c63a73c49efa9c4bb568e05eea2e1dda4e5197a" dependencies = [ "anyhow", "aptos-crypto", @@ -1034,7 +1034,7 @@ dependencies = [ [[package]] name = "aptos-consensus-types" version = "0.1.0" -source = "git+https://github.com/movementlabsxyz/aptos-core?rev=51d875c93565e11f4e3f8321d200b406ed55a286#51d875c93565e11f4e3f8321d200b406ed55a286" +source = "git+https://github.com/movementlabsxyz/aptos-core?rev=1c63a73c49efa9c4bb568e05eea2e1dda4e5197a#1c63a73c49efa9c4bb568e05eea2e1dda4e5197a" dependencies = [ "anyhow", "aptos-bitvec", @@ -1061,7 +1061,7 @@ dependencies = [ [[package]] name = "aptos-crypto" version = "0.0.3" -source = "git+https://github.com/movementlabsxyz/aptos-core?rev=51d875c93565e11f4e3f8321d200b406ed55a286#51d875c93565e11f4e3f8321d200b406ed55a286" +source = "git+https://github.com/movementlabsxyz/aptos-core?rev=1c63a73c49efa9c4bb568e05eea2e1dda4e5197a#1c63a73c49efa9c4bb568e05eea2e1dda4e5197a" dependencies = [ "aes-gcm", "anyhow", @@ -1114,7 +1114,7 @@ dependencies = [ [[package]] name = "aptos-crypto-derive" version = "0.0.3" -source = "git+https://github.com/movementlabsxyz/aptos-core?rev=51d875c93565e11f4e3f8321d200b406ed55a286#51d875c93565e11f4e3f8321d200b406ed55a286" +source = "git+https://github.com/movementlabsxyz/aptos-core?rev=1c63a73c49efa9c4bb568e05eea2e1dda4e5197a#1c63a73c49efa9c4bb568e05eea2e1dda4e5197a" dependencies = [ "proc-macro2", "quote", @@ -1124,7 +1124,7 @@ dependencies = [ [[package]] name = "aptos-db" version = "0.1.0" -source = "git+https://github.com/movementlabsxyz/aptos-core?rev=51d875c93565e11f4e3f8321d200b406ed55a286#51d875c93565e11f4e3f8321d200b406ed55a286" +source = "git+https://github.com/movementlabsxyz/aptos-core?rev=1c63a73c49efa9c4bb568e05eea2e1dda4e5197a#1c63a73c49efa9c4bb568e05eea2e1dda4e5197a" dependencies = [ "anyhow", "aptos-accumulator", @@ -1171,7 +1171,7 @@ dependencies = [ [[package]] name = "aptos-db-indexer" version = "0.1.0" -source = "git+https://github.com/movementlabsxyz/aptos-core?rev=51d875c93565e11f4e3f8321d200b406ed55a286#51d875c93565e11f4e3f8321d200b406ed55a286" +source = "git+https://github.com/movementlabsxyz/aptos-core?rev=1c63a73c49efa9c4bb568e05eea2e1dda4e5197a#1c63a73c49efa9c4bb568e05eea2e1dda4e5197a" dependencies = [ "anyhow", "aptos-config", @@ -1191,7 +1191,7 @@ dependencies = [ [[package]] name = "aptos-db-indexer-schemas" version = "0.1.0" -source = "git+https://github.com/movementlabsxyz/aptos-core?rev=51d875c93565e11f4e3f8321d200b406ed55a286#51d875c93565e11f4e3f8321d200b406ed55a286" +source = "git+https://github.com/movementlabsxyz/aptos-core?rev=1c63a73c49efa9c4bb568e05eea2e1dda4e5197a#1c63a73c49efa9c4bb568e05eea2e1dda4e5197a" dependencies = [ "anyhow", "aptos-schemadb", @@ -1205,7 +1205,7 @@ dependencies = [ [[package]] name = "aptos-dkg" version = "0.1.0" -source = "git+https://github.com/movementlabsxyz/aptos-core?rev=51d875c93565e11f4e3f8321d200b406ed55a286#51d875c93565e11f4e3f8321d200b406ed55a286" +source = "git+https://github.com/movementlabsxyz/aptos-core?rev=1c63a73c49efa9c4bb568e05eea2e1dda4e5197a#1c63a73c49efa9c4bb568e05eea2e1dda4e5197a" dependencies = [ "anyhow", "aptos-crypto", @@ -1236,7 +1236,7 @@ dependencies = [ [[package]] name = "aptos-drop-helper" version = "0.1.0" -source = "git+https://github.com/movementlabsxyz/aptos-core?rev=51d875c93565e11f4e3f8321d200b406ed55a286#51d875c93565e11f4e3f8321d200b406ed55a286" +source = "git+https://github.com/movementlabsxyz/aptos-core?rev=1c63a73c49efa9c4bb568e05eea2e1dda4e5197a#1c63a73c49efa9c4bb568e05eea2e1dda4e5197a" dependencies = [ "aptos-infallible", "aptos-metrics-core", @@ -1247,7 +1247,7 @@ dependencies = [ [[package]] name = "aptos-event-notifications" version = "0.1.0" -source = "git+https://github.com/movementlabsxyz/aptos-core?rev=51d875c93565e11f4e3f8321d200b406ed55a286#51d875c93565e11f4e3f8321d200b406ed55a286" +source = "git+https://github.com/movementlabsxyz/aptos-core?rev=1c63a73c49efa9c4bb568e05eea2e1dda4e5197a#1c63a73c49efa9c4bb568e05eea2e1dda4e5197a" dependencies = [ "anyhow", "aptos-channels", @@ -1263,7 +1263,7 @@ dependencies = [ [[package]] name = "aptos-executor" version = "0.1.0" -source = "git+https://github.com/movementlabsxyz/aptos-core?rev=51d875c93565e11f4e3f8321d200b406ed55a286#51d875c93565e11f4e3f8321d200b406ed55a286" +source = "git+https://github.com/movementlabsxyz/aptos-core?rev=1c63a73c49efa9c4bb568e05eea2e1dda4e5197a#1c63a73c49efa9c4bb568e05eea2e1dda4e5197a" dependencies = [ "anyhow", "aptos-consensus-types", @@ -1295,7 +1295,7 @@ dependencies = [ [[package]] name = "aptos-executor-service" version = "0.1.0" -source = "git+https://github.com/movementlabsxyz/aptos-core?rev=51d875c93565e11f4e3f8321d200b406ed55a286#51d875c93565e11f4e3f8321d200b406ed55a286" +source = "git+https://github.com/movementlabsxyz/aptos-core?rev=1c63a73c49efa9c4bb568e05eea2e1dda4e5197a#1c63a73c49efa9c4bb568e05eea2e1dda4e5197a" dependencies = [ "aptos-block-partitioner", "aptos-config", @@ -1325,7 +1325,7 @@ dependencies = [ [[package]] name = "aptos-executor-test-helpers" version = "0.1.0" -source = "git+https://github.com/movementlabsxyz/aptos-core?rev=51d875c93565e11f4e3f8321d200b406ed55a286#51d875c93565e11f4e3f8321d200b406ed55a286" +source = "git+https://github.com/movementlabsxyz/aptos-core?rev=1c63a73c49efa9c4bb568e05eea2e1dda4e5197a#1c63a73c49efa9c4bb568e05eea2e1dda4e5197a" dependencies = [ "anyhow", "aptos-cached-packages", @@ -1347,7 +1347,7 @@ dependencies = [ [[package]] name = "aptos-executor-types" version = "0.1.0" -source = "git+https://github.com/movementlabsxyz/aptos-core?rev=51d875c93565e11f4e3f8321d200b406ed55a286#51d875c93565e11f4e3f8321d200b406ed55a286" +source = "git+https://github.com/movementlabsxyz/aptos-core?rev=1c63a73c49efa9c4bb568e05eea2e1dda4e5197a#1c63a73c49efa9c4bb568e05eea2e1dda4e5197a" dependencies = [ "anyhow", "aptos-crypto", @@ -1367,7 +1367,7 @@ dependencies = [ [[package]] name = "aptos-experimental-runtimes" version = "0.1.0" -source = "git+https://github.com/movementlabsxyz/aptos-core?rev=51d875c93565e11f4e3f8321d200b406ed55a286#51d875c93565e11f4e3f8321d200b406ed55a286" +source = "git+https://github.com/movementlabsxyz/aptos-core?rev=1c63a73c49efa9c4bb568e05eea2e1dda4e5197a#1c63a73c49efa9c4bb568e05eea2e1dda4e5197a" dependencies = [ "aptos-runtimes", "core_affinity", @@ -1380,7 +1380,7 @@ dependencies = [ [[package]] name = "aptos-faucet-core" version = "2.0.1" -source = "git+https://github.com/movementlabsxyz/aptos-core?rev=51d875c93565e11f4e3f8321d200b406ed55a286#51d875c93565e11f4e3f8321d200b406ed55a286" +source = "git+https://github.com/movementlabsxyz/aptos-core?rev=1c63a73c49efa9c4bb568e05eea2e1dda4e5197a#1c63a73c49efa9c4bb568e05eea2e1dda4e5197a" dependencies = [ "anyhow", "aptos-config", @@ -1414,7 +1414,7 @@ dependencies = [ [[package]] name = "aptos-faucet-metrics-server" version = "2.0.0" -source = "git+https://github.com/movementlabsxyz/aptos-core?rev=51d875c93565e11f4e3f8321d200b406ed55a286#51d875c93565e11f4e3f8321d200b406ed55a286" +source = "git+https://github.com/movementlabsxyz/aptos-core?rev=1c63a73c49efa9c4bb568e05eea2e1dda4e5197a#1c63a73c49efa9c4bb568e05eea2e1dda4e5197a" dependencies = [ "anyhow", "aptos-logger", @@ -1428,7 +1428,7 @@ dependencies = [ [[package]] name = "aptos-framework" version = "0.1.0" -source = "git+https://github.com/movementlabsxyz/aptos-core?rev=51d875c93565e11f4e3f8321d200b406ed55a286#51d875c93565e11f4e3f8321d200b406ed55a286" +source = "git+https://github.com/movementlabsxyz/aptos-core?rev=1c63a73c49efa9c4bb568e05eea2e1dda4e5197a#1c63a73c49efa9c4bb568e05eea2e1dda4e5197a" dependencies = [ "anyhow", "aptos-aggregator", @@ -1496,7 +1496,7 @@ dependencies = [ [[package]] name = "aptos-gas-algebra" version = "0.0.1" -source = "git+https://github.com/movementlabsxyz/aptos-core?rev=51d875c93565e11f4e3f8321d200b406ed55a286#51d875c93565e11f4e3f8321d200b406ed55a286" +source = "git+https://github.com/movementlabsxyz/aptos-core?rev=1c63a73c49efa9c4bb568e05eea2e1dda4e5197a#1c63a73c49efa9c4bb568e05eea2e1dda4e5197a" dependencies = [ "either", "move-core-types", @@ -1505,7 +1505,7 @@ dependencies = [ [[package]] name = "aptos-gas-meter" version = "0.1.0" -source = "git+https://github.com/movementlabsxyz/aptos-core?rev=51d875c93565e11f4e3f8321d200b406ed55a286#51d875c93565e11f4e3f8321d200b406ed55a286" +source = "git+https://github.com/movementlabsxyz/aptos-core?rev=1c63a73c49efa9c4bb568e05eea2e1dda4e5197a#1c63a73c49efa9c4bb568e05eea2e1dda4e5197a" dependencies = [ "aptos-gas-algebra", "aptos-gas-schedule", @@ -1520,7 +1520,7 @@ dependencies = [ [[package]] name = "aptos-gas-profiling" version = "0.1.0" -source = "git+https://github.com/movementlabsxyz/aptos-core?rev=51d875c93565e11f4e3f8321d200b406ed55a286#51d875c93565e11f4e3f8321d200b406ed55a286" +source = "git+https://github.com/movementlabsxyz/aptos-core?rev=1c63a73c49efa9c4bb568e05eea2e1dda4e5197a#1c63a73c49efa9c4bb568e05eea2e1dda4e5197a" dependencies = [ "anyhow", "aptos-gas-algebra", @@ -1540,7 +1540,7 @@ dependencies = [ [[package]] name = "aptos-gas-schedule" version = "0.1.0" -source = "git+https://github.com/movementlabsxyz/aptos-core?rev=51d875c93565e11f4e3f8321d200b406ed55a286#51d875c93565e11f4e3f8321d200b406ed55a286" +source = "git+https://github.com/movementlabsxyz/aptos-core?rev=1c63a73c49efa9c4bb568e05eea2e1dda4e5197a#1c63a73c49efa9c4bb568e05eea2e1dda4e5197a" dependencies = [ "aptos-gas-algebra", "aptos-global-constants", @@ -1553,17 +1553,17 @@ dependencies = [ [[package]] name = "aptos-global-constants" version = "0.1.0" -source = "git+https://github.com/movementlabsxyz/aptos-core?rev=51d875c93565e11f4e3f8321d200b406ed55a286#51d875c93565e11f4e3f8321d200b406ed55a286" +source = "git+https://github.com/movementlabsxyz/aptos-core?rev=1c63a73c49efa9c4bb568e05eea2e1dda4e5197a#1c63a73c49efa9c4bb568e05eea2e1dda4e5197a" [[package]] name = "aptos-id-generator" version = "0.1.0" -source = "git+https://github.com/movementlabsxyz/aptos-core?rev=51d875c93565e11f4e3f8321d200b406ed55a286#51d875c93565e11f4e3f8321d200b406ed55a286" +source = "git+https://github.com/movementlabsxyz/aptos-core?rev=1c63a73c49efa9c4bb568e05eea2e1dda4e5197a#1c63a73c49efa9c4bb568e05eea2e1dda4e5197a" [[package]] name = "aptos-indexer" version = "0.0.1" -source = "git+https://github.com/movementlabsxyz/aptos-core?rev=51d875c93565e11f4e3f8321d200b406ed55a286#51d875c93565e11f4e3f8321d200b406ed55a286" +source = "git+https://github.com/movementlabsxyz/aptos-core?rev=1c63a73c49efa9c4bb568e05eea2e1dda4e5197a#1c63a73c49efa9c4bb568e05eea2e1dda4e5197a" dependencies = [ "anyhow", "aptos-api", @@ -1595,7 +1595,7 @@ dependencies = [ [[package]] name = "aptos-indexer-grpc-fullnode" version = "1.0.0" -source = "git+https://github.com/movementlabsxyz/aptos-core?rev=51d875c93565e11f4e3f8321d200b406ed55a286#51d875c93565e11f4e3f8321d200b406ed55a286" +source = "git+https://github.com/movementlabsxyz/aptos-core?rev=1c63a73c49efa9c4bb568e05eea2e1dda4e5197a#1c63a73c49efa9c4bb568e05eea2e1dda4e5197a" dependencies = [ "anyhow", "aptos-api", @@ -1633,7 +1633,7 @@ dependencies = [ [[package]] name = "aptos-indexer-grpc-table-info" version = "1.0.0" -source = "git+https://github.com/movementlabsxyz/aptos-core?rev=51d875c93565e11f4e3f8321d200b406ed55a286#51d875c93565e11f4e3f8321d200b406ed55a286" +source = "git+https://github.com/movementlabsxyz/aptos-core?rev=1c63a73c49efa9c4bb568e05eea2e1dda4e5197a#1c63a73c49efa9c4bb568e05eea2e1dda4e5197a" dependencies = [ "anyhow", "aptos-api", @@ -1664,7 +1664,7 @@ dependencies = [ [[package]] name = "aptos-indexer-grpc-utils" version = "1.0.0" -source = "git+https://github.com/movementlabsxyz/aptos-core?rev=51d875c93565e11f4e3f8321d200b406ed55a286#51d875c93565e11f4e3f8321d200b406ed55a286" +source = "git+https://github.com/movementlabsxyz/aptos-core?rev=1c63a73c49efa9c4bb568e05eea2e1dda4e5197a#1c63a73c49efa9c4bb568e05eea2e1dda4e5197a" dependencies = [ "anyhow", "aptos-metrics-core", @@ -1696,12 +1696,12 @@ dependencies = [ [[package]] name = "aptos-infallible" version = "0.1.0" -source = "git+https://github.com/movementlabsxyz/aptos-core?rev=51d875c93565e11f4e3f8321d200b406ed55a286#51d875c93565e11f4e3f8321d200b406ed55a286" +source = "git+https://github.com/movementlabsxyz/aptos-core?rev=1c63a73c49efa9c4bb568e05eea2e1dda4e5197a#1c63a73c49efa9c4bb568e05eea2e1dda4e5197a" [[package]] name = "aptos-jellyfish-merkle" version = "0.1.0" -source = "git+https://github.com/movementlabsxyz/aptos-core?rev=51d875c93565e11f4e3f8321d200b406ed55a286#51d875c93565e11f4e3f8321d200b406ed55a286" +source = "git+https://github.com/movementlabsxyz/aptos-core?rev=1c63a73c49efa9c4bb568e05eea2e1dda4e5197a#1c63a73c49efa9c4bb568e05eea2e1dda4e5197a" dependencies = [ "anyhow", "aptos-crypto", @@ -1729,7 +1729,7 @@ dependencies = [ [[package]] name = "aptos-keygen" version = "0.1.0" -source = "git+https://github.com/movementlabsxyz/aptos-core?rev=51d875c93565e11f4e3f8321d200b406ed55a286#51d875c93565e11f4e3f8321d200b406ed55a286" +source = "git+https://github.com/movementlabsxyz/aptos-core?rev=1c63a73c49efa9c4bb568e05eea2e1dda4e5197a#1c63a73c49efa9c4bb568e05eea2e1dda4e5197a" dependencies = [ "aptos-crypto", "aptos-types", @@ -1739,7 +1739,7 @@ dependencies = [ [[package]] name = "aptos-language-e2e-tests" version = "0.1.0" -source = "git+https://github.com/movementlabsxyz/aptos-core?rev=51d875c93565e11f4e3f8321d200b406ed55a286#51d875c93565e11f4e3f8321d200b406ed55a286" +source = "git+https://github.com/movementlabsxyz/aptos-core?rev=1c63a73c49efa9c4bb568e05eea2e1dda4e5197a#1c63a73c49efa9c4bb568e05eea2e1dda4e5197a" dependencies = [ "anyhow", "aptos-abstract-gas-usage", @@ -1783,7 +1783,7 @@ dependencies = [ [[package]] name = "aptos-ledger" version = "0.2.0" -source = "git+https://github.com/movementlabsxyz/aptos-core?rev=51d875c93565e11f4e3f8321d200b406ed55a286#51d875c93565e11f4e3f8321d200b406ed55a286" +source = "git+https://github.com/movementlabsxyz/aptos-core?rev=1c63a73c49efa9c4bb568e05eea2e1dda4e5197a#1c63a73c49efa9c4bb568e05eea2e1dda4e5197a" dependencies = [ "aptos-crypto", "aptos-types", @@ -1796,7 +1796,7 @@ dependencies = [ [[package]] name = "aptos-log-derive" version = "0.1.0" -source = "git+https://github.com/movementlabsxyz/aptos-core?rev=51d875c93565e11f4e3f8321d200b406ed55a286#51d875c93565e11f4e3f8321d200b406ed55a286" +source = "git+https://github.com/movementlabsxyz/aptos-core?rev=1c63a73c49efa9c4bb568e05eea2e1dda4e5197a#1c63a73c49efa9c4bb568e05eea2e1dda4e5197a" dependencies = [ "proc-macro2", "quote", @@ -1806,7 +1806,7 @@ dependencies = [ [[package]] name = "aptos-logger" version = "0.1.0" -source = "git+https://github.com/movementlabsxyz/aptos-core?rev=51d875c93565e11f4e3f8321d200b406ed55a286#51d875c93565e11f4e3f8321d200b406ed55a286" +source = "git+https://github.com/movementlabsxyz/aptos-core?rev=1c63a73c49efa9c4bb568e05eea2e1dda4e5197a#1c63a73c49efa9c4bb568e05eea2e1dda4e5197a" dependencies = [ "aptos-infallible", "aptos-log-derive", @@ -1830,7 +1830,7 @@ dependencies = [ [[package]] name = "aptos-memory-usage-tracker" version = "0.1.0" -source = "git+https://github.com/movementlabsxyz/aptos-core?rev=51d875c93565e11f4e3f8321d200b406ed55a286#51d875c93565e11f4e3f8321d200b406ed55a286" +source = "git+https://github.com/movementlabsxyz/aptos-core?rev=1c63a73c49efa9c4bb568e05eea2e1dda4e5197a#1c63a73c49efa9c4bb568e05eea2e1dda4e5197a" dependencies = [ "aptos-gas-algebra", "aptos-gas-meter", @@ -1843,7 +1843,7 @@ dependencies = [ [[package]] name = "aptos-mempool" version = "0.1.0" -source = "git+https://github.com/movementlabsxyz/aptos-core?rev=51d875c93565e11f4e3f8321d200b406ed55a286#51d875c93565e11f4e3f8321d200b406ed55a286" +source = "git+https://github.com/movementlabsxyz/aptos-core?rev=1c63a73c49efa9c4bb568e05eea2e1dda4e5197a#1c63a73c49efa9c4bb568e05eea2e1dda4e5197a" dependencies = [ "anyhow", "aptos-bounded-executor", @@ -1883,7 +1883,7 @@ dependencies = [ [[package]] name = "aptos-mempool-notifications" version = "0.1.0" -source = "git+https://github.com/movementlabsxyz/aptos-core?rev=51d875c93565e11f4e3f8321d200b406ed55a286#51d875c93565e11f4e3f8321d200b406ed55a286" +source = "git+https://github.com/movementlabsxyz/aptos-core?rev=1c63a73c49efa9c4bb568e05eea2e1dda4e5197a#1c63a73c49efa9c4bb568e05eea2e1dda4e5197a" dependencies = [ "aptos-types", "async-trait", @@ -1896,7 +1896,7 @@ dependencies = [ [[package]] name = "aptos-memsocket" version = "0.1.0" -source = "git+https://github.com/movementlabsxyz/aptos-core?rev=51d875c93565e11f4e3f8321d200b406ed55a286#51d875c93565e11f4e3f8321d200b406ed55a286" +source = "git+https://github.com/movementlabsxyz/aptos-core?rev=1c63a73c49efa9c4bb568e05eea2e1dda4e5197a#1c63a73c49efa9c4bb568e05eea2e1dda4e5197a" dependencies = [ "aptos-infallible", "bytes 1.7.1", @@ -1907,7 +1907,7 @@ dependencies = [ [[package]] name = "aptos-metrics-core" version = "0.1.0" -source = "git+https://github.com/movementlabsxyz/aptos-core?rev=51d875c93565e11f4e3f8321d200b406ed55a286#51d875c93565e11f4e3f8321d200b406ed55a286" +source = "git+https://github.com/movementlabsxyz/aptos-core?rev=1c63a73c49efa9c4bb568e05eea2e1dda4e5197a#1c63a73c49efa9c4bb568e05eea2e1dda4e5197a" dependencies = [ "anyhow", "prometheus", @@ -1916,7 +1916,7 @@ dependencies = [ [[package]] name = "aptos-move-stdlib" version = "0.1.1" -source = "git+https://github.com/movementlabsxyz/aptos-core?rev=51d875c93565e11f4e3f8321d200b406ed55a286#51d875c93565e11f4e3f8321d200b406ed55a286" +source = "git+https://github.com/movementlabsxyz/aptos-core?rev=1c63a73c49efa9c4bb568e05eea2e1dda4e5197a#1c63a73c49efa9c4bb568e05eea2e1dda4e5197a" dependencies = [ "aptos-gas-schedule", "aptos-native-interface", @@ -1939,7 +1939,7 @@ dependencies = [ [[package]] name = "aptos-mvhashmap" version = "0.1.0" -source = "git+https://github.com/movementlabsxyz/aptos-core?rev=51d875c93565e11f4e3f8321d200b406ed55a286#51d875c93565e11f4e3f8321d200b406ed55a286" +source = "git+https://github.com/movementlabsxyz/aptos-core?rev=1c63a73c49efa9c4bb568e05eea2e1dda4e5197a#1c63a73c49efa9c4bb568e05eea2e1dda4e5197a" dependencies = [ "anyhow", "aptos-aggregator", @@ -1960,7 +1960,7 @@ dependencies = [ [[package]] name = "aptos-native-interface" version = "0.1.0" -source = "git+https://github.com/movementlabsxyz/aptos-core?rev=51d875c93565e11f4e3f8321d200b406ed55a286#51d875c93565e11f4e3f8321d200b406ed55a286" +source = "git+https://github.com/movementlabsxyz/aptos-core?rev=1c63a73c49efa9c4bb568e05eea2e1dda4e5197a#1c63a73c49efa9c4bb568e05eea2e1dda4e5197a" dependencies = [ "aptos-gas-algebra", "aptos-gas-schedule", @@ -1977,7 +1977,7 @@ dependencies = [ [[package]] name = "aptos-netcore" version = "0.1.0" -source = "git+https://github.com/movementlabsxyz/aptos-core?rev=51d875c93565e11f4e3f8321d200b406ed55a286#51d875c93565e11f4e3f8321d200b406ed55a286" +source = "git+https://github.com/movementlabsxyz/aptos-core?rev=1c63a73c49efa9c4bb568e05eea2e1dda4e5197a#1c63a73c49efa9c4bb568e05eea2e1dda4e5197a" dependencies = [ "aptos-memsocket", "aptos-proxy", @@ -1994,7 +1994,7 @@ dependencies = [ [[package]] name = "aptos-network" version = "0.1.0" -source = "git+https://github.com/movementlabsxyz/aptos-core?rev=51d875c93565e11f4e3f8321d200b406ed55a286#51d875c93565e11f4e3f8321d200b406ed55a286" +source = "git+https://github.com/movementlabsxyz/aptos-core?rev=1c63a73c49efa9c4bb568e05eea2e1dda4e5197a#1c63a73c49efa9c4bb568e05eea2e1dda4e5197a" dependencies = [ "anyhow", "aptos-bitvec", @@ -2039,7 +2039,7 @@ dependencies = [ [[package]] name = "aptos-node-identity" version = "0.1.0" -source = "git+https://github.com/movementlabsxyz/aptos-core?rev=51d875c93565e11f4e3f8321d200b406ed55a286#51d875c93565e11f4e3f8321d200b406ed55a286" +source = "git+https://github.com/movementlabsxyz/aptos-core?rev=1c63a73c49efa9c4bb568e05eea2e1dda4e5197a#1c63a73c49efa9c4bb568e05eea2e1dda4e5197a" dependencies = [ "anyhow", "aptos-types", @@ -2050,7 +2050,7 @@ dependencies = [ [[package]] name = "aptos-node-resource-metrics" version = "0.1.0" -source = "git+https://github.com/movementlabsxyz/aptos-core?rev=51d875c93565e11f4e3f8321d200b406ed55a286#51d875c93565e11f4e3f8321d200b406ed55a286" +source = "git+https://github.com/movementlabsxyz/aptos-core?rev=1c63a73c49efa9c4bb568e05eea2e1dda4e5197a#1c63a73c49efa9c4bb568e05eea2e1dda4e5197a" dependencies = [ "aptos-build-info", "aptos-infallible", @@ -2066,7 +2066,7 @@ dependencies = [ [[package]] name = "aptos-num-variants" version = "0.1.0" -source = "git+https://github.com/movementlabsxyz/aptos-core?rev=51d875c93565e11f4e3f8321d200b406ed55a286#51d875c93565e11f4e3f8321d200b406ed55a286" +source = "git+https://github.com/movementlabsxyz/aptos-core?rev=1c63a73c49efa9c4bb568e05eea2e1dda4e5197a#1c63a73c49efa9c4bb568e05eea2e1dda4e5197a" dependencies = [ "proc-macro2", "quote", @@ -2076,7 +2076,7 @@ dependencies = [ [[package]] name = "aptos-openapi" version = "0.1.0" -source = "git+https://github.com/movementlabsxyz/aptos-core?rev=51d875c93565e11f4e3f8321d200b406ed55a286#51d875c93565e11f4e3f8321d200b406ed55a286" +source = "git+https://github.com/movementlabsxyz/aptos-core?rev=1c63a73c49efa9c4bb568e05eea2e1dda4e5197a#1c63a73c49efa9c4bb568e05eea2e1dda4e5197a" dependencies = [ "async-trait", "percent-encoding", @@ -2089,7 +2089,7 @@ dependencies = [ [[package]] name = "aptos-package-builder" version = "0.1.0" -source = "git+https://github.com/movementlabsxyz/aptos-core?rev=51d875c93565e11f4e3f8321d200b406ed55a286#51d875c93565e11f4e3f8321d200b406ed55a286" +source = "git+https://github.com/movementlabsxyz/aptos-core?rev=1c63a73c49efa9c4bb568e05eea2e1dda4e5197a#1c63a73c49efa9c4bb568e05eea2e1dda4e5197a" dependencies = [ "anyhow", "aptos-framework", @@ -2102,7 +2102,7 @@ dependencies = [ [[package]] name = "aptos-peer-monitoring-service-types" version = "0.1.0" -source = "git+https://github.com/movementlabsxyz/aptos-core?rev=51d875c93565e11f4e3f8321d200b406ed55a286#51d875c93565e11f4e3f8321d200b406ed55a286" +source = "git+https://github.com/movementlabsxyz/aptos-core?rev=1c63a73c49efa9c4bb568e05eea2e1dda4e5197a#1c63a73c49efa9c4bb568e05eea2e1dda4e5197a" dependencies = [ "aptos-config", "aptos-types", @@ -2114,7 +2114,7 @@ dependencies = [ [[package]] name = "aptos-proptest-helpers" version = "0.1.0" -source = "git+https://github.com/movementlabsxyz/aptos-core?rev=51d875c93565e11f4e3f8321d200b406ed55a286#51d875c93565e11f4e3f8321d200b406ed55a286" +source = "git+https://github.com/movementlabsxyz/aptos-core?rev=1c63a73c49efa9c4bb568e05eea2e1dda4e5197a#1c63a73c49efa9c4bb568e05eea2e1dda4e5197a" dependencies = [ "crossbeam", "proptest", @@ -2124,7 +2124,7 @@ dependencies = [ [[package]] name = "aptos-protos" version = "1.3.0" -source = "git+https://github.com/movementlabsxyz/aptos-core?rev=51d875c93565e11f4e3f8321d200b406ed55a286#51d875c93565e11f4e3f8321d200b406ed55a286" +source = "git+https://github.com/movementlabsxyz/aptos-core?rev=1c63a73c49efa9c4bb568e05eea2e1dda4e5197a#1c63a73c49efa9c4bb568e05eea2e1dda4e5197a" dependencies = [ "futures-core", "pbjson", @@ -2136,7 +2136,7 @@ dependencies = [ [[package]] name = "aptos-proxy" version = "0.1.0" -source = "git+https://github.com/movementlabsxyz/aptos-core?rev=51d875c93565e11f4e3f8321d200b406ed55a286#51d875c93565e11f4e3f8321d200b406ed55a286" +source = "git+https://github.com/movementlabsxyz/aptos-core?rev=1c63a73c49efa9c4bb568e05eea2e1dda4e5197a#1c63a73c49efa9c4bb568e05eea2e1dda4e5197a" dependencies = [ "ipnet", ] @@ -2144,7 +2144,7 @@ dependencies = [ [[package]] name = "aptos-push-metrics" version = "0.1.0" -source = "git+https://github.com/movementlabsxyz/aptos-core?rev=51d875c93565e11f4e3f8321d200b406ed55a286#51d875c93565e11f4e3f8321d200b406ed55a286" +source = "git+https://github.com/movementlabsxyz/aptos-core?rev=1c63a73c49efa9c4bb568e05eea2e1dda4e5197a#1c63a73c49efa9c4bb568e05eea2e1dda4e5197a" dependencies = [ "aptos-logger", "aptos-metrics-core", @@ -2155,7 +2155,7 @@ dependencies = [ [[package]] name = "aptos-resource-viewer" version = "0.1.0" -source = "git+https://github.com/movementlabsxyz/aptos-core?rev=51d875c93565e11f4e3f8321d200b406ed55a286#51d875c93565e11f4e3f8321d200b406ed55a286" +source = "git+https://github.com/movementlabsxyz/aptos-core?rev=1c63a73c49efa9c4bb568e05eea2e1dda4e5197a#1c63a73c49efa9c4bb568e05eea2e1dda4e5197a" dependencies = [ "anyhow", "aptos-types", @@ -2169,7 +2169,7 @@ dependencies = [ [[package]] name = "aptos-rest-client" version = "0.0.0" -source = "git+https://github.com/movementlabsxyz/aptos-core?rev=51d875c93565e11f4e3f8321d200b406ed55a286#51d875c93565e11f4e3f8321d200b406ed55a286" +source = "git+https://github.com/movementlabsxyz/aptos-core?rev=1c63a73c49efa9c4bb568e05eea2e1dda4e5197a#1c63a73c49efa9c4bb568e05eea2e1dda4e5197a" dependencies = [ "anyhow", "aptos-api-types", @@ -2192,7 +2192,7 @@ dependencies = [ [[package]] name = "aptos-rocksdb-options" version = "0.1.0" -source = "git+https://github.com/movementlabsxyz/aptos-core?rev=51d875c93565e11f4e3f8321d200b406ed55a286#51d875c93565e11f4e3f8321d200b406ed55a286" +source = "git+https://github.com/movementlabsxyz/aptos-core?rev=1c63a73c49efa9c4bb568e05eea2e1dda4e5197a#1c63a73c49efa9c4bb568e05eea2e1dda4e5197a" dependencies = [ "aptos-config", "rocksdb", @@ -2201,7 +2201,7 @@ dependencies = [ [[package]] name = "aptos-runtimes" version = "0.1.0" -source = "git+https://github.com/movementlabsxyz/aptos-core?rev=51d875c93565e11f4e3f8321d200b406ed55a286#51d875c93565e11f4e3f8321d200b406ed55a286" +source = "git+https://github.com/movementlabsxyz/aptos-core?rev=1c63a73c49efa9c4bb568e05eea2e1dda4e5197a#1c63a73c49efa9c4bb568e05eea2e1dda4e5197a" dependencies = [ "rayon", "tokio", @@ -2210,7 +2210,7 @@ dependencies = [ [[package]] name = "aptos-schemadb" version = "0.1.0" -source = "git+https://github.com/movementlabsxyz/aptos-core?rev=51d875c93565e11f4e3f8321d200b406ed55a286#51d875c93565e11f4e3f8321d200b406ed55a286" +source = "git+https://github.com/movementlabsxyz/aptos-core?rev=1c63a73c49efa9c4bb568e05eea2e1dda4e5197a#1c63a73c49efa9c4bb568e05eea2e1dda4e5197a" dependencies = [ "anyhow", "aptos-infallible", @@ -2227,7 +2227,7 @@ dependencies = [ [[package]] name = "aptos-scratchpad" version = "0.1.0" -source = "git+https://github.com/movementlabsxyz/aptos-core?rev=51d875c93565e11f4e3f8321d200b406ed55a286#51d875c93565e11f4e3f8321d200b406ed55a286" +source = "git+https://github.com/movementlabsxyz/aptos-core?rev=1c63a73c49efa9c4bb568e05eea2e1dda4e5197a#1c63a73c49efa9c4bb568e05eea2e1dda4e5197a" dependencies = [ "aptos-crypto", "aptos-drop-helper", @@ -2246,7 +2246,7 @@ dependencies = [ [[package]] name = "aptos-sdk" version = "0.0.3" -source = "git+https://github.com/movementlabsxyz/aptos-core?rev=51d875c93565e11f4e3f8321d200b406ed55a286#51d875c93565e11f4e3f8321d200b406ed55a286" +source = "git+https://github.com/movementlabsxyz/aptos-core?rev=1c63a73c49efa9c4bb568e05eea2e1dda4e5197a#1c63a73c49efa9c4bb568e05eea2e1dda4e5197a" dependencies = [ "anyhow", "aptos-cached-packages", @@ -2268,7 +2268,7 @@ dependencies = [ [[package]] name = "aptos-sdk-builder" version = "0.2.0" -source = "git+https://github.com/movementlabsxyz/aptos-core?rev=51d875c93565e11f4e3f8321d200b406ed55a286#51d875c93565e11f4e3f8321d200b406ed55a286" +source = "git+https://github.com/movementlabsxyz/aptos-core?rev=1c63a73c49efa9c4bb568e05eea2e1dda4e5197a#1c63a73c49efa9c4bb568e05eea2e1dda4e5197a" dependencies = [ "anyhow", "aptos-types", @@ -2286,7 +2286,7 @@ dependencies = [ [[package]] name = "aptos-secure-net" version = "0.1.0" -source = "git+https://github.com/movementlabsxyz/aptos-core?rev=51d875c93565e11f4e3f8321d200b406ed55a286#51d875c93565e11f4e3f8321d200b406ed55a286" +source = "git+https://github.com/movementlabsxyz/aptos-core?rev=1c63a73c49efa9c4bb568e05eea2e1dda4e5197a#1c63a73c49efa9c4bb568e05eea2e1dda4e5197a" dependencies = [ "aptos-logger", "aptos-metrics-core", @@ -2304,7 +2304,7 @@ dependencies = [ [[package]] name = "aptos-secure-storage" version = "0.1.0" -source = "git+https://github.com/movementlabsxyz/aptos-core?rev=51d875c93565e11f4e3f8321d200b406ed55a286#51d875c93565e11f4e3f8321d200b406ed55a286" +source = "git+https://github.com/movementlabsxyz/aptos-core?rev=1c63a73c49efa9c4bb568e05eea2e1dda4e5197a#1c63a73c49efa9c4bb568e05eea2e1dda4e5197a" dependencies = [ "aptos-crypto", "aptos-infallible", @@ -2325,7 +2325,7 @@ dependencies = [ [[package]] name = "aptos-short-hex-str" version = "0.1.0" -source = "git+https://github.com/movementlabsxyz/aptos-core?rev=51d875c93565e11f4e3f8321d200b406ed55a286#51d875c93565e11f4e3f8321d200b406ed55a286" +source = "git+https://github.com/movementlabsxyz/aptos-core?rev=1c63a73c49efa9c4bb568e05eea2e1dda4e5197a#1c63a73c49efa9c4bb568e05eea2e1dda4e5197a" dependencies = [ "mirai-annotations", "serde", @@ -2336,7 +2336,7 @@ dependencies = [ [[package]] name = "aptos-speculative-state-helper" version = "0.1.0" -source = "git+https://github.com/movementlabsxyz/aptos-core?rev=51d875c93565e11f4e3f8321d200b406ed55a286#51d875c93565e11f4e3f8321d200b406ed55a286" +source = "git+https://github.com/movementlabsxyz/aptos-core?rev=1c63a73c49efa9c4bb568e05eea2e1dda4e5197a#1c63a73c49efa9c4bb568e05eea2e1dda4e5197a" dependencies = [ "anyhow", "aptos-infallible", @@ -2347,7 +2347,7 @@ dependencies = [ [[package]] name = "aptos-storage-interface" version = "0.1.0" -source = "git+https://github.com/movementlabsxyz/aptos-core?rev=51d875c93565e11f4e3f8321d200b406ed55a286#51d875c93565e11f4e3f8321d200b406ed55a286" +source = "git+https://github.com/movementlabsxyz/aptos-core?rev=1c63a73c49efa9c4bb568e05eea2e1dda4e5197a#1c63a73c49efa9c4bb568e05eea2e1dda4e5197a" dependencies = [ "anyhow", "aptos-crypto", @@ -2375,7 +2375,7 @@ dependencies = [ [[package]] name = "aptos-table-natives" version = "0.1.0" -source = "git+https://github.com/movementlabsxyz/aptos-core?rev=51d875c93565e11f4e3f8321d200b406ed55a286#51d875c93565e11f4e3f8321d200b406ed55a286" +source = "git+https://github.com/movementlabsxyz/aptos-core?rev=1c63a73c49efa9c4bb568e05eea2e1dda4e5197a#1c63a73c49efa9c4bb568e05eea2e1dda4e5197a" dependencies = [ "aptos-gas-schedule", "aptos-native-interface", @@ -2393,7 +2393,7 @@ dependencies = [ [[package]] name = "aptos-temppath" version = "0.1.0" -source = "git+https://github.com/movementlabsxyz/aptos-core?rev=51d875c93565e11f4e3f8321d200b406ed55a286#51d875c93565e11f4e3f8321d200b406ed55a286" +source = "git+https://github.com/movementlabsxyz/aptos-core?rev=1c63a73c49efa9c4bb568e05eea2e1dda4e5197a#1c63a73c49efa9c4bb568e05eea2e1dda4e5197a" dependencies = [ "hex", "rand 0.7.3", @@ -2402,7 +2402,7 @@ dependencies = [ [[package]] name = "aptos-time-service" version = "0.1.0" -source = "git+https://github.com/movementlabsxyz/aptos-core?rev=51d875c93565e11f4e3f8321d200b406ed55a286#51d875c93565e11f4e3f8321d200b406ed55a286" +source = "git+https://github.com/movementlabsxyz/aptos-core?rev=1c63a73c49efa9c4bb568e05eea2e1dda4e5197a#1c63a73c49efa9c4bb568e05eea2e1dda4e5197a" dependencies = [ "aptos-infallible", "enum_dispatch", @@ -2415,7 +2415,7 @@ dependencies = [ [[package]] name = "aptos-types" version = "0.0.3" -source = "git+https://github.com/movementlabsxyz/aptos-core?rev=51d875c93565e11f4e3f8321d200b406ed55a286#51d875c93565e11f4e3f8321d200b406ed55a286" +source = "git+https://github.com/movementlabsxyz/aptos-core?rev=1c63a73c49efa9c4bb568e05eea2e1dda4e5197a#1c63a73c49efa9c4bb568e05eea2e1dda4e5197a" dependencies = [ "anyhow", "aptos-bitvec", @@ -2472,12 +2472,12 @@ dependencies = [ [[package]] name = "aptos-utils" version = "0.1.0" -source = "git+https://github.com/movementlabsxyz/aptos-core?rev=51d875c93565e11f4e3f8321d200b406ed55a286#51d875c93565e11f4e3f8321d200b406ed55a286" +source = "git+https://github.com/movementlabsxyz/aptos-core?rev=1c63a73c49efa9c4bb568e05eea2e1dda4e5197a#1c63a73c49efa9c4bb568e05eea2e1dda4e5197a" [[package]] name = "aptos-vault-client" version = "0.1.0" -source = "git+https://github.com/movementlabsxyz/aptos-core?rev=51d875c93565e11f4e3f8321d200b406ed55a286#51d875c93565e11f4e3f8321d200b406ed55a286" +source = "git+https://github.com/movementlabsxyz/aptos-core?rev=1c63a73c49efa9c4bb568e05eea2e1dda4e5197a#1c63a73c49efa9c4bb568e05eea2e1dda4e5197a" dependencies = [ "aptos-crypto", "base64 0.13.1", @@ -2493,7 +2493,7 @@ dependencies = [ [[package]] name = "aptos-vm" version = "0.1.0" -source = "git+https://github.com/movementlabsxyz/aptos-core?rev=51d875c93565e11f4e3f8321d200b406ed55a286#51d875c93565e11f4e3f8321d200b406ed55a286" +source = "git+https://github.com/movementlabsxyz/aptos-core?rev=1c63a73c49efa9c4bb568e05eea2e1dda4e5197a#1c63a73c49efa9c4bb568e05eea2e1dda4e5197a" dependencies = [ "anyhow", "aptos-aggregator", @@ -2543,7 +2543,7 @@ dependencies = [ [[package]] name = "aptos-vm-genesis" version = "0.1.0" -source = "git+https://github.com/movementlabsxyz/aptos-core?rev=51d875c93565e11f4e3f8321d200b406ed55a286#51d875c93565e11f4e3f8321d200b406ed55a286" +source = "git+https://github.com/movementlabsxyz/aptos-core?rev=1c63a73c49efa9c4bb568e05eea2e1dda4e5197a#1c63a73c49efa9c4bb568e05eea2e1dda4e5197a" dependencies = [ "aptos-cached-packages", "aptos-crypto", @@ -2564,7 +2564,7 @@ dependencies = [ [[package]] name = "aptos-vm-logging" version = "0.1.0" -source = "git+https://github.com/movementlabsxyz/aptos-core?rev=51d875c93565e11f4e3f8321d200b406ed55a286#51d875c93565e11f4e3f8321d200b406ed55a286" +source = "git+https://github.com/movementlabsxyz/aptos-core?rev=1c63a73c49efa9c4bb568e05eea2e1dda4e5197a#1c63a73c49efa9c4bb568e05eea2e1dda4e5197a" dependencies = [ "aptos-crypto", "aptos-logger", @@ -2579,7 +2579,7 @@ dependencies = [ [[package]] name = "aptos-vm-types" version = "0.0.1" -source = "git+https://github.com/movementlabsxyz/aptos-core?rev=51d875c93565e11f4e3f8321d200b406ed55a286#51d875c93565e11f4e3f8321d200b406ed55a286" +source = "git+https://github.com/movementlabsxyz/aptos-core?rev=1c63a73c49efa9c4bb568e05eea2e1dda4e5197a#1c63a73c49efa9c4bb568e05eea2e1dda4e5197a" dependencies = [ "anyhow", "aptos-aggregator", @@ -2601,7 +2601,7 @@ dependencies = [ [[package]] name = "aptos-vm-validator" version = "0.1.0" -source = "git+https://github.com/movementlabsxyz/aptos-core?rev=51d875c93565e11f4e3f8321d200b406ed55a286#51d875c93565e11f4e3f8321d200b406ed55a286" +source = "git+https://github.com/movementlabsxyz/aptos-core?rev=1c63a73c49efa9c4bb568e05eea2e1dda4e5197a#1c63a73c49efa9c4bb568e05eea2e1dda4e5197a" dependencies = [ "anyhow", "aptos-logger", @@ -3607,7 +3607,10 @@ dependencies = [ "alloy-network", "alloy-sol-types", "anyhow", + "aptos-language-e2e-tests", + "aptos-logger", "aptos-sdk", + "aptos-types", "bridge-shared", "ethereum-bridge", "movement-bridge", @@ -3615,6 +3618,7 @@ dependencies = [ "reqwest 0.12.5", "serde_json", "tokio", + "url", ] [[package]] @@ -3625,6 +3629,7 @@ version = "0.0.2" name = "bridge-shared" version = "0.0.2" dependencies = [ + "alloy", "async-trait", "dashmap 6.0.1", "delegate", @@ -5403,6 +5408,8 @@ dependencies = [ "keccak-hash", "mcr-settlement-client", "poem", + "rand 0.7.3", + "rand_chacha 0.2.2", "serde", "serde_with", "thiserror", @@ -8156,7 +8163,7 @@ checksum = "1fafa6961cabd9c63bcd77a45d7e3b7f3b552b70417831fb0f56db717e72407e" [[package]] name = "move-abigen" version = "0.1.0" -source = "git+https://github.com/movementlabsxyz/aptos-core?rev=51d875c93565e11f4e3f8321d200b406ed55a286#51d875c93565e11f4e3f8321d200b406ed55a286" +source = "git+https://github.com/movementlabsxyz/aptos-core?rev=1c63a73c49efa9c4bb568e05eea2e1dda4e5197a#1c63a73c49efa9c4bb568e05eea2e1dda4e5197a" dependencies = [ "anyhow", "bcs 0.1.4", @@ -8173,7 +8180,7 @@ dependencies = [ [[package]] name = "move-binary-format" version = "0.0.3" -source = "git+https://github.com/movementlabsxyz/aptos-core?rev=51d875c93565e11f4e3f8321d200b406ed55a286#51d875c93565e11f4e3f8321d200b406ed55a286" +source = "git+https://github.com/movementlabsxyz/aptos-core?rev=1c63a73c49efa9c4bb568e05eea2e1dda4e5197a#1c63a73c49efa9c4bb568e05eea2e1dda4e5197a" dependencies = [ "anyhow", "backtrace", @@ -8188,12 +8195,12 @@ dependencies = [ [[package]] name = "move-borrow-graph" version = "0.0.1" -source = "git+https://github.com/movementlabsxyz/aptos-core?rev=51d875c93565e11f4e3f8321d200b406ed55a286#51d875c93565e11f4e3f8321d200b406ed55a286" +source = "git+https://github.com/movementlabsxyz/aptos-core?rev=1c63a73c49efa9c4bb568e05eea2e1dda4e5197a#1c63a73c49efa9c4bb568e05eea2e1dda4e5197a" [[package]] name = "move-bytecode-source-map" version = "0.1.0" -source = "git+https://github.com/movementlabsxyz/aptos-core?rev=51d875c93565e11f4e3f8321d200b406ed55a286#51d875c93565e11f4e3f8321d200b406ed55a286" +source = "git+https://github.com/movementlabsxyz/aptos-core?rev=1c63a73c49efa9c4bb568e05eea2e1dda4e5197a#1c63a73c49efa9c4bb568e05eea2e1dda4e5197a" dependencies = [ "anyhow", "bcs 0.1.4", @@ -8208,7 +8215,7 @@ dependencies = [ [[package]] name = "move-bytecode-spec" version = "0.1.0" -source = "git+https://github.com/movementlabsxyz/aptos-core?rev=51d875c93565e11f4e3f8321d200b406ed55a286#51d875c93565e11f4e3f8321d200b406ed55a286" +source = "git+https://github.com/movementlabsxyz/aptos-core?rev=1c63a73c49efa9c4bb568e05eea2e1dda4e5197a#1c63a73c49efa9c4bb568e05eea2e1dda4e5197a" dependencies = [ "once_cell", "quote", @@ -8218,7 +8225,7 @@ dependencies = [ [[package]] name = "move-bytecode-utils" version = "0.1.0" -source = "git+https://github.com/movementlabsxyz/aptos-core?rev=51d875c93565e11f4e3f8321d200b406ed55a286#51d875c93565e11f4e3f8321d200b406ed55a286" +source = "git+https://github.com/movementlabsxyz/aptos-core?rev=1c63a73c49efa9c4bb568e05eea2e1dda4e5197a#1c63a73c49efa9c4bb568e05eea2e1dda4e5197a" dependencies = [ "anyhow", "move-binary-format", @@ -8230,7 +8237,7 @@ dependencies = [ [[package]] name = "move-bytecode-verifier" version = "0.1.0" -source = "git+https://github.com/movementlabsxyz/aptos-core?rev=51d875c93565e11f4e3f8321d200b406ed55a286#51d875c93565e11f4e3f8321d200b406ed55a286" +source = "git+https://github.com/movementlabsxyz/aptos-core?rev=1c63a73c49efa9c4bb568e05eea2e1dda4e5197a#1c63a73c49efa9c4bb568e05eea2e1dda4e5197a" dependencies = [ "fail", "move-binary-format", @@ -8244,7 +8251,7 @@ dependencies = [ [[package]] name = "move-bytecode-viewer" version = "0.1.0" -source = "git+https://github.com/movementlabsxyz/aptos-core?rev=51d875c93565e11f4e3f8321d200b406ed55a286#51d875c93565e11f4e3f8321d200b406ed55a286" +source = "git+https://github.com/movementlabsxyz/aptos-core?rev=1c63a73c49efa9c4bb568e05eea2e1dda4e5197a#1c63a73c49efa9c4bb568e05eea2e1dda4e5197a" dependencies = [ "anyhow", "clap 4.5.13", @@ -8259,7 +8266,7 @@ dependencies = [ [[package]] name = "move-cli" version = "0.1.0" -source = "git+https://github.com/movementlabsxyz/aptos-core?rev=51d875c93565e11f4e3f8321d200b406ed55a286#51d875c93565e11f4e3f8321d200b406ed55a286" +source = "git+https://github.com/movementlabsxyz/aptos-core?rev=1c63a73c49efa9c4bb568e05eea2e1dda4e5197a#1c63a73c49efa9c4bb568e05eea2e1dda4e5197a" dependencies = [ "anyhow", "clap 4.5.13", @@ -8289,7 +8296,7 @@ dependencies = [ [[package]] name = "move-command-line-common" version = "0.1.0" -source = "git+https://github.com/movementlabsxyz/aptos-core?rev=51d875c93565e11f4e3f8321d200b406ed55a286#51d875c93565e11f4e3f8321d200b406ed55a286" +source = "git+https://github.com/movementlabsxyz/aptos-core?rev=1c63a73c49efa9c4bb568e05eea2e1dda4e5197a#1c63a73c49efa9c4bb568e05eea2e1dda4e5197a" dependencies = [ "anyhow", "difference", @@ -8306,7 +8313,7 @@ dependencies = [ [[package]] name = "move-compiler" version = "0.0.1" -source = "git+https://github.com/movementlabsxyz/aptos-core?rev=51d875c93565e11f4e3f8321d200b406ed55a286#51d875c93565e11f4e3f8321d200b406ed55a286" +source = "git+https://github.com/movementlabsxyz/aptos-core?rev=1c63a73c49efa9c4bb568e05eea2e1dda4e5197a#1c63a73c49efa9c4bb568e05eea2e1dda4e5197a" dependencies = [ "anyhow", "bcs 0.1.4", @@ -8332,7 +8339,7 @@ dependencies = [ [[package]] name = "move-compiler-v2" version = "0.1.0" -source = "git+https://github.com/movementlabsxyz/aptos-core?rev=51d875c93565e11f4e3f8321d200b406ed55a286#51d875c93565e11f4e3f8321d200b406ed55a286" +source = "git+https://github.com/movementlabsxyz/aptos-core?rev=1c63a73c49efa9c4bb568e05eea2e1dda4e5197a#1c63a73c49efa9c4bb568e05eea2e1dda4e5197a" dependencies = [ "abstract-domain-derive", "anyhow", @@ -8363,7 +8370,7 @@ dependencies = [ [[package]] name = "move-core-types" version = "0.0.4" -source = "git+https://github.com/movementlabsxyz/aptos-core?rev=51d875c93565e11f4e3f8321d200b406ed55a286#51d875c93565e11f4e3f8321d200b406ed55a286" +source = "git+https://github.com/movementlabsxyz/aptos-core?rev=1c63a73c49efa9c4bb568e05eea2e1dda4e5197a#1c63a73c49efa9c4bb568e05eea2e1dda4e5197a" dependencies = [ "anyhow", "arbitrary", @@ -8388,7 +8395,7 @@ dependencies = [ [[package]] name = "move-coverage" version = "0.1.0" -source = "git+https://github.com/movementlabsxyz/aptos-core?rev=51d875c93565e11f4e3f8321d200b406ed55a286#51d875c93565e11f4e3f8321d200b406ed55a286" +source = "git+https://github.com/movementlabsxyz/aptos-core?rev=1c63a73c49efa9c4bb568e05eea2e1dda4e5197a#1c63a73c49efa9c4bb568e05eea2e1dda4e5197a" dependencies = [ "anyhow", "bcs 0.1.4", @@ -8407,7 +8414,7 @@ dependencies = [ [[package]] name = "move-disassembler" version = "0.1.0" -source = "git+https://github.com/movementlabsxyz/aptos-core?rev=51d875c93565e11f4e3f8321d200b406ed55a286#51d875c93565e11f4e3f8321d200b406ed55a286" +source = "git+https://github.com/movementlabsxyz/aptos-core?rev=1c63a73c49efa9c4bb568e05eea2e1dda4e5197a#1c63a73c49efa9c4bb568e05eea2e1dda4e5197a" dependencies = [ "anyhow", "clap 4.5.13", @@ -8424,7 +8431,7 @@ dependencies = [ [[package]] name = "move-docgen" version = "0.1.0" -source = "git+https://github.com/movementlabsxyz/aptos-core?rev=51d875c93565e11f4e3f8321d200b406ed55a286#51d875c93565e11f4e3f8321d200b406ed55a286" +source = "git+https://github.com/movementlabsxyz/aptos-core?rev=1c63a73c49efa9c4bb568e05eea2e1dda4e5197a#1c63a73c49efa9c4bb568e05eea2e1dda4e5197a" dependencies = [ "anyhow", "clap 4.5.13", @@ -8443,7 +8450,7 @@ dependencies = [ [[package]] name = "move-errmapgen" version = "0.1.0" -source = "git+https://github.com/movementlabsxyz/aptos-core?rev=51d875c93565e11f4e3f8321d200b406ed55a286#51d875c93565e11f4e3f8321d200b406ed55a286" +source = "git+https://github.com/movementlabsxyz/aptos-core?rev=1c63a73c49efa9c4bb568e05eea2e1dda4e5197a#1c63a73c49efa9c4bb568e05eea2e1dda4e5197a" dependencies = [ "anyhow", "move-command-line-common", @@ -8455,7 +8462,7 @@ dependencies = [ [[package]] name = "move-ir-compiler" version = "0.1.0" -source = "git+https://github.com/movementlabsxyz/aptos-core?rev=51d875c93565e11f4e3f8321d200b406ed55a286#51d875c93565e11f4e3f8321d200b406ed55a286" +source = "git+https://github.com/movementlabsxyz/aptos-core?rev=1c63a73c49efa9c4bb568e05eea2e1dda4e5197a#1c63a73c49efa9c4bb568e05eea2e1dda4e5197a" dependencies = [ "anyhow", "bcs 0.1.4", @@ -8471,7 +8478,7 @@ dependencies = [ [[package]] name = "move-ir-to-bytecode" version = "0.1.0" -source = "git+https://github.com/movementlabsxyz/aptos-core?rev=51d875c93565e11f4e3f8321d200b406ed55a286#51d875c93565e11f4e3f8321d200b406ed55a286" +source = "git+https://github.com/movementlabsxyz/aptos-core?rev=1c63a73c49efa9c4bb568e05eea2e1dda4e5197a#1c63a73c49efa9c4bb568e05eea2e1dda4e5197a" dependencies = [ "anyhow", "codespan-reporting", @@ -8489,7 +8496,7 @@ dependencies = [ [[package]] name = "move-ir-to-bytecode-syntax" version = "0.1.0" -source = "git+https://github.com/movementlabsxyz/aptos-core?rev=51d875c93565e11f4e3f8321d200b406ed55a286#51d875c93565e11f4e3f8321d200b406ed55a286" +source = "git+https://github.com/movementlabsxyz/aptos-core?rev=1c63a73c49efa9c4bb568e05eea2e1dda4e5197a#1c63a73c49efa9c4bb568e05eea2e1dda4e5197a" dependencies = [ "anyhow", "hex", @@ -8502,7 +8509,7 @@ dependencies = [ [[package]] name = "move-ir-types" version = "0.1.0" -source = "git+https://github.com/movementlabsxyz/aptos-core?rev=51d875c93565e11f4e3f8321d200b406ed55a286#51d875c93565e11f4e3f8321d200b406ed55a286" +source = "git+https://github.com/movementlabsxyz/aptos-core?rev=1c63a73c49efa9c4bb568e05eea2e1dda4e5197a#1c63a73c49efa9c4bb568e05eea2e1dda4e5197a" dependencies = [ "hex", "move-command-line-common", @@ -8515,7 +8522,7 @@ dependencies = [ [[package]] name = "move-model" version = "0.1.0" -source = "git+https://github.com/movementlabsxyz/aptos-core?rev=51d875c93565e11f4e3f8321d200b406ed55a286#51d875c93565e11f4e3f8321d200b406ed55a286" +source = "git+https://github.com/movementlabsxyz/aptos-core?rev=1c63a73c49efa9c4bb568e05eea2e1dda4e5197a#1c63a73c49efa9c4bb568e05eea2e1dda4e5197a" dependencies = [ "anyhow", "codespan", @@ -8541,7 +8548,7 @@ dependencies = [ [[package]] name = "move-package" version = "0.1.0" -source = "git+https://github.com/movementlabsxyz/aptos-core?rev=51d875c93565e11f4e3f8321d200b406ed55a286#51d875c93565e11f4e3f8321d200b406ed55a286" +source = "git+https://github.com/movementlabsxyz/aptos-core?rev=1c63a73c49efa9c4bb568e05eea2e1dda4e5197a#1c63a73c49efa9c4bb568e05eea2e1dda4e5197a" dependencies = [ "anyhow", "clap 4.5.13", @@ -8575,7 +8582,7 @@ dependencies = [ [[package]] name = "move-prover" version = "0.1.0" -source = "git+https://github.com/movementlabsxyz/aptos-core?rev=51d875c93565e11f4e3f8321d200b406ed55a286#51d875c93565e11f4e3f8321d200b406ed55a286" +source = "git+https://github.com/movementlabsxyz/aptos-core?rev=1c63a73c49efa9c4bb568e05eea2e1dda4e5197a#1c63a73c49efa9c4bb568e05eea2e1dda4e5197a" dependencies = [ "anyhow", "atty", @@ -8602,7 +8609,7 @@ dependencies = [ [[package]] name = "move-prover-boogie-backend" version = "0.1.0" -source = "git+https://github.com/movementlabsxyz/aptos-core?rev=51d875c93565e11f4e3f8321d200b406ed55a286#51d875c93565e11f4e3f8321d200b406ed55a286" +source = "git+https://github.com/movementlabsxyz/aptos-core?rev=1c63a73c49efa9c4bb568e05eea2e1dda4e5197a#1c63a73c49efa9c4bb568e05eea2e1dda4e5197a" dependencies = [ "anyhow", "async-trait", @@ -8631,7 +8638,7 @@ dependencies = [ [[package]] name = "move-prover-bytecode-pipeline" version = "0.1.0" -source = "git+https://github.com/movementlabsxyz/aptos-core?rev=51d875c93565e11f4e3f8321d200b406ed55a286#51d875c93565e11f4e3f8321d200b406ed55a286" +source = "git+https://github.com/movementlabsxyz/aptos-core?rev=1c63a73c49efa9c4bb568e05eea2e1dda4e5197a#1c63a73c49efa9c4bb568e05eea2e1dda4e5197a" dependencies = [ "abstract-domain-derive", "anyhow", @@ -8648,7 +8655,7 @@ dependencies = [ [[package]] name = "move-resource-viewer" version = "0.1.0" -source = "git+https://github.com/movementlabsxyz/aptos-core?rev=51d875c93565e11f4e3f8321d200b406ed55a286#51d875c93565e11f4e3f8321d200b406ed55a286" +source = "git+https://github.com/movementlabsxyz/aptos-core?rev=1c63a73c49efa9c4bb568e05eea2e1dda4e5197a#1c63a73c49efa9c4bb568e05eea2e1dda4e5197a" dependencies = [ "anyhow", "hex", @@ -8675,7 +8682,7 @@ dependencies = [ [[package]] name = "move-stackless-bytecode" version = "0.1.0" -source = "git+https://github.com/movementlabsxyz/aptos-core?rev=51d875c93565e11f4e3f8321d200b406ed55a286#51d875c93565e11f4e3f8321d200b406ed55a286" +source = "git+https://github.com/movementlabsxyz/aptos-core?rev=1c63a73c49efa9c4bb568e05eea2e1dda4e5197a#1c63a73c49efa9c4bb568e05eea2e1dda4e5197a" dependencies = [ "abstract-domain-derive", "codespan-reporting", @@ -8694,7 +8701,7 @@ dependencies = [ [[package]] name = "move-stdlib" version = "0.1.1" -source = "git+https://github.com/movementlabsxyz/aptos-core?rev=51d875c93565e11f4e3f8321d200b406ed55a286#51d875c93565e11f4e3f8321d200b406ed55a286" +source = "git+https://github.com/movementlabsxyz/aptos-core?rev=1c63a73c49efa9c4bb568e05eea2e1dda4e5197a#1c63a73c49efa9c4bb568e05eea2e1dda4e5197a" dependencies = [ "anyhow", "hex", @@ -8717,7 +8724,7 @@ dependencies = [ [[package]] name = "move-symbol-pool" version = "0.1.0" -source = "git+https://github.com/movementlabsxyz/aptos-core?rev=51d875c93565e11f4e3f8321d200b406ed55a286#51d875c93565e11f4e3f8321d200b406ed55a286" +source = "git+https://github.com/movementlabsxyz/aptos-core?rev=1c63a73c49efa9c4bb568e05eea2e1dda4e5197a#1c63a73c49efa9c4bb568e05eea2e1dda4e5197a" dependencies = [ "once_cell", "serde", @@ -8726,7 +8733,7 @@ dependencies = [ [[package]] name = "move-table-extension" version = "0.1.0" -source = "git+https://github.com/movementlabsxyz/aptos-core?rev=51d875c93565e11f4e3f8321d200b406ed55a286#51d875c93565e11f4e3f8321d200b406ed55a286" +source = "git+https://github.com/movementlabsxyz/aptos-core?rev=1c63a73c49efa9c4bb568e05eea2e1dda4e5197a#1c63a73c49efa9c4bb568e05eea2e1dda4e5197a" dependencies = [ "better_any", "bytes 1.7.1", @@ -8741,7 +8748,7 @@ dependencies = [ [[package]] name = "move-unit-test" version = "0.1.0" -source = "git+https://github.com/movementlabsxyz/aptos-core?rev=51d875c93565e11f4e3f8321d200b406ed55a286#51d875c93565e11f4e3f8321d200b406ed55a286" +source = "git+https://github.com/movementlabsxyz/aptos-core?rev=1c63a73c49efa9c4bb568e05eea2e1dda4e5197a#1c63a73c49efa9c4bb568e05eea2e1dda4e5197a" dependencies = [ "anyhow", "better_any", @@ -8769,7 +8776,7 @@ dependencies = [ [[package]] name = "move-vm-runtime" version = "0.1.0" -source = "git+https://github.com/movementlabsxyz/aptos-core?rev=51d875c93565e11f4e3f8321d200b406ed55a286#51d875c93565e11f4e3f8321d200b406ed55a286" +source = "git+https://github.com/movementlabsxyz/aptos-core?rev=1c63a73c49efa9c4bb568e05eea2e1dda4e5197a#1c63a73c49efa9c4bb568e05eea2e1dda4e5197a" dependencies = [ "better_any", "bytes 1.7.1", @@ -8793,7 +8800,7 @@ dependencies = [ [[package]] name = "move-vm-test-utils" version = "0.1.0" -source = "git+https://github.com/movementlabsxyz/aptos-core?rev=51d875c93565e11f4e3f8321d200b406ed55a286#51d875c93565e11f4e3f8321d200b406ed55a286" +source = "git+https://github.com/movementlabsxyz/aptos-core?rev=1c63a73c49efa9c4bb568e05eea2e1dda4e5197a#1c63a73c49efa9c4bb568e05eea2e1dda4e5197a" dependencies = [ "anyhow", "bytes 1.7.1", @@ -8808,7 +8815,7 @@ dependencies = [ [[package]] name = "move-vm-types" version = "0.1.0" -source = "git+https://github.com/movementlabsxyz/aptos-core?rev=51d875c93565e11f4e3f8321d200b406ed55a286#51d875c93565e11f4e3f8321d200b406ed55a286" +source = "git+https://github.com/movementlabsxyz/aptos-core?rev=1c63a73c49efa9c4bb568e05eea2e1dda4e5197a#1c63a73c49efa9c4bb568e05eea2e1dda4e5197a" dependencies = [ "bcs 0.1.4", "derivative", @@ -8843,7 +8850,6 @@ dependencies = [ "bcs 0.1.4", "bridge-shared", "derive-new", - "dot-movement", "hex", "keccak-hash", "mcr-settlement-client", @@ -8852,7 +8858,6 @@ dependencies = [ "rand 0.7.3", "serde", "serde_json", - "suzuka-config", "thiserror", "tokio", "tracing", diff --git a/Cargo.toml b/Cargo.toml index 844623c5a..679044367 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -112,40 +112,40 @@ serde_yaml = "0.9.34" ## Aptos dependencies ### We use a forked version so that we can override dependency versions. This is required ### to be avoid dependency conflicts with other Sovereign Labs crates. -aptos-api = { git = "https://github.com/movementlabsxyz/aptos-core", rev = "51d875c93565e11f4e3f8321d200b406ed55a286" } -aptos-api-types = { git = "https://github.com/movementlabsxyz/aptos-core", rev = "51d875c93565e11f4e3f8321d200b406ed55a286" } -aptos-bitvec = { git = "https://github.com/movementlabsxyz/aptos-core", rev = "51d875c93565e11f4e3f8321d200b406ed55a286" } -aptos-block-executor = { git = "https://github.com/movementlabsxyz/aptos-core.git", rev = "51d875c93565e11f4e3f8321d200b406ed55a286" } -aptos-cached-packages = { git = "https://github.com/movementlabsxyz/aptos-core", rev = "51d875c93565e11f4e3f8321d200b406ed55a286" } -aptos-config = { git = "https://github.com/movementlabsxyz/aptos-core", rev = "51d875c93565e11f4e3f8321d200b406ed55a286" } -aptos-consensus-types = { git = "https://github.com/movementlabsxyz/aptos-core", rev = "51d875c93565e11f4e3f8321d200b406ed55a286" } -aptos-crypto = { git = "https://github.com/movementlabsxyz/aptos-core", rev = "51d875c93565e11f4e3f8321d200b406ed55a286", features = [ +aptos-api = { git = "https://github.com/movementlabsxyz/aptos-core", rev = "1c63a73c49efa9c4bb568e05eea2e1dda4e5197a" } +aptos-api-types = { git = "https://github.com/movementlabsxyz/aptos-core", rev = "1c63a73c49efa9c4bb568e05eea2e1dda4e5197a" } +aptos-bitvec = { git = "https://github.com/movementlabsxyz/aptos-core", rev = "1c63a73c49efa9c4bb568e05eea2e1dda4e5197a" } +aptos-block-executor = { git = "https://github.com/movementlabsxyz/aptos-core.git", rev = "1c63a73c49efa9c4bb568e05eea2e1dda4e5197a" } +aptos-cached-packages = { git = "https://github.com/movementlabsxyz/aptos-core", rev = "1c63a73c49efa9c4bb568e05eea2e1dda4e5197a" } +aptos-config = { git = "https://github.com/movementlabsxyz/aptos-core", rev = "1c63a73c49efa9c4bb568e05eea2e1dda4e5197a" } +aptos-consensus-types = { git = "https://github.com/movementlabsxyz/aptos-core", rev = "1c63a73c49efa9c4bb568e05eea2e1dda4e5197a" } +aptos-crypto = { git = "https://github.com/movementlabsxyz/aptos-core", rev = "1c63a73c49efa9c4bb568e05eea2e1dda4e5197a", features = [ "cloneable-private-keys", ] } -aptos-db = { git = "https://github.com/movementlabsxyz/aptos-core.git", rev = "51d875c93565e11f4e3f8321d200b406ed55a286" } -aptos-executor = { git = "https://github.com/movementlabsxyz/aptos-core", rev = "51d875c93565e11f4e3f8321d200b406ed55a286" } -aptos-executor-test-helpers = { git = "https://github.com/movementlabsxyz/aptos-core", rev = "51d875c93565e11f4e3f8321d200b406ed55a286" } -aptos-executor-types = { git = "https://github.com/movementlabsxyz/aptos-core", rev = "51d875c93565e11f4e3f8321d200b406ed55a286" } -aptos-faucet-core = { git = "https://github.com/movementlabsxyz/aptos-core", rev = "51d875c93565e11f4e3f8321d200b406ed55a286" } -aptos-framework = { git = "https://github.com/movementlabsxyz/aptos-core", rev = "51d875c93565e11f4e3f8321d200b406ed55a286" } -aptos-language-e2e-tests = { git = "https://github.com/movementlabsxyz/aptos-core", rev = "51d875c93565e11f4e3f8321d200b406ed55a286" } -aptos-mempool = { git = "https://github.com/movementlabsxyz/aptos-core", rev = "51d875c93565e11f4e3f8321d200b406ed55a286" } -aptos-proptest-helpers = { git = "https://github.com/movementlabsxyz/aptos-core", rev = "51d875c93565e11f4e3f8321d200b406ed55a286" } -aptos-sdk = { git = "https://github.com/movementlabsxyz/aptos-core", rev = "51d875c93565e11f4e3f8321d200b406ed55a286" } -aptos-state-view = { git = "https://github.com/movementlabsxyz/aptos-core", rev = "51d875c93565e11f4e3f8321d200b406ed55a286" } -aptos-storage-interface = { git = "https://github.com/movementlabsxyz/aptos-core", rev = "51d875c93565e11f4e3f8321d200b406ed55a286" } -aptos-temppath = { git = "https://github.com/movementlabsxyz/aptos-core", rev = "51d875c93565e11f4e3f8321d200b406ed55a286" } -aptos-types = { git = "https://github.com/movementlabsxyz/aptos-core", rev = "51d875c93565e11f4e3f8321d200b406ed55a286" } -aptos-vm = { git = "https://github.com/movementlabsxyz/aptos-core", rev = "51d875c93565e11f4e3f8321d200b406ed55a286" } -aptos-vm-genesis = { git = "https://github.com/movementlabsxyz/aptos-core", rev = "51d875c93565e11f4e3f8321d200b406ed55a286" } -aptos-vm-logging = { git = "https://github.com/movementlabsxyz/aptos-core", rev = "51d875c93565e11f4e3f8321d200b406ed55a286" } -aptos-vm-validator = { git = "https://github.com/movementlabsxyz/aptos-core", rev = "51d875c93565e11f4e3f8321d200b406ed55a286" } -aptos-logger = { git = "https://github.com/movementlabsxyz/aptos-core", rev = "51d875c93565e11f4e3f8321d200b406ed55a286" } -aptos-vm-types = { git = "https://github.com/movementlabsxyz/aptos-core", rev = "51d875c93565e11f4e3f8321d200b406ed55a286" } -aptos-indexer = { git = "https://github.com/movementlabsxyz/aptos-core", rev = "51d875c93565e11f4e3f8321d200b406ed55a286" } -aptos-indexer-grpc-fullnode = { git = "https://github.com/movementlabsxyz/aptos-core", rev = "51d875c93565e11f4e3f8321d200b406ed55a286" } -aptos-indexer-grpc-table-info = { git = "https://github.com/movementlabsxyz/aptos-core", rev = "51d875c93565e11f4e3f8321d200b406ed55a286" } -aptos-protos = { git = "https://github.com/movementlabsxyz/aptos-core", rev = "51d875c93565e11f4e3f8321d200b406ed55a286" } +aptos-db = { git = "https://github.com/movementlabsxyz/aptos-core.git", rev = "1c63a73c49efa9c4bb568e05eea2e1dda4e5197a" } +aptos-executor = { git = "https://github.com/movementlabsxyz/aptos-core", rev = "1c63a73c49efa9c4bb568e05eea2e1dda4e5197a" } +aptos-executor-test-helpers = { git = "https://github.com/movementlabsxyz/aptos-core", rev = "1c63a73c49efa9c4bb568e05eea2e1dda4e5197a" } +aptos-executor-types = { git = "https://github.com/movementlabsxyz/aptos-core", rev = "1c63a73c49efa9c4bb568e05eea2e1dda4e5197a" } +aptos-faucet-core = { git = "https://github.com/movementlabsxyz/aptos-core", rev = "1c63a73c49efa9c4bb568e05eea2e1dda4e5197a" } +aptos-framework = { git = "https://github.com/movementlabsxyz/aptos-core", rev = "1c63a73c49efa9c4bb568e05eea2e1dda4e5197a" } +aptos-language-e2e-tests = { git = "https://github.com/movementlabsxyz/aptos-core", rev = "1c63a73c49efa9c4bb568e05eea2e1dda4e5197a" } +aptos-mempool = { git = "https://github.com/movementlabsxyz/aptos-core", rev = "1c63a73c49efa9c4bb568e05eea2e1dda4e5197a" } +aptos-proptest-helpers = { git = "https://github.com/movementlabsxyz/aptos-core", rev = "1c63a73c49efa9c4bb568e05eea2e1dda4e5197a" } +aptos-sdk = { git = "https://github.com/movementlabsxyz/aptos-core", rev = "1c63a73c49efa9c4bb568e05eea2e1dda4e5197a" } +aptos-state-view = { git = "https://github.com/movementlabsxyz/aptos-core", rev = "1c63a73c49efa9c4bb568e05eea2e1dda4e5197a" } +aptos-storage-interface = { git = "https://github.com/movementlabsxyz/aptos-core", rev = "1c63a73c49efa9c4bb568e05eea2e1dda4e5197a" } +aptos-temppath = { git = "https://github.com/movementlabsxyz/aptos-core", rev = "1c63a73c49efa9c4bb568e05eea2e1dda4e5197a" } +aptos-types = { git = "https://github.com/movementlabsxyz/aptos-core", rev = "1c63a73c49efa9c4bb568e05eea2e1dda4e5197a" } +aptos-vm = { git = "https://github.com/movementlabsxyz/aptos-core", rev = "1c63a73c49efa9c4bb568e05eea2e1dda4e5197a" } +aptos-vm-genesis = { git = "https://github.com/movementlabsxyz/aptos-core", rev = "1c63a73c49efa9c4bb568e05eea2e1dda4e5197a" } +aptos-vm-logging = { git = "https://github.com/movementlabsxyz/aptos-core", rev = "1c63a73c49efa9c4bb568e05eea2e1dda4e5197a" } +aptos-vm-validator = { git = "https://github.com/movementlabsxyz/aptos-core", rev = "1c63a73c49efa9c4bb568e05eea2e1dda4e5197a" } +aptos-logger = { git = "https://github.com/movementlabsxyz/aptos-core", rev = "1c63a73c49efa9c4bb568e05eea2e1dda4e5197a" } +aptos-vm-types = { git = "https://github.com/movementlabsxyz/aptos-core", rev = "1c63a73c49efa9c4bb568e05eea2e1dda4e5197a" } +aptos-indexer = { git = "https://github.com/movementlabsxyz/aptos-core", rev = "1c63a73c49efa9c4bb568e05eea2e1dda4e5197a" } +aptos-indexer-grpc-fullnode = { git = "https://github.com/movementlabsxyz/aptos-core", rev = "1c63a73c49efa9c4bb568e05eea2e1dda4e5197a" } +aptos-indexer-grpc-table-info = { git = "https://github.com/movementlabsxyz/aptos-core", rev = "1c63a73c49efa9c4bb568e05eea2e1dda4e5197a" } +aptos-protos = { git = "https://github.com/movementlabsxyz/aptos-core", rev = "1c63a73c49efa9c4bb568e05eea2e1dda4e5197a" } bcs = { git = "https://github.com/aptos-labs/bcs.git", rev = "d31fab9d81748e2594be5cd5cdf845786a30562d" } ethereum-types = "0.14.1" ethers = "=2.0.10" @@ -182,6 +182,7 @@ alloy = { git = "https://github.com/alloy-rs/alloy.git", package = "alloy", rev "node-bindings", "rpc-types-trace", "json-rpc", + "json-abi", "rpc-client", "signers", "signer-yubihsm", diff --git a/networks/suzuka/suzuka-config/src/execution_extension.rs b/networks/suzuka/suzuka-config/src/execution_extension.rs new file mode 100644 index 000000000..c80b26b31 --- /dev/null +++ b/networks/suzuka/suzuka-config/src/execution_extension.rs @@ -0,0 +1,33 @@ +use godfig::env_default; +use serde::{Deserialize, Serialize}; + +/// The execution extension configuration. +/// This covers Suzuka configurations that do not configure the Maptos executor, but do configure the way it is used. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Config { + /// The number of times to retry a block if it fails to execute. + #[serde(default = "default_block_retry_count")] + pub block_retry_count: u64, + + /// The amount by which to increment the block timestamp if it fails to execute. (This is the most common reason for a block to fail to execute.) + #[serde(default = "default_block_retry_increment_microseconds")] + pub block_retry_increment_microseconds: u64, +} + +impl Default for Config { + fn default() -> Self { + Self { + block_retry_count: default_block_retry_count(), + block_retry_increment_microseconds: default_block_retry_increment_microseconds(), + } + } +} + +env_default!(default_block_retry_count, "SUZUKA_BLOCK_RETRY_COUNT", u64, 10); + +env_default!( + default_block_retry_increment_microseconds, + "SUZUKA_BLOCK_RETRY_INCREMENT_MICROSECONDS", + u64, + 5000 +); diff --git a/networks/suzuka/suzuka-config/src/lib.rs b/networks/suzuka/suzuka-config/src/lib.rs index c5ef6117b..c3f0b6836 100644 --- a/networks/suzuka/suzuka-config/src/lib.rs +++ b/networks/suzuka/suzuka-config/src/lib.rs @@ -1,4 +1,5 @@ pub mod da_db; +pub mod execution_extension; use serde::{Deserialize, Serialize}; @@ -21,6 +22,9 @@ pub struct Config { #[serde(default)] pub da_db: da_db::Config, + + #[serde(default)] + pub execution_extension: execution_extension::Config, } impl Default for Config { @@ -30,6 +34,7 @@ impl Default for Config { m1_da_light_node: M1DaLightNodeConfig::default(), mcr: McrConfig::default(), da_db: da_db::Config::default(), + execution_extension: execution_extension::Config::default(), } } } diff --git a/networks/suzuka/suzuka-full-node/src/partial.rs b/networks/suzuka/suzuka-full-node/src/partial.rs index 51faa85e4..316a80cb2 100644 --- a/networks/suzuka/suzuka-full-node/src/partial.rs +++ b/networks/suzuka/suzuka-full-node/src/partial.rs @@ -221,9 +221,11 @@ where .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) @@ -252,18 +254,21 @@ where /// 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..5 { + 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 += 5000; // increase the timestamp by 5 ms (5000 microseconds) + block_timestamp += + self.config.execution_extension.block_retry_increment_microseconds; // increase the timestamp by 5 ms (5000 microseconds) } } } @@ -329,16 +334,21 @@ where match event { BlockCommitmentEvent::Accepted(commitment) => { debug!("Commitment accepted: {:?}", commitment); - match executor.set_finalized_block_height(commitment.height) { - Ok(_) => {} - Err(e) => { - error!("Failed to set finalized block height: {:?}", e); - } - } + 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); - // TODO: block reversion + 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))?; + } } } } diff --git a/protocol-units/bridge/chains/ethereum/Cargo.toml b/protocol-units/bridge/chains/ethereum/Cargo.toml index 52c9620bc..3cc8fa66f 100644 --- a/protocol-units/bridge/chains/ethereum/Cargo.toml +++ b/protocol-units/bridge/chains/ethereum/Cargo.toml @@ -23,6 +23,8 @@ 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 = [ 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..4c3b4ca79 --- /dev/null +++ b/protocol-units/bridge/chains/ethereum/src/client.rs @@ -0,0 +1,417 @@ +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, +}; +use bridge_shared::types::{ + Amount, 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, +}; + +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 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, + 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, + 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, + 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 async fn initialize_initiator_contract( + &self, + weth: EthAddress, + owner: EthAddress, + ) -> Result<(), anyhow::Error> { + let contract = self.initiator_contract().expect("Initiator contract not set"); + let call = contract.initialize(weth.0, owner.0); + send_transaction(call.to_owned(), &send_transaction_rules(), RETRIES, GAS_LIMIT) + .await + .expect("Failed to send transaction"); + 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 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, + time_lock: TimeLock, + amount: Amount, // the ETH 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(0), // For now a 0 WETH amount + FixedBytes(recipient_bytes), + FixedBytes(hash_lock.0), + U256::from(time_lock.0), + ) + .value(U256::from(amount.0)); + let _ = 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 + .expect("Failed to send transaction"); + 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 + .expect("Failed to send transaction"); + 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), + //@TODO unit test these wrapping to check for any nasty side effects. + time_lock: TimeLock(eth_details.time_lock.wrapping_to::()), + amount: Amount(eth_details.amount.wrapping_to::()), + })) + } +} + +#[async_trait::async_trait] +impl BridgeContractCounterparty for EthClient { + type Address = EthAddress; + type Hash = EthHash; + + async fn lock_bridge_transfer_assets( + &mut self, + bridge_transfer_id: BridgeTransferId, + hash_lock: HashLock, + time_lock: TimeLock, + 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.lockBridgeTransferAssets( + FixedBytes(initiator), + FixedBytes(bridge_transfer_id.0), + FixedBytes(hash_lock.0), + U256::from(time_lock.0), + recipient.0 .0, + U256::from(amount.0), + ); + send_transaction(call, &send_transaction_rules(), RETRIES, GAS_LIMIT) + .await + .expect("Failed to send transaction"); + 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 + .expect("Failed to send transaction"); + 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 + .expect("Failed to send transaction"); + 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), + //@TODO unit test these wrapping to check for any nasty side effects. + time_lock: TimeLock(eth_details.time_lock.wrapping_to::()), + amount: Amount(eth_details.amount.wrapping_to::()), + })) + } +} 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..44a61845c --- /dev/null +++ b/protocol-units/bridge/chains/ethereum/src/event_logging.rs @@ -0,0 +1,255 @@ +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 time_lock = decoded.indexed[5] + .as_uint() + .map(|(u, _)| u.into()) + .ok_or_else(|| anyhow::anyhow!("Failed to decode TimeLock"))?; + + 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), + time_lock, + amount, + }; + + 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 index 8ad9db81e..f2d3eb574 100644 --- a/protocol-units/bridge/chains/ethereum/src/lib.rs +++ b/protocol-units/bridge/chains/ethereum/src/lib.rs @@ -1,419 +1,233 @@ -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::sol_types::SolCall; -use alloy::{ - network::EthereumWallet, - rlp::{RlpDecodable, RlpEncodable}, +use bridge_shared::{ + counterparty_contract::SmartContractCounterparty, + initiator_contract::{InitiatorCall, SmartContractInitiator}, + types::{ + Amount, BridgeAddressType, BridgeHashType, GenUniqueHash, HashLockPreImage, + RecipientAddress, + }, }; -use alloy::{pubsub::PubSubFrontend, signers::local::PrivateKeySigner}; -use alloy_rlp::Decodable; -use bridge_shared::bridge_contracts::{ - BridgeContractCounterparty, BridgeContractCounterpartyError, BridgeContractCounterpartyResult, - BridgeContractInitiator, BridgeContractInitiatorError, BridgeContractInitiatorResult, +use event_types::EthChainEvent; +use futures::{channel::mpsc, task::AtomicWaker, Stream, StreamExt}; +use std::{ + collections::HashMap, + future::Future, + pin::Pin, + task::{Context, Poll}, }; -use bridge_shared::types::{ - Amount, BridgeTransferDetails, BridgeTransferId, HashLock, HashLockPreImage, InitiatorAddress, - RecipientAddress, TimeLock, -}; -use serde_with::serde_as; -use std::fmt::{self, Debug}; -use types::{CounterpartyContract, InitiatorContract}; -use url::Url; -use utils::send_transaction; +use types::CounterpartyCall; +use utils::RngSeededClone; +pub mod client; +mod event_logging; +mod event_types; pub mod types; -pub mod utils; - -use crate::types::{AtomicBridgeCounterparty, AtomicBridgeInitiator, EthAddress, EthHash}; +mod utils; -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) - } +pub enum SmartContractCall { + Initiator(), + Counterparty(CounterpartyCall), } -///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 gas_limit: u64, +/// A Bridge Transaction that can occur on any supported network. +#[derive(Debug)] +pub enum Transaction { + Initiator(InitiatorCall), + Counterparty(CounterpartyCall), } -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, - 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: types::AlloyProvider, - rpc_port: u16, - ws_provider: Option>, - initiator_contract: Option, - counterparty_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, - config, - }) - } +#[allow(unused)] +pub struct EthereumChain { + pub name: String, + pub time: u64, + pub accounts: HashMap, + pub events: Vec>, - pub fn set_initiator_contract(&mut self, contract: InitiatorContract) { - self.initiator_contract = Some(contract); - } + pub initiator_contract: SmartContractInitiator, + pub counterparty_contract: SmartContractCounterparty, - pub fn set_counterparty_contract(&mut self, contract: CounterpartyContract) { - self.counterparty_contract = Some(contract); - } + pub transaction_sender: mpsc::UnboundedSender>, + pub transaction_receiver: mpsc::UnboundedReceiver>, - pub async fn initialize_initiator_contract( - &self, - weth: EthAddress, - owner: EthAddress, - ) -> Result<(), anyhow::Error> { - let contract = self.initiator_contract().expect("Initiator contract not set"); - let call = contract.initialize(weth.0, owner.0); - send_transaction(call.to_owned(), &utils::send_tx_rules(), RETRIES, GAS_LIMIT) - .await - .expect("Failed to send transaction"); - Ok(()) - } + pub event_listeners: Vec>>, - 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)) - } + waker: AtomicWaker, - pub fn get_signer_address(&self) -> Address { - self.config.signer_private_key.address() - } + pub _phantom: std::marker::PhantomData, +} - pub fn set_signer_address(&mut self, key: SecretKey) { - self.config.signer_private_key = LocalSigner::from(key); +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 rpc_provider(&self) -> &types::AlloyProvider { - &self.rpc_provider + pub fn add_event_listener(&mut self) -> mpsc::UnboundedReceiver> { + let (sender, receiver) = mpsc::unbounded(); + self.event_listeners.push(sender); + receiver } - pub fn rpc_provider_mut(&mut self) -> &mut types::AlloyProvider { - &mut self.rpc_provider + pub fn add_account(&mut self, address: A, amount: Amount) { + self.accounts.insert(address, amount); } - pub fn rpc_port(&self) -> u16 { - self.rpc_port + pub fn get_balance(&mut self, address: &A) -> Option<&Amount> { + self.accounts.get(address) } - 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 connection(&self) -> mpsc::UnboundedSender> { + self.transaction_sender.clone() } +} - 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(), - )), +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 } +} - 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(), - )), +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, + time_lock, + hash_lock, + ) => { + this.events.push(EthChainEvent::InitiatorContractEvent( + this.initiator_contract.initiate_bridge_transfer( + initiator_address.clone(), + recipient_address.clone(), + amount, + time_lock.clone(), + 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, + time_lock, + initiator_address, + recipient_address, + amount, + ) => { + this.events.push(EthChainEvent::CounterpartyContractEvent( + this.counterparty_contract.lock_bridge_transfer( + bridge_transfer_id.clone(), + hash_lock.clone(), + time_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 + ); + } } - } - 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(), - )), - } - } + 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"); + } - 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(), - )), + tracing::trace!("AbstractBlockchain[{}]: Poll::Ready({:?})", this.name, event); + return Poll::Ready(Some(event)); } - } -} - -#[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, - time_lock: TimeLock, - amount: Amount, // the ETH 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(0), // For now a 0 WETH amount - FixedBytes(recipient_bytes), - FixedBytes(hash_lock.0), - U256::from(time_lock.0), - ) - .value(U256::from(amount.0)); - let _ = send_transaction(call, &utils::send_tx_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, &utils::send_tx_rules(), RETRIES, GAS_LIMIT) - .await - .expect("Failed to send transaction"); - 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, &utils::send_tx_rules(), RETRIES, GAS_LIMIT) - .await - .expect("Failed to send transaction"); - 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 = utils::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), - //@TODO unit test these wrapping to check for any nasty side effects. - time_lock: TimeLock(eth_details.time_lock.wrapping_to::()), - amount: Amount(eth_details.amount.wrapping_to::()), - })) - } -} - -#[async_trait::async_trait] -impl BridgeContractCounterparty for EthClient { - type Address = EthAddress; - type Hash = EthHash; - - async fn lock_bridge_transfer_assets( - &mut self, - bridge_transfer_id: BridgeTransferId, - hash_lock: HashLock, - time_lock: TimeLock, - 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.lockBridgeTransferAssets( - FixedBytes(initiator), - FixedBytes(bridge_transfer_id.0), - FixedBytes(hash_lock.0), - U256::from(time_lock.0), - recipient.0 .0, - U256::from(amount.0), - ); - send_transaction(call, &utils::send_tx_rules(), RETRIES, GAS_LIMIT) - .await - .expect("Failed to send transaction"); - 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, &utils::send_tx_rules(), RETRIES, GAS_LIMIT) - .await - .expect("Failed to send transaction"); - 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, &utils::send_tx_rules(), RETRIES, GAS_LIMIT) - .await - .expect("Failed to send transaction"); - 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 = utils::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), - //@TODO unit test these wrapping to check for any nasty side effects. - time_lock: TimeLock(eth_details.time_lock.wrapping_to::()), - amount: Amount(eth_details.amount.wrapping_to::()), - })) + 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 index 1e11c8b1d..d7a434aa8 100644 --- a/protocol-units/bridge/chains/ethereum/src/types.rs +++ b/protocol-units/bridge/chains/ethereum/src/types.rs @@ -1,14 +1,25 @@ +use alloy::json_abi::Param; use alloy::network::{Ethereum, EthereumWallet}; -use alloy::primitives::Address; +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}; -use crate::AtomicBridgeInitiator::AtomicBridgeInitiatorInstance; +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!( @@ -27,8 +38,10 @@ alloy::sol!( pub type EthHash = [u8; 32]; -pub type InitiatorContract = AtomicBridgeInitiatorInstance; -pub type CounterpartyContract = AtomicBridgeInitiatorInstance; +pub type InitiatorContract = + AtomicBridgeInitiator::AtomicBridgeInitiatorInstance; +pub type CounterpartyContract = + AtomicBridgeCounterparty::AtomicBridgeCounterpartyInstance; pub type AlloyProvider = FillProvider< JoinFill< @@ -78,3 +91,159 @@ impl From<[u8; 32]> for EthAddress { 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, + TimeLock, + InitiatorAddress>, + RecipientAddress, + Amount, + ), +} diff --git a/protocol-units/bridge/chains/ethereum/src/utils.rs b/protocol-units/bridge/chains/ethereum/src/utils.rs index 34d76333a..114957067 100644 --- a/protocol-units/bridge/chains/ethereum/src/utils.rs +++ b/protocol-units/bridge/chains/ethereum/src/utils.rs @@ -6,7 +6,7 @@ use alloy::network::Ethereum; use alloy::primitives::U256; use alloy::providers::Provider; use alloy::rlp::{Encodable, RlpEncodable}; -use alloy::rpc::types::{Log, TransactionReceipt}; +use alloy::rpc::types::TransactionReceipt; use alloy::transports::Transport; use keccak_hash::keccak; use mcr_settlement_client::send_eth_transaction::{ @@ -14,6 +14,28 @@ use mcr_settlement_client::send_eth_transaction::{ }; 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")] @@ -61,7 +83,7 @@ pub fn calculate_storage_slot(key: [u8; 32], mapping_slot: U256) -> U256 { U256::from_be_slice(&hash.0) } -pub(crate) fn send_tx_rules() -> Vec> { +pub(crate) fn send_transaction_rules() -> Vec> { let rule1: Box = Box::new(SendTransactionErrorRule::::new()); let rule2: Box = Box::new(SendTransactionErrorRule::::new()); vec![rule1, rule2] @@ -153,19 +175,3 @@ pub async fn send_transaction< ) .into()) } - -pub async fn decode_bridge_transfer_id(log: &Log) -> Result<[u8; 32], anyhow::Error> { - let log_data = log.data(); // Access the data via the `inner` field - - if log_data.data.len() != 32 { - return Err(anyhow::anyhow!("Log data is not the correct length: expected 32 bytes")); - } - - let bridge_transfer_id: [u8; 32] = log_data - .data - .as_ref() - .try_into() - .map_err(|_| anyhow::anyhow!("Failed to convert log data to [u8; 32]"))?; - - Ok(bridge_transfer_id) -} diff --git a/protocol-units/bridge/chains/movement/Cargo.toml b/protocol-units/bridge/chains/movement/Cargo.toml index 0da5d0a48..cbdb099ac 100644 --- a/protocol-units/bridge/chains/movement/Cargo.toml +++ b/protocol-units/bridge/chains/movement/Cargo.toml @@ -34,7 +34,5 @@ rand = { workspace = true } url = { workspace = true } once_cell = { workspace = true } -dot-movement = { workspace = true } -suzuka-config = { 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 index f36262c85..e80875bc7 100644 --- a/protocol-units/bridge/chains/movement/src/lib.rs +++ b/protocol-units/bridge/chains/movement/src/lib.rs @@ -1,5 +1,12 @@ use aptos_api::accounts::Account; use aptos_sdk::{move_types::language_storage::TypeTag, rest_client::Client, types::LocalAccount}; +use crate::utils::MovementAddress; +use anyhow::Result; +use aptos_sdk::{ + move_types::language_storage::TypeTag, + rest_client::{Client, FaucetClient}, + types::LocalAccount, +}; use aptos_types::account_address::AccountAddress; use bridge_shared::{ @@ -15,17 +22,24 @@ use bridge_shared::{ use utils::send_view_request; use rand::prelude::*; use serde::Serialize; +use std::process::Stdio; use std::str::FromStr; -use std::sync::{Arc, Mutex}; -use url::Url; +use std::sync::{Arc, RwLock}; +use tokio::{ + io::{AsyncBufReadExt, BufReader}, + process::Command as TokioCommand, + sync::oneshot, + task, +}; -use crate::utils::MovementAddress; +use url::Url; 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, @@ -35,20 +49,25 @@ enum Call { #[derive(Clone, Debug)] pub struct Config { - pub rpc_url: Url, - pub ws_url: Option, - pub signer_private_key: Option, - pub initiator_contract: Option, - pub counterparty_contract: Option, + pub rpc_url: Option, + pub ws_url: Option, + pub chain_id: String, + pub signer_private_key: Arc>, + pub initiator_contract: Option, + pub counterparty_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: "http://localhost:30731".parse().unwrap(), - ws_url: Some(Url::parse("ws://localhost:30731").unwrap()), - signer_private_key: None, + 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, counterparty_contract: None, gas_limit: 10_000_000_000, @@ -64,27 +83,16 @@ pub struct MovementClient { ///Address of the initiator module initiator_address: AccountAddress, ///The Apotos Rest Client - rest_client: Client, + pub rest_client: Client, + ///The Apotos Rest Client + pub faucet_client: Option>>, ///The signer account signer: Arc, - config: Config, } impl MovementClient { pub async fn new(config: impl Into) -> Result { - let dot_movement = dot_movement::DotMovement::try_from_env().unwrap(); - let suzuka_config = - dot_movement.try_get_config_from_json::().unwrap(); - let node_connection_address = suzuka_config - .execution_config - .maptos_config - .client - .maptos_rest_connection_hostname; - let node_connection_port = - suzuka_config.execution_config.maptos_config.client.maptos_rest_connection_port; - - let node_connection_url = - format!("http://{}:{}", node_connection_address, node_connection_port); + let node_connection_url = "http://127.0.0.1:8080".to_string(); let node_connection_url = Url::from_str(node_connection_url.as_str()).unwrap(); let rest_client = Client::new(node_connection_url.clone()); @@ -99,10 +107,10 @@ impl MovementClient { let initiator_address = AccountAddress::new(initiator_address_array); Ok(MovementClient { initiator_address, - rest_client, counterparty_address: DUMMY_ADDRESS, + rest_client, + faucet_client: None, signer: Arc::new(signer), - config: config.into(), }) } @@ -113,6 +121,105 @@ impl MovementClient { pub async fn get_block_number(&self) -> Result { Ok(0) } + pub async fn new_for_test( + _config: Config, + ) -> Result<(Self, tokio::process::Child), anyhow::Error> { + let (setup_complete_tx, setup_complete_rx) = oneshot::channel(); + let mut child = TokioCommand::new("aptos") + .args(["node", "run-local-testnet"]) + .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()).unwrap(); + 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()).unwrap(); + 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]); + + let initiator_address = AccountAddress::new([0; 32]); + Ok(( + MovementClient { + counterparty_address: DUMMY_ADDRESS, + initiator_address, + rest_client, + faucet_client: Some(faucet_client), + signer: Arc::new(LocalAccount::generate(&mut rng)), + }, + child, + )) + } + + pub fn rest_client(&self) -> &Client { + &self.rest_client + } + + 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] @@ -145,9 +252,13 @@ impl BridgeContractCounterparty for MovementClient { self.counterparty_type_args(Call::Lock), args, ); - let _ = utils::send_aptos_transaction(&self.rest_client, self.signer.as_ref(), payload) - .await - .map_err(|_| BridgeContractCounterpartyError::LockTransferAssetsError); + let _ = utils::send_aptos_transaction( + &self.rest_client, + self.signer.as_ref(), + payload, + ) + .await + .map_err(|_| BridgeContractCounterpartyError::LockTransferAssetsError); Ok(()) } @@ -169,9 +280,13 @@ impl BridgeContractCounterparty for MovementClient { args, ); - let _ = utils::send_aptos_transaction(&self.rest_client, self.signer.as_ref(), payload) - .await - .map_err(|_| BridgeContractCounterpartyError::CompleteTransferError); + let _ = utils::send_aptos_transaction( + &self.rest_client, + self.signer.as_ref(), + payload, + ) + .await + .map_err(|_| BridgeContractCounterpartyError::CompleteTransferError); Ok(()) } @@ -190,9 +305,13 @@ impl BridgeContractCounterparty for MovementClient { self.counterparty_type_args(Call::Abort), args, ); - let _ = utils::send_aptos_transaction(&self.rest_client, self.signer.as_ref(), payload) - .await - .map_err(|_| BridgeContractCounterpartyError::AbortTransferError); + let _ = utils::send_aptos_transaction( + &self.rest_client, + self.signer.as_ref(), + payload, + ) + .await + .map_err(|_| BridgeContractCounterpartyError::AbortTransferError); Ok(()) } @@ -290,7 +409,8 @@ impl BridgeContractInitiator for MovementClient { bridge_transfer_id: BridgeTransferId, ) -> BridgeContractInitiatorResult>> { - let response = self.rest_client.view + todo!(); + // let response = self.rest_client.view } } diff --git a/protocol-units/bridge/cli/src/eth_to_moveth.rs b/protocol-units/bridge/cli/src/eth_to_moveth.rs index 1eafbf505..e737c048e 100644 --- a/protocol-units/bridge/cli/src/eth_to_moveth.rs +++ b/protocol-units/bridge/cli/src/eth_to_moveth.rs @@ -1,9 +1,11 @@ use crate::clap::eth_to_movement::{Commands, EthSharedArgs, MoveSharedArgs}; use alloy::primitives::keccak256; use anyhow::Result; -use bridge_shared::types::{Amount, HashLock, HashLockPreImage, RecipientAddress, TimeLock}; -use bridge_shared::{types::InitiatorAddress}; -use ethereum_bridge::{types::EthAddress, EthClient}; +use bridge_shared::bridge_contracts::BridgeContractInitiator; +use bridge_shared::types::{ + Amount, HashLock, HashLockPreImage, InitiatorAddress, RecipientAddress, TimeLock, +}; +use ethereum_bridge::{client::EthClient, types::EthAddress}; use movement_bridge::utils::MovementAddress; use movement_bridge::MovementClient; use bridge_shared::bridge_contracts::BridgeContractInitiator; diff --git a/protocol-units/bridge/cli/src/types.rs b/protocol-units/bridge/cli/src/types.rs index a4e02f734..2aac96a08 100644 --- a/protocol-units/bridge/cli/src/types.rs +++ b/protocol-units/bridge/cli/src/types.rs @@ -26,6 +26,7 @@ impl From for MovementConfig { Self { rpc_url: args.move_rpc_url, ws_url: Some(args.move_ws_url), + chain_id: args.move_chain_id, signer_private_key: Some(args.move_signer_private_key), initiator_contract: Some(args.move_initiator_contract.0), counterparty_contract: Some(args.move_counterparty_contract.0), diff --git a/protocol-units/bridge/contracts/src/AtomicBridgeInitiator.sol b/protocol-units/bridge/contracts/src/AtomicBridgeInitiator.sol index 9476ec373..202c3432e 100644 --- a/protocol-units/bridge/contracts/src/AtomicBridgeInitiator.sol +++ b/protocol-units/bridge/contracts/src/AtomicBridgeInitiator.sol @@ -66,6 +66,8 @@ contract AtomicBridgeInitiator is IAtomicBridgeInitiator, OwnableUpgradeable { // Update the pool balance poolBalance += totalAmount; + // The nonce is used to generate a unique bridge transfer id, without it + // we can't guarantee the uniqueness of the id. nonce++; // increment the nonce bridgeTransferId = keccak256(abi.encodePacked(originator, recipient, hashLock, timeLock, block.number, nonce)); diff --git a/protocol-units/bridge/integration-tests/Cargo.toml b/protocol-units/bridge/integration-tests/Cargo.toml index 81dc54c04..e2a00642c 100644 --- a/protocol-units/bridge/integration-tests/Cargo.toml +++ b/protocol-units/bridge/integration-tests/Cargo.toml @@ -12,6 +12,9 @@ version = { workspace = true } path = "src/lib.rs" [dependencies] +aptos-language-e2e-tests = { workspace = true } +aptos-logger = { workspace = true } +aptos-types = { workspace = true } aptos-sdk = { workspace = true } anyhow = { workspace = true } alloy = { workspace = true } @@ -21,10 +24,11 @@ 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 } +tokio = { 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 index 76b327562..f29882b90 100644 --- a/protocol-units/bridge/integration-tests/src/lib.rs +++ b/protocol-units/bridge/integration-tests/src/lib.rs @@ -1,5 +1,4 @@ use alloy::{ - node_bindings::Anvil, primitives::Address, providers::WalletProvider, signers::{ @@ -9,13 +8,15 @@ use alloy::{ }; use alloy_network::{Ethereum, EthereumWallet, NetworkWallet}; use anyhow::Result; +use aptos_sdk::rest_client::{Client, FaucetClient}; use aptos_sdk::types::LocalAccount; use ethereum_bridge::{ + client::{Config as EthConfig, EthClient}, types::{AlloyProvider, AtomicBridgeInitiator, EthAddress}, - Config as EthConfig, EthClient, }; -use movement_bridge::MovementClient; +use movement_bridge::{Config as MovementConfig, MovementClient}; use rand::SeedableRng; +use std::sync::{Arc, RwLock}; alloy::sol!( #[allow(missing_docs)] @@ -29,6 +30,37 @@ pub struct TestHarness { } 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 diff --git a/protocol-units/bridge/integration-tests/tests/eth_movement.rs b/protocol-units/bridge/integration-tests/tests/eth_movement.rs index 5bdac7110..a38e03919 100644 --- a/protocol-units/bridge/integration-tests/tests/eth_movement.rs +++ b/protocol-units/bridge/integration-tests/tests/eth_movement.rs @@ -11,13 +11,13 @@ use bridge_shared::{ use ethereum_bridge::types::EthAddress; #[tokio::test] -async fn test_client_should_build_and_fetch_accounts() { +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 = vec![ + let expected_accounts = [ address!("f39fd6e51aad88f6f4ce6ab8827279cfffb92266"), address!("70997970c51812dc3a010c7d01b50e0d17dc79c8"), address!("3c44cdddb6a900fa2b585dd299e03d12fa4293bc"), @@ -87,63 +87,3 @@ async fn test_client_should_successfully_call_initiate_transfer() { .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), - TimeLock(100), - Amount(1000), // 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_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(); - - let _ = harness - .eth_client_mut() - .expect("Failed to get EthClient") - .initiate_bridge_transfer( - InitiatorAddress(EthAddress(signer_address)), - RecipientAddress(recipient_bytes), - HashLock(hash_lock), - TimeLock(1000), - Amount(42), - ) - .await - .expect("Failed to initiate bridge transfer"); - - //TODO: Here call complete with the id captured from the event -} diff --git a/protocol-units/bridge/move-modules/Move.toml b/protocol-units/bridge/move-modules/Move.toml index b032772c6..0b6be281e 100644 --- a/protocol-units/bridge/move-modules/Move.toml +++ b/protocol-units/bridge/move-modules/Move.toml @@ -4,11 +4,14 @@ version = "1.0.0" authors = [] [addresses] -atomic_bridge = "0xc8d39501a74b964b169845e1fe733fae91890437d3e0af6328987b668e97ad8a" -moveth = "0xc8d39501a74b964b169845e1fe733fae91890437d3e0af6328987b668e97ad8a" -master_minter = "0xc8d39501a74b964b169845e1fe733fae91890437d3e0af6328987b668e97ad8a" -minter = "0xc8d39501a74b964b169845e1fe733fae91890437d3e0af6328987b668e97ad8a" -admin = "0xc8d39501a74b964b169845e1fe733fae91890437d3e0af6328987b668e97ad8a" +resource_addr = "0xc3bb8488ab1a5815a9d543d7e41b0e0df46a7396f89b22821f07a4362f75ddc5" +origin_addr = "0xcafe" +atomic_bridge = "0xc3bb8488ab1a5815a9d543d7e41b0e0df46a7396f89b22821f07a4362f75ddc5" +moveth = "0xc3bb8488ab1a5815a9d543d7e41b0e0df46a7396f89b22821f07a4362f75ddc5" +master_minter = "0xc3bb8488ab1a5815a9d543d7e41b0e0df46a7396f89b22821f07a4362f75ddc5" +minter = "0xc3bb8488ab1a5815a9d543d7e41b0e0df46a7396f89b22821f07a4362f75ddc5" +admin = "0xc3bb8488ab1a5815a9d543d7e41b0e0df46a7396f89b22821f07a4362f75ddc5" +source_account = "0xc3bb8488ab1a5815a9d543d7e41b0e0df46a7396f89b22821f07a4362f75ddc5" pauser = "0xdafe" denylister = "0xcade" diff --git a/protocol-units/bridge/move-modules/README.md b/protocol-units/bridge/move-modules/README.md index f6163c1b1..6807ed21b 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. \ No newline at end of file diff --git a/protocol-units/bridge/move-modules/sources/MOVETH.move b/protocol-units/bridge/move-modules/sources/MOVETH.move index 223144a55..9ad88ae27 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}; @@ -101,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(), @@ -121,11 +122,11 @@ module moveth::moveth { let metadata_object_signer = &object::generate_signer(constructor_ref); let minters = vector::empty
(); - vector::push_back(&mut minters, @minter); + vector::push_back(&mut minters, @resource_addr); move_to(metadata_object_signer, Roles { master_minter: @master_minter, - admin: @admin, + admin: signer::address_of(resource_account), minters, pauser: @pauser, denylister: @denylister, @@ -147,12 +148,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"), ); @@ -312,29 +313,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.admin || signer::address_of(admin) == roles.master_minter, EUNAUTHORIZED); - assert!(!vector::contains(&roles.minters, &minter), EALREADY_MINTER); - vector::push_back(&mut roles.minters, minter); - } - - /// Remove the minter at the end of roles.minters. This checks that the caller is the master minter and the account is the most recently added minter. - public entry fun remove_minter(admin: &signer, minter: address) acquires Roles, State { - assert_not_paused(); - let roles = borrow_global_mut(moveth_address()); - assert!(signer::address_of(admin) == roles.admin || signer::address_of(admin) == roles.master_minter, EUNAUTHORIZED); - assert!(vector::contains(&roles.minters, &minter), ENOT_MINTER); - vector::pop_back(&mut roles.minters); - } - 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_counterparty.move b/protocol-units/bridge/move-modules/sources/atomic_bridge_counterparty.move index 83ed50ca1..759cfe6db 100644 --- a/protocol-units/bridge/move-modules/sources/atomic_bridge_counterparty.move +++ b/protocol-units/bridge/move-modules/sources/atomic_bridge_counterparty.move @@ -2,6 +2,10 @@ module atomic_bridge::atomic_bridge_counterparty { use std::signer; use std::event; use std::vector; + use aptos_framework::account; + #[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}; @@ -25,6 +29,7 @@ module atomic_bridge::atomic_bridge_counterparty { struct BridgeConfig has key { moveth_minter: address, bridge_module_deployer: address, + signer_cap: account::SignerCapability, } #[event] @@ -50,18 +55,22 @@ module atomic_bridge::atomic_bridge_counterparty { bridge_transfer_id: vector, } - entry fun init_module(deployer: &signer) { + entry fun init_module(resource: &signer) { + + let resource_signer_cap = resource_account::retrieve_resource_account_cap(resource, @origin_addr); + 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), + moveth_minter: signer::address_of(resource), + bridge_module_deployer: signer::address_of(resource), + signer_cap: resource_signer_cap }; - move_to(deployer, bridge_transfer_store); - move_to(deployer, bridge_config); + move_to(resource, bridge_transfer_store); + move_to(resource, bridge_config); } public fun lock_bridge_transfer_assets( @@ -73,7 +82,8 @@ module atomic_bridge::atomic_bridge_counterparty { recipient: address, amount: u64 ): bool acquires BridgeTransferStore { - let bridge_store = borrow_global_mut(signer::address_of(caller)); + assert!(signer::address_of(caller) == @origin_addr, 1); + let bridge_store = borrow_global_mut(@resource_addr); let details = BridgeTransferDetails { recipient, initiator, @@ -100,23 +110,16 @@ module atomic_bridge::atomic_bridge_counterparty { caller: &signer, bridge_transfer_id: vector, pre_image: vector, - master_minter: &signer, - ) acquires BridgeTransferStore, BridgeConfig { - let config_address = borrow_global(@atomic_bridge).bridge_module_deployer; + ) 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 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); - // Make caller a minter of MovETH - moveth::add_minter(master_minter, signer::address_of(caller)); - - // Mint moveth tokens to the recipient - moveth::mint(caller, details.recipient, details.amount); - - // Remove caller from the minter list, now that minting is complete - moveth::remove_minter(master_minter, signer::address_of(caller)); + moveth::mint(&resource_signer, details.recipient, details.amount); smart_table::add(&mut bridge_store.completed_transfers, bridge_transfer_id, details); event::emit( @@ -146,22 +149,21 @@ module atomic_bridge::atomic_bridge_counterparty { }, ); } - - #[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); + #[test_only] + public fun set_up_test(origin_account: &signer, resource_addr: &signer) { + + create_account_for_test(signer::address_of(origin_account)); - // 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)); + // 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); + } - assert!(bridge_config.moveth_minter == signer::address_of(creator), 1); - assert!(bridge_config.bridge_module_deployer == owner, 2); + #[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; @@ -169,31 +171,33 @@ module atomic_bridge::atomic_bridge_counterparty { use aptos_framework::create_signer::create_signer; use aptos_framework::primary_fungible_store; - #[test(aptos_framework = @0x1, creator = @atomic_bridge, moveth = @moveth, admin = @admin, client = @0xdca, master_minter = @master_minter)] - fun test_complete_transfer_assets_non_minter( + #[test(origin_account = @origin_addr, resource_addr = @resource_addr, aptos_framework = @0x1, creator = @atomic_bridge, source_account = @source_account, 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, + aptos_framework: signer, master_minter: &signer, creator: &signer, moveth: &signer, + source_account: &signer ) acquires BridgeTransferStore, BridgeConfig { - timestamp::set_time_has_started_for_testing(aptos_framework); + set_up_test(origin_account, &resource_addr); + + timestamp::set_time_has_started_for_testing(&aptos_framework); moveth::init_for_test(moveth); - let receiver_address = @0xcafe1; + let receiver_address = @0xdada; let initiator = b"0x123"; //In real world this would be an ethereum address let recipient = @0xface; let asset = moveth::metadata(); - - 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 hash_lock = keccak256(pre_image); let time_lock = 3600; let amount = 100; - let result = lock_bridge_transfer_assets( - creator, + origin_account, initiator, bridge_transfer_id, hash_lock, @@ -201,99 +205,26 @@ module atomic_bridge::atomic_bridge_counterparty { recipient, amount ); - assert!(result, 1); - // Verify that the transfer is stored in pending_transfers - let bridge_store = borrow_global(signer::address_of(creator)); + let bridge_store = borrow_global(signer::address_of(&resource_addr)); 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"; + let pre_image = b"secret"; + let msg:vector = b"secret"; debug::print(&utf8(msg)); - - complete_bridge_transfer( - client, - bridge_transfer_id, - pre_image, - master_minter - ); - - 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); - } - - #[test(aptos_framework = @0x1, creator = @atomic_bridge, moveth = @moveth, admin = @admin, client = @minter, master_minter = @master_minter)] - #[expected_failure] - fun test_complete_transfer_assets_minter( - client: &signer, - aptos_framework: &signer, - master_minter: &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(); - - 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, + complete_bridge_transfer( + client, bridge_transfer_id, - hash_lock, - time_lock, - recipient, - amount + pre_image, ); - - 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)); - - complete_bridge_transfer( - client, - bridge_transfer_id, - pre_image, - master_minter - ); - - 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); + let bridge_store = borrow_global(signer::address_of(&resource_addr)); + 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); 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 925b8b160..34537d6af 100644 --- a/protocol-units/bridge/move-modules/sources/tests/MOVETH_tests.move +++ b/protocol-units/bridge/move-modules/sources/tests/MOVETH_tests.move @@ -12,9 +12,7 @@ module moveth::moveth_tests{ let receiver_address = @0xcafe1; let minter_address = signer::address_of(minter); - // set minter and have minter call mint, check balance - moveth::add_minter(admin, 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, admin = @admin, master_minter = @0xbab)] - #[expected_failure(abort_code = 2, location = moveth::moveth)] - fun test_pause(creator: &signer, pauser: &signer, minter: &signer, admin: &signer, master_minter: &signer) { - moveth::init_for_test(creator); - let minter_address = signer::address_of(minter); - moveth::set_pause(pauser, true); - moveth::add_minter(admin, 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 e4833b65e..87b3b01ba 100644 --- a/protocol-units/bridge/shared/Cargo.toml +++ b/protocol-units/bridge/shared/Cargo.toml @@ -23,7 +23,15 @@ 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/src/counterparty_contract.rs b/protocol-units/bridge/shared/src/counterparty_contract.rs new file mode 100644 index 000000000..a00cacd1f --- /dev/null +++ b/protocol-units/bridge/shared/src/counterparty_contract.rs @@ -0,0 +1,123 @@ +use std::collections::HashMap; + +use crate::types::{ + Amount, BridgeAddressType, BridgeHashType, BridgeTransferId, CounterpartyCompletedDetails, + GenUniqueHash, HashLock, HashLockPreImage, InitiatorAddress, LockDetails, RecipientAddress, + TimeLock, +}; +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, + time_lock: TimeLock, + 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(), + time_lock: time_lock.clone(), + amount, + }, + ); + + Ok(SmartContractCounterpartyEvent::LockedBridgeTransfer(LockDetails { + bridge_transfer_id, + initiator_address, + recipient_address, + hash_lock, + time_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(0)); + **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..192a78ba7 --- /dev/null +++ b/protocol-units/bridge/shared/src/initiator_contract.rs @@ -0,0 +1,153 @@ +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, TimeLock, + }, +}; + +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, + TimeLock, + 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, + time_lock: TimeLock, + 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(), + time_lock: time_lock.clone(), + amount, + }, + ); + + Ok(SmartContractInitiatorEvent::InitiatedBridgeTransfer(BridgeTransferDetails { + bridge_transfer_id, + initiator_address: initiator, + recipient_address: recipient, + hash_lock, + time_lock, + amount, + })) + } + + 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 dc790b844..31ea5b40d 100644 --- a/protocol-units/bridge/shared/src/lib.rs +++ b/protocol-units/bridge/shared/src/lib.rs @@ -2,6 +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/types.rs b/protocol-units/bridge/shared/src/types.rs index eb828762f..31329cd07 100644 --- a/protocol-units/bridge/shared/src/types.rs +++ b/protocol-units/bridge/shared/src/types.rs @@ -1,3 +1,4 @@ +use alloy::primitives::Uint; use derive_more::{Deref, DerefMut}; use hex::{self, FromHexError}; use rand::{Rng, RngCore}; @@ -6,6 +7,12 @@ use std::{fmt::Debug, hash::Hash}; #[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)?; @@ -78,6 +85,12 @@ 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)?; @@ -113,9 +126,25 @@ impl HashLockPreImage { #[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); +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(lower_64_bits) + } +} + #[derive(Debug, PartialEq, Eq, Clone)] pub struct BridgeTransferDetails { pub bridge_transfer_id: BridgeTransferId, diff --git a/protocol-units/execution/dof/src/lib.rs b/protocol-units/execution/dof/src/lib.rs index a0bc09b12..7cc1e922f 100644 --- a/protocol-units/execution/dof/src/lib.rs +++ b/protocol-units/execution/dof/src/lib.rs @@ -32,6 +32,9 @@ pub trait DynOptFinExecutor { /// Update the height of the latest finalized block 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); diff --git a/protocol-units/execution/dof/src/v1.rs b/protocol-units/execution/dof/src/v1.rs index ecb61a875..034e11d9f 100644 --- a/protocol-units/execution/dof/src/v1.rs +++ b/protocol-units/execution/dof/src/v1.rs @@ -1,20 +1,22 @@ use crate::{BlockMetadata, DynOptFinExecutor, ExecutableBlock, HashValue, SignedTransaction}; use aptos_api::runtime::Apis; -use aptos_config::config::NodeConfig; use aptos_mempool::core_mempool::CoreMempool; -use async_channel::Sender; -use async_trait::async_trait; use maptos_fin_view::FinalityView; use maptos_opt_executor::transaction_pipe::TransactionPipeError; use maptos_opt_executor::Executor as OptExecutor; use movement_types::BlockCommitment; -use std::sync::atomic::Ordering; + +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 std::sync::atomic::Ordering; + #[derive(Clone)] pub struct Executor { pub executor: OptExecutor, @@ -96,6 +98,17 @@ impl DynOptFinExecutor for Executor { self.finality_view.set_finalized_block_height(height) } + 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!( + "Can't revert to height {block_height} preciding the finalized height {final_height}" + )); + } + } + 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; diff --git a/protocol-units/execution/fin-view/src/fin_view.rs b/protocol-units/execution/fin-view/src/fin_view.rs index 5b543ab11..eb139e7ab 100644 --- a/protocol-units/execution/fin-view/src/fin_view.rs +++ b/protocol-units/execution/fin-view/src/fin_view.rs @@ -45,6 +45,14 @@ impl FinalityView { Ok(Self::new(inner, context, listen_url)) } + /// Retrieve the finalized block height. + /// + /// If the height was never updated by [`set_finalized_block_height`], + /// this method returns `None`. + pub fn finalized_block_height(&self) -> Option { + self.inner.finalized_block_height() + } + /// Update the finalized view with the latest block height. /// /// The block must be found on the committed chain. diff --git a/protocol-units/execution/opt-executor/src/executor/execution.rs b/protocol-units/execution/opt-executor/src/executor/execution.rs index 6a2531ed8..58b3c09b0 100644 --- a/protocol-units/execution/opt-executor/src/executor/execution.rs +++ b/protocol-units/execution/opt-executor/src/executor/execution.rs @@ -61,12 +61,10 @@ impl Executor { (block_metadata, block, senders_and_sequence_numbers) }; - let block_executor = self.block_executor.clone(); - let block_id = block.block_id.clone(); - let parent_block_id = block_executor.committed_block_id(); + let parent_block_id = self.block_executor.committed_block_id(); - let block_executor_clone = block_executor.clone(); + let block_executor_clone = self.block_executor.clone(); let state_compute = tokio::task::spawn_blocking(move || { block_executor_clone.execute_block( block, @@ -90,7 +88,7 @@ impl Executor { state_compute.root_hash(), version, ); - let block_executor_clone = block_executor.clone(); + let block_executor_clone = self.block_executor.clone(); tokio::task::spawn_blocking(move || { block_executor_clone.commit_blocks(vec![block_id], ledger_info_with_sigs) }) @@ -119,6 +117,27 @@ impl Executor { Ok(ledger_info.block_height.into()) } + pub 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)?; + let block_info = BlockInfo::new( + block_event.epoch(), + block_event.round(), + block_event.hash()?, + self.db.reader.get_accumulator_root_hash(end_ver)?, + end_ver, + block_event.proposed_time(), + None, + ); + 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)?; + // Reset the executor state to the reverted storage + self.block_executor.reset()?; + Ok(()) + } + pub fn context(&self) -> Arc { self.context.clone() } diff --git a/protocol-units/settlement/mcr/contracts/src/staking/MovementStaking.sol b/protocol-units/settlement/mcr/contracts/src/staking/MovementStaking.sol index 8f7405fa0..5251ba8b8 100644 --- a/protocol-units/settlement/mcr/contracts/src/staking/MovementStaking.sol +++ b/protocol-units/settlement/mcr/contracts/src/staking/MovementStaking.sol @@ -91,11 +91,11 @@ contract MovementStaking is } function setGenesisCeremony( - address domain, address[] calldata custodians, address[] calldata attesters, uint256[] calldata stakes ) public { + address domain = msg.sender; currentEpochByDomain[domain] = getEpochByBlockTime(domain); for (uint256 i = 0; i < attesters.length; i++) { @@ -121,7 +121,7 @@ contract MovementStaking is // transfer the outstanding stake back to the attester uint256 refundAmount = stakes[i] - attesterStake; - _payAttester(attesters[i], custodian, refundAmount); + _payAttester(address(this), attesters[i], custodian, refundAmount); } } @@ -268,7 +268,11 @@ contract MovementStaking is } // stakes for the next epoch - function stake(address domain, IERC20 custodian, uint256 amount) external onlyRole(WHITELIST_ROLE) { + function stake( + address domain, + IERC20 custodian, + uint256 amount + ) external onlyRole(WHITELIST_ROLE) { // add the attester to the list of attesters attestersByDomain[domain].add(msg.sender); @@ -364,7 +368,7 @@ contract MovementStaking is // note: this is the only place this takes place // there's not risk of double payout, so long as rollOverattester 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 - _payAttester(attester, custodian, unstakeAmount); + _payAttester(address(this), attester, custodian, unstakeAmount); emit AttesterEpochRolledOver( attester, @@ -496,7 +500,12 @@ contract MovementStaking is ), Math.min(amounts[i], refundAmounts[i]) ); - _payAttester(attesters[i], custodians[i], refundAmount); + _payAttester( + address(this), // this contract is paying the attester, it should always have enough balance + attesters[i], + custodians[i], + refundAmount + ); // slash both stake and unstake so that the weight of the attester is reduced and they can't withdraw the unstake at the next epoch _slashStake( @@ -517,22 +526,39 @@ contract MovementStaking is } function _payAttester( + address from, address attester, address custodian, uint256 amount ) internal { - if (address(token) == custodian) { - // if there isn't a custodian - - token.transfer(attester, amount); // just transfer the token + if (from == address(this)) { + // this contract is paying the attester + if (address(token) == custodian) { + // if there isn't a custodian... + token.transfer(attester, amount); // just transfer the token + } else { + // approve the custodian to spend the base token + token.approve(custodian, amount); + + // purchase the custodial token for the attester + ICustodianToken(custodian).buyCustodialToken(attester, amount); + } } else { - // if there is a custodian - - // approve the custodian to spend the base token - token.approve(custodian, amount); - - // purchase the custodial token for the attester - ICustodianToken(custodian).buyCustodialTokenFor(attester, amount); + // This can be used by the domain to pay the attester, but it's just as convenient for the domain to reward the attester directly. + // This is, currently, there is no added benefit of issuing a reward through this contract--other than Riccardian clarity. + + // somebody else is trying to pay the attester, e.g., the domain + if (address(token) == custodian) { + // if there isn't a custodian... + token.transferFrom(from, attester, amount); // just transfer the token + } else { + // purchase the custodial token for the attester + ICustodianToken(custodian).buyCustodialTokenFrom( + from, + attester, + amount + ); + } } } @@ -544,16 +570,19 @@ contract MovementStaking is // note: you may want to apply this directly to the attester's stake if the Domain sets an automatic restake policy for (uint256 i = 0; i < attesters.length; i++) { // pay the attester - _payAttester(attesters[i], custodians[i], amounts[i]); + _payAttester(msg.sender, attesters[i], custodians[i], amounts[i]); } } - function whitelistAddress(address addr) external onlyRole(DEFAULT_ADMIN_ROLE) { + function whitelistAddress( + address addr + ) external onlyRole(DEFAULT_ADMIN_ROLE) { grantRole(WHITELIST_ROLE, addr); } - function removeAddressFromWhitelist(address addr) external onlyRole(DEFAULT_ADMIN_ROLE) { + function removeAddressFromWhitelist( + address addr + ) external onlyRole(DEFAULT_ADMIN_ROLE) { revokeRole(WHITELIST_ROLE, addr); } - } 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 dcc78ea95..9c70704e9 100644 --- a/protocol-units/settlement/mcr/contracts/src/staking/interfaces/IMovementStaking.sol +++ b/protocol-units/settlement/mcr/contracts/src/staking/interfaces/IMovementStaking.sol @@ -10,7 +10,6 @@ interface IMovementStaking { ) external; function acceptGenesisCeremony() external; function setGenesisCeremony( - address, address[] calldata, address[] calldata, uint256[] calldata diff --git a/protocol-units/settlement/mcr/contracts/src/token/custodian/CustodianToken.sol b/protocol-units/settlement/mcr/contracts/src/token/custodian/CustodianToken.sol index a347c6614..8609d28e8 100644 --- a/protocol-units/settlement/mcr/contracts/src/token/custodian/CustodianToken.sol +++ b/protocol-units/settlement/mcr/contracts/src/token/custodian/CustodianToken.sol @@ -14,14 +14,21 @@ interface ICustodianToken is IERC20 { function grantBuyerRole(address account) external; function revokeBuyerRole(address account) external; - function buyCustodialTokenFor(address account, uint256 amount) external; + function buyCustodialToken(address account, uint256 amount) external; + function buyCustodialTokenFrom( + address buyer, + address account, + uint256 amount + ) external; } contract CustodianToken is ICustodianToken, WrappedToken { using SafeERC20 for IERC20; - bytes32 public constant TRANSFER_SINK_ROLE = keccak256("TRANSFER_SINK_ROLE"); - bytes32 public constant TRANSFER_SINK_ADMIN_ROLE = keccak256("TRANSFER_SINK_ADMIN_ROLE"); + bytes32 public constant TRANSFER_SINK_ROLE = + keccak256("TRANSFER_SINK_ROLE"); + bytes32 public constant TRANSFER_SINK_ADMIN_ROLE = + keccak256("TRANSFER_SINK_ADMIN_ROLE"); bytes32 public constant BUYER_ROLE = keccak256("BUYER_ROLE"); bytes32 public constant BUYER_ADMIN_ROLE = keccak256("BUYER_ADMIN_ROLE"); @@ -35,19 +42,19 @@ contract CustodianToken is ICustodianToken, WrappedToken { * @param symbol The symbol of the token * @param _underlyingToken The underlying token to wrap */ - function initialize(string memory name, string memory symbol, IMintableToken _underlyingToken) - public - virtual - override - initializer - { + function initialize( + string memory name, + string memory symbol, + IMintableToken _underlyingToken + ) public virtual override initializer { __CustodianToken_init(name, symbol, _underlyingToken); } - function __CustodianToken_init(string memory name, string memory symbol, IMintableToken _underlyingToken) - internal - onlyInitializing - { + function __CustodianToken_init( + string memory name, + string memory symbol, + IMintableToken _underlyingToken + ) internal onlyInitializing { __ERC20_init_unchained(name, symbol); __BaseToken_init_unchained(); __MintableToken_init_unchained(); @@ -62,11 +69,15 @@ contract CustodianToken is ICustodianToken, WrappedToken { _grantRole(BUYER_ROLE, msg.sender); } - function grantTransferSinkRole(address account) public onlyRole(TRANSFER_SINK_ADMIN_ROLE) { + function grantTransferSinkRole( + address account + ) public onlyRole(TRANSFER_SINK_ADMIN_ROLE) { _grantRole(TRANSFER_SINK_ROLE, account); } - function revokeTransferSinkRole(address account) public onlyRole(TRANSFER_SINK_ADMIN_ROLE) { + function revokeTransferSinkRole( + address account + ) public onlyRole(TRANSFER_SINK_ADMIN_ROLE) { _revokeRole(TRANSFER_SINK_ROLE, account); } @@ -76,14 +87,13 @@ contract CustodianToken is ICustodianToken, WrappedToken { * @param amount The amount of tokens to approve * @return A boolean indicating whether the approval was successful */ - function approve(address spender, uint256 amount) - public - virtual - override(IERC20, ERC20Upgradeable) - returns (bool) - { + function approve( + address spender, + uint256 amount + ) public virtual override(IERC20, ERC20Upgradeable) returns (bool) { // require the spender is a transfer sink - if (!hasRole(TRANSFER_SINK_ROLE, spender)) revert RestrictedToTransferSinkRole(); + if (!hasRole(TRANSFER_SINK_ROLE, spender)) + revert RestrictedToTransferSinkRole(); return underlyingToken.approve(spender, amount); } @@ -95,14 +105,14 @@ contract CustodianToken is ICustodianToken, WrappedToken { * @param amount The amount of tokens to transfer * @return A boolean indicating whether the transfer was successful */ - function transferFrom(address from, address to, uint256 amount) - public - virtual - override(IERC20, ERC20Upgradeable) - returns (bool) - { + function transferFrom( + address from, + address to, + uint256 amount + ) public virtual override(IERC20, ERC20Upgradeable) returns (bool) { // require the destination is a transfer sink - if (!hasRole(TRANSFER_SINK_ROLE, to)) revert RestrictedToTransferSinkRole(); + if (!hasRole(TRANSFER_SINK_ROLE, to)) + revert RestrictedToTransferSinkRole(); // burn the tokens from the sender super.transferFrom(from, address(this), amount); @@ -117,9 +127,13 @@ contract CustodianToken is ICustodianToken, WrappedToken { * @param amount The amount of tokens to transfer * @return A boolean indicating whether the transfer was successful */ - function transfer(address to, uint256 amount) public virtual override(IERC20, ERC20Upgradeable) returns (bool) { + function transfer( + address to, + uint256 amount + ) public virtual override(IERC20, ERC20Upgradeable) returns (bool) { // require the destination is a transfer sink - if (!hasRole(TRANSFER_SINK_ROLE, to)) revert RestrictedToTransferSinkRole(); + if (!hasRole(TRANSFER_SINK_ROLE, to)) + revert RestrictedToTransferSinkRole(); // burn the tokens from the sender super.transfer(address(this), amount); @@ -132,15 +146,29 @@ contract CustodianToken is ICustodianToken, WrappedToken { _grantRole(BUYER_ROLE, account); } - function revokeBuyerRole(address account) public onlyRole(BUYER_ADMIN_ROLE) { + function revokeBuyerRole( + address account + ) public onlyRole(BUYER_ADMIN_ROLE) { _revokeRole(BUYER_ROLE, account); } - function buyCustodialTokenFor(address account, uint256 amount) public override { - if (!hasRole(BUYER_ROLE, msg.sender)) revert RestrictedToBuyerRole(); + function buyCustodialToken( + address account, + uint256 amount + ) public override { + buyCustodialTokenFrom(msg.sender, account, amount); + } + + function buyCustodialTokenFrom( + address buyer, + address account, + uint256 amount + ) public override { + // todo: this might need to check msg.sender instead or in addition to buyer + if (!hasRole(BUYER_ROLE, buyer)) revert RestrictedToBuyerRole(); // transfer the approved value from the buyer to this contract - underlyingToken.transferFrom(msg.sender, address(this), amount); + underlyingToken.transferFrom(buyer, address(this), amount); // mint the custodial token for the buyer at their desired address // ! maybe this should also be managed through the minter role, so the buyer would have to be buyer and minter diff --git a/protocol-units/settlement/mcr/contracts/src/token/locked/LockedToken.sol b/protocol-units/settlement/mcr/contracts/src/token/locked/LockedToken.sol index d1ee1365d..877c6e5be 100644 --- a/protocol-units/settlement/mcr/contracts/src/token/locked/LockedToken.sol +++ b/protocol-units/settlement/mcr/contracts/src/token/locked/LockedToken.sol @@ -1,6 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.19; +import "forge-std/Test.sol"; import {WrappedToken} from "../base/WrappedToken.sol"; import {IMintableToken} from "../base/MintableToken.sol"; import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; @@ -81,7 +82,7 @@ contract LockedToken is WrappedToken, LockedTokenStorage { function release() external { uint256 totalUnlocked = 0; Lock[] storage userLocks = locks[msg.sender]; - for (uint256 i = 0; i < userLocks.length; i++) { + for (uint256 i; i < userLocks.length;) { if (block.timestamp > userLocks[i].releaseTime) { // compute the max possible amount to withdraw uint256 amount = Math.min( @@ -102,8 +103,10 @@ contract LockedToken is WrappedToken, LockedTokenStorage { if (userLocks[i].amount == 0) { userLocks[i] = userLocks[userLocks.length - 1]; userLocks.pop(); + continue; } } + i++; } // transfer the underlying token 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 6fb529d26..88aad1ba0 100644 --- a/protocol-units/settlement/mcr/contracts/test/staking/MovementStaking.t.sol +++ b/protocol-units/settlement/mcr/contracts/test/staking/MovementStaking.t.sol @@ -6,19 +6,15 @@ import "../../src/staking/MovementStaking.sol"; import "../../src/token/MOVEToken.sol"; contract MovementStakingTest is Test { - function testInitialize() public { - MOVEToken moveToken = new MOVEToken(); moveToken.initialize(); MovementStaking staking = new MovementStaking(); staking.initialize(moveToken); - } function testCannotInitializeTwice() public { - MOVEToken moveToken = new MOVEToken(); moveToken.initialize(); @@ -31,7 +27,6 @@ contract MovementStakingTest is Test { } function testRegister() public { - MOVEToken moveToken = new MOVEToken(); moveToken.initialize(); @@ -46,7 +41,6 @@ contract MovementStakingTest is Test { staking.registerDomain(1 seconds, custodians); assertEq(staking.getCurrentEpoch(domain), 0); - } function testWhitelist() public { @@ -55,7 +49,7 @@ contract MovementStakingTest is Test { MovementStaking staking = new MovementStaking(); staking.initialize(moveToken); - + // Our whitelister address whitelister = vm.addr(1); // Whitelist them @@ -71,7 +65,6 @@ contract MovementStakingTest is Test { } function testSimpleStaker() public { - MOVEToken moveToken = new MOVEToken(); moveToken.initialize(); @@ -94,11 +87,13 @@ contract MovementStakingTest is Test { vm.prank(staker); staking.stake(domain, moveToken, 100); assertEq(moveToken.balanceOf(staker), 0); - assertEq(staking.getStakeAtEpoch(domain, 0, address(moveToken), staker), 100); + assertEq( + staking.getStakeAtEpoch(domain, 0, address(moveToken), staker), + 100 + ); } function testSimpleGenesisCeremony() public { - MOVEToken moveToken = new MOVEToken(); moveToken.initialize(); @@ -123,12 +118,13 @@ contract MovementStakingTest is Test { vm.prank(domain); staking.acceptGenesisCeremony(); assertNotEq(staking.currentEpochByDomain(domain), 0); - assertEq(staking.getCurrentEpochStake(domain, address(moveToken), staker), 100); - + assertEq( + staking.getCurrentEpochStake(domain, address(moveToken), staker), + 100 + ); } function testSimpleRolloverEpoch() public { - MOVEToken moveToken = new MOVEToken(); moveToken.initialize(); @@ -153,22 +149,27 @@ contract MovementStakingTest is Test { staking.stake(domain, moveToken, 100); vm.prank(domain); staking.acceptGenesisCeremony(); - + // rollover epoch - for(uint256 i = 0; i < 10; i++) { + for (uint256 i = 0; i < 10; i++) { vm.warp((i + 1) * 1 seconds); uint256 epochBefore = staking.getCurrentEpoch(domain); vm.prank(domain); staking.rollOverEpoch(); uint256 epochAfter = staking.getCurrentEpoch(domain); assertEq(epochAfter, epochBefore + 1); - assertEq(staking.getCurrentEpochStake(domain, address(moveToken), staker), 100); + assertEq( + staking.getCurrentEpochStake( + domain, + address(moveToken), + staker + ), + 100 + ); } - } function testUnstakeRolloverEpoch() public { - MOVEToken moveToken = new MOVEToken(); moveToken.initialize(); @@ -192,16 +193,22 @@ contract MovementStakingTest is Test { staking.stake(domain, moveToken, 100); vm.prank(domain); staking.acceptGenesisCeremony(); - - for(uint256 i = 0; i < 10; i++) { + for (uint256 i = 0; i < 10; i++) { vm.warp((i + 1) * 1 seconds); uint256 epochBefore = staking.getCurrentEpoch(domain); // unstake vm.prank(staker); staking.unstake(domain, address(moveToken), 10); - assertEq(staking.getCurrentEpochStake(domain, address(moveToken), staker), 100 - (i * 10)); + assertEq( + staking.getCurrentEpochStake( + domain, + address(moveToken), + staker + ), + 100 - (i * 10) + ); assertEq(moveToken.balanceOf(staker), i * 10); // roll over @@ -209,13 +216,10 @@ contract MovementStakingTest is Test { staking.rollOverEpoch(); uint256 epochAfter = staking.getCurrentEpoch(domain); assertEq(epochAfter, epochBefore + 1); - } - } function testUnstakeAndStakeRolloverEpoch() public { - MOVEToken moveToken = new MOVEToken(); moveToken.initialize(); @@ -239,9 +243,8 @@ contract MovementStakingTest is Test { staking.stake(domain, moveToken, 100); vm.prank(domain); staking.acceptGenesisCeremony(); - - for(uint256 i = 0; i < 10; i++) { + for (uint256 i = 0; i < 10; i++) { vm.warp((i + 1) * 1 seconds); uint256 epochBefore = staking.getCurrentEpoch(domain); @@ -257,11 +260,15 @@ contract MovementStakingTest is Test { // check stake assertEq( - staking.getCurrentEpochStake(domain, address(moveToken), staker), + staking.getCurrentEpochStake( + domain, + address(moveToken), + staker + ), (100 - (i * 10)) + (i * 5) ); assertEq( - moveToken.balanceOf(staker), + moveToken.balanceOf(staker), (50 - (i + 1) * 5) + (i * 10) ); @@ -270,13 +277,10 @@ contract MovementStakingTest is Test { staking.rollOverEpoch(); uint256 epochAfter = staking.getCurrentEpoch(domain); assertEq(epochAfter, epochBefore + 1); - } - } function testUnstakeStakeAndSlashRolloverEpoch() public { - MOVEToken moveToken = new MOVEToken(); moveToken.initialize(); @@ -300,9 +304,8 @@ contract MovementStakingTest is Test { staking.stake(domain, moveToken, 100); vm.prank(domain); staking.acceptGenesisCeremony(); - - for(uint256 i = 0; i < 5; i++) { + for (uint256 i = 0; i < 5; i++) { vm.warp((i + 1) * 1 seconds); uint256 epochBefore = staking.getCurrentEpoch(domain); @@ -318,11 +321,15 @@ contract MovementStakingTest is Test { // check stake assertEq( - staking.getCurrentEpochStake(domain, address(moveToken), staker), + staking.getCurrentEpochStake( + domain, + address(moveToken), + staker + ), (100 - (i * 10)) + (i * 5) - (i * 1) ); assertEq( - moveToken.balanceOf(staker), + moveToken.balanceOf(staker), (50 - (i + 1) * 5) + (i * 10) ); @@ -340,7 +347,11 @@ contract MovementStakingTest is Test { // slash immediately takes effect assertEq( - staking.getCurrentEpochStake(domain, address(moveToken), staker), + staking.getCurrentEpochStake( + domain, + address(moveToken), + staker + ), (100 - (i * 10)) + (i * 5) - ((i + 1) * 1) ); @@ -349,9 +360,73 @@ contract MovementStakingTest is Test { staking.rollOverEpoch(); uint256 epochAfter = staking.getCurrentEpoch(domain); assertEq(epochAfter, epochBefore + 1); - } - } -} \ No newline at end of file + function testHalbornReward() public { + MOVEToken moveToken = new MOVEToken(); + moveToken.initialize(); + + MovementStaking staking = new MovementStaking(); + staking.initialize(moveToken); + + // Register a domain + address payable domain = payable(vm.addr(1)); + address[] memory custodians = new address[](1); + custodians[0] = address(moveToken); + vm.prank(domain); + staking.registerDomain(1 seconds, custodians); + + // Alice stakes 1000 tokens + address payable alice = payable(vm.addr(2)); + staking.whitelistAddress(alice); + moveToken.mint(alice, 1000); + vm.prank(alice); + moveToken.approve(address(staking), 1000); + vm.prank(alice); + staking.stake(domain, moveToken, 1000); + + // Bob stakes 100 tokens + address payable bob = payable(vm.addr(3)); + staking.whitelistAddress(bob); + moveToken.mint(bob, 100); + vm.prank(bob); + moveToken.approve(address(staking), 100); + vm.prank(bob); + staking.stake(domain, moveToken, 100); + + // Assertions on stakes and balances + assertEq(moveToken.balanceOf(alice), 0); + assertEq(moveToken.balanceOf(bob), 0); + assertEq(moveToken.balanceOf(address(staking)), 1100); + assertEq( + staking.getTotalStakeForEpoch(domain, 0, address(moveToken)), + 1100 + ); + assertEq( + staking.getStakeAtEpoch(domain, 0, address(moveToken), alice), + 1000 + ); + assertEq( + staking.getStakeAtEpoch(domain, 0, address(moveToken), bob), + 100 + ); + + // Charlie calls reward with himself only to steal tokens + address charlie = vm.addr(4); + address[] memory attesters = new address[](1); + attesters[0] = charlie; + uint256[] memory amounts = new uint256[](1); + amounts[0] = 1000; + vm.prank(charlie); + vm.expectRevert( + abi.encodeWithSignature( + "ERC20InsufficientAllowance(address,uint256,uint256)", + address(staking), // should be called by the staking contract + 0, + 1000 + ) + ); + staking.reward(attesters, amounts, custodians); + } +} diff --git a/protocol-units/settlement/mcr/contracts/test/token/custodian/CustodianToken.t.sol b/protocol-units/settlement/mcr/contracts/test/token/custodian/CustodianToken.t.sol index 9f3964584..0d86c554e 100644 --- a/protocol-units/settlement/mcr/contracts/test/token/custodian/CustodianToken.t.sol +++ b/protocol-units/settlement/mcr/contracts/test/token/custodian/CustodianToken.t.sol @@ -7,9 +7,7 @@ import "../../../src/token/custodian/CustodianToken.sol"; // import base access control instead of upgradeable access control contract CustodianTokenTest is Test { - function testInitialize() public { - MintableToken underlyingToken = new MintableToken(); underlyingToken.initialize("Underlying Token", "UNDERLYING"); @@ -19,25 +17,21 @@ contract CustodianTokenTest is Test { // Check the token details assertEq(token.name(), "Custodian Token"); assertEq(token.symbol(), "CUSTODIAN"); - } function testCannotInitializeTwice() public { - MintableToken underlyingToken = new MintableToken(); underlyingToken.initialize("Underlying Token", "UNDERLYING"); CustodianToken token = new CustodianToken(); token.initialize("Custodian Token", "CUSTODIAN", underlyingToken); - + // Attempt to initialize again should fail vm.expectRevert(0xf92ee8a9); token.initialize("Custodian Token", "CUSTODIAN", underlyingToken); - } function testGrants() public { - MintableToken underlyingToken = new MintableToken(); underlyingToken.initialize("Underlying Token", "UNDERLYING"); @@ -45,7 +39,12 @@ contract CustodianTokenTest is Test { token.initialize("Custodian Token", "CUSTODIAN", underlyingToken); underlyingToken.grantMinterRole(address(token)); - assert(underlyingToken.hasRole(underlyingToken.MINTER_ROLE(), address(token))); + assert( + underlyingToken.hasRole( + underlyingToken.MINTER_ROLE(), + address(token) + ) + ); // valid minting succeeds vm.prank(address(token)); @@ -53,15 +52,13 @@ contract CustodianTokenTest is Test { assert(underlyingToken.balanceOf(address(this)) == 100); // invalid minting fails - address payable signer = payable(vm.addr(1)); + address payable signer = payable(vm.addr(1)); vm.prank(signer); vm.expectRevert(); // todo: catch type underlyingToken.mint(signer, 100); - } function testCustodianMint() public { - MintableToken underlyingToken = new MintableToken(); underlyingToken.initialize("Underlying Token", "UNDERLYING"); @@ -69,7 +66,12 @@ contract CustodianTokenTest is Test { token.initialize("Custodian Token", "CUSTODIAN", underlyingToken); underlyingToken.grantMinterRole(address(token)); - assert(underlyingToken.hasRole(underlyingToken.MINTER_ROLE(), address(token))); + assert( + underlyingToken.hasRole( + underlyingToken.MINTER_ROLE(), + address(token) + ) + ); // valid minting succeeds token.mint(address(this), 100); @@ -77,7 +79,7 @@ contract CustodianTokenTest is Test { assert(underlyingToken.balanceOf(address(token)) == 100); // valid minting is incremental - address payable signer = payable(vm.addr(1)); + address payable signer = payable(vm.addr(1)); token.mint(signer, 100); assert(token.balanceOf(signer) == 100); assert(underlyingToken.balanceOf(address(token)) == 200); @@ -94,11 +96,9 @@ contract CustodianTokenTest is Test { vm.prank(signer); vm.expectRevert(); // todo: catch type token.mint(signer, 100); - } function testCustodianTransferToValidSink() public { - MintableToken underlyingToken = new MintableToken(); underlyingToken.initialize("Underlying Token", "UNDERLYING"); @@ -106,7 +106,12 @@ contract CustodianTokenTest is Test { token.initialize("Custodian Token", "CUSTODIAN", underlyingToken); underlyingToken.grantMinterRole(address(token)); - assert(underlyingToken.hasRole(underlyingToken.MINTER_ROLE(), address(token))); + assert( + underlyingToken.hasRole( + underlyingToken.MINTER_ROLE(), + address(token) + ) + ); // signers address payable validSink = payable(vm.addr(2)); @@ -119,11 +124,9 @@ contract CustodianTokenTest is Test { token.transfer(validSink, 100); assert(token.balanceOf(alice) == 0); assert(underlyingToken.balanceOf(validSink) == 100); - } function testCustodianTransferToInvalidSink() public { - MintableToken underlyingToken = new MintableToken(); underlyingToken.initialize("Underlying Token", "UNDERLYING"); @@ -131,7 +134,12 @@ contract CustodianTokenTest is Test { token.initialize("Custodian Token", "CUSTODIAN", underlyingToken); underlyingToken.grantMinterRole(address(token)); - assert(underlyingToken.hasRole(underlyingToken.MINTER_ROLE(), address(token))); + assert( + underlyingToken.hasRole( + underlyingToken.MINTER_ROLE(), + address(token) + ) + ); // signers address payable invalidSink = payable(vm.addr(2)); @@ -142,11 +150,9 @@ contract CustodianTokenTest is Test { vm.prank(alice); vm.expectRevert(); // todo: catch type token.transfer(invalidSink, 100); - } function testCustodianBuyValidSource() public { - MintableToken underlyingToken = new MintableToken(); underlyingToken.initialize("Underlying Token", "UNDERLYING"); @@ -154,7 +160,12 @@ contract CustodianTokenTest is Test { token.initialize("Custodian Token", "CUSTODIAN", underlyingToken); underlyingToken.grantMinterRole(address(token)); - assert(underlyingToken.hasRole(underlyingToken.MINTER_ROLE(), address(token))); + assert( + underlyingToken.hasRole( + underlyingToken.MINTER_ROLE(), + address(token) + ) + ); // signers address payable validSource = payable(vm.addr(2)); @@ -170,15 +181,13 @@ contract CustodianTokenTest is Test { // buy from valid source succeeds vm.prank(validSource); - token.buyCustodialTokenFor(alice, 100); + token.buyCustodialToken(alice, 100); assert(token.balanceOf(alice) == 100); assert(underlyingToken.balanceOf(address(token)) == 100); assert(underlyingToken.balanceOf(validSource) == 0); - } function testCustodianBuyInvalidSource() public { - MintableToken underlyingToken = new MintableToken(); underlyingToken.initialize("Underlying Token", "UNDERLYING"); @@ -186,7 +195,12 @@ contract CustodianTokenTest is Test { token.initialize("Custodian Token", "CUSTODIAN", underlyingToken); underlyingToken.grantMinterRole(address(token)); - assert(underlyingToken.hasRole(underlyingToken.MINTER_ROLE(), address(token))); + assert( + underlyingToken.hasRole( + underlyingToken.MINTER_ROLE(), + address(token) + ) + ); // signers address payable invalidSource = payable(vm.addr(2)); @@ -202,8 +216,6 @@ contract CustodianTokenTest is Test { // buy from valid source succeeds vm.prank(invalidSource); vm.expectRevert(); // todo: catch type - token.buyCustodialTokenFor(alice, 100); - + token.buyCustodialToken(alice, 100); } - -} \ No newline at end of file +} diff --git a/protocol-units/settlement/mcr/contracts/test/token/locked/LockedToken.t.sol b/protocol-units/settlement/mcr/contracts/test/token/locked/LockedToken.t.sol index 4a4dd0a54..40aeed307 100644 --- a/protocol-units/settlement/mcr/contracts/test/token/locked/LockedToken.t.sol +++ b/protocol-units/settlement/mcr/contracts/test/token/locked/LockedToken.t.sol @@ -128,4 +128,213 @@ contract LockedTokenTest is Test { assert(underlyingToken.balanceOf(alice) == 150); } + function testLockMultiple() public { + + MintableToken underlyingToken = new MintableToken(); + underlyingToken.initialize("Underlying Token", "UNDERLYING"); + + LockedToken token = new LockedToken(); + token.initialize("Locked Token", "LOCKED", underlyingToken); + + underlyingToken.grantMinterRole(address(token)); + + // signers + address payable alice = payable(vm.addr(1)); + + // mint locked tokens + address[] memory addresses = new address[](3); + addresses[0] = alice; + addresses[1] = alice; + addresses[2] = alice; + uint256[] memory mintAmounts = new uint256[](3); + mintAmounts[0] = 100; + mintAmounts[1] = 50; + mintAmounts[2] = 25; + uint256[] memory lockAmounts = new uint256[](3); + lockAmounts[0] = 100; + lockAmounts[1] = 50; + lockAmounts[2] = 25; + uint256[] memory locks = new uint256[](3); + locks[0] = block.timestamp + 100; + locks[1] = block.timestamp + 200; + locks[2] = block.timestamp + 300; + token.mintAndLock( + addresses, + mintAmounts, + lockAmounts, + locks + ); + assert(token.balanceOf(alice) == 175); + assert(underlyingToken.balanceOf(address(token)) == 175); + assert(underlyingToken.balanceOf(alice) == 0); + + // cannot release locked tokens + vm.warp(block.timestamp + 1); + vm.prank(alice); + token.release(); + assert(token.balanceOf(alice) == 175); + assert(underlyingToken.balanceOf(address(token)) == 175); + assert(underlyingToken.balanceOf(alice) == 0); + + // tick forward + vm.warp(block.timestamp + 301); + + // release locked tokens + vm.prank(alice); + token.release(); + assert(token.balanceOf(alice) == 0); + assert(underlyingToken.balanceOf(address(token)) == 0); + assert(underlyingToken.balanceOf(alice) == 175); + } + + function testLockMultiplePrematureClaim() public { + + MintableToken underlyingToken = new MintableToken(); + underlyingToken.initialize("Underlying Token", "UNDERLYING"); + + LockedToken token = new LockedToken(); + token.initialize("Locked Token", "LOCKED", underlyingToken); + + underlyingToken.grantMinterRole(address(token)); + + // signers + address payable alice = payable(vm.addr(1)); + + // mint locked tokens + address[] memory addresses = new address[](3); + addresses[0] = alice; + addresses[1] = alice; + addresses[2] = alice; + uint256[] memory mintAmounts = new uint256[](3); + mintAmounts[0] = 100; + mintAmounts[1] = 50; + mintAmounts[2] = 25; + uint256[] memory lockAmounts = new uint256[](3); + lockAmounts[0] = 100; + lockAmounts[1] = 50; + lockAmounts[2] = 25; + uint256[] memory locks = new uint256[](3); + locks[0] = block.timestamp + 100; + locks[1] = block.timestamp + 200; + locks[2] = block.timestamp + 400; + token.mintAndLock( + addresses, + mintAmounts, + lockAmounts, + locks + ); + assert(token.balanceOf(alice) == 175); + assert(underlyingToken.balanceOf(address(token)) == 175); + assert(underlyingToken.balanceOf(alice) == 0); + + // cannot release locked tokens + vm.warp(block.timestamp + 1); + vm.prank(alice); + token.release(); + assert(token.balanceOf(alice) == 175); + assert(underlyingToken.balanceOf(address(token)) == 175); + assert(underlyingToken.balanceOf(alice) == 0); + + // tick forward + vm.warp(block.timestamp + 301); + + // release locked tokens + vm.prank(alice); + token.release(); + assert(token.balanceOf(alice) == 25); + assert(underlyingToken.balanceOf(address(token)) == 25); + assert(underlyingToken.balanceOf(alice) == 150); + // two releases occurred, alice lock index 0 should still be present + (uint256 lock1,) = token.locks(alice, 0); + assert(lock1 == 25); + + // tick forward + vm.warp(block.timestamp + 101); + vm.prank(alice); + token.release(); + assert(token.balanceOf(alice) == 0); + assert(underlyingToken.balanceOf(address(token)) == 0); + assert(underlyingToken.balanceOf(alice) == 175); + // call should revert with no locks existent + vm.expectRevert(); + (uint256 lock2,) = token.locks(alice, 0); + } + + + function testTransferLockedAsset() public { + + MintableToken underlyingToken = new MintableToken(); + underlyingToken.initialize("Underlying Token", "UNDERLYING"); + + LockedToken token = new LockedToken(); + token.initialize("Locked Token", "LOCKED", underlyingToken); + + underlyingToken.grantMinterRole(address(token)); + + // signers + address payable alice = payable(vm.addr(1)); + + // mint locked tokens + address[] memory addresses = new address[](3); + addresses[0] = alice; + addresses[1] = alice; + addresses[2] = alice; + uint256[] memory mintAmounts = new uint256[](3); + mintAmounts[0] = 100; + mintAmounts[1] = 50; + mintAmounts[2] = 25; + uint256[] memory lockAmounts = new uint256[](3); + lockAmounts[0] = 100; + lockAmounts[1] = 50; + lockAmounts[2] = 25; + uint256[] memory locks = new uint256[](3); + locks[0] = block.timestamp + 100; + locks[1] = block.timestamp + 200; + locks[2] = block.timestamp + 400; + token.mintAndLock( + addresses, + mintAmounts, + lockAmounts, + locks + ); + assert(token.balanceOf(alice) == 175); + assert(underlyingToken.balanceOf(address(token)) == 175); + assert(underlyingToken.balanceOf(alice) == 0); + + // cannot release locked tokens + vm.warp(block.timestamp + 1); + vm.prank(alice); + token.release(); + assert(token.balanceOf(alice) == 175); + assert(underlyingToken.balanceOf(address(token)) == 175); + assert(underlyingToken.balanceOf(alice) == 0); + + // tick forward + vm.warp(block.timestamp + 301); + + // release locked tokens + vm.prank(alice); + token.release(); + assert(token.balanceOf(alice) == 25); + assert(underlyingToken.balanceOf(address(token)) == 25); + assert(underlyingToken.balanceOf(alice) == 150); + // two releases occurred, alice lock index 0 should still be present + (uint256 lock1,) = token.locks(alice, 0); + assert(lock1 == 25); + + vm.prank(alice); + token.transfer(address(0x1337), 20); + // tick forward + vm.warp(block.timestamp + 101); + vm.prank(alice); + token.release(); + assert(token.balanceOf(alice) == 0); + assert(underlyingToken.balanceOf(address(token)) == 20); + assert(underlyingToken.balanceOf(alice) == 155); + // call should revert with no locks existent + (uint256 lock2,) = token.locks(alice, 0); + assert(lock2 == 20); + } + + } \ No newline at end of file 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 ab596923e..d160ceca7 100644 --- a/protocol-units/settlement/mcr/contracts/test/token/stlMoveToken.t.sol +++ b/protocol-units/settlement/mcr/contracts/test/token/stlMoveToken.t.sol @@ -38,7 +38,12 @@ contract stlMoveTokenTest is Test { token.initialize(underlyingToken); underlyingToken.grantMinterRole(address(token)); - assert(underlyingToken.hasRole(underlyingToken.MINTER_ROLE(), address(token))); + assert( + underlyingToken.hasRole( + underlyingToken.MINTER_ROLE(), + address(token) + ) + ); // signers address payable alice = payable(vm.addr(1)); @@ -139,7 +144,7 @@ contract stlMoveTokenTest is Test { vm.prank(stakingPool); underlyingToken.approve(address(token), 110); vm.prank(stakingPool); - token.buyCustodialTokenFor(alice, 110); + token.buyCustodialToken(alice, 110); assertEq(token.balanceOf(alice), 110); assertEq(underlyingToken.balanceOf(stakingPool), 290); assertEq(underlyingToken.balanceOf(address(token)), 210); @@ -153,7 +158,7 @@ contract stlMoveTokenTest is Test { vm.prank(stakingPool); underlyingToken.approve(address(token), 100); vm.prank(stakingPool); - token.buyCustodialTokenFor(bob, 100); + token.buyCustodialToken(bob, 100); assertEq(token.balanceOf(bob), 100); assertEq(underlyingToken.balanceOf(stakingPool), 190); assertEq(underlyingToken.balanceOf(address(token)), 310); @@ -191,7 +196,7 @@ contract stlMoveTokenTest is Test { vm.prank(stakingPool); underlyingToken.approve(address(token), 110); vm.prank(stakingPool); - token.buyCustodialTokenFor(carol, 110); + token.buyCustodialToken(carol, 110); assertEq(token.balanceOf(carol), 110); assertEq(underlyingToken.balanceOf(stakingPool), 80); // spent 20 in total on rewards assertEq(underlyingToken.balanceOf(address(token)), 220);