ci/test: improve CI jobs and tests (#2189)
* ci: move to scripts directory
* nits
* ci: improve main CI jobs
* fix: install script
* fix
* fix: use curl for windows installation
* fix: wasm typo
* tests: move to single binary
* chore: clippy
* chore: clippy
* chore: clippy
* fix: test command
* fix: quote tests
* update script
* fix: action exclude
* fix: dev deps
* fix: only run wasm in own job
* ci: add aarch64 targets
* test: rm useless test
* ci: update security audit
* ci: add deny CI
* chore: rm unused audit.toml
* chore: update geth.rs
* ci: remove unusable targets
* fix: install script path
* fix: wasm
* improve script
* fix: failing ci
* fix: contract tests
* ci: improve install script
* update middleware tests
* move integration etherscan tests to tests/ dir
* fix: eip2930 access_list field name
* add pendingtransaction must_use
* add random anvil comment
* ci: add miri job
* ci: simplify
* fixci
* Revert "add pendingtransaction must_use"
This reverts commit 770b21b4a3
.
* fix: macos script
* fix: use curl in script
* unused ci
* update script
* fix wasm
* rm_miri
* fix: signer test
* fix: wasm ci
* fix: ipc test
* fix: live celo tests
* fix: abi online source test
* fix: windows paths in test
* chore: update serial_test
* ci: run live tests separately
* fix: provider tests
* fix: unused var
* fix: feature
* fix merge
* fix: etherscan key tests
* ci: rm duplicate audit
* fix: split etherscan test ci
* fix: etherscan test
* fix: generate multiple unused ports
* fix: source test
* fix: udeps
* rm unused
This commit is contained in:
parent
34775178f9
commit
da743fc8b2
|
@ -1,4 +0,0 @@
|
||||||
[advisories]
|
|
||||||
ignore = [
|
|
||||||
"RUSTSEC-2021-0127", # serde_cbor dependency through the criterion dev-dependency
|
|
||||||
]
|
|
|
@ -0,0 +1,89 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# Installs Solc and Geth binaries
|
||||||
|
# Note: intended for use only with CI (x86_64 Ubuntu, MacOS or Windows)
|
||||||
|
set -e
|
||||||
|
|
||||||
|
GETH_BUILD=${GETH_BUILD:-"1.11.2-73b01f40"}
|
||||||
|
|
||||||
|
BIN_DIR=${BIN_DIR:-"$HOME/bin"}
|
||||||
|
|
||||||
|
PLATFORM="$(uname -s | awk '{print tolower($0)}')"
|
||||||
|
if [ "$PLATFORM" != "linux" ] && [ "$PLATFORM" != "darwin" ]; then
|
||||||
|
EXT=".exe"
|
||||||
|
fi
|
||||||
|
|
||||||
|
main() {
|
||||||
|
mkdir -p "$BIN_DIR"
|
||||||
|
cd "$BIN_DIR"
|
||||||
|
export PATH="$BIN_DIR:$PATH"
|
||||||
|
if [ "$GITHUB_PATH" ]; then
|
||||||
|
echo "$BIN_DIR" >> "$GITHUB_PATH"
|
||||||
|
fi
|
||||||
|
|
||||||
|
install_geth &
|
||||||
|
g=$!
|
||||||
|
install_solc &
|
||||||
|
wait $g $!
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "Installed Geth:"
|
||||||
|
geth version
|
||||||
|
echo ""
|
||||||
|
echo "Installed Solc:"
|
||||||
|
solc --version
|
||||||
|
}
|
||||||
|
|
||||||
|
# Installs geth from https://geth.ethereum.org/downloads
|
||||||
|
install_geth() {
|
||||||
|
case "$PLATFORM" in
|
||||||
|
linux|darwin)
|
||||||
|
name="geth-$PLATFORM-amd64-$GETH_BUILD"
|
||||||
|
curl -s "https://gethstore.blob.core.windows.net/builds/$name.tar.gz" | tar -xzf -
|
||||||
|
mv -f "$name/geth" ./
|
||||||
|
rm -rf "$name"
|
||||||
|
chmod +x geth
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
name="geth-windows-amd64-$GETH_BUILD"
|
||||||
|
zip="$name.zip"
|
||||||
|
curl -so "$zip" "https://gethstore.blob.core.windows.net/builds/$zip"
|
||||||
|
unzip "$zip"
|
||||||
|
mv -f "$name/geth.exe" ./
|
||||||
|
rm -rf "$name" "$zip"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
# Installs solc from https://binaries.soliditylang.org (https://github.com/ethereum/solc-bin)
|
||||||
|
install_solc() {
|
||||||
|
bins_url="https://binaries.soliditylang.org"
|
||||||
|
case "$PLATFORM" in
|
||||||
|
linux) bins_url+="/linux-amd64";;
|
||||||
|
darwin) bins_url+="/macosx-amd64";;
|
||||||
|
*) bins_url+="/windows-amd64";;
|
||||||
|
esac
|
||||||
|
|
||||||
|
list=$(curl -s "$bins_url/list.json")
|
||||||
|
# use latest version
|
||||||
|
if [ -z "$SOLC_VERSION" ]; then
|
||||||
|
SOLC_VERSION="$(echo "$list" | jq -r ".latestRelease")"
|
||||||
|
fi
|
||||||
|
bin=$(echo "$list" | jq -r ".releases[\"$SOLC_VERSION\"]")
|
||||||
|
|
||||||
|
if [ "$bin" = "null" ]; then
|
||||||
|
echo "Invalid Solc version: $SOLC_VERSION" 1>&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# windows versions <= 0.7.1 use .zip
|
||||||
|
if [[ "$bin" = *.zip ]]; then
|
||||||
|
echo "Cannot install solc <= 0.7.1" 1>&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
curl -so "$bin" "$bins_url/$bin"
|
||||||
|
mv -f "$bin" "solc$EXT"
|
||||||
|
chmod +x "solc$EXT"
|
||||||
|
}
|
||||||
|
|
||||||
|
main
|
|
@ -1,14 +0,0 @@
|
||||||
name: Security audit
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
paths:
|
|
||||||
- "**/Cargo.toml"
|
|
||||||
- "**/Cargo.lock"
|
|
||||||
jobs:
|
|
||||||
security_audit:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v1
|
|
||||||
- uses: actions-rs/audit-check@v1
|
|
||||||
with:
|
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
|
|
@ -1,16 +1,17 @@
|
||||||
name: book
|
name: book
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: [master]
|
branches: [master]
|
||||||
paths:
|
paths:
|
||||||
- 'book/**'
|
- "book/**"
|
||||||
- 'book.toml'
|
- "book.toml"
|
||||||
|
|
||||||
pull_request:
|
pull_request:
|
||||||
branches: [master]
|
branches: [master]
|
||||||
paths:
|
paths:
|
||||||
- 'book/**'
|
- "book/**"
|
||||||
- 'book.toml'
|
- "book.toml"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
|
|
|
@ -1,288 +1,180 @@
|
||||||
|
name: CI
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches: [master]
|
||||||
- master
|
|
||||||
pull_request:
|
pull_request:
|
||||||
|
|
||||||
name: Tests
|
|
||||||
|
|
||||||
# Yeah I know it's bad practice to have API keys, this is a read-only API key
|
# Yeah I know it's bad practice to have API keys, this is a read-only API key
|
||||||
# so that we do not get rate limited by Etherscan (and it's free to generate as
|
# so that we do not get rate limited by Etherscan (and it's free to generate as
|
||||||
# many as you want)
|
# many as you want)
|
||||||
env:
|
env:
|
||||||
ETHERSCAN_API_KEY_ETHEREUM: I5BXNZYP5GEDWFINGVEZKYIVU2695NPQZB
|
ETHERSCAN_API_KEY: "I5BXNZYP5GEDWFINGVEZKYIVU2695NPQZB"
|
||||||
ETHERSCAN_API_KEY_CELO: B13XSMUT6Q3Q4WZ5DNQR8RXDBA2KNTMT4M
|
|
||||||
GOERLI_PRIVATE_KEY: "fa4a1a79e869a96fcb42727f75e3232d6865a82ea675bb95de967a7fe6a773b2"
|
GOERLI_PRIVATE_KEY: "fa4a1a79e869a96fcb42727f75e3232d6865a82ea675bb95de967a7fe6a773b2"
|
||||||
GETH_BUILD: 1.10.26-e5eb32ac
|
GETH_BUILD: "1.11.2-73b01f40"
|
||||||
|
SOLC_VERSION: "0.8.19"
|
||||||
|
CARGO_TERM_COLOR: always
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
tests:
|
test:
|
||||||
name: ethereum tests
|
name: test ${{ matrix.os }} ${{ matrix.flags }}
|
||||||
runs-on: ubuntu-latest
|
runs-on: ${{ matrix.os }}
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
os: ["ubuntu-latest", "macos-latest", "windows-latest"]
|
||||||
|
flags: ["--workspace", "--workspace --all-features"]
|
||||||
|
exclude:
|
||||||
|
# sha2-asm
|
||||||
|
- os: windows-latest
|
||||||
|
flags: --workspace --all-features
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout sources
|
- uses: actions/checkout@v3
|
||||||
uses: actions/checkout@v3
|
- uses: dtolnay/rust-toolchain@stable
|
||||||
- name: Install Anvil
|
- name: Install Anvil
|
||||||
uses: foundry-rs/foundry-toolchain@v1
|
uses: foundry-rs/foundry-toolchain@v1
|
||||||
with:
|
with:
|
||||||
version: nightly
|
version: nightly
|
||||||
- name: Install Solc
|
- name: Install test binaries
|
||||||
|
shell: bash
|
||||||
|
run: ./.github/scripts/install_test_binaries.sh
|
||||||
|
- name: Install nextest
|
||||||
|
uses: taiki-e/install-action@nextest
|
||||||
|
- uses: Swatinem/rust-cache@v2
|
||||||
|
- name: test
|
||||||
|
shell: bash
|
||||||
|
# skip `ethers_etherscan::it` and `ethers::live`
|
||||||
run: |
|
run: |
|
||||||
mkdir -p "$HOME/bin"
|
cargo nextest run \
|
||||||
wget -q https://github.com/ethereum/solidity/releases/download/v0.8.10/solc-static-linux -O $HOME/bin/solc
|
${{ matrix.flags }} \
|
||||||
chmod u+x "$HOME/bin/solc"
|
-E "!binary(~live) & !(deps(ethers-etherscan) & kind(test))"
|
||||||
export PATH=$HOME/bin:$PATH
|
|
||||||
solc --version
|
|
||||||
|
|
||||||
- name: Install geth
|
etherscan-tests:
|
||||||
run: |
|
name: etherscan tests
|
||||||
mkdir -p "$HOME/bin"
|
runs-on: ubuntu-latest
|
||||||
wget -q https://gethstore.blob.core.windows.net/builds/geth-linux-amd64-$GETH_BUILD.tar.gz
|
concurrency: etherscan-tests-${{ github.head_ref || github.run_id }}
|
||||||
tar -xvf geth-linux-amd64-$GETH_BUILD.tar.gz
|
# Run after main tests are done to avoid rate limiting,
|
||||||
mv geth-linux-amd64-$GETH_BUILD/geth $HOME/bin/geth
|
# regardless of whether they were successful
|
||||||
chmod u+x "$HOME/bin/geth"
|
if: ${{ always() }}
|
||||||
export PATH=$HOME/bin:$PATH
|
needs: test
|
||||||
geth version
|
steps:
|
||||||
- name: Install stable toolchain
|
- uses: actions/checkout@v3
|
||||||
uses: actions-rs/toolchain@v1
|
- uses: dtolnay/rust-toolchain@stable
|
||||||
with:
|
- uses: Swatinem/rust-cache@v2
|
||||||
profile: minimal
|
# Don't use cargo-nextest since all the tests have to be ran sequentially
|
||||||
toolchain: stable
|
- name: live tests
|
||||||
override: true
|
run: cargo test -p ethers-etherscan --test it
|
||||||
components: rustfmt, clippy
|
|
||||||
- uses: Swatinem/rust-cache@v1
|
|
||||||
with:
|
|
||||||
cache-on-failure: true
|
|
||||||
- name: cargo test
|
|
||||||
run: |
|
|
||||||
export PATH=$HOME/bin:$PATH
|
|
||||||
export ETHERSCAN_API_KEY=$ETHERSCAN_API_KEY_ETHEREUM
|
|
||||||
cargo test
|
|
||||||
|
|
||||||
feature-tests:
|
live-tests:
|
||||||
name: celo tests
|
name: live tests
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
concurrency: live-tests-${{ github.head_ref || github.run_id }}
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
- uses: dtolnay/rust-toolchain@stable
|
||||||
|
- uses: Swatinem/rust-cache@v2
|
||||||
|
- name: live tests
|
||||||
|
run: cargo test -p ethers --test live --all-features
|
||||||
|
|
||||||
|
# TODO: [#2191](https://github.com/gakonst/ethers-rs/issues/2191)
|
||||||
|
# feature-checks:
|
||||||
|
# name: feature checks
|
||||||
|
# runs-on: ubuntu-latest
|
||||||
|
# steps:
|
||||||
|
# - uses: actions/checkout@v3
|
||||||
|
# - uses: dtolnay/rust-toolchain@nightly
|
||||||
|
# - uses: taiki-e/install-action@cargo-hack
|
||||||
|
# - uses: Swatinem/rust-cache@v2
|
||||||
|
# - name: cargo hack
|
||||||
|
# run:
|
||||||
|
# cargo hack check --all --feature-powerset --depth 2 -Z avoid-dev-deps --keep-going
|
||||||
|
|
||||||
|
clippy:
|
||||||
|
name: clippy
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout sources
|
- uses: actions/checkout@v3
|
||||||
uses: actions/checkout@v3
|
- uses: dtolnay/rust-toolchain@nightly
|
||||||
# TODO: can we combine these shared steps in github actions?
|
|
||||||
- name: Install Anvil
|
|
||||||
uses: foundry-rs/foundry-toolchain@v1
|
|
||||||
with:
|
with:
|
||||||
version: nightly
|
components: clippy
|
||||||
- name: Install Solc
|
- uses: Swatinem/rust-cache@v2
|
||||||
run: |
|
- name: clippy
|
||||||
mkdir -p "$HOME/bin"
|
run: cargo clippy --workspace --tests --all-features
|
||||||
wget -q https://github.com/ethereum/solidity/releases/download/v0.8.10/solc-static-linux -O $HOME/bin/solc
|
env:
|
||||||
chmod u+x "$HOME/bin/solc"
|
RUSTFLAGS: "-D warnings"
|
||||||
export PATH=$HOME/bin:$PATH
|
|
||||||
solc --version
|
|
||||||
- name: Install geth
|
|
||||||
run: |
|
|
||||||
mkdir -p "$HOME/bin"
|
|
||||||
wget -q https://gethstore.blob.core.windows.net/builds/geth-linux-amd64-$GETH_BUILD.tar.gz
|
|
||||||
tar -xvf geth-linux-amd64-$GETH_BUILD.tar.gz
|
|
||||||
mv geth-linux-amd64-$GETH_BUILD/geth $HOME/bin/geth
|
|
||||||
chmod u+x "$HOME/bin/geth"
|
|
||||||
export PATH=$HOME/bin:$PATH
|
|
||||||
geth version
|
|
||||||
|
|
||||||
- name: Install stable toolchain
|
|
||||||
uses: actions-rs/toolchain@v1
|
|
||||||
with:
|
|
||||||
profile: minimal
|
|
||||||
toolchain: stable
|
|
||||||
override: true
|
|
||||||
components: rustfmt, clippy
|
|
||||||
- uses: Swatinem/rust-cache@v1
|
|
||||||
with:
|
|
||||||
cache-on-failure: true
|
|
||||||
- name: cargo test (Celo)
|
|
||||||
run: |
|
|
||||||
export PATH=$HOME/bin:$PATH
|
|
||||||
export ETHERSCAN_API_KEY=$ETHERSCAN_API_KEY_CELO
|
|
||||||
cargo test --all-features
|
|
||||||
|
|
||||||
lint:
|
|
||||||
name: lints
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout sources
|
|
||||||
uses: actions/checkout@v3
|
|
||||||
- name: Install toolchain
|
|
||||||
uses: actions-rs/toolchain@v1
|
|
||||||
with:
|
|
||||||
toolchain: nightly
|
|
||||||
profile: minimal
|
|
||||||
components: rustfmt, clippy
|
|
||||||
override: true
|
|
||||||
- uses: Swatinem/rust-cache@v1
|
|
||||||
with:
|
|
||||||
cache-on-failure: true
|
|
||||||
- name: cargo fmt
|
|
||||||
run: cargo +nightly fmt --all -- --check
|
|
||||||
- name: cargo clippy
|
|
||||||
run: cargo +nightly clippy --all-features -- -D warnings
|
|
||||||
|
|
||||||
docs:
|
docs:
|
||||||
name: docs
|
name: docs
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout sources
|
- uses: actions/checkout@v3
|
||||||
uses: actions/checkout@v3
|
- uses: dtolnay/rust-toolchain@nightly
|
||||||
- name: Install toolchain
|
|
||||||
uses: actions-rs/toolchain@v1
|
|
||||||
with:
|
with:
|
||||||
toolchain: nightly
|
components: rust-docs
|
||||||
override: true
|
- uses: Swatinem/rust-cache@v2
|
||||||
- uses: Swatinem/rust-cache@v1
|
- name: doc
|
||||||
with:
|
run: cargo doc --workspace --all-features --no-deps --document-private-items
|
||||||
cache-on-failure: true
|
|
||||||
- name: cargo doc
|
|
||||||
run: cargo doc --no-deps --all --all-features
|
|
||||||
env:
|
env:
|
||||||
RUSTFLAGS: --cfg docsrs
|
RUSTFLAGS: "--cfg docsrs"
|
||||||
RUSTDOCFLAGS: --cfg docsrs
|
RUSTDOCFLAGS: "--cfg docsrs -D warnings"
|
||||||
|
|
||||||
|
fmt:
|
||||||
|
name: fmt
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
- uses: dtolnay/rust-toolchain@nightly
|
||||||
|
with:
|
||||||
|
components: rustfmt
|
||||||
|
- name: fmt --check
|
||||||
|
run: cargo fmt --all --check
|
||||||
|
|
||||||
wasm:
|
wasm:
|
||||||
name: WASM
|
name: WASM
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout sources
|
- uses: actions/checkout@v3
|
||||||
uses: actions/checkout@v3
|
- uses: dtolnay/rust-toolchain@stable
|
||||||
- name: Install rust
|
|
||||||
uses: actions-rs/toolchain@v1
|
|
||||||
with:
|
with:
|
||||||
toolchain: stable
|
|
||||||
target: wasm32-unknown-unknown
|
target: wasm32-unknown-unknown
|
||||||
profile: minimal
|
|
||||||
override: true
|
|
||||||
|
|
||||||
- name: Install Anvil
|
- name: Install Anvil
|
||||||
uses: foundry-rs/foundry-toolchain@v1
|
uses: foundry-rs/foundry-toolchain@v1
|
||||||
with:
|
with:
|
||||||
version: nightly
|
version: nightly
|
||||||
|
|
||||||
- uses: Swatinem/rust-cache@v1
|
- uses: Swatinem/rust-cache@v2
|
||||||
with:
|
|
||||||
cache-on-failure: true
|
|
||||||
|
|
||||||
- name: Check
|
- name: Check
|
||||||
uses: actions-rs/cargo@v1
|
run: cargo check --workspace --target wasm32-unknown-unknown
|
||||||
with:
|
# TODO: [#2191](https://github.com/gakonst/ethers-rs/issues/2191)
|
||||||
command: check
|
# - name: Check all features
|
||||||
args: --target wasm32-unknown-unknown
|
# run: cargo check --workspace --target wasm32-unknown-unknown --all-features
|
||||||
|
|
||||||
- name: Launch Anvil
|
|
||||||
run:
|
|
||||||
anvil --block-time 2 -m "stuff inherit faith park genre spread huge knee ecology
|
|
||||||
private marble supreme" &
|
|
||||||
|
|
||||||
- name: Install wasm-pack
|
- name: Install wasm-pack
|
||||||
run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
|
run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
|
||||||
|
- uses: actions/setup-node@v3
|
||||||
- name: Wasm-pack test firefox
|
with:
|
||||||
|
node-version: 16
|
||||||
|
- name: Run wasm example
|
||||||
|
working-directory: examples/ethers-wasm
|
||||||
run: |
|
run: |
|
||||||
cd examples/ethers-wasm
|
yarn
|
||||||
|
yarn anvil &
|
||||||
wasm-pack test --headless --firefox
|
wasm-pack test --headless --firefox
|
||||||
|
|
||||||
- name: Wasm-pack test chrome
|
|
||||||
run: |
|
|
||||||
cd examples/ethers-wasm
|
|
||||||
wasm-pack test --headless --chrome
|
wasm-pack test --headless --chrome
|
||||||
|
|
||||||
examples:
|
examples:
|
||||||
name: Examples
|
name: Examples
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
concurrency: examples-${{ github.head_ref || github.run_id }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout sources
|
- uses: actions/checkout@v3
|
||||||
uses: actions/checkout@v3
|
- uses: dtolnay/rust-toolchain@stable
|
||||||
|
|
||||||
- name: Install geth (for state overrides example)
|
|
||||||
run: |
|
|
||||||
mkdir -p "$HOME/bin"
|
|
||||||
wget -q https://gethstore.blob.core.windows.net/builds/geth-linux-amd64-$GETH_BUILD.tar.gz
|
|
||||||
tar -xvf geth-linux-amd64-$GETH_BUILD.tar.gz
|
|
||||||
mv geth-linux-amd64-$GETH_BUILD/geth $HOME/bin/geth
|
|
||||||
chmod u+x "$HOME/bin/geth"
|
|
||||||
export PATH=$HOME/bin:$PATH
|
|
||||||
geth version
|
|
||||||
|
|
||||||
- name: Install Anvil
|
- name: Install Anvil
|
||||||
uses: foundry-rs/foundry-toolchain@v1
|
uses: foundry-rs/foundry-toolchain@v1
|
||||||
with:
|
with:
|
||||||
version: nightly
|
version: nightly
|
||||||
|
- name: Install test binaries
|
||||||
- name: Install Solc
|
run: ./.github/scripts/install_test_binaries.sh
|
||||||
run: |
|
- uses: Swatinem/rust-cache@v2
|
||||||
mkdir -p "$HOME/bin"
|
|
||||||
wget -q https://github.com/ethereum/solidity/releases/download/v0.8.10/solc-static-linux -O $HOME/bin/solc
|
|
||||||
chmod u+x "$HOME/bin/solc"
|
|
||||||
export PATH=$HOME/bin:$PATH
|
|
||||||
solc --version
|
|
||||||
|
|
||||||
- name: Install stable toolchain
|
|
||||||
uses: actions-rs/toolchain@v1
|
|
||||||
with:
|
|
||||||
profile: minimal
|
|
||||||
toolchain: stable
|
|
||||||
override: true
|
|
||||||
components: rustfmt, clippy
|
|
||||||
- uses: Swatinem/rust-cache@v1
|
|
||||||
with:
|
|
||||||
cache-on-failure: true
|
|
||||||
- name: Build and run all examples
|
- name: Build and run all examples
|
||||||
run: |
|
run: ./scripts/examples.sh
|
||||||
export PATH=$HOME/bin:$PATH
|
|
||||||
chmod +x ./scripts/examples.sh
|
|
||||||
./scripts/examples.sh
|
|
||||||
|
|
||||||
windows-build:
|
|
||||||
runs-on: windows-latest
|
|
||||||
name: (${{ matrix.target }}, ${{ matrix.cfg_release_channel }})
|
|
||||||
env:
|
|
||||||
CFG_RELEASE_CHANNEL: ${{ matrix.cfg_release_channel }}
|
|
||||||
strategy:
|
|
||||||
fail-fast: false
|
|
||||||
matrix:
|
|
||||||
target:
|
|
||||||
[
|
|
||||||
i686-pc-windows-gnu,
|
|
||||||
i686-pc-windows-msvc,
|
|
||||||
x86_64-pc-windows-gnu,
|
|
||||||
x86_64-pc-windows-msvc,
|
|
||||||
]
|
|
||||||
cfg_release_channel: [nightly]
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: checkout
|
|
||||||
uses: actions/checkout@v3
|
|
||||||
|
|
||||||
# Run build
|
|
||||||
- name: Update Rustup
|
|
||||||
run: |
|
|
||||||
# disable download progress bar
|
|
||||||
$ProgressPreference = "SilentlyContinue"
|
|
||||||
rustup update
|
|
||||||
rustup target add ${{ matrix.target }}
|
|
||||||
shell: powershell
|
|
||||||
|
|
||||||
- name: Add mingw32 to path for i686-gnu
|
|
||||||
run: |
|
|
||||||
echo "C:\msys64\mingw32\bin" >> $GITHUB_PATH
|
|
||||||
if: matrix.target == 'i686-pc-windows-gnu' && matrix.channel == 'nightly'
|
|
||||||
shell: bash
|
|
||||||
|
|
||||||
- name: Add mingw64 to path for x86_64-gnu
|
|
||||||
run: echo "C:\msys64\mingw64\bin" >> $GITHUB_PATH
|
|
||||||
if: matrix.target == 'x86_64-pc-windows-gnu' && matrix.channel == 'nightly'
|
|
||||||
shell: bash
|
|
||||||
|
|
||||||
- name: build
|
|
||||||
run: |
|
|
||||||
rustc -Vv
|
|
||||||
cargo -V
|
|
||||||
# we test without --all-features on Windows so that sha2-asm is not activated.
|
|
||||||
cargo check
|
|
||||||
shell: cmd
|
|
||||||
|
|
|
@ -0,0 +1,33 @@
|
||||||
|
name: deps
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [master]
|
||||||
|
paths: [Cargo.lock]
|
||||||
|
pull_request:
|
||||||
|
branches: [master]
|
||||||
|
paths: [Cargo.lock]
|
||||||
|
|
||||||
|
env:
|
||||||
|
RUSTFLAGS: -D warnings
|
||||||
|
CARGO_TERM_COLOR: always
|
||||||
|
|
||||||
|
concurrency: deps-${{ github.head_ref || github.run_id }}
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
deny:
|
||||||
|
name: deny (${{ matrix.checks }})
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
checks:
|
||||||
|
- advisories
|
||||||
|
- bans licenses sources
|
||||||
|
|
||||||
|
continue-on-error: ${{ matrix.checks == 'advisories' }}
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
- uses: EmbarkStudios/cargo-deny-action@v1
|
||||||
|
with:
|
||||||
|
command: check ${{ matrix.checks }}
|
|
@ -1,4 +1,4 @@
|
||||||
name: Release
|
name: release
|
||||||
|
|
||||||
on:
|
on:
|
||||||
schedule:
|
schedule:
|
||||||
|
@ -72,7 +72,7 @@ jobs:
|
||||||
id: changelog
|
id: changelog
|
||||||
run: |
|
run: |
|
||||||
current_version=$(git tag --contains HEAD -l "v*" | head -1)
|
current_version=$(git tag --contains HEAD -l "v*" | head -1)
|
||||||
from_version=$(node .github/workflows/release-tag-from.js $current_version $RELEASE_TYPE)
|
from_version=$(node .github/scripts/release-tag-from.js $current_version $RELEASE_TYPE)
|
||||||
echo from $from_version to $current_version
|
echo from $from_version to $current_version
|
||||||
|
|
||||||
echo "::set-output name=release_version::$(echo $current_version)"
|
echo "::set-output name=release_version::$(echo $current_version)"
|
||||||
|
|
|
@ -1475,7 +1475,6 @@ dependencies = [
|
||||||
"reqwest",
|
"reqwest",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"serial_test",
|
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tracing",
|
"tracing",
|
||||||
|
@ -1550,7 +1549,6 @@ dependencies = [
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tracing",
|
"tracing",
|
||||||
"tracing-subscriber",
|
|
||||||
"trezor-client",
|
"trezor-client",
|
||||||
"yubihsm",
|
"yubihsm",
|
||||||
]
|
]
|
||||||
|
@ -2635,16 +2633,6 @@ dependencies = [
|
||||||
"static_assertions",
|
"static_assertions",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "nu-ansi-term"
|
|
||||||
version = "0.46.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84"
|
|
||||||
dependencies = [
|
|
||||||
"overload",
|
|
||||||
"winapi",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num-integer"
|
name = "num-integer"
|
||||||
version = "0.1.45"
|
version = "0.1.45"
|
||||||
|
@ -2810,12 +2798,6 @@ dependencies = [
|
||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "overload"
|
|
||||||
version = "0.1.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "p256"
|
name = "p256"
|
||||||
version = "0.11.1"
|
version = "0.11.1"
|
||||||
|
@ -3841,9 +3823,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serial_test"
|
name = "serial_test"
|
||||||
version = "0.10.0"
|
version = "1.0.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1c789ec87f4687d022a2405cf46e0cd6284889f1839de292cadeb6c6019506f2"
|
checksum = "538c30747ae860d6fb88330addbbd3e0ddbe46d662d032855596d8a8ca260611"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"dashmap",
|
"dashmap",
|
||||||
"futures",
|
"futures",
|
||||||
|
@ -3855,9 +3837,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serial_test_derive"
|
name = "serial_test_derive"
|
||||||
version = "0.10.0"
|
version = "1.0.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b64f9e531ce97c88b4778aad0ceee079216071cffec6ac9b904277f8f92e7fe3"
|
checksum = "079a83df15f85d89a68d64ae1238f142f172b1fa915d0d76b26a7cba1b659a69"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
@ -4432,7 +4414,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a"
|
checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"valuable",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -4445,17 +4426,6 @@ dependencies = [
|
||||||
"tracing",
|
"tracing",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "tracing-log"
|
|
||||||
version = "0.1.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922"
|
|
||||||
dependencies = [
|
|
||||||
"lazy_static",
|
|
||||||
"log",
|
|
||||||
"tracing-core",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tracing-subscriber"
|
name = "tracing-subscriber"
|
||||||
version = "0.3.16"
|
version = "0.3.16"
|
||||||
|
@ -4463,15 +4433,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a6176eae26dd70d0c919749377897b54a9276bd7061339665dd68777926b5a70"
|
checksum = "a6176eae26dd70d0c919749377897b54a9276bd7061339665dd68777926b5a70"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"matchers",
|
"matchers",
|
||||||
"nu-ansi-term",
|
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"regex",
|
"regex",
|
||||||
"sharded-slab",
|
"sharded-slab",
|
||||||
"smallvec",
|
|
||||||
"thread_local",
|
"thread_local",
|
||||||
"tracing",
|
"tracing",
|
||||||
"tracing-core",
|
"tracing-core",
|
||||||
"tracing-log",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -4616,12 +4583,6 @@ dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "valuable"
|
|
||||||
version = "0.1.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "vcpkg"
|
name = "vcpkg"
|
||||||
version = "0.2.15"
|
version = "0.2.15"
|
||||||
|
|
13
Cargo.toml
13
Cargo.toml
|
@ -107,26 +107,21 @@ ethers-contract = { version = "^1.0.0", default-features = false, path = "./ethe
|
||||||
"abigen",
|
"abigen",
|
||||||
"eip712",
|
"eip712",
|
||||||
] }
|
] }
|
||||||
ethers-providers = { version = "^1.0.0", default-features = false, path = "./ethers-providers", features = [
|
|
||||||
"ws",
|
|
||||||
] }
|
|
||||||
tempfile = "3.4.0"
|
|
||||||
|
|
||||||
[target.'cfg(target_family = "unix")'.dev-dependencies]
|
|
||||||
ethers-providers = { version = "^1.0.0", default-features = false, path = "./ethers-providers", features = [
|
ethers-providers = { version = "^1.0.0", default-features = false, path = "./ethers-providers", features = [
|
||||||
"ws",
|
"ws",
|
||||||
"ipc",
|
"ipc",
|
||||||
] }
|
] }
|
||||||
|
|
||||||
|
bytes = "1.4.0"
|
||||||
eyre = "0.6"
|
eyre = "0.6"
|
||||||
|
hex = "0.4.3"
|
||||||
rand = "0.8.5"
|
rand = "0.8.5"
|
||||||
serde = { version = "1.0.124", features = ["derive"] }
|
serde = { version = "1.0.124", features = ["derive"] }
|
||||||
serde_json = "1.0.64"
|
serde_json = "1.0.64"
|
||||||
|
tempfile = "3.3.0"
|
||||||
tokio = { version = "1.18", features = ["macros", "rt-multi-thread"] }
|
tokio = { version = "1.18", features = ["macros", "rt-multi-thread"] }
|
||||||
hex = "0.4.3"
|
|
||||||
bytes = "1.4.0"
|
|
||||||
|
|
||||||
# profile for the wasm example
|
# profile for the wasm example
|
||||||
[profile.release.package.ethers-wasm]
|
[profile.release.package.ethers-wasm]
|
||||||
# Tell `rustc` to optimize for small code size.
|
# Tell `rustc` to optimize for small code size.
|
||||||
opt-level = "s"
|
opt-level = "s"
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,89 @@
|
||||||
|
# This section is considered when running `cargo deny check advisories`
|
||||||
|
# More documentation for the advisories section can be found here:
|
||||||
|
# https://embarkstudios.github.io/cargo-deny/checks/advisories/cfg.html
|
||||||
|
[advisories]
|
||||||
|
vulnerability = "deny"
|
||||||
|
unmaintained = "warn"
|
||||||
|
unsound = "warn"
|
||||||
|
yanked = "warn"
|
||||||
|
notice = "warn"
|
||||||
|
|
||||||
|
# This section is considered when running `cargo deny check bans`.
|
||||||
|
# More documentation about the 'bans' section can be found here:
|
||||||
|
# https://embarkstudios.github.io/cargo-deny/checks/bans/cfg.html
|
||||||
|
[bans]
|
||||||
|
# Lint level for when multiple versions of the same crate are detected
|
||||||
|
multiple-versions = "warn"
|
||||||
|
# Lint level for when a crate version requirement is `*`
|
||||||
|
wildcards = "allow"
|
||||||
|
highlight = "all"
|
||||||
|
# List of crates to deny
|
||||||
|
deny = [
|
||||||
|
# Each entry the name of a crate and a version range. If version is
|
||||||
|
# not specified, all versions will be matched.
|
||||||
|
#{ name = "ansi_term", version = "=0.11.0" },
|
||||||
|
]
|
||||||
|
# Certain crates/versions that will be skipped when doing duplicate detection.
|
||||||
|
skip = []
|
||||||
|
# Similarly to `skip` allows you to skip certain crates during duplicate
|
||||||
|
# detection. Unlike skip, it also includes the entire tree of transitive
|
||||||
|
# dependencies starting at the specified crate, up to a certain depth, which is
|
||||||
|
# by default infinite
|
||||||
|
skip-tree = []
|
||||||
|
|
||||||
|
[licenses]
|
||||||
|
unlicensed = "deny"
|
||||||
|
# List of explicitly allowed licenses
|
||||||
|
# See https://spdx.org/licenses/ for list of possible licenses
|
||||||
|
# [possible values: any SPDX 3.7 short identifier (+ optional exception)].
|
||||||
|
allow = [
|
||||||
|
"MIT",
|
||||||
|
"Apache-2.0",
|
||||||
|
"Apache-2.0 WITH LLVM-exception",
|
||||||
|
"BSD-2-Clause",
|
||||||
|
"BSD-3-Clause",
|
||||||
|
"ISC",
|
||||||
|
"Unicode-DFS-2016",
|
||||||
|
"OpenSSL",
|
||||||
|
"Unlicense",
|
||||||
|
]
|
||||||
|
|
||||||
|
# Allow 1 or more licenses on a per-crate basis, so that particular licenses
|
||||||
|
# aren't accepted for every possible crate as with the normal allow list
|
||||||
|
exceptions = [
|
||||||
|
# CC0 is a permissive license but somewhat unclear status for source code
|
||||||
|
# so we prefer to not have dependencies using it
|
||||||
|
# https://tldrlegal.com/license/creative-commons-cc0-1.0-universal
|
||||||
|
{ allow = ["CC0-1.0"], name = "secp256k1" },
|
||||||
|
{ allow = ["CC0-1.0"], name = "secp256k1-sys" },
|
||||||
|
{ allow = ["CC0-1.0"], name = "tiny-keccak" },
|
||||||
|
{ allow = ["CC0-1.0"], name = "more-asserts" },
|
||||||
|
|
||||||
|
# TODO: ethers transitive deps
|
||||||
|
{ allow = ["GPL-3.0"], name = "fastrlp" },
|
||||||
|
{ allow = ["GPL-3.0"], name = "fastrlp-derive" },
|
||||||
|
]
|
||||||
|
#copyleft = "deny"
|
||||||
|
|
||||||
|
# See note in unicode-ident's readme!
|
||||||
|
[[licenses.clarify]]
|
||||||
|
name = "unicode-ident"
|
||||||
|
version = "*"
|
||||||
|
expression = "(MIT OR Apache-2.0) AND Unicode-DFS-2016"
|
||||||
|
license-files = [{ path = "LICENSE-UNICODE", hash = 0x3fb01745 }]
|
||||||
|
[[licenses.clarify]]
|
||||||
|
name = "ring"
|
||||||
|
version = "*"
|
||||||
|
expression = "OpenSSL"
|
||||||
|
license-files = [{ path = "LICENSE", hash = 0xbd0eed23 }]
|
||||||
|
|
||||||
|
# This section is considered when running `cargo deny check sources`.
|
||||||
|
# More documentation about the 'sources' section can be found here:
|
||||||
|
# https://embarkstudios.github.io/cargo-deny/checks/sources/cfg.html
|
||||||
|
[sources]
|
||||||
|
# Lint level for what to happen when a crate from a crate registry that is not
|
||||||
|
# in the allow list is encountered
|
||||||
|
unknown-registry = "warn"
|
||||||
|
# Lint level for what to happen when a crate from a git repository that is not
|
||||||
|
# in the allow list is encountered
|
||||||
|
unknown-git = "allow"
|
|
@ -45,9 +45,11 @@ tokio = { version = "1.18", default-features = false, features = ["macros"] }
|
||||||
default = ["abigen"]
|
default = ["abigen"]
|
||||||
|
|
||||||
eip712 = ["ethers-derive-eip712", "ethers-core/eip712"]
|
eip712 = ["ethers-derive-eip712", "ethers-core/eip712"]
|
||||||
abigen = ["ethers-contract-abigen/reqwest", "ethers-contract-derive"]
|
|
||||||
abigen-offline = ["ethers-contract-abigen", "ethers-contract-derive"]
|
abigen-offline = ["ethers-contract-abigen", "ethers-contract-derive"]
|
||||||
celo = ["legacy", "ethers-core/celo", "ethers-core/celo", "ethers-providers/celo"]
|
abigen = ["abigen-offline", "ethers-contract-abigen/online"]
|
||||||
|
|
||||||
|
celo = ["legacy", "ethers-core/celo", "ethers-providers/celo"]
|
||||||
legacy = []
|
legacy = []
|
||||||
|
|
||||||
rustls = ["ethers-contract-abigen/rustls"]
|
rustls = ["ethers-contract-abigen/rustls"]
|
||||||
|
|
|
@ -34,10 +34,7 @@ regex = "1.6.0"
|
||||||
toml = "0.5.9"
|
toml = "0.5.9"
|
||||||
|
|
||||||
reqwest = { version = "0.11.3", default-features = false, features = ["blocking"], optional = true }
|
reqwest = { version = "0.11.3", default-features = false, features = ["blocking"], optional = true }
|
||||||
tokio = { version = "1.0", default-features = false, features = [
|
tokio = { version = "1.0", default-features = false, features = ["sync"], optional = true }
|
||||||
"rt-multi-thread",
|
|
||||||
"sync",
|
|
||||||
], optional = true }
|
|
||||||
url = { version = "2.3.1", default-features = false, optional = true }
|
url = { version = "2.3.1", default-features = false, optional = true }
|
||||||
|
|
||||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||||
|
|
|
@ -230,19 +230,7 @@ pub(crate) fn event_struct_alias(event_name: &str) -> Ident {
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::Abigen;
|
use crate::Abigen;
|
||||||
use ethers_core::abi::{EventParam, Hash, ParamType};
|
use ethers_core::abi::{EventParam, ParamType};
|
||||||
use proc_macro2::Literal;
|
|
||||||
|
|
||||||
/// Expands a 256-bit `Hash` into a literal representation that can be used with
|
|
||||||
/// quasi-quoting for code generation. We do this to avoid allocating at runtime
|
|
||||||
fn expand_hash(hash: Hash) -> TokenStream {
|
|
||||||
let bytes = hash.as_bytes().iter().copied().map(Literal::u8_unsuffixed);
|
|
||||||
let ethers_core = ethers_core_crate();
|
|
||||||
|
|
||||||
quote! {
|
|
||||||
#ethers_core::types::H256([#( #bytes ),*])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn test_context() -> Context {
|
fn test_context() -> Context {
|
||||||
Context::from_abigen(Abigen::new("TestToken", "[]").unwrap()).unwrap()
|
Context::from_abigen(Abigen::new("TestToken", "[]").unwrap()).unwrap()
|
||||||
|
@ -254,44 +242,30 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[rustfmt::skip]
|
|
||||||
fn expand_transfer_filter_with_alias() {
|
fn expand_transfer_filter_with_alias() {
|
||||||
let event = Event {
|
let event = Event {
|
||||||
name: "Transfer".into(),
|
name: "Transfer".into(),
|
||||||
inputs: vec![
|
inputs: vec![
|
||||||
EventParam {
|
EventParam { name: "from".into(), kind: ParamType::Address, indexed: true },
|
||||||
name: "from".into(),
|
EventParam { name: "to".into(), kind: ParamType::Address, indexed: true },
|
||||||
kind: ParamType::Address,
|
EventParam { name: "amount".into(), kind: ParamType::Uint(256), indexed: false },
|
||||||
indexed: true,
|
|
||||||
},
|
|
||||||
EventParam {
|
|
||||||
name: "to".into(),
|
|
||||||
kind: ParamType::Address,
|
|
||||||
indexed: true,
|
|
||||||
},
|
|
||||||
EventParam {
|
|
||||||
name: "amount".into(),
|
|
||||||
kind: ParamType::Uint(256),
|
|
||||||
indexed: false,
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
anonymous: false,
|
anonymous: false,
|
||||||
};
|
};
|
||||||
let sig = "Transfer(address,address,uint256)";
|
let sig = "Transfer(address,address,uint256)";
|
||||||
let cx = test_context_with_alias(sig, "TransferEvent");
|
let cx = test_context_with_alias(sig, "TransferEvent");
|
||||||
|
#[rustfmt::skip]
|
||||||
assert_quote!(cx.expand_filter(&event), {
|
assert_quote!(cx.expand_filter(&event), {
|
||||||
#[doc = "Gets the contract's `Transfer` event"]
|
#[doc = "Gets the contract's `Transfer` event"]
|
||||||
pub fn transfer_event_filter(
|
pub fn transfer_event_filter(
|
||||||
&self
|
&self
|
||||||
) -> ::ethers_contract::builders::Event<
|
) -> ::ethers_contract::builders::Event<::std::sync::Arc<M>, M, TransferEventFilter>
|
||||||
::std::sync::Arc<M>,
|
{
|
||||||
M,
|
|
||||||
TransferEventFilter,
|
|
||||||
> {
|
|
||||||
self.0.event()
|
self.0.event()
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn expand_transfer_filter() {
|
fn expand_transfer_filter() {
|
||||||
let event = Event {
|
let event = Event {
|
||||||
|
@ -304,10 +278,11 @@ mod tests {
|
||||||
anonymous: false,
|
anonymous: false,
|
||||||
};
|
};
|
||||||
let cx = test_context();
|
let cx = test_context();
|
||||||
|
#[rustfmt::skip]
|
||||||
assert_quote!(cx.expand_filter(&event), {
|
assert_quote!(cx.expand_filter(&event), {
|
||||||
#[doc = "Gets the contract's `Transfer` event"]
|
#[doc = "Gets the contract's `Transfer` event"]
|
||||||
pub fn transfer_filter(
|
pub fn transfer_filter(
|
||||||
&self,
|
&self
|
||||||
) -> ::ethers_contract::builders::Event<::std::sync::Arc<M>, M, TransferFilter>
|
) -> ::ethers_contract::builders::Event<::std::sync::Arc<M>, M, TransferFilter>
|
||||||
{
|
{
|
||||||
self.0.event()
|
self.0.event()
|
||||||
|
@ -406,20 +381,4 @@ mod tests {
|
||||||
struct FooAliasedFilter(pub bool, pub ::ethers_core::types::Address);
|
struct FooAliasedFilter(pub bool, pub ::ethers_core::types::Address);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
#[rustfmt::skip]
|
|
||||||
fn expand_hash_value() {
|
|
||||||
assert_quote!(
|
|
||||||
expand_hash(
|
|
||||||
"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f".parse().unwrap()
|
|
||||||
),
|
|
||||||
{
|
|
||||||
::ethers_core::types::H256([
|
|
||||||
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
|
|
||||||
16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31
|
|
||||||
])
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,16 +23,16 @@ pub enum Source {
|
||||||
Local(PathBuf),
|
Local(PathBuf),
|
||||||
|
|
||||||
/// An address of a smart contract address verified at a supported blockchain explorer.
|
/// An address of a smart contract address verified at a supported blockchain explorer.
|
||||||
#[cfg(feature = "online")]
|
#[cfg(all(feature = "online", not(target_arch = "wasm32")))]
|
||||||
Explorer(Explorer, ethers_core::types::Address),
|
Explorer(Explorer, ethers_core::types::Address),
|
||||||
|
|
||||||
/// The package identifier of an npm package with a path to a Truffle artifact or ABI to be
|
/// The package identifier of an npm package with a path to a Truffle artifact or ABI to be
|
||||||
/// retrieved from `unpkg.io`.
|
/// retrieved from `unpkg.io`.
|
||||||
#[cfg(feature = "online")]
|
#[cfg(all(feature = "online", not(target_arch = "wasm32")))]
|
||||||
Npm(String),
|
Npm(String),
|
||||||
|
|
||||||
/// An ABI to be retrieved over HTTP(S).
|
/// An ABI to be retrieved over HTTP(S).
|
||||||
#[cfg(feature = "online")]
|
#[cfg(all(feature = "online", not(target_arch = "wasm32")))]
|
||||||
Http(url::Url),
|
Http(url::Url),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -77,12 +77,12 @@ impl Source {
|
||||||
match source.chars().next() {
|
match source.chars().next() {
|
||||||
Some('[' | '{') => Ok(Self::String(source.to_string())),
|
Some('[' | '{') => Ok(Self::String(source.to_string())),
|
||||||
|
|
||||||
#[cfg(not(feature = "online"))]
|
#[cfg(any(not(feature = "online"), target_arch = "wasm32"))]
|
||||||
_ => Ok(Self::local(source)?),
|
_ => Ok(Self::local(source)?),
|
||||||
|
|
||||||
#[cfg(feature = "online")]
|
#[cfg(all(feature = "online", not(target_arch = "wasm32")))]
|
||||||
Some('/') => Self::local(source),
|
Some('/') => Self::local(source),
|
||||||
#[cfg(feature = "online")]
|
#[cfg(all(feature = "online", not(target_arch = "wasm32")))]
|
||||||
_ => Self::parse_online(source),
|
_ => Self::parse_online(source),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -120,16 +120,8 @@ impl Source {
|
||||||
Self::Local(path) => Ok(fs::read_to_string(path)?),
|
Self::Local(path) => Ok(fs::read_to_string(path)?),
|
||||||
Self::String(abi) => Ok(abi.clone()),
|
Self::String(abi) => Ok(abi.clone()),
|
||||||
|
|
||||||
#[cfg(feature = "online")]
|
#[cfg(all(feature = "online", not(target_arch = "wasm32")))]
|
||||||
_ => {
|
_ => self.get_online(),
|
||||||
cfg_if::cfg_if! {
|
|
||||||
if #[cfg(target_arch = "wasm32")] {
|
|
||||||
Err(eyre::eyre!("Online ABI locations are currently unsupported for WASM builds."))
|
|
||||||
} else {
|
|
||||||
self.get_online()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -148,7 +140,7 @@ mod tests {
|
||||||
env!("CARGO_MANIFEST_DIR"),
|
env!("CARGO_MANIFEST_DIR"),
|
||||||
"/../tests/solidity-contracts/console.json"
|
"/../tests/solidity-contracts/console.json"
|
||||||
);
|
);
|
||||||
let exp = Source::Local(Path::new(rel).canonicalize().unwrap());
|
let exp = Source::Local(dunce::canonicalize(Path::new(rel)).unwrap());
|
||||||
assert_eq!(Source::parse(rel).unwrap(), exp);
|
assert_eq!(Source::parse(rel).unwrap(), exp);
|
||||||
assert_eq!(Source::parse(abs).unwrap(), exp);
|
assert_eq!(Source::parse(abs).unwrap(), exp);
|
||||||
assert_eq!(Source::parse(abs_url).unwrap(), exp);
|
assert_eq!(Source::parse(abs_url).unwrap(), exp);
|
||||||
|
|
|
@ -67,8 +67,7 @@ impl Explorer {
|
||||||
let chain = self.chain();
|
let chain = self.chain();
|
||||||
let client = match api_key {
|
let client = match api_key {
|
||||||
Some(api_key) => Client::new(chain, api_key),
|
Some(api_key) => Client::new(chain, api_key),
|
||||||
None => Client::new_from_env(chain)
|
None => Client::new_from_opt_env(chain),
|
||||||
.or_else(|_| Client::builder().chain(chain).and_then(|b| b.build())),
|
|
||||||
}?;
|
}?;
|
||||||
Ok(client)
|
Ok(client)
|
||||||
}
|
}
|
||||||
|
@ -207,6 +206,11 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn get_mainnet_contract() {
|
fn get_mainnet_contract() {
|
||||||
|
// Skip if ETHERSCAN_API_KEY is not set
|
||||||
|
if std::env::var("ETHERSCAN_API_KEY").is_err() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
let source = Source::parse("mainnet:0x6b175474e89094c44da98b954eedeac495271d0f").unwrap();
|
let source = Source::parse("mainnet:0x6b175474e89094c44da98b954eedeac495271d0f").unwrap();
|
||||||
let abi = source.get().unwrap();
|
let abi = source.get().unwrap();
|
||||||
assert!(!abi.is_empty());
|
assert!(!abi.is_empty());
|
||||||
|
|
|
@ -5,6 +5,9 @@
|
||||||
/// If the expanded source does not match the quoted source.
|
/// If the expanded source does not match the quoted source.
|
||||||
macro_rules! assert_quote {
|
macro_rules! assert_quote {
|
||||||
($ex:expr, { $($t:tt)* } $(,)?) => {
|
($ex:expr, { $($t:tt)* } $(,)?) => {
|
||||||
assert_eq!($ex.to_string(), quote::quote! { $($t)* }.to_string())
|
assert_eq!(
|
||||||
|
$ex.to_string(),
|
||||||
|
quote::quote! { $($t)* }.to_string(),
|
||||||
|
)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,17 +1,9 @@
|
||||||
#![cfg(not(target_arch = "wasm32"))]
|
use ethers_contract::{Contract, ContractFactory, EthEvent};
|
||||||
#![allow(dead_code)]
|
use ethers_core::{
|
||||||
|
abi::Abi,
|
||||||
#[cfg(feature = "abigen")]
|
types::{Address, Bytes},
|
||||||
use ethers_core::types::Address;
|
utils::AnvilInstance,
|
||||||
|
};
|
||||||
#[cfg(feature = "abigen")]
|
|
||||||
use ethers_contract::EthEvent;
|
|
||||||
|
|
||||||
#[cfg(feature = "abigen")]
|
|
||||||
mod derive;
|
|
||||||
|
|
||||||
use ethers_contract::{Contract, ContractFactory};
|
|
||||||
use ethers_core::{abi::Abi, types::Bytes, utils::AnvilInstance};
|
|
||||||
use ethers_providers::{Http, Middleware, Provider};
|
use ethers_providers::{Http, Middleware, Provider};
|
||||||
use ethers_solc::Solc;
|
use ethers_solc::Solc;
|
||||||
use std::{convert::TryFrom, sync::Arc, time::Duration};
|
use std::{convert::TryFrom, sync::Arc, time::Duration};
|
|
@ -1,24 +1,18 @@
|
||||||
#![allow(unused)]
|
use crate::common::*;
|
||||||
|
|
||||||
pub use crate::common::*;
|
|
||||||
use ethers_contract::{abigen, ContractFactory, EthAbiType};
|
|
||||||
use ethers_core::types::{Filter, ValueOrArray, H256};
|
|
||||||
|
|
||||||
#[cfg(not(feature = "celo"))]
|
|
||||||
mod eth_tests {
|
|
||||||
use super::*;
|
|
||||||
use ethers_contract::{
|
use ethers_contract::{
|
||||||
ContractInstance, EthEvent, LogMeta, Multicall, MulticallError, MulticallVersion,
|
abigen, ContractFactory, ContractInstance, EthAbiType, EthEvent, LogMeta, Multicall,
|
||||||
|
MulticallError, MulticallVersion,
|
||||||
};
|
};
|
||||||
use ethers_core::{
|
use ethers_core::{
|
||||||
abi::{encode, AbiEncode, Detokenize, Token, Tokenizable},
|
abi::{encode, AbiEncode, Token, Tokenizable},
|
||||||
types::{transaction::eip712::Eip712, Address, BlockId, Bytes, H160, I256, U256},
|
types::{
|
||||||
|
transaction::eip712::Eip712, Address, BlockId, Bytes, Filter, ValueOrArray, H160, H256,
|
||||||
|
I256, U256,
|
||||||
|
},
|
||||||
utils::{keccak256, Anvil},
|
utils::{keccak256, Anvil},
|
||||||
};
|
};
|
||||||
use ethers_derive_eip712::*;
|
use ethers_derive_eip712::*;
|
||||||
use ethers_providers::{
|
use ethers_providers::{Http, Middleware, MiddlewareError, Provider, StreamExt};
|
||||||
Http, Middleware, MiddlewareError, PendingTransaction, Provider, StreamExt,
|
|
||||||
};
|
|
||||||
use ethers_signers::{LocalWallet, Signer};
|
use ethers_signers::{LocalWallet, Signer};
|
||||||
use std::{convert::TryFrom, iter::FromIterator, sync::Arc, time::Duration};
|
use std::{convert::TryFrom, iter::FromIterator, sync::Arc, time::Duration};
|
||||||
|
|
||||||
|
@ -46,7 +40,7 @@ mod eth_tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<M: Middleware> std::fmt::Display for MwErr<M> {
|
impl<M: Middleware> std::fmt::Display for MwErr<M> {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, _f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -67,8 +61,8 @@ mod eth_tests {
|
||||||
// this is not a test. It is a compile check. :)
|
// this is not a test. It is a compile check. :)
|
||||||
// It exists to ensure that trait bounds on contract internal behave as
|
// It exists to ensure that trait bounds on contract internal behave as
|
||||||
// expected. It should not be run
|
// expected. It should not be run
|
||||||
fn it_compiles() {
|
fn _it_compiles() {
|
||||||
let (abi, bytecode) = compile_contract("SimpleStorage", "SimpleStorage.sol");
|
let (abi, _bytecode) = compile_contract("SimpleStorage", "SimpleStorage.sol");
|
||||||
|
|
||||||
// launch anvil
|
// launch anvil
|
||||||
let anvil = Anvil::new().spawn();
|
let anvil = Anvil::new().spawn();
|
||||||
|
@ -141,16 +135,14 @@ mod eth_tests {
|
||||||
// need to declare the method first, and only then send it
|
// need to declare the method first, and only then send it
|
||||||
// this is because it internally clones an Arc which would otherwise
|
// this is because it internally clones an Arc which would otherwise
|
||||||
// get immediately dropped
|
// get immediately dropped
|
||||||
let contract_call = contract
|
let contract_call =
|
||||||
.connect(client2.clone())
|
contract.connect(client2.clone()).method::<_, H256>("setValue", "hi".to_owned()).unwrap();
|
||||||
.method::<_, H256>("setValue", "hi".to_owned())
|
|
||||||
.unwrap();
|
|
||||||
let calldata = contract_call.calldata().unwrap();
|
let calldata = contract_call.calldata().unwrap();
|
||||||
let gas_estimate = contract_call.estimate_gas().await.unwrap();
|
let _gas_estimate = contract_call.estimate_gas().await.unwrap();
|
||||||
let contract_call = contract_call.legacy();
|
let contract_call = contract_call.legacy();
|
||||||
let pending_tx = contract_call.send().await.unwrap();
|
let pending_tx = contract_call.send().await.unwrap();
|
||||||
let tx = client.get_transaction(*pending_tx).await.unwrap().unwrap();
|
let tx = client.get_transaction(*pending_tx).await.unwrap().unwrap();
|
||||||
let tx_receipt = pending_tx.await.unwrap().unwrap();
|
let _tx_receipt = pending_tx.await.unwrap().unwrap();
|
||||||
assert_eq!(last_sender.clone().call().await.unwrap(), addr2);
|
assert_eq!(last_sender.clone().call().await.unwrap(), addr2);
|
||||||
assert_eq!(get_value.clone().call().await.unwrap(), "hi");
|
assert_eq!(get_value.clone().call().await.unwrap(), "hi");
|
||||||
assert_eq!(tx.input, calldata);
|
assert_eq!(tx.input, calldata);
|
||||||
|
@ -453,8 +445,7 @@ mod eth_tests {
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let value: String =
|
let value: String = contract.method::<_, String>("getValue", ()).unwrap().call().await.unwrap();
|
||||||
contract.method::<_, String>("getValue", ()).unwrap().call().await.unwrap();
|
|
||||||
assert_eq!(value, "hi");
|
assert_eq!(value, "hi");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -488,23 +479,16 @@ mod eth_tests {
|
||||||
let client4 = connect(&anvil, 3);
|
let client4 = connect(&anvil, 3);
|
||||||
|
|
||||||
// create a factory which will be used to deploy instances of the contract
|
// create a factory which will be used to deploy instances of the contract
|
||||||
let multicall_factory =
|
let multicall_factory = ContractFactory::new(multicall_abi, multicall_bytecode, client.clone());
|
||||||
ContractFactory::new(multicall_abi, multicall_bytecode, client.clone());
|
|
||||||
let simple_factory = ContractFactory::new(abi.clone(), bytecode.clone(), client2.clone());
|
let simple_factory = ContractFactory::new(abi.clone(), bytecode.clone(), client2.clone());
|
||||||
let not_so_simple_factory =
|
let not_so_simple_factory =
|
||||||
ContractFactory::new(not_so_simple_abi, not_so_simple_bytecode, client3.clone());
|
ContractFactory::new(not_so_simple_abi, not_so_simple_bytecode, client3.clone());
|
||||||
|
|
||||||
let multicall_contract =
|
let multicall_contract = multicall_factory.deploy(()).unwrap().legacy().send().await.unwrap();
|
||||||
multicall_factory.deploy(()).unwrap().legacy().send().await.unwrap();
|
|
||||||
let addr = multicall_contract.address();
|
let addr = multicall_contract.address();
|
||||||
|
|
||||||
let simple_contract = simple_factory
|
let simple_contract =
|
||||||
.deploy("the first one".to_string())
|
simple_factory.deploy("the first one".to_string()).unwrap().legacy().send().await.unwrap();
|
||||||
.unwrap()
|
|
||||||
.legacy()
|
|
||||||
.send()
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
let not_so_simple_contract = not_so_simple_factory
|
let not_so_simple_contract = not_so_simple_factory
|
||||||
.deploy("the second one".to_string())
|
.deploy("the second one".to_string())
|
||||||
.unwrap()
|
.unwrap()
|
||||||
|
@ -534,8 +518,7 @@ mod eth_tests {
|
||||||
|
|
||||||
// get the calls for `value` and `last_sender` for both SimpleStorage contracts
|
// get the calls for `value` and `last_sender` for both SimpleStorage contracts
|
||||||
let value = simple_contract.method::<_, String>("getValue", ()).unwrap();
|
let value = simple_contract.method::<_, String>("getValue", ()).unwrap();
|
||||||
let value2 =
|
let value2 = not_so_simple_contract.method::<_, (String, Address)>("getValues", ()).unwrap();
|
||||||
not_so_simple_contract.method::<_, (String, Address)>("getValues", ()).unwrap();
|
|
||||||
let last_sender = simple_contract.method::<_, Address>("lastSender", ()).unwrap();
|
let last_sender = simple_contract.method::<_, Address>("lastSender", ()).unwrap();
|
||||||
let last_sender2 = not_so_simple_contract.method::<_, Address>("lastSender", ()).unwrap();
|
let last_sender2 = not_so_simple_contract.method::<_, Address>("lastSender", ()).unwrap();
|
||||||
|
|
||||||
|
@ -639,8 +622,7 @@ mod eth_tests {
|
||||||
|
|
||||||
multicall.add_calls(
|
multicall.add_calls(
|
||||||
false,
|
false,
|
||||||
std::iter::repeat(simple_contract.method::<_, String>("getValue", ()).unwrap())
|
std::iter::repeat(simple_contract.method::<_, String>("getValue", ()).unwrap()).take(17),
|
||||||
.take(17),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
let tokens = multicall.call_raw().await.unwrap();
|
let tokens = multicall.call_raw().await.unwrap();
|
||||||
|
@ -682,14 +664,10 @@ mod eth_tests {
|
||||||
.connect(client3.clone())
|
.connect(client3.clone())
|
||||||
.method::<_, H256>("setValue", ("this reverted".to_owned(), true))
|
.method::<_, H256>("setValue", ("this reverted".to_owned(), true))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let get_value_call = reverting_contract
|
let get_value_call =
|
||||||
.connect(client2.clone())
|
reverting_contract.connect(client2.clone()).method::<_, String>("getValue", false).unwrap();
|
||||||
.method::<_, String>("getValue", false)
|
let get_value_reverting_call =
|
||||||
.unwrap();
|
reverting_contract.connect(client.clone()).method::<_, String>("getValue", true).unwrap();
|
||||||
let get_value_reverting_call = reverting_contract
|
|
||||||
.connect(client.clone())
|
|
||||||
.method::<_, String>("getValue", true)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
// .send reverts
|
// .send reverts
|
||||||
// don't allow revert
|
// don't allow revert
|
||||||
|
@ -783,7 +761,7 @@ mod eth_tests {
|
||||||
|
|
||||||
// string revert
|
// string revert
|
||||||
let string_revert =
|
let string_revert =
|
||||||
reverting_contract.method::<_, H256>("stringRevert", ("String".to_string())).unwrap();
|
reverting_contract.method::<_, H256>("stringRevert", "String".to_string()).unwrap();
|
||||||
multicall.clear_calls().add_call(string_revert, true);
|
multicall.clear_calls().add_call(string_revert, true);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
multicall.call::<(String,)>().await.unwrap_err().as_revert().unwrap()[4..],
|
multicall.call::<(String,)>().await.unwrap_err().as_revert().unwrap()[4..],
|
||||||
|
@ -799,9 +777,8 @@ mod eth_tests {
|
||||||
);
|
);
|
||||||
|
|
||||||
// custom error with data revert
|
// custom error with data revert
|
||||||
let custom_error_with_data = reverting_contract
|
let custom_error_with_data =
|
||||||
.method::<_, H256>("customErrorWithData", ("Data".to_string()))
|
reverting_contract.method::<_, H256>("customErrorWithData", "Data".to_string()).unwrap();
|
||||||
.unwrap();
|
|
||||||
multicall.clear_calls().add_call(custom_error_with_data, true);
|
multicall.clear_calls().add_call(custom_error_with_data, true);
|
||||||
let err = multicall.call::<(Bytes,)>().await.unwrap_err();
|
let err = multicall.call::<(Bytes,)>().await.unwrap_err();
|
||||||
let bytes = err.as_revert().unwrap();
|
let bytes = err.as_revert().unwrap();
|
||||||
|
@ -948,4 +925,3 @@ mod eth_tests {
|
||||||
|
|
||||||
assert!(verify, "typed data signature failed!");
|
assert!(verify, "typed data signature failed!");
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
|
@ -6,9 +6,8 @@ use std::{
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[tokio::test]
|
fn _contract_call_into_future_is_send() {
|
||||||
async fn contract_call_into_future_is_send() {
|
abigen!(DsProxyFactory, "./../ethers-middleware/contracts/DSProxyFactory.json");
|
||||||
abigen!(DsProxyFactory, "ethers-middleware/contracts/DsProxyFactory.json");
|
|
||||||
let (provider, _) = Provider::mocked();
|
let (provider, _) = Provider::mocked();
|
||||||
let client = Arc::new(provider);
|
let client = Arc::new(provider);
|
||||||
let contract = DsProxyFactory::new(Address::zero(), client);
|
let contract = DsProxyFactory::new(Address::zero(), client);
|
||||||
|
|
|
@ -261,8 +261,9 @@ fn can_derive_indexed_and_anonymous_attribute() {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn can_generate_ethevent_from_json() {
|
fn can_generate_ethevent_from_json() {
|
||||||
abigen!(DsProxyFactory,
|
abigen!(
|
||||||
"ethers-middleware/contracts/DsProxyFactory.json",
|
DsProxyFactory,
|
||||||
|
"./../ethers-middleware/contracts/DSProxyFactory.json",
|
||||||
methods {
|
methods {
|
||||||
build(address) as build_with_owner;
|
build(address) as build_with_owner;
|
||||||
}
|
}
|
||||||
|
@ -399,7 +400,7 @@ fn eth_display_works() {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn eth_display_works_for_human_readable() {
|
fn eth_display_works_for_human_readable() {
|
||||||
ethers_contract::abigen!(
|
abigen!(
|
||||||
HevmConsole,
|
HevmConsole,
|
||||||
r#"[
|
r#"[
|
||||||
event log(string)
|
event log(string)
|
|
@ -1,10 +1,14 @@
|
||||||
// #![allow(clippy::extra_unused_type_parameters)]
|
#![allow(clippy::extra_unused_type_parameters)]
|
||||||
|
#![cfg(feature = "abigen")]
|
||||||
|
|
||||||
#[cfg(feature = "abigen")]
|
|
||||||
mod abigen;
|
mod abigen;
|
||||||
pub(crate) mod common;
|
|
||||||
|
|
||||||
#[cfg(feature = "abigen")]
|
mod derive;
|
||||||
mod contract;
|
|
||||||
|
|
||||||
mod contract_call;
|
mod contract_call;
|
||||||
|
|
||||||
|
#[cfg(all(not(target_arch = "wasm32"), not(feature = "celo")))]
|
||||||
|
mod common;
|
||||||
|
|
||||||
|
#[cfg(all(not(target_arch = "wasm32"), not(feature = "celo")))]
|
||||||
|
mod contract;
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
#![allow(clippy::extra_unused_type_parameters)]
|
||||||
|
|
||||||
use ethers_contract_derive::EthAbiType;
|
use ethers_contract_derive::EthAbiType;
|
||||||
use ethers_core::{
|
use ethers_core::{
|
||||||
types::{
|
types::{
|
||||||
|
|
|
@ -252,8 +252,8 @@ impl Chain {
|
||||||
Dev | AnvilHardhat => 200,
|
Dev | AnvilHardhat => 200,
|
||||||
Celo | CeloAlfajores | CeloBaklava => 5_000,
|
Celo | CeloAlfajores | CeloBaklava => 5_000,
|
||||||
FilecoinHyperspaceTestnet | FilecoinMainnet => 30_000,
|
FilecoinHyperspaceTestnet | FilecoinMainnet => 30_000,
|
||||||
// Explictly handle all network to make it easier not to forget this match when new
|
|
||||||
// networks are added.
|
// Explicitly exhaustive. See NB above.
|
||||||
Morden | Ropsten | Rinkeby | Goerli | Kovan | XDai | Chiado | Sepolia | Moonbase |
|
Morden | Ropsten | Rinkeby | Goerli | Kovan | XDai | Chiado | Sepolia | Moonbase |
|
||||||
MoonbeamDev | Optimism | OptimismGoerli | OptimismKovan | Poa | Sokol | Rsk |
|
MoonbeamDev | Optimism | OptimismGoerli | OptimismKovan | Poa | Sokol | Rsk |
|
||||||
EmeraldTestnet => return None,
|
EmeraldTestnet => return None,
|
||||||
|
|
|
@ -95,6 +95,7 @@ pub enum Eip2930RequestError {
|
||||||
pub struct Eip2930TransactionRequest {
|
pub struct Eip2930TransactionRequest {
|
||||||
#[serde(flatten)]
|
#[serde(flatten)]
|
||||||
pub tx: TransactionRequest,
|
pub tx: TransactionRequest,
|
||||||
|
#[serde(rename = "accessList")]
|
||||||
pub access_list: AccessList,
|
pub access_list: AccessList,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
types::{Address, Chain},
|
types::{Address, Chain},
|
||||||
utils::{secret_key_to_address, unused_port},
|
utils::{secret_key_to_address, unused_ports},
|
||||||
};
|
};
|
||||||
use k256::{ecdsa::SigningKey, SecretKey as K256SecretKey};
|
use k256::{ecdsa::SigningKey, SecretKey as K256SecretKey};
|
||||||
use std::{
|
use std::{
|
||||||
|
@ -161,7 +161,7 @@ impl Anvil {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the block-time which will be used when the `anvil` instance is launched.
|
/// Sets the block-time in seconds which will be used when the `anvil` instance is launched.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn block_time<T: Into<u64>>(mut self, block_time: T) -> Self {
|
pub fn block_time<T: Into<u64>>(mut self, block_time: T) -> Self {
|
||||||
self.block_time = Some(block_time.into());
|
self.block_time = Some(block_time.into());
|
||||||
|
@ -223,7 +223,7 @@ impl Anvil {
|
||||||
Command::new("anvil")
|
Command::new("anvil")
|
||||||
};
|
};
|
||||||
cmd.stdout(std::process::Stdio::piped()).stderr(std::process::Stdio::inherit());
|
cmd.stdout(std::process::Stdio::piped()).stderr(std::process::Stdio::inherit());
|
||||||
let port = if let Some(port) = self.port { port } else { unused_port() };
|
let port = if let Some(port) = self.port { port } else { unused_ports::<1>()[0] };
|
||||||
cmd.arg("-p").arg(port.to_string());
|
cmd.arg("-p").arg(port.to_string());
|
||||||
|
|
||||||
if let Some(mnemonic) = self.mnemonic {
|
if let Some(mnemonic) = self.mnemonic {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
types::Address,
|
types::Address,
|
||||||
utils::{secret_key_to_address, unused_port},
|
utils::{secret_key_to_address, unused_ports},
|
||||||
};
|
};
|
||||||
use k256::{ecdsa::SigningKey, SecretKey as K256SecretKey};
|
use k256::{ecdsa::SigningKey, SecretKey as K256SecretKey};
|
||||||
use std::{
|
use std::{
|
||||||
|
@ -157,7 +157,7 @@ impl Ganache {
|
||||||
pub fn spawn(self) -> GanacheInstance {
|
pub fn spawn(self) -> GanacheInstance {
|
||||||
let mut cmd = Command::new("ganache-cli");
|
let mut cmd = Command::new("ganache-cli");
|
||||||
cmd.stdout(std::process::Stdio::piped());
|
cmd.stdout(std::process::Stdio::piped());
|
||||||
let port = if let Some(port) = self.port { port } else { unused_port() };
|
let port = if let Some(port) = self.port { port } else { unused_ports::<1>()[0] };
|
||||||
cmd.arg("-p").arg(port.to_string());
|
cmd.arg("-p").arg(port.to_string());
|
||||||
|
|
||||||
if let Some(mnemonic) = self.mnemonic {
|
if let Some(mnemonic) = self.mnemonic {
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
use k256::ecdsa::SigningKey;
|
use super::{unused_ports, CliqueConfig, Genesis};
|
||||||
|
|
||||||
use super::{unused_port, Genesis};
|
|
||||||
use crate::{
|
use crate::{
|
||||||
types::{Bytes, H256},
|
types::{Bytes, H256},
|
||||||
utils::secret_key_to_address,
|
utils::secret_key_to_address,
|
||||||
};
|
};
|
||||||
|
use k256::ecdsa::SigningKey;
|
||||||
use std::{
|
use std::{
|
||||||
fs::{create_dir, File},
|
fs::{create_dir, File},
|
||||||
io::{BufRead, BufReader},
|
io::{BufRead, BufReader},
|
||||||
|
@ -15,10 +14,10 @@ use std::{
|
||||||
use tempfile::tempdir;
|
use tempfile::tempdir;
|
||||||
|
|
||||||
/// How long we will wait for geth to indicate that it is ready.
|
/// How long we will wait for geth to indicate that it is ready.
|
||||||
const GETH_STARTUP_TIMEOUT_MILLIS: u64 = 10_000;
|
const GETH_STARTUP_TIMEOUT: Duration = Duration::from_secs(10);
|
||||||
|
|
||||||
/// Timeout for waiting for geth to add a peer.
|
/// Timeout for waiting for geth to add a peer.
|
||||||
const GETH_DIAL_LOOP_TIMEOUT: Duration = Duration::new(20, 0);
|
const GETH_DIAL_LOOP_TIMEOUT: Duration = Duration::from_secs(20);
|
||||||
|
|
||||||
/// The exposed APIs
|
/// The exposed APIs
|
||||||
const API: &str = "eth,net,web3,txpool,admin,personal,miner,debug";
|
const API: &str = "eth,net,web3,txpool,admin,personal,miner,debug";
|
||||||
|
@ -354,13 +353,21 @@ impl Geth {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Consumes the builder and spawns `geth` with stdout redirected
|
/// Consumes the builder and spawns `geth` with stdout redirected to /dev/null.
|
||||||
/// to /dev/null.
|
#[must_use]
|
||||||
|
#[track_caller]
|
||||||
pub fn spawn(mut self) -> GethInstance {
|
pub fn spawn(mut self) -> GethInstance {
|
||||||
let mut cmd =
|
let bin_path = match self.program.as_ref() {
|
||||||
if let Some(ref prg) = self.program { Command::new(prg) } else { Command::new(GETH) };
|
Some(bin) => bin.as_os_str(),
|
||||||
|
None => GETH.as_ref(),
|
||||||
|
}
|
||||||
|
.to_os_string();
|
||||||
|
let mut cmd = Command::new(&bin_path);
|
||||||
// geth uses stderr for its logs
|
// geth uses stderr for its logs
|
||||||
cmd.stderr(Stdio::piped());
|
cmd.stderr(Stdio::piped());
|
||||||
|
|
||||||
|
let mut unused_ports = unused_ports::<3>().into_iter();
|
||||||
|
let mut unused_port = || unused_ports.next().unwrap();
|
||||||
let port = if let Some(port) = self.port { port } else { unused_port() };
|
let port = if let Some(port) = self.port { port } else { unused_port() };
|
||||||
let authrpc_port = if let Some(port) = self.authrpc_port { port } else { unused_port() };
|
let authrpc_port = if let Some(port) = self.authrpc_port { port } else { unused_port() };
|
||||||
|
|
||||||
|
@ -390,7 +397,6 @@ impl Geth {
|
||||||
// use geth init to initialize the datadir if the genesis exists
|
// use geth init to initialize the datadir if the genesis exists
|
||||||
if let Some(ref mut genesis) = self.genesis {
|
if let Some(ref mut genesis) = self.genesis {
|
||||||
if is_clique {
|
if is_clique {
|
||||||
use super::CliqueConfig;
|
|
||||||
// set up a clique config with an instant sealing period and short (8 block) epoch
|
// set up a clique config with an instant sealing period and short (8 block) epoch
|
||||||
let clique_config = CliqueConfig { period: Some(0), epoch: Some(8) };
|
let clique_config = CliqueConfig { period: Some(0), epoch: Some(8) };
|
||||||
genesis.config.clique = Some(clique_config);
|
genesis.config.clique = Some(clique_config);
|
||||||
|
@ -440,7 +446,7 @@ impl Geth {
|
||||||
serde_json::to_writer_pretty(&mut file, &genesis)
|
serde_json::to_writer_pretty(&mut file, &genesis)
|
||||||
.expect("could not write genesis to file");
|
.expect("could not write genesis to file");
|
||||||
|
|
||||||
let mut init_cmd = Command::new(GETH);
|
let mut init_cmd = Command::new(bin_path);
|
||||||
if let Some(ref data_dir) = self.data_dir {
|
if let Some(ref data_dir) = self.data_dir {
|
||||||
init_cmd.arg("--datadir").arg(data_dir);
|
init_cmd.arg("--datadir").arg(data_dir);
|
||||||
}
|
}
|
||||||
|
@ -514,11 +520,11 @@ impl Geth {
|
||||||
let mut http_started = false;
|
let mut http_started = false;
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
if start + Duration::from_millis(GETH_STARTUP_TIMEOUT_MILLIS) <= Instant::now() {
|
if start + GETH_STARTUP_TIMEOUT <= Instant::now() {
|
||||||
panic!("Timed out waiting for geth to start. Is geth installed?")
|
panic!("Timed out waiting for geth to start. Is geth installed?")
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut line = String::new();
|
let mut line = String::with_capacity(120);
|
||||||
reader.read_line(&mut line).expect("Failed to read line from geth process");
|
reader.read_line(&mut line).expect("Failed to read line from geth process");
|
||||||
|
|
||||||
if matches!(self.mode, GethMode::NonDev(_)) && line.contains("Started P2P networking") {
|
if matches!(self.mode, GethMode::NonDev(_)) && line.contains("Started P2P networking") {
|
||||||
|
|
|
@ -624,18 +624,24 @@ fn base_fee_surged(base_fee_per_gas: U256) -> U256 {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A bit of hack to find an unused TCP port.
|
/// A bit of hack to find unused TCP ports.
|
||||||
///
|
///
|
||||||
/// Does not guarantee that the given port is unused after the function exists, just that it was
|
/// Does not guarantee that the given port is unused after the function exists, just that it was
|
||||||
/// unused before the function started (i.e., it does not reserve a port).
|
/// unused before the function started (i.e., it does not reserve a port).
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
pub(crate) fn unused_port() -> u16 {
|
pub(crate) fn unused_ports<const N: usize>() -> [u16; N] {
|
||||||
let listener = std::net::TcpListener::bind("127.0.0.1:0")
|
use std::net::{SocketAddr, TcpListener};
|
||||||
.expect("Failed to create TCP listener to find unused port");
|
|
||||||
|
|
||||||
let local_addr =
|
std::array::from_fn(|_| {
|
||||||
listener.local_addr().expect("Failed to read TCP listener local_addr to find unused port");
|
let addr = SocketAddr::from(([127, 0, 0, 1], 0));
|
||||||
local_addr.port()
|
TcpListener::bind(addr).expect("Failed to create TCP listener to find unused port")
|
||||||
|
})
|
||||||
|
.map(|listener| {
|
||||||
|
listener
|
||||||
|
.local_addr()
|
||||||
|
.expect("Failed to read TCP listener local_addr to find unused port")
|
||||||
|
.port()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|
|
@ -33,11 +33,12 @@ semver = "1.0.16"
|
||||||
getrandom = { version = "0.2", features = ["js"] }
|
getrandom = { version = "0.2", features = ["js"] }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
|
ethers-solc = { version = "^1.0.0", path = "../ethers-solc", default-features = false }
|
||||||
|
|
||||||
tempfile = "3.4.0"
|
tempfile = "3.4.0"
|
||||||
tokio = { version = "1.18", features = ["macros", "rt-multi-thread", "time"] }
|
tokio = { version = "1.18", features = ["macros", "rt-multi-thread", "time"] }
|
||||||
serial_test = "0.10.0"
|
serial_test = "1.0.0"
|
||||||
tracing-subscriber = { version = "0.3", default-features = false, features = ["env-filter", "fmt"] }
|
tracing-subscriber = { version = "0.3", default-features = false, features = ["env-filter", "fmt"] }
|
||||||
ethers-solc = { version = "^1.0.0", path = "../ethers-solc", default-features = false }
|
|
||||||
|
|
||||||
[package.metadata.docs.rs]
|
[package.metadata.docs.rs]
|
||||||
all-features = true
|
all-features = true
|
||||||
|
|
|
@ -733,202 +733,3 @@ impl Client {
|
||||||
Ok(response.result)
|
Ok(response.result)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use std::time::Duration;
|
|
||||||
|
|
||||||
use serial_test::serial;
|
|
||||||
|
|
||||||
use crate::{tests::run_at_least_duration, Chain};
|
|
||||||
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
#[serial]
|
|
||||||
async fn get_ether_balance_single_success() {
|
|
||||||
run_at_least_duration(Duration::from_millis(250), async {
|
|
||||||
let client = Client::new_from_env(Chain::Mainnet).unwrap();
|
|
||||||
|
|
||||||
let balance = client
|
|
||||||
.get_ether_balance_single(
|
|
||||||
&"0x58eB28A67731c570Ef827C365c89B5751F9E6b0a".parse().unwrap(),
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
balance.unwrap();
|
|
||||||
})
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
#[serial]
|
|
||||||
async fn get_ether_balance_multi_success() {
|
|
||||||
run_at_least_duration(Duration::from_millis(250), async {
|
|
||||||
let client = Client::new_from_env(Chain::Mainnet).unwrap();
|
|
||||||
|
|
||||||
let balances = client
|
|
||||||
.get_ether_balance_multi(
|
|
||||||
&[&"0x58eB28A67731c570Ef827C365c89B5751F9E6b0a".parse().unwrap()],
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
assert!(balances.is_ok());
|
|
||||||
let balances = balances.unwrap();
|
|
||||||
assert_eq!(balances.len(), 1);
|
|
||||||
})
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
#[serial]
|
|
||||||
async fn get_transactions_success() {
|
|
||||||
run_at_least_duration(Duration::from_millis(250), async {
|
|
||||||
let client = Client::new_from_env(Chain::Mainnet).unwrap();
|
|
||||||
|
|
||||||
let txs = client
|
|
||||||
.get_transactions(
|
|
||||||
&"0x4F26FfBe5F04ED43630fdC30A87638d53D0b0876".parse().unwrap(),
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
txs.unwrap();
|
|
||||||
})
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
#[serial]
|
|
||||||
async fn get_internal_transactions_success() {
|
|
||||||
run_at_least_duration(Duration::from_millis(250), async {
|
|
||||||
let client = Client::new_from_env(Chain::Mainnet).unwrap();
|
|
||||||
|
|
||||||
let txs = client
|
|
||||||
.get_internal_transactions(
|
|
||||||
InternalTxQueryOption::ByAddress(
|
|
||||||
"0x2c1ba59d6f58433fb1eaee7d20b26ed83bda51a3".parse().unwrap(),
|
|
||||||
),
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
txs.unwrap();
|
|
||||||
})
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
#[serial]
|
|
||||||
async fn get_internal_transactions_by_tx_hash_success() {
|
|
||||||
run_at_least_duration(Duration::from_millis(250), async {
|
|
||||||
let client = Client::new_from_env(Chain::Mainnet).unwrap();
|
|
||||||
|
|
||||||
let txs = client
|
|
||||||
.get_internal_transactions(
|
|
||||||
InternalTxQueryOption::ByTransactionHash(
|
|
||||||
"0x40eb908387324f2b575b4879cd9d7188f69c8fc9d87c901b9e2daaea4b442170"
|
|
||||||
.parse()
|
|
||||||
.unwrap(),
|
|
||||||
),
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
txs.unwrap();
|
|
||||||
})
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
#[serial]
|
|
||||||
async fn get_erc20_transfer_events_success() {
|
|
||||||
run_at_least_duration(Duration::from_millis(250), async {
|
|
||||||
let client = Client::new_from_env(Chain::Mainnet).unwrap();
|
|
||||||
|
|
||||||
let txs = client
|
|
||||||
.get_erc20_token_transfer_events(
|
|
||||||
TokenQueryOption::ByAddress(
|
|
||||||
"0x4e83362442b8d1bec281594cea3050c8eb01311c".parse().unwrap(),
|
|
||||||
),
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
let tx = txs.get(0).unwrap();
|
|
||||||
assert_eq!(tx.gas_used, 93657u64.into());
|
|
||||||
assert_eq!(tx.nonce, 10u64.into());
|
|
||||||
assert_eq!(tx.block_number, 2228258u64.into());
|
|
||||||
})
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
#[serial]
|
|
||||||
async fn get_erc721_transfer_events_success() {
|
|
||||||
run_at_least_duration(Duration::from_millis(250), async {
|
|
||||||
let client = Client::new_from_env(Chain::Mainnet).unwrap();
|
|
||||||
|
|
||||||
let txs = client
|
|
||||||
.get_erc721_token_transfer_events(
|
|
||||||
TokenQueryOption::ByAddressAndContract(
|
|
||||||
"0x6975be450864c02b4613023c2152ee0743572325".parse().unwrap(),
|
|
||||||
"0x06012c8cf97bead5deae237070f9587f8e7a266d".parse().unwrap(),
|
|
||||||
),
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
txs.unwrap();
|
|
||||||
})
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
#[serial]
|
|
||||||
async fn get_erc1155_transfer_events_success() {
|
|
||||||
run_at_least_duration(Duration::from_millis(250), async {
|
|
||||||
let client = Client::new_from_env(Chain::Mainnet).unwrap();
|
|
||||||
|
|
||||||
let txs = client
|
|
||||||
.get_erc1155_token_transfer_events(
|
|
||||||
TokenQueryOption::ByAddressAndContract(
|
|
||||||
"0x216CD350a4044e7016f14936663e2880Dd2A39d7".parse().unwrap(),
|
|
||||||
"0x495f947276749ce646f68ac8c248420045cb7b5e".parse().unwrap(),
|
|
||||||
),
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
txs.unwrap();
|
|
||||||
})
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
#[serial]
|
|
||||||
async fn get_mined_blocks_success() {
|
|
||||||
run_at_least_duration(Duration::from_millis(250), async {
|
|
||||||
let client = Client::new_from_env(Chain::Mainnet).unwrap();
|
|
||||||
|
|
||||||
let blocks = client
|
|
||||||
.get_mined_blocks(
|
|
||||||
&"0x9dd134d14d1e65f84b706d6f205cd5b1cd03a46b".parse().unwrap(),
|
|
||||||
None,
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
blocks.unwrap();
|
|
||||||
})
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
#[serial]
|
|
||||||
async fn get_avalanche_transactions() {
|
|
||||||
if std::env::var("SNOWTRACE_API_KEY").is_err() {
|
|
||||||
// nothing to do if api key unset
|
|
||||||
return
|
|
||||||
}
|
|
||||||
let client = Client::new_from_env(Chain::Avalanche).unwrap();
|
|
||||||
let txs = client
|
|
||||||
.get_transactions(&"0x1549ea9b546ba9ffb306d78a1e1f304760cc4abf".parse().unwrap(), None)
|
|
||||||
.await;
|
|
||||||
txs.unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -67,60 +67,3 @@ impl Client {
|
||||||
Ok(response.result)
|
Ok(response.result)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
use crate::tests::run_at_least_duration;
|
|
||||||
use ethers_core::types::Chain;
|
|
||||||
use serial_test::serial;
|
|
||||||
use std::time::Duration;
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
#[serial]
|
|
||||||
async fn gas_estimate_success() {
|
|
||||||
run_at_least_duration(Duration::from_millis(250), async {
|
|
||||||
let client = Client::new_from_env(Chain::Mainnet).unwrap();
|
|
||||||
|
|
||||||
let result = client.gas_estimate(2000000000u32.into()).await;
|
|
||||||
|
|
||||||
result.unwrap();
|
|
||||||
})
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
#[serial]
|
|
||||||
async fn gas_estimate_error() {
|
|
||||||
run_at_least_duration(Duration::from_millis(250), async {
|
|
||||||
let client = Client::new_from_env(Chain::Mainnet).unwrap();
|
|
||||||
|
|
||||||
let err = client.gas_estimate(7123189371829732819379218u128.into()).await.unwrap_err();
|
|
||||||
|
|
||||||
assert!(matches!(err, EtherscanError::GasEstimationFailed));
|
|
||||||
})
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
#[serial]
|
|
||||||
async fn gas_oracle_success() {
|
|
||||||
run_at_least_duration(Duration::from_millis(250), async {
|
|
||||||
let client = Client::new_from_env(Chain::Mainnet).unwrap();
|
|
||||||
|
|
||||||
let result = client.gas_oracle().await;
|
|
||||||
|
|
||||||
assert!(result.is_ok());
|
|
||||||
|
|
||||||
let oracle = result.unwrap();
|
|
||||||
|
|
||||||
assert!(oracle.safe_gas_price > 0);
|
|
||||||
assert!(oracle.propose_gas_price > 0);
|
|
||||||
assert!(oracle.fast_gas_price > 0);
|
|
||||||
assert!(oracle.last_block > 0);
|
|
||||||
assert!(oracle.suggested_base_fee > 0.0);
|
|
||||||
assert!(!oracle.gas_used_ratio.is_empty());
|
|
||||||
})
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -108,6 +108,20 @@ impl Client {
|
||||||
Self::new(chain, api_key)
|
Self::new(chain, api_key)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Create a new client with the correct endpoints based on the chain and API key
|
||||||
|
/// from the default environment variable defined in [`Chain`].
|
||||||
|
///
|
||||||
|
/// If the environment variable is not set, create a new client without it.
|
||||||
|
pub fn new_from_opt_env(chain: Chain) -> Result<Self> {
|
||||||
|
match Self::new_from_env(chain) {
|
||||||
|
Ok(client) => Ok(client),
|
||||||
|
Err(EtherscanError::EnvVarNotFound(_)) => {
|
||||||
|
Self::builder().chain(chain).and_then(|c| c.build())
|
||||||
|
}
|
||||||
|
Err(e) => Err(e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Sets the root to the cache dir and the ttl to use
|
/// Sets the root to the cache dir and the ttl to use
|
||||||
pub fn set_cache(&mut self, root: impl Into<PathBuf>, ttl: Duration) -> &mut Self {
|
pub fn set_cache(&mut self, root: impl Into<PathBuf>, ttl: Duration) -> &mut Self {
|
||||||
self.cache = Some(Cache { root: root.into(), ttl });
|
self.cache = Some(Cache { root: root.into(), ttl });
|
||||||
|
@ -305,7 +319,8 @@ impl ClientBuilder {
|
||||||
/// Returns a Client that uses this ClientBuilder configuration.
|
/// Returns a Client that uses this ClientBuilder configuration.
|
||||||
///
|
///
|
||||||
/// # Errors
|
/// # Errors
|
||||||
/// if required fields are missing:
|
///
|
||||||
|
/// If the following required fields are missing:
|
||||||
/// - `etherscan_api_url`
|
/// - `etherscan_api_url`
|
||||||
/// - `etherscan_url`
|
/// - `etherscan_url`
|
||||||
pub fn build(self) -> Result<Client> {
|
pub fn build(self) -> Result<Client> {
|
||||||
|
@ -448,10 +463,6 @@ fn into_url(url: impl IntoUrl) -> std::result::Result<Url, reqwest::Error> {
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::{Client, EtherscanError};
|
use crate::{Client, EtherscanError};
|
||||||
use ethers_core::types::{Address, Chain, H256};
|
use ethers_core::types::{Address, Chain, H256};
|
||||||
use std::{
|
|
||||||
future::Future,
|
|
||||||
time::{Duration, SystemTime},
|
|
||||||
};
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_api_paths() {
|
fn test_api_paths() {
|
||||||
|
@ -461,17 +472,9 @@ mod tests {
|
||||||
assert_eq!(client.block_url(100), "https://goerli.etherscan.io/block/100");
|
assert_eq!(client.block_url(100), "https://goerli.etherscan.io/block/100");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn chain_not_supported() {
|
|
||||||
let err = Client::new_from_env(Chain::Morden).unwrap_err();
|
|
||||||
|
|
||||||
assert!(matches!(err, EtherscanError::ChainNotSupported(_)));
|
|
||||||
assert_eq!(err.to_string(), "Chain morden not supported");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn stringifies_block_url() {
|
fn stringifies_block_url() {
|
||||||
let etherscan = Client::new_from_env(Chain::Mainnet).unwrap();
|
let etherscan = Client::new(Chain::Mainnet, "").unwrap();
|
||||||
let block: u64 = 1;
|
let block: u64 = 1;
|
||||||
let block_url: String = etherscan.block_url(block);
|
let block_url: String = etherscan.block_url(block);
|
||||||
assert_eq!(block_url, format!("https://etherscan.io/block/{block}"));
|
assert_eq!(block_url, format!("https://etherscan.io/block/{block}"));
|
||||||
|
@ -479,7 +482,7 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn stringifies_address_url() {
|
fn stringifies_address_url() {
|
||||||
let etherscan = Client::new_from_env(Chain::Mainnet).unwrap();
|
let etherscan = Client::new(Chain::Mainnet, "").unwrap();
|
||||||
let addr: Address = Address::zero();
|
let addr: Address = Address::zero();
|
||||||
let address_url: String = etherscan.address_url(addr);
|
let address_url: String = etherscan.address_url(addr);
|
||||||
assert_eq!(address_url, format!("https://etherscan.io/address/{addr:?}"));
|
assert_eq!(address_url, format!("https://etherscan.io/address/{addr:?}"));
|
||||||
|
@ -487,7 +490,7 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn stringifies_transaction_url() {
|
fn stringifies_transaction_url() {
|
||||||
let etherscan = Client::new_from_env(Chain::Mainnet).unwrap();
|
let etherscan = Client::new(Chain::Mainnet, "").unwrap();
|
||||||
let tx_hash = H256::zero();
|
let tx_hash = H256::zero();
|
||||||
let tx_url: String = etherscan.transaction_url(tx_hash);
|
let tx_url: String = etherscan.transaction_url(tx_hash);
|
||||||
assert_eq!(tx_url, format!("https://etherscan.io/tx/{tx_hash:?}"));
|
assert_eq!(tx_url, format!("https://etherscan.io/tx/{tx_hash:?}"));
|
||||||
|
@ -495,7 +498,7 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn stringifies_token_url() {
|
fn stringifies_token_url() {
|
||||||
let etherscan = Client::new_from_env(Chain::Mainnet).unwrap();
|
let etherscan = Client::new(Chain::Mainnet, "").unwrap();
|
||||||
let token_hash = Address::zero();
|
let token_hash = Address::zero();
|
||||||
let token_url: String = etherscan.token_url(token_hash);
|
let token_url: String = etherscan.token_url(token_hash);
|
||||||
assert_eq!(token_url, format!("https://etherscan.io/token/{token_hash:?}"));
|
assert_eq!(token_url, format!("https://etherscan.io/token/{token_hash:?}"));
|
||||||
|
@ -506,23 +509,4 @@ mod tests {
|
||||||
let err = Client::new_from_env(Chain::Dev).unwrap_err();
|
let err = Client::new_from_env(Chain::Dev).unwrap_err();
|
||||||
assert!(matches!(err, EtherscanError::LocalNetworksNotSupported));
|
assert!(matches!(err, EtherscanError::LocalNetworksNotSupported));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn check_wrong_etherscan_api_key() {
|
|
||||||
let client = Client::new(Chain::Mainnet, "ABCDEFG").unwrap();
|
|
||||||
let resp = client
|
|
||||||
.contract_source_code("0xBB9bc244D798123fDe783fCc1C72d3Bb8C189413".parse().unwrap())
|
|
||||||
.await
|
|
||||||
.unwrap_err();
|
|
||||||
|
|
||||||
assert!(matches!(resp, EtherscanError::InvalidApiKey));
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn run_at_least_duration(duration: Duration, block: impl Future) {
|
|
||||||
let start = SystemTime::now();
|
|
||||||
block.await;
|
|
||||||
if let Some(sleep) = duration.checked_sub(start.elapsed().unwrap()) {
|
|
||||||
tokio::time::sleep(sleep).await;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -49,82 +49,3 @@ impl Client {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
use crate::{tests::run_at_least_duration, Chain};
|
|
||||||
use serial_test::serial;
|
|
||||||
use std::time::Duration;
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
#[serial]
|
|
||||||
async fn check_contract_execution_status_success() {
|
|
||||||
run_at_least_duration(Duration::from_millis(250), async {
|
|
||||||
let client = Client::new_from_env(Chain::Mainnet).unwrap();
|
|
||||||
|
|
||||||
let status = client
|
|
||||||
.check_contract_execution_status(
|
|
||||||
"0x16197e2a0eacc44c1ebdfddcfcfcafb3538de557c759a66e0ba95263b23d9007",
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
|
|
||||||
status.unwrap();
|
|
||||||
})
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
#[serial]
|
|
||||||
async fn check_contract_execution_status_error() {
|
|
||||||
run_at_least_duration(Duration::from_millis(250), async {
|
|
||||||
let client = Client::new_from_env(Chain::Mainnet).unwrap();
|
|
||||||
|
|
||||||
let err = client
|
|
||||||
.check_contract_execution_status(
|
|
||||||
"0x15f8e5ea1079d9a0bb04a4c58ae5fe7654b5b2b4463375ff7ffb490aa0032f3a",
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.unwrap_err();
|
|
||||||
|
|
||||||
assert!(matches!(err, EtherscanError::ExecutionFailed(_)));
|
|
||||||
assert_eq!(err.to_string(), "Contract execution call failed: Bad jump destination");
|
|
||||||
})
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
#[serial]
|
|
||||||
async fn check_transaction_receipt_status_success() {
|
|
||||||
run_at_least_duration(Duration::from_millis(250), async {
|
|
||||||
let client = Client::new_from_env(Chain::Mainnet).unwrap();
|
|
||||||
|
|
||||||
let success = client
|
|
||||||
.check_transaction_receipt_status(
|
|
||||||
"0x513c1ba0bebf66436b5fed86ab668452b7805593c05073eb2d51d3a52f480a76",
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
|
|
||||||
success.unwrap();
|
|
||||||
})
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
#[serial]
|
|
||||||
async fn check_transaction_receipt_status_failed() {
|
|
||||||
run_at_least_duration(Duration::from_millis(250), async {
|
|
||||||
let client = Client::new_from_env(Chain::Mainnet).unwrap();
|
|
||||||
|
|
||||||
let err = client
|
|
||||||
.check_transaction_receipt_status(
|
|
||||||
"0x21a29a497cb5d4bf514c0cca8d9235844bd0215c8fab8607217546a892fd0758",
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.unwrap_err();
|
|
||||||
|
|
||||||
assert!(matches!(err, EtherscanError::TransactionReceiptFailed));
|
|
||||||
})
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -70,36 +70,7 @@ pub fn deserialize_source_code<'de, D: Deserializer<'de>>(
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::{contract::SourceCodeLanguage, tests::run_at_least_duration};
|
use crate::contract::SourceCodeLanguage;
|
||||||
use semver::{BuildMetadata, Prerelease};
|
|
||||||
use serial_test::serial;
|
|
||||||
use std::time::Duration;
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
#[serial]
|
|
||||||
async fn can_lookup_compiler_version_build_metadata() {
|
|
||||||
run_at_least_duration(Duration::from_millis(250), async {
|
|
||||||
let v = Version::new(0, 8, 13);
|
|
||||||
let version = lookup_compiler_version(&v).await.unwrap();
|
|
||||||
assert_eq!(v.major, version.major);
|
|
||||||
assert_eq!(v.minor, version.minor);
|
|
||||||
assert_eq!(v.patch, version.patch);
|
|
||||||
assert_ne!(version.build, BuildMetadata::EMPTY);
|
|
||||||
assert_eq!(version.pre, Prerelease::EMPTY);
|
|
||||||
})
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
#[serial]
|
|
||||||
async fn errors_on_invalid_solc() {
|
|
||||||
run_at_least_duration(Duration::from_millis(250), async {
|
|
||||||
let v = Version::new(100, 0, 0);
|
|
||||||
let err = lookup_compiler_version(&v).await.unwrap_err();
|
|
||||||
assert!(matches!(err, EtherscanError::MissingSolcVersion(_)));
|
|
||||||
})
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn can_deserialize_address_opt() {
|
fn can_deserialize_address_opt() {
|
||||||
|
|
|
@ -153,55 +153,3 @@ impl Client {
|
||||||
self.post_form(&body).await
|
self.post_form(&body).await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
use crate::{tests::run_at_least_duration, Client};
|
|
||||||
use ethers_core::types::Chain;
|
|
||||||
use ethers_solc::{Project, ProjectPathsConfig};
|
|
||||||
use serial_test::serial;
|
|
||||||
use std::{path::PathBuf, time::Duration};
|
|
||||||
|
|
||||||
#[allow(unused)]
|
|
||||||
fn init_tracing() {
|
|
||||||
tracing_subscriber::fmt()
|
|
||||||
.with_env_filter(tracing_subscriber::EnvFilter::from_default_env())
|
|
||||||
.init();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
#[serial]
|
|
||||||
#[ignore]
|
|
||||||
async fn can_flatten_and_verify_contract() {
|
|
||||||
init_tracing();
|
|
||||||
run_at_least_duration(Duration::from_millis(250), async {
|
|
||||||
let root = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("resources");
|
|
||||||
let paths = ProjectPathsConfig::builder()
|
|
||||||
.sources(&root)
|
|
||||||
.build()
|
|
||||||
.expect("failed to resolve project paths");
|
|
||||||
let project = Project::builder()
|
|
||||||
.paths(paths)
|
|
||||||
.build()
|
|
||||||
.expect("failed to build the project");
|
|
||||||
|
|
||||||
let address = "0x9e744c9115b74834c0f33f4097f40c02a9ac5c33".parse().unwrap();
|
|
||||||
let compiler_version = "v0.5.17+commit.d19bba13";
|
|
||||||
let constructor_args = "0x000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000005f5e1000000000000000000000000000000000000000000000000000000000000000007596179537761700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000035941590000000000000000000000000000000000000000000000000000000000";
|
|
||||||
let contract = project.flatten(&root.join("UniswapExchange.sol")).expect("failed to flatten contract");
|
|
||||||
let contract_name = "UniswapExchange".to_owned();
|
|
||||||
|
|
||||||
let client = Client::new_from_env(Chain::Mainnet).unwrap();
|
|
||||||
|
|
||||||
let contract =
|
|
||||||
VerifyContract::new(address, contract_name, contract, compiler_version.to_string())
|
|
||||||
.constructor_arguments(Some(constructor_args))
|
|
||||||
.optimization(true)
|
|
||||||
.runs(200);
|
|
||||||
let resp = client.submit_contract_verification(&contract).await.expect("failed to send the request");
|
|
||||||
assert_ne!(resp.result, "Error!"); // `Error!` result means that request was malformatted
|
|
||||||
})
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -0,0 +1,168 @@
|
||||||
|
use crate::*;
|
||||||
|
use ethers_etherscan::account::{InternalTxQueryOption, TokenQueryOption};
|
||||||
|
use serial_test::serial;
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
#[serial]
|
||||||
|
async fn get_ether_balance_single_success() {
|
||||||
|
run_with_client(Chain::Mainnet, |client| async move {
|
||||||
|
let balance = client
|
||||||
|
.get_ether_balance_single(
|
||||||
|
&"0x58eB28A67731c570Ef827C365c89B5751F9E6b0a".parse().unwrap(),
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
balance.unwrap();
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
#[serial]
|
||||||
|
async fn get_ether_balance_multi_success() {
|
||||||
|
run_with_client(Chain::Mainnet, |client| async move {
|
||||||
|
let balances = client
|
||||||
|
.get_ether_balance_multi(
|
||||||
|
&[&"0x58eB28A67731c570Ef827C365c89B5751F9E6b0a".parse().unwrap()],
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
assert!(balances.is_ok());
|
||||||
|
let balances = balances.unwrap();
|
||||||
|
assert_eq!(balances.len(), 1);
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
#[serial]
|
||||||
|
async fn get_transactions_success() {
|
||||||
|
run_with_client(Chain::Mainnet, |client| async move {
|
||||||
|
let txs = client
|
||||||
|
.get_transactions(&"0x4F26FfBe5F04ED43630fdC30A87638d53D0b0876".parse().unwrap(), None)
|
||||||
|
.await;
|
||||||
|
txs.unwrap();
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
#[serial]
|
||||||
|
async fn get_internal_transactions_success() {
|
||||||
|
run_with_client(Chain::Mainnet, |client| async move {
|
||||||
|
let txs = client
|
||||||
|
.get_internal_transactions(
|
||||||
|
InternalTxQueryOption::ByAddress(
|
||||||
|
"0x2c1ba59d6f58433fb1eaee7d20b26ed83bda51a3".parse().unwrap(),
|
||||||
|
),
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
txs.unwrap();
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
#[serial]
|
||||||
|
async fn get_internal_transactions_by_tx_hash_success() {
|
||||||
|
run_with_client(Chain::Mainnet, |client| async move {
|
||||||
|
let txs = client
|
||||||
|
.get_internal_transactions(
|
||||||
|
InternalTxQueryOption::ByTransactionHash(
|
||||||
|
"0x40eb908387324f2b575b4879cd9d7188f69c8fc9d87c901b9e2daaea4b442170"
|
||||||
|
.parse()
|
||||||
|
.unwrap(),
|
||||||
|
),
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
txs.unwrap();
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
#[serial]
|
||||||
|
async fn get_erc20_transfer_events_success() {
|
||||||
|
run_with_client(Chain::Mainnet, |client| async move {
|
||||||
|
let txs = client
|
||||||
|
.get_erc20_token_transfer_events(
|
||||||
|
TokenQueryOption::ByAddress(
|
||||||
|
"0x4e83362442b8d1bec281594cea3050c8eb01311c".parse().unwrap(),
|
||||||
|
),
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
let tx = txs.get(0).unwrap();
|
||||||
|
assert_eq!(tx.gas_used, 93657u64.into());
|
||||||
|
assert_eq!(tx.nonce, 10u64.into());
|
||||||
|
assert_eq!(tx.block_number, 2228258u64.into());
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
#[serial]
|
||||||
|
async fn get_erc721_transfer_events_success() {
|
||||||
|
run_with_client(Chain::Mainnet, |client| async move {
|
||||||
|
let txs = client
|
||||||
|
.get_erc721_token_transfer_events(
|
||||||
|
TokenQueryOption::ByAddressAndContract(
|
||||||
|
"0x6975be450864c02b4613023c2152ee0743572325".parse().unwrap(),
|
||||||
|
"0x06012c8cf97bead5deae237070f9587f8e7a266d".parse().unwrap(),
|
||||||
|
),
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
txs.unwrap();
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
#[serial]
|
||||||
|
async fn get_erc1155_transfer_events_success() {
|
||||||
|
run_with_client(Chain::Mainnet, |client| async move {
|
||||||
|
let txs = client
|
||||||
|
.get_erc1155_token_transfer_events(
|
||||||
|
TokenQueryOption::ByAddressAndContract(
|
||||||
|
"0x216CD350a4044e7016f14936663e2880Dd2A39d7".parse().unwrap(),
|
||||||
|
"0x495f947276749ce646f68ac8c248420045cb7b5e".parse().unwrap(),
|
||||||
|
),
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
txs.unwrap();
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
#[serial]
|
||||||
|
async fn get_mined_blocks_success() {
|
||||||
|
run_with_client(Chain::Mainnet, |client| async move {
|
||||||
|
client
|
||||||
|
.get_mined_blocks(
|
||||||
|
&"0x9dd134d14d1e65f84b706d6f205cd5b1cd03a46b".parse().unwrap(),
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
#[serial]
|
||||||
|
async fn get_avalanche_transactions() {
|
||||||
|
run_with_client(Chain::Avalanche, |client| async move {
|
||||||
|
let txs = client
|
||||||
|
.get_transactions(&"0x1549ea9b546ba9ffb306d78a1e1f304760cc4abf".parse().unwrap(), None)
|
||||||
|
.await;
|
||||||
|
txs.unwrap();
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
}
|
|
@ -0,0 +1,99 @@
|
||||||
|
use crate::*;
|
||||||
|
use ethers_core::types::Chain;
|
||||||
|
use ethers_etherscan::contract::SourceCodeMetadata;
|
||||||
|
use serial_test::serial;
|
||||||
|
|
||||||
|
/// Abi of [0xBB9bc244D798123fDe783fCc1C72d3Bb8C189413](https://api.etherscan.io/api?module=contract&action=getsourcecode&address=0xBB9bc244D798123fDe783fCc1C72d3Bb8C189413).
|
||||||
|
const DAO_ABI: &str = include!("../../../tests/testdata/the_dao_abi.expr");
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
#[serial]
|
||||||
|
async fn can_fetch_ftm_contract_abi() {
|
||||||
|
run_with_client(Chain::Fantom, |client| async move {
|
||||||
|
let _abi = client
|
||||||
|
.contract_abi("0x80AA7cb0006d5DDD91cce684229Ac6e398864606".parse().unwrap())
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
#[serial]
|
||||||
|
async fn can_fetch_contract_abi() {
|
||||||
|
run_with_client(Chain::Mainnet, |client| async move {
|
||||||
|
let abi = client
|
||||||
|
.contract_abi("0xBB9bc244D798123fDe783fCc1C72d3Bb8C189413".parse().unwrap())
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(abi, serde_json::from_str(DAO_ABI).unwrap());
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
#[serial]
|
||||||
|
async fn can_fetch_contract_source_code() {
|
||||||
|
run_with_client(Chain::Mainnet, |client| async move {
|
||||||
|
let meta = client
|
||||||
|
.contract_source_code("0xBB9bc244D798123fDe783fCc1C72d3Bb8C189413".parse().unwrap())
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(meta.items.len(), 1);
|
||||||
|
let item = &meta.items[0];
|
||||||
|
assert!(matches!(item.source_code, SourceCodeMetadata::SourceCode(_)));
|
||||||
|
assert_eq!(item.source_code.sources().len(), 1);
|
||||||
|
assert_eq!(item.abi().unwrap(), serde_json::from_str(DAO_ABI).unwrap());
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
#[serial]
|
||||||
|
async fn can_get_error_on_unverified_contract() {
|
||||||
|
init_tracing();
|
||||||
|
run_with_client(Chain::Mainnet, |client| async move {
|
||||||
|
let addr = "0xb5c31a0e22cae98ac08233e512bd627885aa24e5".parse().unwrap();
|
||||||
|
let err = client.contract_source_code(addr).await.unwrap_err();
|
||||||
|
assert!(matches!(err, EtherscanError::ContractCodeNotVerified(_)));
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Query a contract that has a single string source entry instead of underlying JSON metadata.
|
||||||
|
#[tokio::test]
|
||||||
|
#[serial]
|
||||||
|
async fn can_fetch_contract_source_tree_for_singleton_contract() {
|
||||||
|
run_with_client(Chain::Mainnet, |client| async move {
|
||||||
|
let meta = client
|
||||||
|
.contract_source_code("0xBB9bc244D798123fDe783fCc1C72d3Bb8C189413".parse().unwrap())
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(meta.items.len(), 1);
|
||||||
|
let item = &meta.items[0];
|
||||||
|
assert!(matches!(item.source_code, SourceCodeMetadata::SourceCode(_)));
|
||||||
|
assert_eq!(item.source_code.sources().len(), 1);
|
||||||
|
assert_eq!(item.abi().unwrap(), serde_json::from_str(DAO_ABI).unwrap());
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Query a contract that has many source entries as JSON metadata and ensure they are reflected.
|
||||||
|
#[tokio::test]
|
||||||
|
#[serial]
|
||||||
|
async fn can_fetch_contract_source_tree_for_multi_entry_contract() {
|
||||||
|
run_with_client(Chain::Mainnet, |client| async move {
|
||||||
|
let meta = client
|
||||||
|
.contract_source_code("0x8d04a8c79cEB0889Bdd12acdF3Fa9D207eD3Ff63".parse().unwrap())
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(meta.items.len(), 1);
|
||||||
|
assert!(matches!(meta.items[0].source_code, SourceCodeMetadata::Metadata { .. }));
|
||||||
|
let source_tree = meta.source_tree();
|
||||||
|
assert_eq!(source_tree.entries.len(), 15);
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
}
|
|
@ -0,0 +1,45 @@
|
||||||
|
use crate::*;
|
||||||
|
use ethers_core::types::Chain;
|
||||||
|
use serial_test::serial;
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
#[serial]
|
||||||
|
async fn gas_estimate_success() {
|
||||||
|
run_with_client(Chain::Mainnet, |client| async move {
|
||||||
|
let result = client.gas_estimate(2000000000u32.into()).await;
|
||||||
|
|
||||||
|
result.unwrap();
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
#[serial]
|
||||||
|
async fn gas_estimate_error() {
|
||||||
|
run_with_client(Chain::Mainnet, |client| async move {
|
||||||
|
let err = client.gas_estimate(7123189371829732819379218u128.into()).await.unwrap_err();
|
||||||
|
|
||||||
|
assert!(matches!(err, EtherscanError::GasEstimationFailed));
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
#[serial]
|
||||||
|
async fn gas_oracle_success() {
|
||||||
|
run_with_client(Chain::Mainnet, |client| async move {
|
||||||
|
let result = client.gas_oracle().await;
|
||||||
|
|
||||||
|
assert!(result.is_ok());
|
||||||
|
|
||||||
|
let oracle = result.unwrap();
|
||||||
|
|
||||||
|
assert!(oracle.safe_gas_price > 0);
|
||||||
|
assert!(oracle.propose_gas_price > 0);
|
||||||
|
assert!(oracle.fast_gas_price > 0);
|
||||||
|
assert!(oracle.last_block > 0);
|
||||||
|
assert!(oracle.suggested_base_fee > 0.0);
|
||||||
|
assert!(!oracle.gas_used_ratio.is_empty());
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
}
|
|
@ -0,0 +1,76 @@
|
||||||
|
//! Etherscan integration tests
|
||||||
|
|
||||||
|
#![cfg(not(target_arch = "wasm32"))]
|
||||||
|
|
||||||
|
use ethers_core::types::Chain;
|
||||||
|
use ethers_etherscan::{errors::EtherscanError, Client};
|
||||||
|
use std::{
|
||||||
|
future::Future,
|
||||||
|
time::{Duration, Instant},
|
||||||
|
};
|
||||||
|
|
||||||
|
mod account;
|
||||||
|
mod contract;
|
||||||
|
mod gas;
|
||||||
|
mod transaction;
|
||||||
|
mod verify;
|
||||||
|
mod version;
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn check_wrong_etherscan_api_key() {
|
||||||
|
let client = Client::new(Chain::Mainnet, "ABCDEFG").unwrap();
|
||||||
|
let resp = client
|
||||||
|
.contract_source_code("0xBB9bc244D798123fDe783fCc1C72d3Bb8C189413".parse().unwrap())
|
||||||
|
.await
|
||||||
|
.unwrap_err();
|
||||||
|
|
||||||
|
assert!(matches!(resp, EtherscanError::InvalidApiKey));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Calls the function with a new Etherscan Client.
|
||||||
|
pub async fn run_with_client<F, Fut, T>(chain: Chain, f: F) -> T
|
||||||
|
where
|
||||||
|
F: FnOnce(Client) -> Fut,
|
||||||
|
Fut: Future<Output = T>,
|
||||||
|
{
|
||||||
|
init_tracing();
|
||||||
|
let (client, duration) = match Client::new_from_env(chain) {
|
||||||
|
Ok(c) => (c, rate_limit(chain, true)),
|
||||||
|
Err(_) => {
|
||||||
|
(Client::builder().chain(chain).unwrap().build().unwrap(), rate_limit(chain, false))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
run_at_least_duration(duration, f(client)).await
|
||||||
|
}
|
||||||
|
|
||||||
|
#[track_caller]
|
||||||
|
fn rate_limit(chain: Chain, key: bool) -> Duration {
|
||||||
|
match (chain, key) {
|
||||||
|
// Rate limit with an API key is 5 call per second.
|
||||||
|
(_, true) => Duration::from_millis(250),
|
||||||
|
|
||||||
|
// Rate limit without an API key is 1 call every 5 seconds.
|
||||||
|
// (Chain::Mainnet, false) => Duration::from_millis(5100),
|
||||||
|
(Chain::Mainnet, false) => panic!("ETHERSCAN_API_KEY is not set"),
|
||||||
|
|
||||||
|
// Ignore other chains since we don't have more than 1 test with each.
|
||||||
|
(_, false) => Duration::ZERO,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn run_at_least_duration<T>(duration: Duration, block: impl Future<Output = T>) -> T {
|
||||||
|
let start = Instant::now();
|
||||||
|
let output = block.await;
|
||||||
|
let elapsed = start.elapsed();
|
||||||
|
if elapsed < duration {
|
||||||
|
tokio::time::sleep(duration - elapsed).await;
|
||||||
|
}
|
||||||
|
output
|
||||||
|
}
|
||||||
|
|
||||||
|
#[track_caller]
|
||||||
|
fn init_tracing() {
|
||||||
|
let _ = tracing_subscriber::fmt()
|
||||||
|
.with_env_filter(tracing_subscriber::EnvFilter::from_default_env())
|
||||||
|
.try_init();
|
||||||
|
}
|
|
@ -0,0 +1,65 @@
|
||||||
|
use crate::*;
|
||||||
|
use serial_test::serial;
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
#[serial]
|
||||||
|
async fn check_contract_execution_status_success() {
|
||||||
|
run_with_client(Chain::Mainnet, |client| async move {
|
||||||
|
let status = client
|
||||||
|
.check_contract_execution_status(
|
||||||
|
"0x16197e2a0eacc44c1ebdfddcfcfcafb3538de557c759a66e0ba95263b23d9007",
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
status.unwrap();
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
#[serial]
|
||||||
|
async fn check_contract_execution_status_error() {
|
||||||
|
run_with_client(Chain::Mainnet, |client| async move {
|
||||||
|
let err = client
|
||||||
|
.check_contract_execution_status(
|
||||||
|
"0x15f8e5ea1079d9a0bb04a4c58ae5fe7654b5b2b4463375ff7ffb490aa0032f3a",
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap_err();
|
||||||
|
|
||||||
|
assert!(matches!(err, EtherscanError::ExecutionFailed(_)));
|
||||||
|
assert_eq!(err.to_string(), "Contract execution call failed: Bad jump destination");
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
#[serial]
|
||||||
|
async fn check_transaction_receipt_status_success() {
|
||||||
|
run_with_client(Chain::Mainnet, |client| async move {
|
||||||
|
let success = client
|
||||||
|
.check_transaction_receipt_status(
|
||||||
|
"0x513c1ba0bebf66436b5fed86ab668452b7805593c05073eb2d51d3a52f480a76",
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
success.unwrap();
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
#[serial]
|
||||||
|
async fn check_transaction_receipt_status_failed() {
|
||||||
|
run_with_client(Chain::Mainnet, |client| async move {
|
||||||
|
let err = client
|
||||||
|
.check_transaction_receipt_status(
|
||||||
|
"0x21a29a497cb5d4bf514c0cca8d9235844bd0215c8fab8607217546a892fd0758",
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap_err();
|
||||||
|
|
||||||
|
assert!(matches!(err, EtherscanError::TransactionReceiptFailed));
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
}
|
|
@ -0,0 +1,41 @@
|
||||||
|
use crate::*;
|
||||||
|
use ethers_core::types::Chain;
|
||||||
|
use ethers_etherscan::verify::VerifyContract;
|
||||||
|
use ethers_solc::{Project, ProjectPathsConfig};
|
||||||
|
use serial_test::serial;
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
#[serial]
|
||||||
|
#[ignore]
|
||||||
|
async fn can_flatten_and_verify_contract() {
|
||||||
|
let root = Path::new(concat!(env!("CARGO_MANIFEST_DIR"), "/../../../tests/testdata/uniswap"));
|
||||||
|
let paths = ProjectPathsConfig::builder()
|
||||||
|
.sources(root)
|
||||||
|
.build()
|
||||||
|
.expect("failed to resolve project paths");
|
||||||
|
let project = Project::builder().paths(paths).build().expect("failed to build the project");
|
||||||
|
|
||||||
|
let address = "0x9e744c9115b74834c0f33f4097f40c02a9ac5c33".parse().unwrap();
|
||||||
|
let compiler_version = "v0.5.17+commit.d19bba13";
|
||||||
|
let constructor_args = "0x000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000005f5e1000000000000000000000000000000000000000000000000000000000000000007596179537761700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000035941590000000000000000000000000000000000000000000000000000000000";
|
||||||
|
let contract =
|
||||||
|
project.flatten(&root.join("UniswapExchange.sol")).expect("failed to flatten contract");
|
||||||
|
let contract_name = "UniswapExchange".to_owned();
|
||||||
|
let contract =
|
||||||
|
VerifyContract::new(address, contract_name, contract, compiler_version.to_string())
|
||||||
|
.constructor_arguments(Some(constructor_args))
|
||||||
|
.optimization(true)
|
||||||
|
.runs(200);
|
||||||
|
|
||||||
|
run_with_client(Chain::Mainnet, |client| async move {
|
||||||
|
let resp = client
|
||||||
|
.submit_contract_verification(&contract)
|
||||||
|
.await
|
||||||
|
.expect("failed to send the request");
|
||||||
|
// `Error!` result means that request was malformatted
|
||||||
|
assert_ne!(resp.result, "Error!", "{resp:?}");
|
||||||
|
assert_ne!(resp.message, "NOTOK", "{resp:?}");
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
use crate::*;
|
||||||
|
use ethers_etherscan::utils::lookup_compiler_version;
|
||||||
|
use semver::{BuildMetadata, Prerelease, Version};
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn can_lookup_compiler_version_build_metadata() {
|
||||||
|
let v = Version::new(0, 8, 13);
|
||||||
|
let version = lookup_compiler_version(&v).await.unwrap();
|
||||||
|
assert_eq!(v.major, version.major);
|
||||||
|
assert_eq!(v.minor, version.minor);
|
||||||
|
assert_eq!(v.patch, version.patch);
|
||||||
|
assert_ne!(version.build, BuildMetadata::EMPTY);
|
||||||
|
assert_eq!(version.pre, Prerelease::EMPTY);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn errors_on_invalid_solc() {
|
||||||
|
let v = Version::new(100, 0, 0);
|
||||||
|
let err = lookup_compiler_version(&v).await.unwrap_err();
|
||||||
|
assert!(matches!(err, EtherscanError::MissingSolcVersion(_)));
|
||||||
|
}
|
|
@ -15,7 +15,9 @@ all-features = true
|
||||||
rustdoc-args = ["--cfg", "docsrs"]
|
rustdoc-args = ["--cfg", "docsrs"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
ethers-contract = { version = "^1.0.0", path = "../ethers-contract", default-features = false }
|
ethers-contract = { version = "^1.0.0", path = "../ethers-contract", default-features = false, features = [
|
||||||
|
"abigen",
|
||||||
|
] }
|
||||||
ethers-core = { version = "^1.0.0", path = "../ethers-core", default-features = false }
|
ethers-core = { version = "^1.0.0", path = "../ethers-core", default-features = false }
|
||||||
ethers-etherscan = { version = "^1.0.0", path = "../ethers-etherscan", default-features = false }
|
ethers-etherscan = { version = "^1.0.0", path = "../ethers-etherscan", default-features = false }
|
||||||
ethers-providers = { version = "^1.0.0", path = "../ethers-providers", default-features = false }
|
ethers-providers = { version = "^1.0.0", path = "../ethers-providers", default-features = false }
|
||||||
|
@ -41,15 +43,15 @@ instant = { version = "0.1.12", features = ["now"] }
|
||||||
tokio = { version = "1.18" }
|
tokio = { version = "1.18" }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
hex = { version = "0.4.3", default-features = false, features = ["std"] }
|
|
||||||
rand = { version = "0.8.5", default-features = false }
|
|
||||||
ethers-providers = { version = "^1.0.0", path = "../ethers-providers", default-features = false, features = [
|
ethers-providers = { version = "^1.0.0", path = "../ethers-providers", default-features = false, features = [
|
||||||
"ws",
|
"ws",
|
||||||
"rustls",
|
"rustls",
|
||||||
] }
|
] }
|
||||||
once_cell = "1.17.1"
|
|
||||||
ethers-solc = { version = "^1.0.0", path = "../ethers-solc" }
|
ethers-solc = { version = "^1.0.0", path = "../ethers-solc" }
|
||||||
serial_test = "0.10.0"
|
|
||||||
|
hex = { version = "0.4.3", default-features = false, features = ["std"] }
|
||||||
|
rand = { version = "0.8.5", default-features = false }
|
||||||
|
once_cell = "1.17.1"
|
||||||
reqwest = { version = "0.11.14", default-features = false, features = ["json", "rustls"] }
|
reqwest = { version = "0.11.14", default-features = false, features = ["json", "rustls"] }
|
||||||
|
|
||||||
[target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies]
|
[target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies]
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -1,95 +0,0 @@
|
||||||
[
|
|
||||||
{
|
|
||||||
"constant": true,
|
|
||||||
"inputs": [
|
|
||||||
{
|
|
||||||
"name": "",
|
|
||||||
"type": "address"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"name": "isProxy",
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"name": "",
|
|
||||||
"type": "bool"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"payable": false,
|
|
||||||
"stateMutability": "view",
|
|
||||||
"type": "function"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"constant": true,
|
|
||||||
"inputs": [],
|
|
||||||
"name": "cache",
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"name": "",
|
|
||||||
"type": "address"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"payable": false,
|
|
||||||
"stateMutability": "view",
|
|
||||||
"type": "function"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"constant": false,
|
|
||||||
"inputs": [],
|
|
||||||
"name": "build",
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"name": "proxy",
|
|
||||||
"type": "address"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"payable": false,
|
|
||||||
"stateMutability": "nonpayable",
|
|
||||||
"type": "function"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"constant": false,
|
|
||||||
"inputs": [
|
|
||||||
{
|
|
||||||
"name": "owner",
|
|
||||||
"type": "address"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"name": "build",
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"name": "proxy",
|
|
||||||
"type": "address"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"payable": false,
|
|
||||||
"stateMutability": "nonpayable",
|
|
||||||
"type": "function"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"anonymous": false,
|
|
||||||
"inputs": [
|
|
||||||
{
|
|
||||||
"indexed": true,
|
|
||||||
"name": "sender",
|
|
||||||
"type": "address"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"indexed": true,
|
|
||||||
"name": "owner",
|
|
||||||
"type": "address"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"indexed": false,
|
|
||||||
"name": "proxy",
|
|
||||||
"type": "address"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"indexed": false,
|
|
||||||
"name": "cache",
|
|
||||||
"type": "address"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"name": "Created",
|
|
||||||
"type": "event"
|
|
||||||
}
|
|
||||||
]
|
|
|
@ -499,6 +499,9 @@ mod tests {
|
||||||
None,
|
None,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let client = SignerMiddleware::new_with_provider_chain(provider, key).await.unwrap();
|
let client = SignerMiddleware::new_with_provider_chain(provider, key).await.unwrap();
|
||||||
|
|
||||||
|
|
|
@ -1,204 +1,20 @@
|
||||||
use ethers_contract::Lazy;
|
use ethers_contract::{abigen, Lazy};
|
||||||
use ethers_core::types::*;
|
use ethers_core::types::{Address, U256};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
/// A lazily computed hash map with the Ethereum network IDs as keys and the corresponding
|
/// A lazily computed hash map with the Ethereum network IDs as keys and the corresponding
|
||||||
/// DsProxyFactory contract addresses as values
|
/// DsProxyFactory contract addresses as values
|
||||||
pub static ADDRESS_BOOK: Lazy<HashMap<U256, Address>> = Lazy::new(|| {
|
pub static ADDRESS_BOOK: Lazy<HashMap<U256, Address>> = Lazy::new(|| {
|
||||||
let mut m = HashMap::with_capacity(1);
|
HashMap::from([
|
||||||
|
// Mainnet
|
||||||
// mainnet
|
(U256::from(1_u64), "eefba1e63905ef1d7acba5a8513c70307c1ce441".parse().unwrap()),
|
||||||
let addr = "eefba1e63905ef1d7acba5a8513c70307c1ce441".parse().unwrap();
|
])
|
||||||
m.insert(U256::from(1_u64), addr);
|
|
||||||
|
|
||||||
m
|
|
||||||
});
|
});
|
||||||
|
|
||||||
/// Generated with abigen:
|
abigen!(
|
||||||
///
|
DsProxyFactory,
|
||||||
/// ```ignore
|
"./contracts/DSProxyFactory.json",
|
||||||
/// # use ethers_contract::abigen;
|
methods {
|
||||||
/// abigen!(DsProxyFactory,
|
build() as build_with_sender;
|
||||||
/// "ethers-middleware/contracts/DsProxyFactory.json",
|
|
||||||
/// methods {
|
|
||||||
/// build() as build_with_sender;
|
|
||||||
/// }
|
|
||||||
/// );
|
|
||||||
/// ```
|
|
||||||
pub use dsproxyfactory_mod::*;
|
|
||||||
#[allow(clippy::too_many_arguments)]
|
|
||||||
mod dsproxyfactory_mod {
|
|
||||||
#![allow(dead_code)]
|
|
||||||
#![allow(unused_imports)]
|
|
||||||
use ethers_contract::{
|
|
||||||
builders::{ContractCall, Event},
|
|
||||||
Contract, EthEvent, Lazy,
|
|
||||||
};
|
|
||||||
use ethers_core::{
|
|
||||||
abi::{
|
|
||||||
parse_abi, Abi, Detokenize, InvalidOutputType, ParamType, Token, Tokenizable,
|
|
||||||
TokenizableItem,
|
|
||||||
},
|
|
||||||
types::*,
|
|
||||||
};
|
|
||||||
use ethers_providers::Middleware;
|
|
||||||
#[doc = "DsProxyFactory was auto-generated with ethers-rs Abigen. More information at: <https://github.com/gakonst/ethers-rs>"]
|
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
pub static DSPROXYFACTORY_ABI: Lazy<Abi> = Lazy::new(|| {
|
|
||||||
serde_json :: from_str ("[{\"constant\":true,\"inputs\":[{\"name\":\"\",\"type\":\"address\"}],\"name\":\"isProxy\",\"outputs\":[{\"name\":\"\",\"type\":\"bool\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"cache\",\"outputs\":[{\"name\":\"\",\"type\":\"address\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[],\"name\":\"build\",\"outputs\":[{\"name\":\"proxy\",\"type\":\"address\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"owner\",\"type\":\"address\"}],\"name\":\"build\",\"outputs\":[{\"name\":\"proxy\",\"type\":\"address\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"sender\",\"type\":\"address\"},{\"indexed\":true,\"name\":\"owner\",\"type\":\"address\"},{\"indexed\":false,\"name\":\"proxy\",\"type\":\"address\"},{\"indexed\":false,\"name\":\"cache\",\"type\":\"address\"}],\"name\":\"Created\",\"type\":\"event\"}]\n") . expect ("invalid abi")
|
|
||||||
});
|
|
||||||
pub struct DsProxyFactory<M>(Contract<M>);
|
|
||||||
impl<M> Clone for DsProxyFactory<M> {
|
|
||||||
fn clone(&self) -> Self {
|
|
||||||
DsProxyFactory(self.0.clone())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<M> std::ops::Deref for DsProxyFactory<M> {
|
|
||||||
type Target = Contract<M>;
|
|
||||||
fn deref(&self) -> &Self::Target {
|
|
||||||
&self.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<M: Middleware> std::fmt::Debug for DsProxyFactory<M> {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
|
||||||
f.debug_tuple(stringify!(DsProxyFactory)).field(&self.address()).finish()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<M: Middleware> DsProxyFactory<M> {
|
|
||||||
#[doc = r" Creates a new contract instance with the specified `ethers`"]
|
|
||||||
#[doc = r" client at the given `Address`. The contract derefs to a `ethers::Contract`"]
|
|
||||||
#[doc = r" object"]
|
|
||||||
pub fn new<T: Into<Address>>(address: T, client: Arc<M>) -> Self {
|
|
||||||
let contract = Contract::new(address.into(), DSPROXYFACTORY_ABI.clone(), client);
|
|
||||||
Self(contract)
|
|
||||||
}
|
|
||||||
#[doc = "Calls the contract's `isProxy` (0x29710388) function"]
|
|
||||||
pub fn is_proxy(&self, p0: Address) -> ContractCall<M, bool> {
|
|
||||||
self.0
|
|
||||||
.method_hash([41, 113, 3, 136], p0)
|
|
||||||
.expect("method not found (this should never happen)")
|
|
||||||
}
|
|
||||||
///Calls the contract's `build` (0x8e1a55fc) function
|
|
||||||
pub fn build_with_sender(&self) -> ContractCall<M, Address> {
|
|
||||||
self.0
|
|
||||||
.method_hash([142, 26, 85, 252], ())
|
|
||||||
.expect("method not found (this should never happen)")
|
|
||||||
}
|
|
||||||
///Calls the contract's `build` (0xf3701da2) function
|
|
||||||
pub fn build(&self, owner: Address) -> ContractCall<M, Address> {
|
|
||||||
self.0
|
|
||||||
.method_hash([243, 112, 29, 162], owner)
|
|
||||||
.expect("method not found (this should never happen)")
|
|
||||||
}
|
|
||||||
#[doc = "Calls the contract's `cache` (0x60c7d295) function"]
|
|
||||||
pub fn cache(&self) -> ContractCall<M, Address> {
|
|
||||||
self.0
|
|
||||||
.method_hash([96, 199, 210, 149], ())
|
|
||||||
.expect("method not found (this should never happen)")
|
|
||||||
}
|
|
||||||
///Gets the contract's `Created` event
|
|
||||||
pub fn created_filter(&self) -> Event<Arc<M>, M, CreatedFilter> {
|
|
||||||
self.0.event()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns an [`Event`](ethers_contract::builders::Event) builder for all events of this
|
|
||||||
/// contract
|
|
||||||
pub fn events(&self) -> Event<Arc<M>, M, CreatedFilter> {
|
|
||||||
self.0.event_with_filter(Default::default())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default, Eq, PartialEq)]
|
|
||||||
pub struct CreatedFilter {
|
|
||||||
pub sender: Address,
|
|
||||||
pub owner: Address,
|
|
||||||
pub proxy: Address,
|
|
||||||
pub cache: Address,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ethers_core::abi::AbiType for CreatedFilter {
|
|
||||||
fn param_type() -> ParamType {
|
|
||||||
ParamType::Tuple(::std::vec![
|
|
||||||
ParamType::Address; 4
|
|
||||||
])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl ethers_core::abi::AbiArrayType for CreatedFilter {}
|
|
||||||
|
|
||||||
impl Tokenizable for CreatedFilter {
|
|
||||||
fn from_token(token: Token) -> Result<Self, InvalidOutputType>
|
|
||||||
where
|
|
||||||
Self: Sized,
|
|
||||||
{
|
|
||||||
if let Token::Tuple(tokens) = token {
|
|
||||||
if tokens.len() != 4usize {
|
|
||||||
return Err(InvalidOutputType(format!(
|
|
||||||
"Expected {} tokens, got {}: {:?}",
|
|
||||||
4usize,
|
|
||||||
tokens.len(),
|
|
||||||
tokens
|
|
||||||
)))
|
|
||||||
}
|
|
||||||
let mut iter = tokens.into_iter();
|
|
||||||
Ok(Self {
|
|
||||||
sender: Tokenizable::from_token(iter.next().unwrap())?,
|
|
||||||
owner: Tokenizable::from_token(iter.next().unwrap())?,
|
|
||||||
proxy: Tokenizable::from_token(iter.next().unwrap())?,
|
|
||||||
cache: Tokenizable::from_token(iter.next().unwrap())?,
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
Err(InvalidOutputType(format!("Expected Tuple, got {token:?}")))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fn into_token(self) -> Token {
|
|
||||||
Token::Tuple(::std::vec![
|
|
||||||
self.sender.into_token(),
|
|
||||||
self.owner.into_token(),
|
|
||||||
self.proxy.into_token(),
|
|
||||||
self.cache.into_token(),
|
|
||||||
])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl TokenizableItem for CreatedFilter {}
|
|
||||||
|
|
||||||
impl ethers_contract::EthEvent for CreatedFilter {
|
|
||||||
fn name() -> std::borrow::Cow<'static, str> {
|
|
||||||
"Created".into()
|
|
||||||
}
|
|
||||||
fn signature() -> H256 {
|
|
||||||
H256([
|
|
||||||
37, 155, 48, 202, 57, 136, 92, 109, 128, 26, 11, 93, 188, 152, 134, 64, 243, 194,
|
|
||||||
94, 47, 55, 83, 31, 225, 56, 197, 197, 175, 137, 85, 212, 27,
|
|
||||||
])
|
|
||||||
}
|
|
||||||
fn abi_signature() -> std::borrow::Cow<'static, str> {
|
|
||||||
"Created(address,address,address,address)".into()
|
|
||||||
}
|
|
||||||
fn decode_log(log: ðers_core::abi::RawLog) -> Result<Self, ethers_core::abi::Error>
|
|
||||||
where
|
|
||||||
Self: Sized,
|
|
||||||
{
|
|
||||||
let ethers_core::abi::RawLog { data, topics } = log;
|
|
||||||
let event_signature = topics.get(0).ok_or(ethers_core::abi::Error::InvalidData)?;
|
|
||||||
if event_signature != &Self::signature() {
|
|
||||||
return Err(ethers_core::abi::Error::InvalidData)
|
|
||||||
}
|
|
||||||
let topic_types = ::std::vec![ParamType::Address, ParamType::Address];
|
|
||||||
let data_types = [ParamType::Address, ParamType::Address];
|
|
||||||
let flat_topics =
|
|
||||||
topics.iter().skip(1).flat_map(|t| t.as_ref().to_vec()).collect::<Vec<u8>>();
|
|
||||||
let topic_tokens = ethers_core::abi::decode(&topic_types, &flat_topics)?;
|
|
||||||
if topic_tokens.len() != topics.len() - 1 {
|
|
||||||
return Err(ethers_core::abi::Error::InvalidData)
|
|
||||||
}
|
|
||||||
let data_tokens = ethers_core::abi::decode(&data_types, data)?;
|
|
||||||
let tokens: Vec<_> = topic_tokens.into_iter().chain(data_tokens.into_iter()).collect();
|
|
||||||
Tokenizable::from_token(Token::Tuple(tokens))
|
|
||||||
.map_err(|_| ethers_core::abi::Error::InvalidData)
|
|
||||||
}
|
|
||||||
fn is_anonymous() -> bool {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
);
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
mod factory;
|
pub mod factory;
|
||||||
use factory::{CreatedFilter, DsProxyFactory, ADDRESS_BOOK};
|
use factory::{CreatedFilter, DsProxyFactory, ADDRESS_BOOK};
|
||||||
|
|
||||||
use super::{Transformer, TransformerError};
|
use super::{Transformer, TransformerError};
|
||||||
|
@ -18,7 +18,6 @@ const DS_PROXY_EXECUTE_TARGET: &str =
|
||||||
const DS_PROXY_EXECUTE_CODE: &str =
|
const DS_PROXY_EXECUTE_CODE: &str =
|
||||||
"function execute(bytes memory code, bytes memory data) public payable returns (address target, bytes memory response)";
|
"function execute(bytes memory code, bytes memory data) public payable returns (address target, bytes memory response)";
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
/// Represents the DsProxy type that implements the [Transformer](super::Transformer) trait.
|
/// Represents the DsProxy type that implements the [Transformer](super::Transformer) trait.
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
|
@ -57,6 +56,7 @@ const DS_PROXY_EXECUTE_CODE: &str =
|
||||||
/// # Ok(())
|
/// # Ok(())
|
||||||
/// # }
|
/// # }
|
||||||
/// ```
|
/// ```
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
pub struct DsProxy {
|
pub struct DsProxy {
|
||||||
address: Address,
|
address: Address,
|
||||||
contract: BaseContract,
|
contract: BaseContract,
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
mod ds_proxy;
|
pub mod ds_proxy;
|
||||||
pub use ds_proxy::DsProxy;
|
pub use ds_proxy::DsProxy;
|
||||||
|
|
||||||
mod middleware;
|
mod middleware;
|
||||||
|
|
|
@ -1,42 +0,0 @@
|
||||||
#![cfg(not(target_arch = "wasm32"))]
|
|
||||||
|
|
||||||
use ethers_core::types::*;
|
|
||||||
use ethers_middleware::{
|
|
||||||
gas_escalator::{Frequency, GasEscalatorMiddleware, GeometricGasPrice},
|
|
||||||
signer::SignerMiddleware,
|
|
||||||
};
|
|
||||||
use ethers_providers::Middleware;
|
|
||||||
use ethers_signers::{LocalWallet, Signer};
|
|
||||||
use std::time::Duration;
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
#[ignore]
|
|
||||||
async fn gas_escalator_live() {
|
|
||||||
// connect to ropsten for getting bad block times
|
|
||||||
#[allow(deprecated)]
|
|
||||||
let provider = ethers_providers::ROPSTEN.ws().await;
|
|
||||||
let provider = provider.interval(Duration::from_millis(2000u64));
|
|
||||||
let wallet = "fdb33e2105f08abe41a8ee3b758726a31abdd57b7a443f470f23efce853af169"
|
|
||||||
.parse::<LocalWallet>()
|
|
||||||
.unwrap();
|
|
||||||
let address = wallet.address();
|
|
||||||
let provider = SignerMiddleware::new(provider, wallet);
|
|
||||||
|
|
||||||
let escalator = GeometricGasPrice::new(5.0, 10u64, Some(2_000_000_000_000u64));
|
|
||||||
|
|
||||||
let provider = GasEscalatorMiddleware::new(provider, escalator, Frequency::Duration(3000));
|
|
||||||
|
|
||||||
let nonce = provider.get_transaction_count(address, None).await.unwrap();
|
|
||||||
let tx = TransactionRequest::pay(Address::zero(), 1u64).gas_price(10_000_000);
|
|
||||||
|
|
||||||
// broadcast 3 txs
|
|
||||||
provider.send_transaction(tx.clone().nonce(nonce), None).await.unwrap();
|
|
||||||
provider.send_transaction(tx.clone().nonce(nonce + 1), None).await.unwrap();
|
|
||||||
provider.send_transaction(tx.clone().nonce(nonce + 2), None).await.unwrap();
|
|
||||||
|
|
||||||
// Wait a bunch of seconds and refresh etherscan to see the transactions get bumped
|
|
||||||
tokio::time::sleep(Duration::from_secs(100)).await;
|
|
||||||
|
|
||||||
// TODO: Figure out how to test this behavior properly in a local network. If the gas price was
|
|
||||||
// bumped then the tx hash will be different
|
|
||||||
}
|
|
|
@ -1,5 +1,3 @@
|
||||||
#![cfg(not(target_arch = "wasm32"))]
|
|
||||||
|
|
||||||
use ethers_core::{rand::thread_rng, types::U64};
|
use ethers_core::{rand::thread_rng, types::U64};
|
||||||
use ethers_middleware::{
|
use ethers_middleware::{
|
||||||
builder::MiddlewareBuilder,
|
builder::MiddlewareBuilder,
|
|
@ -0,0 +1,41 @@
|
||||||
|
use ethers_core::{types::*, utils::Anvil};
|
||||||
|
use ethers_middleware::{
|
||||||
|
gas_escalator::{Frequency, GasEscalatorMiddleware, GeometricGasPrice},
|
||||||
|
MiddlewareBuilder,
|
||||||
|
};
|
||||||
|
use ethers_providers::{Http, Middleware, Provider};
|
||||||
|
use ethers_signers::{LocalWallet, Signer};
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
#[ignore]
|
||||||
|
async fn gas_escalator() {
|
||||||
|
let anvil = Anvil::new().block_time(2u64).spawn();
|
||||||
|
let chain_id = anvil.chain_id();
|
||||||
|
let provider = Provider::<Http>::try_from(anvil.endpoint()).unwrap();
|
||||||
|
|
||||||
|
// wrap with signer
|
||||||
|
let wallet: LocalWallet = anvil.keys().first().unwrap().clone().into();
|
||||||
|
let wallet = wallet.with_chain_id(chain_id);
|
||||||
|
let address = wallet.address();
|
||||||
|
let provider = provider.with_signer(wallet);
|
||||||
|
|
||||||
|
// wrap with escalator
|
||||||
|
let escalator = GeometricGasPrice::new(5.0, 10u64, Some(2_000_000_000_000u64));
|
||||||
|
let provider = GasEscalatorMiddleware::new(provider, escalator, Frequency::Duration(300));
|
||||||
|
|
||||||
|
let nonce = provider.get_transaction_count(address, None).await.unwrap();
|
||||||
|
// 1 gwei default base fee
|
||||||
|
let gas_price = U256::from(1_000_000_000_u64);
|
||||||
|
let tx = TransactionRequest::pay(Address::zero(), 1u64)
|
||||||
|
.gas_price(gas_price)
|
||||||
|
.nonce(nonce)
|
||||||
|
.chain_id(chain_id);
|
||||||
|
|
||||||
|
eprintln!("sending");
|
||||||
|
let pending = provider.send_transaction(tx, None).await.expect("could not send");
|
||||||
|
eprintln!("waiting");
|
||||||
|
let receipt = pending.await.expect("reverted").expect("dropped");
|
||||||
|
assert_eq!(receipt.from, address);
|
||||||
|
assert_eq!(receipt.to, Some(Address::zero()));
|
||||||
|
assert!(receipt.effective_gas_price.unwrap() > gas_price * 2, "{receipt:?}");
|
||||||
|
}
|
|
@ -1,14 +1,11 @@
|
||||||
#![cfg(not(target_arch = "wasm32"))]
|
|
||||||
|
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use ethers_core::{types::*, utils::Anvil};
|
use ethers_core::{types::*, utils::Anvil};
|
||||||
|
use ethers_etherscan::Client;
|
||||||
use ethers_middleware::gas_oracle::{
|
use ethers_middleware::gas_oracle::{
|
||||||
BlockNative, Etherchain, Etherscan, GasCategory, GasNow, GasOracle, GasOracleError,
|
BlockNative, Etherchain, Etherscan, GasCategory, GasNow, GasOracle, GasOracleError,
|
||||||
GasOracleMiddleware, Polygon, ProviderOracle, Result,
|
GasOracleMiddleware, Polygon, ProviderOracle, Result,
|
||||||
};
|
};
|
||||||
use ethers_providers::{Http, Middleware, Provider};
|
use ethers_providers::{Http, Middleware, Provider};
|
||||||
use serial_test::serial;
|
|
||||||
use std::convert::TryFrom;
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct FakeGasOracle {
|
struct FakeGasOracle {
|
||||||
|
@ -28,7 +25,6 @@ impl GasOracle for FakeGasOracle {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
#[serial]
|
|
||||||
async fn provider_using_gas_oracle() {
|
async fn provider_using_gas_oracle() {
|
||||||
let anvil = Anvil::new().spawn();
|
let anvil = Anvil::new().spawn();
|
||||||
|
|
||||||
|
@ -54,7 +50,6 @@ async fn provider_using_gas_oracle() {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
#[serial]
|
|
||||||
async fn provider_oracle() {
|
async fn provider_oracle() {
|
||||||
// spawn anvil and connect to it
|
// spawn anvil and connect to it
|
||||||
let anvil = Anvil::new().spawn();
|
let anvil = Anvil::new().spawn();
|
||||||
|
@ -93,7 +88,8 @@ async fn etherchain() {
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn etherscan() {
|
async fn etherscan() {
|
||||||
let etherscan_client = ethers_etherscan::Client::new_from_env(Chain::Mainnet).unwrap();
|
let chain = Chain::Mainnet;
|
||||||
|
let etherscan_client = Client::new_from_opt_env(chain).unwrap();
|
||||||
|
|
||||||
// initialize and fetch gas estimates from Etherscan
|
// initialize and fetch gas estimates from Etherscan
|
||||||
// since etherscan does not support `fastest` category, we expect an error
|
// since etherscan does not support `fastest` category, we expect an error
|
|
@ -0,0 +1,49 @@
|
||||||
|
#![allow(clippy::extra_unused_type_parameters)]
|
||||||
|
#![cfg(not(target_arch = "wasm32"))]
|
||||||
|
|
||||||
|
use ethers_core::utils::{Anvil, AnvilInstance};
|
||||||
|
use ethers_providers::{Http, Provider, Ws};
|
||||||
|
use ethers_signers::{LocalWallet, Signer};
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
mod builder;
|
||||||
|
|
||||||
|
mod gas_escalator;
|
||||||
|
|
||||||
|
mod gas_oracle;
|
||||||
|
|
||||||
|
#[cfg(not(feature = "celo"))]
|
||||||
|
mod signer;
|
||||||
|
|
||||||
|
#[cfg(not(feature = "celo"))]
|
||||||
|
mod nonce_manager;
|
||||||
|
|
||||||
|
#[cfg(not(feature = "celo"))]
|
||||||
|
mod stack;
|
||||||
|
|
||||||
|
#[cfg(not(feature = "celo"))]
|
||||||
|
mod transformer;
|
||||||
|
|
||||||
|
/// Spawns Anvil and instantiates an Http provider.
|
||||||
|
pub fn spawn_anvil() -> (Provider<Http>, AnvilInstance) {
|
||||||
|
let anvil = Anvil::new().block_time(1u64).spawn();
|
||||||
|
let provider = Provider::<Http>::try_from(anvil.endpoint())
|
||||||
|
.unwrap()
|
||||||
|
.interval(Duration::from_millis(50u64));
|
||||||
|
(provider, anvil)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Spawns Anvil and instantiates a Ws provider.
|
||||||
|
pub async fn spawn_anvil_ws() -> (Provider<Ws>, AnvilInstance) {
|
||||||
|
let anvil = Anvil::new().block_time(1u64).spawn();
|
||||||
|
let provider = Provider::<Ws>::connect(anvil.ws_endpoint())
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.interval(Duration::from_millis(50u64));
|
||||||
|
(provider, anvil)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets `idx` wallet from the given anvil instance.
|
||||||
|
pub fn get_wallet(anvil: &AnvilInstance, idx: usize) -> LocalWallet {
|
||||||
|
LocalWallet::from(anvil.keys()[idx].clone()).with_chain_id(anvil.chain_id())
|
||||||
|
}
|
|
@ -1,18 +1,13 @@
|
||||||
#![cfg(all(not(target_arch = "wasm32"), not(feature = "celo")))]
|
use crate::spawn_anvil;
|
||||||
|
use ethers_core::types::*;
|
||||||
use ethers_core::{types::*, utils::Anvil};
|
|
||||||
use ethers_middleware::MiddlewareBuilder;
|
use ethers_middleware::MiddlewareBuilder;
|
||||||
use ethers_providers::{Http, Middleware, Provider};
|
use ethers_providers::Middleware;
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn nonce_manager() {
|
async fn nonce_manager() {
|
||||||
let anvil = Anvil::new().spawn();
|
let (provider, anvil) = spawn_anvil();
|
||||||
let endpoint = anvil.endpoint();
|
let address = anvil.addresses()[0];
|
||||||
|
let to = anvil.addresses()[1];
|
||||||
let provider = Provider::<Http>::try_from(endpoint).unwrap();
|
|
||||||
let accounts = provider.get_accounts().await.unwrap();
|
|
||||||
let address = accounts[0];
|
|
||||||
let to = accounts[1];
|
|
||||||
|
|
||||||
// the nonce manager must be over the Client so that it overrides the nonce
|
// the nonce manager must be over the Client so that it overrides the nonce
|
||||||
// before the client gets it
|
// before the client gets it
|
|
@ -0,0 +1,115 @@
|
||||||
|
use crate::{get_wallet, spawn_anvil, spawn_anvil_ws};
|
||||||
|
use ethers_core::types::*;
|
||||||
|
use ethers_middleware::{signer::SignerMiddleware, MiddlewareBuilder};
|
||||||
|
use ethers_providers::{JsonRpcClient, Middleware};
|
||||||
|
use ethers_signers::{LocalWallet, Signer};
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn send_eth() {
|
||||||
|
let (provider, anvil) = spawn_anvil();
|
||||||
|
let wallet = get_wallet(&anvil, 0);
|
||||||
|
let address = wallet.address();
|
||||||
|
let provider = provider.with_signer(wallet);
|
||||||
|
|
||||||
|
let to = anvil.addresses()[1];
|
||||||
|
|
||||||
|
// craft the transaction
|
||||||
|
let tx = TransactionRequest::new().to(to).value(10000);
|
||||||
|
|
||||||
|
let balance_before = provider.get_balance(address, None).await.unwrap();
|
||||||
|
|
||||||
|
// send it!
|
||||||
|
provider.send_transaction(tx, None).await.unwrap().await.unwrap().unwrap();
|
||||||
|
|
||||||
|
let balance_after = provider.get_balance(address, None).await.unwrap();
|
||||||
|
|
||||||
|
assert!(balance_before > balance_after);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn typed_txs() {
|
||||||
|
let (provider, anvil) = spawn_anvil();
|
||||||
|
let wallet = get_wallet(&anvil, 0);
|
||||||
|
let address = wallet.address();
|
||||||
|
let provider = provider.with_signer(wallet);
|
||||||
|
|
||||||
|
let nonce = provider.get_transaction_count(address, None).await.unwrap();
|
||||||
|
let bn = Some(BlockNumber::Pending.into());
|
||||||
|
let gas_price = provider.get_gas_price().await.unwrap() * 125 / 100;
|
||||||
|
|
||||||
|
let tx = TransactionRequest::new().from(address).to(address).nonce(nonce).gas_price(gas_price);
|
||||||
|
let tx1 = provider.send_transaction(tx.clone(), bn).await.unwrap();
|
||||||
|
|
||||||
|
let tx = tx.from(address).to(address).nonce(nonce + 1).with_access_list(vec![]);
|
||||||
|
let tx2 = provider.send_transaction(tx, bn).await.unwrap();
|
||||||
|
|
||||||
|
let tx = Eip1559TransactionRequest::new()
|
||||||
|
.from(address)
|
||||||
|
.to(address)
|
||||||
|
.nonce(nonce + 2)
|
||||||
|
.max_fee_per_gas(gas_price)
|
||||||
|
.max_priority_fee_per_gas(gas_price);
|
||||||
|
let tx3 = provider.send_transaction(tx, bn).await.unwrap();
|
||||||
|
|
||||||
|
futures_util::join!(check_tx(tx1, 0), check_tx(tx2, 1), check_tx(tx3, 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn send_transaction_handles_tx_from_field() {
|
||||||
|
// launch anvil
|
||||||
|
let (provider, anvil) = spawn_anvil_ws().await;
|
||||||
|
|
||||||
|
// grab 2 wallets
|
||||||
|
let signer: LocalWallet = anvil.keys()[0].clone().into();
|
||||||
|
let other: LocalWallet = anvil.keys()[1].clone().into();
|
||||||
|
|
||||||
|
// connect to the network
|
||||||
|
let provider =
|
||||||
|
SignerMiddleware::new_with_provider_chain(provider, signer.clone()).await.unwrap();
|
||||||
|
|
||||||
|
// sending a TransactionRequest with a from field of None should result
|
||||||
|
// in a transaction from the signer address
|
||||||
|
let request_from_none = TransactionRequest::new();
|
||||||
|
let receipt =
|
||||||
|
provider.send_transaction(request_from_none, None).await.unwrap().await.unwrap().unwrap();
|
||||||
|
let sent_tx = provider.get_transaction(receipt.transaction_hash).await.unwrap().unwrap();
|
||||||
|
|
||||||
|
assert_eq!(sent_tx.from, signer.address());
|
||||||
|
|
||||||
|
// sending a TransactionRequest with the signer as the from address should
|
||||||
|
// result in a transaction from the signer address
|
||||||
|
let request_from_signer = TransactionRequest::new().from(signer.address());
|
||||||
|
let receipt =
|
||||||
|
provider.send_transaction(request_from_signer, None).await.unwrap().await.unwrap().unwrap();
|
||||||
|
let sent_tx = provider.get_transaction(receipt.transaction_hash).await.unwrap().unwrap();
|
||||||
|
|
||||||
|
assert_eq!(sent_tx.from, signer.address());
|
||||||
|
|
||||||
|
// sending a TransactionRequest with a from address that is not the signer
|
||||||
|
// should result in a transaction from the specified address
|
||||||
|
let request_from_other = TransactionRequest::new().from(other.address());
|
||||||
|
let receipt =
|
||||||
|
provider.send_transaction(request_from_other, None).await.unwrap().await.unwrap().unwrap();
|
||||||
|
let sent_tx = provider.get_transaction(receipt.transaction_hash).await.unwrap().unwrap();
|
||||||
|
|
||||||
|
assert_eq!(sent_tx.from, other.address());
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn check_tx<P: JsonRpcClient + Clone>(
|
||||||
|
pending_tx: ethers_providers::PendingTransaction<'_, P>,
|
||||||
|
expected: u64,
|
||||||
|
) {
|
||||||
|
let provider = pending_tx.provider();
|
||||||
|
let receipt = pending_tx.await.unwrap().unwrap();
|
||||||
|
let tx = provider.get_transaction(receipt.transaction_hash).await.unwrap().unwrap();
|
||||||
|
|
||||||
|
let expected = U64::from(expected);
|
||||||
|
for ty in [receipt.transaction_type, tx.transaction_type] {
|
||||||
|
// legacy can be either None or Some(0)
|
||||||
|
if expected.is_zero() {
|
||||||
|
assert!(ty.is_none() || ty == Some(0.into()));
|
||||||
|
} else {
|
||||||
|
assert_eq!(ty, Some(expected));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,3 @@
|
||||||
#![cfg(all(not(target_arch = "wasm32"), not(feature = "celo")))]
|
|
||||||
|
|
||||||
use ethers_core::{rand::thread_rng, types::TransactionRequest, utils::Anvil};
|
use ethers_core::{rand::thread_rng, types::TransactionRequest, utils::Anvil};
|
||||||
use ethers_middleware::{
|
use ethers_middleware::{
|
||||||
gas_escalator::{Frequency, GasEscalatorMiddleware, GeometricGasPrice},
|
gas_escalator::{Frequency, GasEscalatorMiddleware, GeometricGasPrice},
|
|
@ -0,0 +1,120 @@
|
||||||
|
use crate::{get_wallet, spawn_anvil};
|
||||||
|
use ethers_contract::abigen;
|
||||||
|
use ethers_core::{abi::AbiEncode, types::*};
|
||||||
|
use ethers_middleware::{
|
||||||
|
transformer::{ds_proxy::factory::DsProxyFactory, DsProxy, TransformerMiddleware},
|
||||||
|
MiddlewareBuilder, SignerMiddleware,
|
||||||
|
};
|
||||||
|
use ethers_providers::{Http, Middleware, Provider};
|
||||||
|
use ethers_signers::{LocalWallet, Signer};
|
||||||
|
use rand::Rng;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
type HttpWallet = SignerMiddleware<Provider<Http>, LocalWallet>;
|
||||||
|
|
||||||
|
abigen!(SimpleStorage, "../tests/testdata/SimpleStorage.json");
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
#[ignore]
|
||||||
|
async fn ds_proxy_transformer() {
|
||||||
|
// randomness
|
||||||
|
let mut rng = rand::thread_rng();
|
||||||
|
|
||||||
|
// spawn anvil and instantiate a signer middleware.
|
||||||
|
let (provider, anvil) = spawn_anvil();
|
||||||
|
let wallet = get_wallet(&anvil, 0);
|
||||||
|
let address = wallet.address();
|
||||||
|
let provider = Arc::new(provider.with_signer(wallet));
|
||||||
|
|
||||||
|
// deploy DsProxyFactory which we'll use to deploy a new DsProxy contract.
|
||||||
|
let deploy_tx = DsProxyFactory::deploy(provider.clone(), ()).unwrap();
|
||||||
|
let ds_proxy_factory = deploy_tx.send().await.unwrap();
|
||||||
|
|
||||||
|
// deploy a new DsProxy contract.
|
||||||
|
let ds_proxy = DsProxy::build::<HttpWallet, Arc<HttpWallet>>(
|
||||||
|
provider.clone(),
|
||||||
|
Some(ds_proxy_factory.address()),
|
||||||
|
provider.address(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
let ds_proxy_addr = ds_proxy.address();
|
||||||
|
|
||||||
|
// deploy SimpleStorage and try to update its value via transformer middleware.
|
||||||
|
let deploy_tx = SimpleStorage::deploy(provider.clone(), ()).unwrap();
|
||||||
|
let simple_storage = deploy_tx.send().await.unwrap();
|
||||||
|
|
||||||
|
// instantiate a new transformer middleware.
|
||||||
|
let provider = TransformerMiddleware::new(provider, ds_proxy);
|
||||||
|
|
||||||
|
// broadcast the setValue tx via transformer middleware (first wallet).
|
||||||
|
let expected_value: u64 = rng.gen();
|
||||||
|
let _receipt = simple_storage
|
||||||
|
.set_value(expected_value.into())
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// verify that DsProxy's state was updated.
|
||||||
|
let last_sender = provider.get_storage_at(ds_proxy_addr, H256::zero(), None).await.unwrap();
|
||||||
|
let last_value =
|
||||||
|
provider.get_storage_at(ds_proxy_addr, H256::from_low_u64_be(1u64), None).await.unwrap();
|
||||||
|
assert_eq!(last_sender, address.into());
|
||||||
|
assert_eq!(last_value, H256::from_low_u64_be(expected_value));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn ds_proxy_code() {
|
||||||
|
// randomness
|
||||||
|
let mut rng = rand::thread_rng();
|
||||||
|
|
||||||
|
// spawn anvil and instantiate a signer middleware.
|
||||||
|
let (provider, anvil) = spawn_anvil();
|
||||||
|
let wallet = get_wallet(&anvil, 0);
|
||||||
|
let address = wallet.address();
|
||||||
|
let provider = Arc::new(provider.with_signer(wallet));
|
||||||
|
|
||||||
|
// deploy DsProxyFactory which we'll use to deploy a new DsProxy contract.
|
||||||
|
let deploy_tx = DsProxyFactory::deploy(provider.clone(), ()).unwrap();
|
||||||
|
let ds_proxy_factory = deploy_tx.send().await.unwrap();
|
||||||
|
|
||||||
|
// deploy a new DsProxy contract.
|
||||||
|
let ds_proxy = DsProxy::build::<HttpWallet, Arc<HttpWallet>>(
|
||||||
|
provider.clone(),
|
||||||
|
Some(ds_proxy_factory.address()),
|
||||||
|
provider.address(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
let ds_proxy_addr = ds_proxy.address();
|
||||||
|
|
||||||
|
// encode the calldata
|
||||||
|
let expected_value: u64 = rng.gen();
|
||||||
|
let calldata = SetValueCall { value: expected_value.into() }.encode();
|
||||||
|
|
||||||
|
// execute code via the deployed DsProxy contract.
|
||||||
|
ds_proxy
|
||||||
|
.execute::<HttpWallet, Arc<HttpWallet>, Bytes>(
|
||||||
|
Arc::clone(&provider),
|
||||||
|
SIMPLESTORAGE_BYTECODE.clone(),
|
||||||
|
calldata.into(),
|
||||||
|
)
|
||||||
|
.expect("could not construct DSProxy contract call")
|
||||||
|
.legacy()
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// verify that DsProxy's state was updated.
|
||||||
|
let last_sender = provider.get_storage_at(ds_proxy_addr, H256::zero(), None).await.unwrap();
|
||||||
|
let last_value =
|
||||||
|
provider.get_storage_at(ds_proxy_addr, H256::from_low_u64_be(1u64), None).await.unwrap();
|
||||||
|
assert_eq!(last_sender, address.into());
|
||||||
|
assert_eq!(last_value, H256::from_low_u64_be(expected_value));
|
||||||
|
}
|
|
@ -1,342 +0,0 @@
|
||||||
#![allow(unused)]
|
|
||||||
|
|
||||||
use ethers_contract::ContractFactory;
|
|
||||||
use ethers_core::{
|
|
||||||
abi::Abi,
|
|
||||||
types::*,
|
|
||||||
utils::{parse_ether, parse_units, Anvil},
|
|
||||||
};
|
|
||||||
use ethers_middleware::signer::SignerMiddleware;
|
|
||||||
use ethers_providers::{Http, JsonRpcClient, Middleware, Provider, GOERLI};
|
|
||||||
use ethers_signers::{coins_bip39::English, LocalWallet, MnemonicBuilder, Signer};
|
|
||||||
use ethers_solc::Solc;
|
|
||||||
use once_cell::sync::Lazy;
|
|
||||||
use std::{
|
|
||||||
convert::TryFrom,
|
|
||||||
iter::Cycle,
|
|
||||||
sync::{atomic::AtomicU8, Arc},
|
|
||||||
time::Duration,
|
|
||||||
};
|
|
||||||
|
|
||||||
static WALLETS: Lazy<TestWallets> = Lazy::new(|| {
|
|
||||||
TestWallets {
|
|
||||||
mnemonic: MnemonicBuilder::default()
|
|
||||||
// Please don't drain this :)
|
|
||||||
.phrase("impose air often almost medal sudden finish quote dwarf devote theme layer"),
|
|
||||||
next: Default::default(),
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
#[cfg(not(feature = "celo"))]
|
|
||||||
async fn send_eth() {
|
|
||||||
let anvil = Anvil::new().spawn();
|
|
||||||
|
|
||||||
// this private key belongs to the above mnemonic
|
|
||||||
let wallet: LocalWallet = anvil.keys()[0].clone().into();
|
|
||||||
let wallet2: LocalWallet = anvil.keys()[1].clone().into();
|
|
||||||
|
|
||||||
// connect to the network
|
|
||||||
let provider = Provider::<Http>::try_from(anvil.endpoint())
|
|
||||||
.unwrap()
|
|
||||||
.interval(Duration::from_millis(10u64));
|
|
||||||
let chain_id = provider.get_chainid().await.unwrap().as_u64();
|
|
||||||
let provider = SignerMiddleware::new_with_provider_chain(provider, wallet).await.unwrap();
|
|
||||||
|
|
||||||
// craft the transaction
|
|
||||||
let tx = TransactionRequest::new().to(wallet2.address()).value(10000).chain_id(chain_id);
|
|
||||||
|
|
||||||
let balance_before = provider.get_balance(provider.address(), None).await.unwrap();
|
|
||||||
|
|
||||||
// send it!
|
|
||||||
provider.send_transaction(tx, None).await.unwrap();
|
|
||||||
|
|
||||||
let balance_after = provider.get_balance(provider.address(), None).await.unwrap();
|
|
||||||
|
|
||||||
assert!(balance_before > balance_after);
|
|
||||||
}
|
|
||||||
|
|
||||||
// hardhat compatibility test, to show hardhat rejects tx signed for other chains
|
|
||||||
#[tokio::test]
|
|
||||||
#[cfg(not(feature = "celo"))]
|
|
||||||
#[ignore]
|
|
||||||
async fn send_with_chain_id_hardhat() {
|
|
||||||
let wallet: LocalWallet =
|
|
||||||
"ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80".parse().unwrap();
|
|
||||||
let provider = Provider::try_from("http://localhost:8545").unwrap();
|
|
||||||
let client = SignerMiddleware::new(provider, wallet);
|
|
||||||
|
|
||||||
let tx = TransactionRequest::new().to(Address::random()).value(100u64);
|
|
||||||
let res = client.send_transaction(tx, None).await;
|
|
||||||
|
|
||||||
let err = res.unwrap_err();
|
|
||||||
assert!(err
|
|
||||||
.to_string()
|
|
||||||
.contains("Trying to send an incompatible EIP-155 transaction, signed for another chain."));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
#[cfg(not(feature = "celo"))]
|
|
||||||
#[ignore]
|
|
||||||
async fn send_with_chain_id_anvil() {
|
|
||||||
let wallet: LocalWallet =
|
|
||||||
"ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80".parse().unwrap();
|
|
||||||
let provider = Provider::try_from("http://localhost:8545").unwrap();
|
|
||||||
let client = SignerMiddleware::new(provider, wallet);
|
|
||||||
|
|
||||||
let tx = TransactionRequest::new().to(Address::random()).value(100u64);
|
|
||||||
let res = client.send_transaction(tx, None).await;
|
|
||||||
|
|
||||||
let _err = res.unwrap_err();
|
|
||||||
}
|
|
||||||
|
|
||||||
// #[tokio::test]
|
|
||||||
// #[cfg(not(feature = "celo"))]
|
|
||||||
// async fn pending_txs_with_confirmations_testnet() {
|
|
||||||
// let provider = GOERLI.provider().interval(Duration::from_millis(3000));
|
|
||||||
// let chain_id = provider.get_chainid().await.unwrap();
|
|
||||||
// let wallet = WALLETS.next().with_chain_id(chain_id.as_u64());
|
|
||||||
// let address = wallet.address();
|
|
||||||
// let provider = SignerMiddleware::new(provider, wallet);
|
|
||||||
// generic_pending_txs_test(provider, address).await;
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// // different keys to avoid nonce errors
|
|
||||||
// #[tokio::test]
|
|
||||||
// #[cfg(not(feature = "celo"))]
|
|
||||||
// async fn websocket_pending_txs_with_confirmations_testnet() {
|
|
||||||
// let provider = GOERLI.ws().await.interval(Duration::from_millis(3000));
|
|
||||||
// let chain_id = provider.get_chainid().await.unwrap();
|
|
||||||
// let wallet = WALLETS.next().with_chain_id(chain_id.as_u64());
|
|
||||||
// let address = wallet.address();
|
|
||||||
// let provider = SignerMiddleware::new(provider, wallet);
|
|
||||||
// generic_pending_txs_test(provider, address).await;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// #[cfg(not(feature = "celo"))]
|
|
||||||
// async fn generic_pending_txs_test<M: Middleware>(provider: M, who: Address) {
|
|
||||||
// let tx = TransactionRequest::new().to(who).from(who);
|
|
||||||
// let pending_tx = provider.send_transaction(tx, None).await.unwrap();
|
|
||||||
// let tx_hash = *pending_tx;
|
|
||||||
// let receipt = pending_tx.confirmations(1).await.unwrap().unwrap();
|
|
||||||
// // got the correct receipt
|
|
||||||
// assert_eq!(receipt.transaction_hash, tx_hash);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// #[tokio::test]
|
|
||||||
// #[cfg(not(feature = "celo"))]
|
|
||||||
// async fn typed_txs() {
|
|
||||||
// let provider = GOERLI.provider();
|
|
||||||
//
|
|
||||||
// let chain_id = provider.get_chainid().await.unwrap();
|
|
||||||
// let wallet = WALLETS.next().with_chain_id(chain_id.as_u64());
|
|
||||||
// let address = wallet.address();
|
|
||||||
//
|
|
||||||
// dbg!(&wallet, &address);
|
|
||||||
// // our wallet
|
|
||||||
// let provider = SignerMiddleware::new(provider, wallet);
|
|
||||||
//
|
|
||||||
// // Uncomment the below and run this test to re-fund the wallets if they get drained.
|
|
||||||
// // Would be ideal if we'd have a way to do this automatically, but this should be
|
|
||||||
// // happening rarely enough that it doesn't matter.
|
|
||||||
// // WALLETS.fund(provider.provider(), 10u32).await;
|
|
||||||
//
|
|
||||||
// async fn check_tx<P: JsonRpcClient + Clone>(
|
|
||||||
// pending_tx: ethers_providers::PendingTransaction<'_, P>,
|
|
||||||
// expected: u64,
|
|
||||||
// ) {
|
|
||||||
// let provider = pending_tx.provider();
|
|
||||||
// let receipt = pending_tx.await.unwrap().unwrap();
|
|
||||||
// let tx = provider.get_transaction(receipt.transaction_hash).await.unwrap().unwrap();
|
|
||||||
// assert_eq!(receipt.transaction_type, Some(expected.into()));
|
|
||||||
// assert_eq!(tx.transaction_type, Some(expected.into()));
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// let nonce = provider.get_transaction_count(address, None).await.unwrap();
|
|
||||||
// let bn = Some(BlockNumber::Pending.into());
|
|
||||||
// let gas_price = provider.get_gas_price().await.unwrap() * 125 / 100;
|
|
||||||
//
|
|
||||||
// let tx =
|
|
||||||
// TransactionRequest::new().from(address).to(address).nonce(nonce).gas_price(gas_price);
|
|
||||||
// let tx1 = provider.send_transaction(tx.clone(), bn).await.unwrap();
|
|
||||||
//
|
|
||||||
// let tx = tx.clone().from(address).to(address).nonce(nonce + 1).with_access_list(vec![]);
|
|
||||||
// let tx2 = provider.send_transaction(tx, bn).await.unwrap();
|
|
||||||
//
|
|
||||||
// let tx = Eip1559TransactionRequest::new()
|
|
||||||
// .from(address)
|
|
||||||
// .to(address)
|
|
||||||
// .nonce(nonce + 2)
|
|
||||||
// .max_fee_per_gas(gas_price)
|
|
||||||
// .max_priority_fee_per_gas(gas_price);
|
|
||||||
// let tx3 = provider.send_transaction(tx, bn).await.unwrap();
|
|
||||||
//
|
|
||||||
// futures_util::join!(check_tx(tx1, 0), check_tx(tx2, 1), check_tx(tx3, 2),);
|
|
||||||
// }
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
#[cfg(feature = "celo")]
|
|
||||||
async fn test_send_transaction() {
|
|
||||||
// Celo testnet
|
|
||||||
let provider = Provider::<Http>::try_from("https://alfajores-forno.celo-testnet.org")
|
|
||||||
.unwrap()
|
|
||||||
.interval(Duration::from_millis(3000u64));
|
|
||||||
let chain_id = provider.get_chainid().await.unwrap().as_u64();
|
|
||||||
|
|
||||||
// Funded with https://celo.org/developers/faucet
|
|
||||||
// Please do not drain this account :)
|
|
||||||
let wallet = "d652abb81e8c686edba621a895531b1f291289b63b5ef09a94f686a5ecdd5db1"
|
|
||||||
.parse::<LocalWallet>()
|
|
||||||
.unwrap()
|
|
||||||
.with_chain_id(chain_id);
|
|
||||||
let client = SignerMiddleware::new(provider, wallet);
|
|
||||||
|
|
||||||
let balance_before = client.get_balance(client.address(), None).await.unwrap();
|
|
||||||
let tx = TransactionRequest::pay(client.address(), 100);
|
|
||||||
let _receipt = client.send_transaction(tx, None).await.unwrap().confirmations(3).await.unwrap();
|
|
||||||
let balance_after = client.get_balance(client.address(), None).await.unwrap();
|
|
||||||
assert!(balance_before > balance_after);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
#[cfg(not(feature = "celo"))]
|
|
||||||
async fn send_transaction_handles_tx_from_field() {
|
|
||||||
// launch anvil
|
|
||||||
let anvil = Anvil::new().spawn();
|
|
||||||
|
|
||||||
// grab 2 wallets
|
|
||||||
let signer: LocalWallet = anvil.keys()[0].clone().into();
|
|
||||||
let other: LocalWallet = anvil.keys()[1].clone().into();
|
|
||||||
|
|
||||||
// connect to the network
|
|
||||||
let provider = Provider::try_from(anvil.endpoint()).unwrap();
|
|
||||||
let provider =
|
|
||||||
SignerMiddleware::new_with_provider_chain(provider, signer.clone()).await.unwrap();
|
|
||||||
|
|
||||||
// sending a TransactionRequest with a from field of None should result
|
|
||||||
// in a transaction from the signer address
|
|
||||||
let request_from_none = TransactionRequest::new();
|
|
||||||
let receipt =
|
|
||||||
provider.send_transaction(request_from_none, None).await.unwrap().await.unwrap().unwrap();
|
|
||||||
let sent_tx = provider.get_transaction(receipt.transaction_hash).await.unwrap().unwrap();
|
|
||||||
|
|
||||||
assert_eq!(sent_tx.from, signer.address());
|
|
||||||
|
|
||||||
// sending a TransactionRequest with the signer as the from address should
|
|
||||||
// result in a transaction from the signer address
|
|
||||||
let request_from_signer = TransactionRequest::new().from(signer.address());
|
|
||||||
let receipt =
|
|
||||||
provider.send_transaction(request_from_signer, None).await.unwrap().await.unwrap().unwrap();
|
|
||||||
let sent_tx = provider.get_transaction(receipt.transaction_hash).await.unwrap().unwrap();
|
|
||||||
|
|
||||||
assert_eq!(sent_tx.from, signer.address());
|
|
||||||
|
|
||||||
// sending a TransactionRequest with a from address that is not the signer
|
|
||||||
// should result in a transaction from the specified address
|
|
||||||
let request_from_other = TransactionRequest::new().from(other.address());
|
|
||||||
let receipt =
|
|
||||||
provider.send_transaction(request_from_other, None).await.unwrap().await.unwrap().unwrap();
|
|
||||||
let sent_tx = provider.get_transaction(receipt.transaction_hash).await.unwrap().unwrap();
|
|
||||||
|
|
||||||
assert_eq!(sent_tx.from, other.address());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
#[cfg(feature = "celo")]
|
|
||||||
async fn deploy_and_call_contract() {
|
|
||||||
// compiles the given contract and returns the ABI and Bytecode
|
|
||||||
fn compile_contract(path: &str, name: &str) -> (Abi, Bytes) {
|
|
||||||
let path = format!("./tests/solidity-contracts/{path}");
|
|
||||||
let compiled = Solc::default().compile_source(&path).unwrap();
|
|
||||||
let contract = compiled.get(&path, name).expect("could not find contract");
|
|
||||||
let (abi, bin, _) = contract.into_parts_or_default();
|
|
||||||
(abi, bin)
|
|
||||||
}
|
|
||||||
|
|
||||||
let (abi, bytecode) = compile_contract("SimpleStorage.sol", "SimpleStorage");
|
|
||||||
|
|
||||||
// Celo testnet
|
|
||||||
let provider = Provider::<Http>::try_from("https://alfajores-forno.celo-testnet.org")
|
|
||||||
.unwrap()
|
|
||||||
.interval(Duration::from_millis(6000));
|
|
||||||
let chain_id = provider.get_chainid().await.unwrap().as_u64();
|
|
||||||
|
|
||||||
// Funded with https://celo.org/developers/faucet
|
|
||||||
let wallet = "58ea5643a78c36926ad5128a6b0d8dfcc7fc705788a993b1c724be3469bc9697"
|
|
||||||
.parse::<LocalWallet>()
|
|
||||||
.unwrap()
|
|
||||||
.with_chain_id(chain_id);
|
|
||||||
let client = SignerMiddleware::new_with_provider_chain(provider, wallet).await.unwrap();
|
|
||||||
let client = Arc::new(client);
|
|
||||||
|
|
||||||
let factory = ContractFactory::new(abi, bytecode, client);
|
|
||||||
let deployer = factory.deploy(()).unwrap().legacy();
|
|
||||||
let contract = deployer.block(BlockNumber::Pending).send().await.unwrap();
|
|
||||||
|
|
||||||
let value: U256 = contract.method("value", ()).unwrap().call().await.unwrap();
|
|
||||||
assert_eq!(value, 0.into());
|
|
||||||
|
|
||||||
// make a state mutating transaction
|
|
||||||
// gas estimation costs are sometimes under-reported on celo,
|
|
||||||
// so we manually set it to avoid failures
|
|
||||||
let call = contract.method::<_, H256>("setValue", U256::from(1)).unwrap().gas(100000);
|
|
||||||
let pending_tx = call.send().await.unwrap();
|
|
||||||
let _receipt = pending_tx.await.unwrap();
|
|
||||||
|
|
||||||
let value: U256 = contract.method("value", ()).unwrap().call().await.unwrap();
|
|
||||||
assert_eq!(value, 1.into());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
|
||||||
struct TestWallets {
|
|
||||||
mnemonic: MnemonicBuilder<English>,
|
|
||||||
next: AtomicU8,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TestWallets {
|
|
||||||
/// Helper for funding the wallets with an instantiated provider
|
|
||||||
#[allow(unused)]
|
|
||||||
pub async fn fund<T: JsonRpcClient, U: Into<u32>>(&self, provider: &Provider<T>, n: U) {
|
|
||||||
let addrs = (0..n.into()).map(|i| self.get(i).address()).collect::<Vec<_>>();
|
|
||||||
// hardcoded funder address private key, GOERLI
|
|
||||||
let signer = "9867bd0f8d9e16c57f5251b35a73f6f903eb8eee1bdc7f15256d0dc09d1945fb"
|
|
||||||
.parse::<LocalWallet>()
|
|
||||||
.unwrap()
|
|
||||||
.with_chain_id(provider.get_chainid().await.unwrap().as_u64());
|
|
||||||
let provider = SignerMiddleware::new(provider, signer);
|
|
||||||
let addr = provider.address();
|
|
||||||
|
|
||||||
let mut nonce = provider.get_transaction_count(addr, None).await.unwrap();
|
|
||||||
let mut pending_txs = Vec::new();
|
|
||||||
for addr in addrs {
|
|
||||||
println!("Funding wallet {addr:?}");
|
|
||||||
let tx = TransactionRequest::new()
|
|
||||||
.nonce(nonce)
|
|
||||||
.to(addr)
|
|
||||||
// 0.1 eth per wallet
|
|
||||||
.value(parse_ether("1").unwrap());
|
|
||||||
pending_txs.push(
|
|
||||||
provider.send_transaction(tx, Some(BlockNumber::Pending.into())).await.unwrap(),
|
|
||||||
);
|
|
||||||
nonce += 1.into();
|
|
||||||
}
|
|
||||||
|
|
||||||
futures_util::future::join_all(pending_txs).await;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn next(&self) -> LocalWallet {
|
|
||||||
let idx = self.next.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
|
|
||||||
|
|
||||||
// println!("Got wallet {:?}", wallet.address());
|
|
||||||
self.get(idx)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get<T: Into<u32>>(&self, idx: T) -> LocalWallet {
|
|
||||||
self.mnemonic
|
|
||||||
.clone()
|
|
||||||
.index(idx)
|
|
||||||
.expect("index not found")
|
|
||||||
.build()
|
|
||||||
.expect("cannot build wallet")
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,206 +0,0 @@
|
||||||
pragma solidity >=0.6.0;
|
|
||||||
|
|
||||||
pragma solidity >=0.4.23;
|
|
||||||
|
|
||||||
interface DSAuthority {
|
|
||||||
function canCall(
|
|
||||||
address src, address dst, bytes4 sig
|
|
||||||
) external view returns (bool);
|
|
||||||
}
|
|
||||||
|
|
||||||
contract DSAuthEvents {
|
|
||||||
event LogSetAuthority (address indexed authority);
|
|
||||||
event LogSetOwner (address indexed owner);
|
|
||||||
}
|
|
||||||
|
|
||||||
contract DSAuth is DSAuthEvents {
|
|
||||||
DSAuthority public authority;
|
|
||||||
address public owner;
|
|
||||||
|
|
||||||
constructor() public {
|
|
||||||
owner = msg.sender;
|
|
||||||
emit LogSetOwner(msg.sender);
|
|
||||||
}
|
|
||||||
|
|
||||||
function setOwner(address owner_)
|
|
||||||
public
|
|
||||||
auth
|
|
||||||
{
|
|
||||||
owner = owner_;
|
|
||||||
emit LogSetOwner(owner);
|
|
||||||
}
|
|
||||||
|
|
||||||
function setAuthority(DSAuthority authority_)
|
|
||||||
public
|
|
||||||
auth
|
|
||||||
{
|
|
||||||
authority = authority_;
|
|
||||||
emit LogSetAuthority(address(authority));
|
|
||||||
}
|
|
||||||
|
|
||||||
modifier auth {
|
|
||||||
require(isAuthorized(msg.sender, msg.sig), "ds-auth-unauthorized");
|
|
||||||
_;
|
|
||||||
}
|
|
||||||
|
|
||||||
function isAuthorized(address src, bytes4 sig) internal view returns (bool) {
|
|
||||||
if (src == address(this)) {
|
|
||||||
return true;
|
|
||||||
} else if (src == owner) {
|
|
||||||
return true;
|
|
||||||
} else if (authority == DSAuthority(address(uint160(0)))) {
|
|
||||||
return false;
|
|
||||||
} else {
|
|
||||||
return authority.canCall(src, address(this), sig);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
contract DSNote {
|
|
||||||
event LogNote(
|
|
||||||
bytes4 indexed sig,
|
|
||||||
address indexed guy,
|
|
||||||
bytes32 indexed foo,
|
|
||||||
bytes32 indexed bar,
|
|
||||||
uint256 wad,
|
|
||||||
bytes fax
|
|
||||||
) anonymous;
|
|
||||||
|
|
||||||
modifier note {
|
|
||||||
bytes32 foo;
|
|
||||||
bytes32 bar;
|
|
||||||
uint256 wad;
|
|
||||||
|
|
||||||
assembly {
|
|
||||||
foo := calldataload(4)
|
|
||||||
bar := calldataload(36)
|
|
||||||
wad := callvalue()
|
|
||||||
}
|
|
||||||
|
|
||||||
_;
|
|
||||||
|
|
||||||
emit LogNote(msg.sig, msg.sender, foo, bar, wad, msg.data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// DSProxy
|
|
||||||
// Allows code execution using a persistant identity This can be very
|
|
||||||
// useful to execute a sequence of atomic actions. Since the owner of
|
|
||||||
// the proxy can be changed, this allows for dynamic ownership models
|
|
||||||
// i.e. a multisig
|
|
||||||
contract DSProxy is DSAuth, DSNote {
|
|
||||||
DSProxyCache public cache; // global cache for contracts
|
|
||||||
|
|
||||||
constructor(address _cacheAddr) public {
|
|
||||||
require(setCache(_cacheAddr));
|
|
||||||
}
|
|
||||||
|
|
||||||
fallback() external payable {
|
|
||||||
}
|
|
||||||
|
|
||||||
receive() external payable {
|
|
||||||
}
|
|
||||||
|
|
||||||
// use the proxy to execute calldata _data on contract _code
|
|
||||||
function execute(bytes memory _code, bytes memory _data)
|
|
||||||
public
|
|
||||||
payable
|
|
||||||
returns (address target, bytes32 response)
|
|
||||||
{
|
|
||||||
target = cache.read(_code);
|
|
||||||
if (target == address(0x0)) {
|
|
||||||
// deploy contract & store its address in cache
|
|
||||||
target = cache.write(_code);
|
|
||||||
}
|
|
||||||
|
|
||||||
response = execute(target, _data);
|
|
||||||
}
|
|
||||||
|
|
||||||
function execute(address _target, bytes memory _data)
|
|
||||||
public
|
|
||||||
auth
|
|
||||||
note
|
|
||||||
payable
|
|
||||||
returns (bytes32 response)
|
|
||||||
{
|
|
||||||
require(_target != address(0x0));
|
|
||||||
|
|
||||||
// call contract in current context
|
|
||||||
assembly {
|
|
||||||
let succeeded := delegatecall(sub(gas(), 5000), _target, add(_data, 0x20), mload(_data), 0, 32)
|
|
||||||
response := mload(0) // load delegatecall output
|
|
||||||
switch iszero(succeeded)
|
|
||||||
case 1 {
|
|
||||||
// throw if delegatecall failed
|
|
||||||
revert(0, 0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//set new cache
|
|
||||||
function setCache(address _cacheAddr)
|
|
||||||
public
|
|
||||||
auth
|
|
||||||
note
|
|
||||||
returns (bool)
|
|
||||||
{
|
|
||||||
require(_cacheAddr != address(0x0)); // invalid cache address
|
|
||||||
cache = DSProxyCache(_cacheAddr); // overwrite cache
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// DSProxyFactory
|
|
||||||
// This factory deploys new proxy instances through build()
|
|
||||||
// Deployed proxy addresses are logged
|
|
||||||
contract DSProxyFactory {
|
|
||||||
event Created(address indexed sender, address indexed owner, address proxy, address cache);
|
|
||||||
mapping(address=>bool) public isProxy;
|
|
||||||
DSProxyCache public cache = new DSProxyCache();
|
|
||||||
|
|
||||||
// deploys a new proxy instance
|
|
||||||
// sets owner of proxy to caller
|
|
||||||
function build() public returns (DSProxy proxy) {
|
|
||||||
proxy = build(msg.sender);
|
|
||||||
}
|
|
||||||
|
|
||||||
// deploys a new proxy instance
|
|
||||||
// sets custom owner of proxy
|
|
||||||
function build(address owner) public returns (DSProxy proxy) {
|
|
||||||
proxy = new DSProxy(address(cache));
|
|
||||||
emit Created(msg.sender, owner, address(proxy), address(cache));
|
|
||||||
proxy.setOwner(owner);
|
|
||||||
isProxy[address(proxy)] = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// DSProxyCache
|
|
||||||
// This global cache stores addresses of contracts previously deployed
|
|
||||||
// by a proxy. This saves gas from repeat deployment of the same
|
|
||||||
// contracts and eliminates blockchain bloat.
|
|
||||||
|
|
||||||
// By default, all proxies deployed from the same factory store
|
|
||||||
// contracts in the same cache. The cache a proxy instance uses can be
|
|
||||||
// changed. The cache uses the sha3 hash of a contract's bytecode to
|
|
||||||
// lookup the address
|
|
||||||
contract DSProxyCache {
|
|
||||||
mapping(bytes32 => address) cache;
|
|
||||||
|
|
||||||
function read(bytes memory _code) public view returns (address) {
|
|
||||||
bytes32 hash = keccak256(_code);
|
|
||||||
return cache[hash];
|
|
||||||
}
|
|
||||||
|
|
||||||
function write(bytes memory _code) public returns (address target) {
|
|
||||||
assembly {
|
|
||||||
target := create(0, add(_code, 0x20), mload(_code))
|
|
||||||
switch iszero(extcodesize(target))
|
|
||||||
case 1 {
|
|
||||||
// throw if contract failed to deploy
|
|
||||||
revert(0, 0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
bytes32 hash = keccak256(_code);
|
|
||||||
cache[hash] = target;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,15 +0,0 @@
|
||||||
pragma solidity >=0.4.24;
|
|
||||||
|
|
||||||
contract SimpleStorage {
|
|
||||||
|
|
||||||
event ValueChanged(address indexed author, address indexed oldAuthor, uint256 oldValue, uint256 newValue);
|
|
||||||
|
|
||||||
address public lastSender;
|
|
||||||
uint256 public value;
|
|
||||||
|
|
||||||
function setValue(uint256 _value) public {
|
|
||||||
emit ValueChanged(msg.sender, lastSender, value, _value);
|
|
||||||
value = _value;
|
|
||||||
lastSender = msg.sender;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,144 +0,0 @@
|
||||||
#![cfg(all(not(target_arch = "wasm32"), not(feature = "celo")))]
|
|
||||||
|
|
||||||
use ethers_contract::{BaseContract, ContractFactory};
|
|
||||||
use ethers_core::{abi::Abi, types::*, utils::Anvil};
|
|
||||||
use ethers_middleware::{
|
|
||||||
transformer::{DsProxy, TransformerMiddleware},
|
|
||||||
SignerMiddleware,
|
|
||||||
};
|
|
||||||
use ethers_providers::{Http, Middleware, Provider};
|
|
||||||
use ethers_signers::{LocalWallet, Signer};
|
|
||||||
use ethers_solc::Solc;
|
|
||||||
use rand::Rng;
|
|
||||||
use std::{convert::TryFrom, sync::Arc, time::Duration};
|
|
||||||
|
|
||||||
type HttpWallet = SignerMiddleware<Provider<Http>, LocalWallet>;
|
|
||||||
|
|
||||||
// compiles the given contract and returns the ABI and Bytecode
|
|
||||||
fn compile_contract(path: &str, name: &str) -> (Abi, Bytes) {
|
|
||||||
let path = format!("./tests/solidity-contracts/{path}");
|
|
||||||
let compiled = Solc::default().compile_source(&path).unwrap();
|
|
||||||
let contract = compiled.get(&path, name).expect("could not find contract");
|
|
||||||
let (abi, bin, _) = contract.into_parts_or_default();
|
|
||||||
(abi, bin)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn ds_proxy_transformer() {
|
|
||||||
// randomness
|
|
||||||
let mut rng = rand::thread_rng();
|
|
||||||
|
|
||||||
// spawn anvil and instantiate a signer middleware.
|
|
||||||
let anvil = Anvil::new().spawn();
|
|
||||||
let wallet: LocalWallet = anvil.keys()[0].clone().into();
|
|
||||||
let provider = Provider::<Http>::try_from(anvil.endpoint())
|
|
||||||
.unwrap()
|
|
||||||
.interval(Duration::from_millis(10u64));
|
|
||||||
let chain_id = provider.get_chainid().await.unwrap().as_u64();
|
|
||||||
let wallet = wallet.with_chain_id(chain_id);
|
|
||||||
let signer_middleware = SignerMiddleware::new(provider.clone(), wallet);
|
|
||||||
let wallet_addr = signer_middleware.address();
|
|
||||||
let provider = Arc::new(signer_middleware.clone());
|
|
||||||
|
|
||||||
// deploy DsProxyFactory which we'll use to deploy a new DsProxy contract.
|
|
||||||
let (abi, bytecode) = compile_contract("DSProxy.sol", "DSProxyFactory");
|
|
||||||
let factory = ContractFactory::new(abi, bytecode, Arc::clone(&provider));
|
|
||||||
let ds_proxy_factory = factory.deploy(()).unwrap().legacy();
|
|
||||||
let ds_proxy_factory = ds_proxy_factory.send().await.unwrap();
|
|
||||||
|
|
||||||
// deploy a new DsProxy contract.
|
|
||||||
let ds_proxy = DsProxy::build::<HttpWallet, Arc<HttpWallet>>(
|
|
||||||
Arc::clone(&provider),
|
|
||||||
Some(ds_proxy_factory.address()),
|
|
||||||
provider.address(),
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
let ds_proxy_addr = ds_proxy.address();
|
|
||||||
|
|
||||||
// deploy SimpleStorage and try to update its value via transformer middleware.
|
|
||||||
let (abi, bytecode) = compile_contract("SimpleStorage.sol", "SimpleStorage");
|
|
||||||
let factory = ContractFactory::new(abi, bytecode, Arc::clone(&provider));
|
|
||||||
let deployer = factory.deploy(()).unwrap().legacy();
|
|
||||||
let simple_storage = deployer.send().await.unwrap();
|
|
||||||
|
|
||||||
// instantiate a new transformer middleware.
|
|
||||||
let provider = TransformerMiddleware::new(signer_middleware, ds_proxy.clone());
|
|
||||||
|
|
||||||
// broadcast the setValue tx via transformer middleware (first wallet).
|
|
||||||
let expected_value: u64 = rng.gen();
|
|
||||||
let calldata = simple_storage
|
|
||||||
.encode("setValue", U256::from(expected_value))
|
|
||||||
.expect("could not get ABI encoded data");
|
|
||||||
let tx = TransactionRequest::new().to(simple_storage.address()).data(calldata);
|
|
||||||
provider.send_transaction(tx, None).await.unwrap().await.unwrap();
|
|
||||||
|
|
||||||
// verify that DsProxy's state was updated.
|
|
||||||
let last_sender = provider.get_storage_at(ds_proxy_addr, H256::zero(), None).await.unwrap();
|
|
||||||
let last_value =
|
|
||||||
provider.get_storage_at(ds_proxy_addr, H256::from_low_u64_be(1u64), None).await.unwrap();
|
|
||||||
assert_eq!(last_sender, wallet_addr.into());
|
|
||||||
assert_eq!(last_value, H256::from_low_u64_be(expected_value));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn ds_proxy_code() {
|
|
||||||
// randomness
|
|
||||||
let mut rng = rand::thread_rng();
|
|
||||||
|
|
||||||
// spawn anvil and instantiate a signer middleware.
|
|
||||||
let anvil = Anvil::new().spawn();
|
|
||||||
let wallet: LocalWallet = anvil.keys()[1].clone().into();
|
|
||||||
let provider = Provider::<Http>::try_from(anvil.endpoint())
|
|
||||||
.unwrap()
|
|
||||||
.interval(Duration::from_millis(10u64));
|
|
||||||
let chain_id = provider.get_chainid().await.unwrap().as_u64();
|
|
||||||
let wallet = wallet.with_chain_id(chain_id);
|
|
||||||
let signer_middleware = SignerMiddleware::new(provider.clone(), wallet);
|
|
||||||
let wallet_addr = signer_middleware.address();
|
|
||||||
let provider = Arc::new(signer_middleware.clone());
|
|
||||||
|
|
||||||
// deploy DsProxyFactory which we'll use to deploy a new DsProxy contract.
|
|
||||||
let (abi, bytecode) = compile_contract("DSProxy.sol", "DSProxyFactory");
|
|
||||||
let factory = ContractFactory::new(abi, bytecode, Arc::clone(&provider));
|
|
||||||
let ds_proxy_factory = factory.deploy(()).unwrap().legacy();
|
|
||||||
let ds_proxy_factory = ds_proxy_factory.send().await.unwrap();
|
|
||||||
|
|
||||||
// deploy a new DsProxy contract.
|
|
||||||
let ds_proxy = DsProxy::build::<HttpWallet, Arc<HttpWallet>>(
|
|
||||||
Arc::clone(&provider),
|
|
||||||
Some(ds_proxy_factory.address()),
|
|
||||||
provider.address(),
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
let ds_proxy_addr = ds_proxy.address();
|
|
||||||
|
|
||||||
// compile the SimpleStorage contract which we will use to interact via DsProxy.
|
|
||||||
let (abi, bytecode) = compile_contract("SimpleStorage.sol", "SimpleStorage");
|
|
||||||
let ss_base_contract: BaseContract = abi.into();
|
|
||||||
let expected_value: u64 = rng.gen();
|
|
||||||
let calldata = ss_base_contract
|
|
||||||
.encode("setValue", U256::from(expected_value))
|
|
||||||
.expect("could not get ABI encoded data");
|
|
||||||
|
|
||||||
// execute code via the deployed DsProxy contract.
|
|
||||||
ds_proxy
|
|
||||||
.execute::<HttpWallet, Arc<HttpWallet>, Bytes>(
|
|
||||||
Arc::clone(&provider),
|
|
||||||
bytecode.clone(),
|
|
||||||
calldata,
|
|
||||||
)
|
|
||||||
.expect("could not construct DSProxy contract call")
|
|
||||||
.legacy()
|
|
||||||
.send()
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
// verify that DsProxy's state was updated.
|
|
||||||
let last_sender = provider.get_storage_at(ds_proxy_addr, H256::zero(), None).await.unwrap();
|
|
||||||
let last_value =
|
|
||||||
provider.get_storage_at(ds_proxy_addr, H256::from_low_u64_be(1u64), None).await.unwrap();
|
|
||||||
assert_eq!(last_sender, wallet_addr.into());
|
|
||||||
assert_eq!(last_value, H256::from_low_u64_be(expected_value));
|
|
||||||
}
|
|
|
@ -1864,27 +1864,14 @@ mod tests {
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn geth_admin_nodeinfo() {
|
async fn geth_admin_nodeinfo() {
|
||||||
// we can't use the test provider because infura does not expose admin endpoints
|
// we can't use the test provider because infura does not expose admin endpoints
|
||||||
let port = 8546u16;
|
|
||||||
let p2p_listener_port = 13337u16;
|
|
||||||
let authrpc_port = 8552u16;
|
|
||||||
let network = 1337u64;
|
let network = 1337u64;
|
||||||
let temp_dir = tempfile::tempdir().unwrap().into_path();
|
let temp_dir = tempfile::tempdir().unwrap().into_path();
|
||||||
|
|
||||||
let (geth, provider) = spawn_geth_and_create_provider(
|
let (geth, provider) = spawn_geth_and_create_provider(network, Some(temp_dir), None);
|
||||||
network,
|
|
||||||
port,
|
|
||||||
p2p_listener_port,
|
|
||||||
authrpc_port,
|
|
||||||
Some(temp_dir),
|
|
||||||
None,
|
|
||||||
);
|
|
||||||
|
|
||||||
let info = provider.node_info().await.unwrap();
|
let info = provider.node_info().await.unwrap();
|
||||||
drop(geth);
|
drop(geth);
|
||||||
|
|
||||||
// check that the port we set works
|
|
||||||
assert_eq!(info.ports.listener, p2p_listener_port);
|
|
||||||
|
|
||||||
// make sure it is running eth
|
// make sure it is running eth
|
||||||
assert!(info.protocols.eth.is_some());
|
assert!(info.protocols.eth.is_some());
|
||||||
|
|
||||||
|
@ -1897,18 +1884,10 @@ mod tests {
|
||||||
/// These will all use the same genesis config.
|
/// These will all use the same genesis config.
|
||||||
fn spawn_geth_and_create_provider(
|
fn spawn_geth_and_create_provider(
|
||||||
chain_id: u64,
|
chain_id: u64,
|
||||||
rpc_port: u16,
|
|
||||||
p2p_port: u16,
|
|
||||||
authrpc_port: u16,
|
|
||||||
datadir: Option<PathBuf>,
|
datadir: Option<PathBuf>,
|
||||||
genesis: Option<Genesis>,
|
genesis: Option<Genesis>,
|
||||||
) -> (GethInstance, Provider<HttpProvider>) {
|
) -> (GethInstance, Provider<HttpProvider>) {
|
||||||
let geth = Geth::new()
|
let geth = Geth::new().chain_id(chain_id).disable_discovery();
|
||||||
.port(rpc_port)
|
|
||||||
.p2p_port(p2p_port)
|
|
||||||
.authrpc_port(authrpc_port)
|
|
||||||
.chain_id(chain_id)
|
|
||||||
.disable_discovery();
|
|
||||||
|
|
||||||
let geth = match genesis {
|
let geth = match genesis {
|
||||||
Some(genesis) => geth.genesis(genesis),
|
Some(genesis) => geth.genesis(genesis),
|
||||||
|
@ -1921,8 +1900,7 @@ mod tests {
|
||||||
}
|
}
|
||||||
.spawn();
|
.spawn();
|
||||||
|
|
||||||
let url = format!("http://127.0.0.1:{rpc_port}");
|
let provider = Provider::try_from(geth.endpoint()).unwrap();
|
||||||
let provider = Provider::try_from(url).unwrap();
|
|
||||||
(geth, provider)
|
(geth, provider)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1934,26 +1912,13 @@ mod tests {
|
||||||
chain_id: u64,
|
chain_id: u64,
|
||||||
genesis: Option<Genesis>,
|
genesis: Option<Genesis>,
|
||||||
) -> Vec<(GethInstance, Provider<HttpProvider>)> {
|
) -> Vec<(GethInstance, Provider<HttpProvider>)> {
|
||||||
let mut geths = Vec::new();
|
let mut geths = Vec::with_capacity(datadirs.len());
|
||||||
let mut p2p_port = 30303;
|
|
||||||
let mut rpc_port = 8545;
|
|
||||||
let mut authrpc_port = 8551;
|
|
||||||
|
|
||||||
for dir in datadirs {
|
for dir in datadirs {
|
||||||
let (geth, provider) = spawn_geth_and_create_provider(
|
let (geth, provider) =
|
||||||
chain_id,
|
spawn_geth_and_create_provider(chain_id, Some(dir), genesis.clone());
|
||||||
rpc_port,
|
|
||||||
p2p_port,
|
|
||||||
authrpc_port,
|
|
||||||
Some(dir),
|
|
||||||
genesis.clone(),
|
|
||||||
);
|
|
||||||
|
|
||||||
geths.push((geth, provider));
|
geths.push((geth, provider));
|
||||||
|
|
||||||
p2p_port += 1;
|
|
||||||
rpc_port += 1;
|
|
||||||
authrpc_port += 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
geths
|
geths
|
||||||
|
|
|
@ -513,6 +513,7 @@ impl crate::RpcError for IpcError {
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use ethers_core::utils::{Geth, GethInstance};
|
use ethers_core::utils::{Geth, GethInstance};
|
||||||
|
use std::time::Duration;
|
||||||
use tempfile::NamedTempFile;
|
use tempfile::NamedTempFile;
|
||||||
|
|
||||||
async fn connect() -> (Ipc, GethInstance) {
|
async fn connect() -> (Ipc, GethInstance) {
|
||||||
|
@ -534,7 +535,7 @@ mod tests {
|
||||||
let (ipc, _geth) = connect().await;
|
let (ipc, _geth) = connect().await;
|
||||||
|
|
||||||
let block_num: U256 = ipc.request("eth_blockNumber", ()).await.unwrap();
|
let block_num: U256 = ipc.request("eth_blockNumber", ()).await.unwrap();
|
||||||
std::thread::sleep(std::time::Duration::new(3, 0));
|
tokio::time::sleep(Duration::from_secs(2)).await;
|
||||||
let block_num2: U256 = ipc.request("eth_blockNumber", ()).await.unwrap();
|
let block_num2: U256 = ipc.request("eth_blockNumber", ()).await.unwrap();
|
||||||
assert!(block_num2 > block_num);
|
assert!(block_num2 > block_num);
|
||||||
}
|
}
|
||||||
|
@ -559,6 +560,8 @@ mod tests {
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
.await;
|
.await;
|
||||||
assert_eq!(blocks, vec![1, 2, 3]);
|
// `[1, 2, 3]` or `[2, 3, 4]` etc, depending on test latency
|
||||||
|
assert_eq!(blocks[2], blocks[1] + 1);
|
||||||
|
assert_eq!(blocks[1], blocks[0] + 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,53 @@
|
||||||
|
#![cfg(not(target_arch = "wasm32"))]
|
||||||
|
|
||||||
|
use ethers_core::utils::{Anvil, AnvilInstance};
|
||||||
|
use ethers_providers::{Http, Provider};
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
#[cfg(feature = "ipc")]
|
||||||
|
use ethers_providers::Ipc;
|
||||||
|
#[cfg(feature = "ipc")]
|
||||||
|
use tempfile::NamedTempFile;
|
||||||
|
|
||||||
|
#[cfg(feature = "ws")]
|
||||||
|
use ethers_providers::Ws;
|
||||||
|
|
||||||
|
mod provider;
|
||||||
|
|
||||||
|
mod txpool;
|
||||||
|
|
||||||
|
#[cfg(not(feature = "celo"))]
|
||||||
|
mod ws_errors;
|
||||||
|
|
||||||
|
/// Spawns Anvil and instantiates an Http provider.
|
||||||
|
pub fn spawn_anvil() -> (Provider<Http>, AnvilInstance) {
|
||||||
|
let anvil = Anvil::new().block_time(1u64).spawn();
|
||||||
|
let provider = Provider::<Http>::try_from(anvil.endpoint())
|
||||||
|
.unwrap()
|
||||||
|
.interval(Duration::from_millis(50u64));
|
||||||
|
(provider, anvil)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Spawns Anvil and instantiates a Ws provider.
|
||||||
|
#[cfg(feature = "ws")]
|
||||||
|
pub async fn spawn_anvil_ws() -> (Provider<Ws>, AnvilInstance) {
|
||||||
|
let anvil = Anvil::new().block_time(1u64).spawn();
|
||||||
|
let provider = Provider::<Ws>::connect(anvil.ws_endpoint())
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.interval(Duration::from_millis(50u64));
|
||||||
|
(provider, anvil)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Spawns Anvil and instantiates a Ipc provider.
|
||||||
|
#[cfg(feature = "ipc")]
|
||||||
|
pub async fn spawn_anvil_ipc() -> (Provider<Ipc>, AnvilInstance, NamedTempFile) {
|
||||||
|
let ipc = NamedTempFile::new().unwrap();
|
||||||
|
let anvil =
|
||||||
|
Anvil::new().block_time(1u64).arg("--ipc").arg(ipc.path().display().to_string()).spawn();
|
||||||
|
let provider = Provider::<Ipc>::connect_ipc(ipc.path())
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.interval(Duration::from_millis(50u64));
|
||||||
|
(provider, anvil, ipc)
|
||||||
|
}
|
|
@ -1,15 +1,8 @@
|
||||||
#![cfg(not(target_arch = "wasm32"))]
|
|
||||||
use ethers_providers::{Http, Middleware, Provider};
|
|
||||||
use std::{convert::TryFrom, time::Duration};
|
|
||||||
|
|
||||||
#[cfg(not(feature = "celo"))]
|
#[cfg(not(feature = "celo"))]
|
||||||
mod eth_tests {
|
mod eth_tests {
|
||||||
use super::*;
|
use crate::spawn_anvil;
|
||||||
use ethers_core::{
|
use ethers_core::types::{Address, BlockId, BlockNumber, TransactionRequest, H256};
|
||||||
types::{Address, BlockId, TransactionRequest, H256},
|
use ethers_providers::{Middleware, StreamExt, GOERLI};
|
||||||
utils::Anvil,
|
|
||||||
};
|
|
||||||
use ethers_providers::GOERLI;
|
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn non_existing_data_works() {
|
async fn non_existing_data_works() {
|
||||||
|
@ -35,53 +28,10 @@ mod eth_tests {
|
||||||
|
|
||||||
// Without TLS this would error with "TLS Support not compiled in"
|
// Without TLS this would error with "TLS Support not compiled in"
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
|
#[cfg(any(feature = "openssl", feature = "rustls"))]
|
||||||
async fn ssl_websocket() {
|
async fn ssl_websocket() {
|
||||||
let provider = GOERLI.ws().await;
|
let provider = GOERLI.ws().await;
|
||||||
let _number = provider.get_block_number().await.unwrap();
|
assert_ne!(provider.get_block_number().await.unwrap(), 0.into());
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn watch_blocks_websocket() {
|
|
||||||
use ethers_core::types::H256;
|
|
||||||
use ethers_providers::{StreamExt, Ws};
|
|
||||||
|
|
||||||
let anvil = Anvil::new().block_time(2u64).spawn();
|
|
||||||
let (ws, _) = tokio_tungstenite::connect_async(anvil.ws_endpoint()).await.unwrap();
|
|
||||||
let provider = Provider::new(Ws::new(ws)).interval(Duration::from_millis(500u64));
|
|
||||||
|
|
||||||
let stream = provider.watch_blocks().await.unwrap().stream();
|
|
||||||
|
|
||||||
let _blocks = stream.take(3usize).collect::<Vec<H256>>().await;
|
|
||||||
let _number = provider.get_block_number().await.unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn pending_txs_with_confirmations_anvil() {
|
|
||||||
let anvil = Anvil::new().block_time(2u64).spawn();
|
|
||||||
let provider = Provider::<Http>::try_from(anvil.endpoint())
|
|
||||||
.unwrap()
|
|
||||||
.interval(Duration::from_millis(500u64));
|
|
||||||
let accounts = provider.get_accounts().await.unwrap();
|
|
||||||
generic_pending_txs_test(provider, accounts[0]).await;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn websocket_pending_txs_with_confirmations_anvil() {
|
|
||||||
use ethers_providers::Ws;
|
|
||||||
let anvil = Anvil::new().block_time(2u64).spawn();
|
|
||||||
let ws = Ws::connect(anvil.ws_endpoint()).await.unwrap();
|
|
||||||
let provider = Provider::new(ws);
|
|
||||||
let accounts = provider.get_accounts().await.unwrap();
|
|
||||||
generic_pending_txs_test(provider, accounts[0]).await;
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn generic_pending_txs_test<M: Middleware>(provider: M, who: Address) {
|
|
||||||
let tx = TransactionRequest::new().to(who).from(who);
|
|
||||||
let pending_tx = provider.send_transaction(tx, None).await.unwrap();
|
|
||||||
let tx_hash = *pending_tx;
|
|
||||||
let receipt = pending_tx.confirmations(3).await.unwrap().unwrap();
|
|
||||||
// got the correct receipt
|
|
||||||
assert_eq!(receipt.transaction_hash, tx_hash);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
|
@ -93,33 +43,93 @@ mod eth_tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
#[ignore]
|
async fn watch_blocks_http() {
|
||||||
async fn test_hardhat_compatibility() {
|
let (provider, _anvil) = spawn_anvil();
|
||||||
use ethers_providers::RetryClient;
|
generic_watch_blocks_test(provider).await;
|
||||||
|
|
||||||
async fn send_zst_requests<M: Middleware>(provider: M) {
|
|
||||||
let _ = provider.get_chainid().await.unwrap();
|
|
||||||
let _ = provider.get_block_number().await.unwrap();
|
|
||||||
let _ = provider.get_gas_price().await.unwrap();
|
|
||||||
let _ = provider.get_accounts().await.unwrap();
|
|
||||||
let _ = provider.get_net_version().await.unwrap();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let provider = Provider::<Http>::try_from("http://localhost:8545").unwrap();
|
#[tokio::test]
|
||||||
send_zst_requests(provider).await;
|
#[cfg(feature = "ws")]
|
||||||
|
async fn watch_blocks_ws() {
|
||||||
|
let (provider, _anvil) = crate::spawn_anvil_ws().await;
|
||||||
|
generic_watch_blocks_test(provider).await;
|
||||||
|
}
|
||||||
|
|
||||||
let provider =
|
#[tokio::test]
|
||||||
Provider::<RetryClient<Http>>::new_client("http://localhost:8545", 10, 200).unwrap();
|
#[cfg(feature = "ipc")]
|
||||||
|
async fn watch_blocks_ipc() {
|
||||||
|
let (provider, _anvil, _ipc) = crate::spawn_anvil_ipc().await;
|
||||||
|
generic_watch_blocks_test(provider).await;
|
||||||
|
}
|
||||||
|
|
||||||
send_zst_requests(provider).await;
|
async fn generic_watch_blocks_test<M: Middleware>(provider: M) {
|
||||||
|
let stream = provider.watch_blocks().await.unwrap().stream();
|
||||||
|
let hashes = stream.take(3).collect::<Vec<H256>>().await;
|
||||||
|
let block = provider.get_block(BlockNumber::Latest).await.unwrap().unwrap();
|
||||||
|
assert_eq!(block.hash.unwrap(), *hashes.last().unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
#[cfg(feature = "ws")]
|
||||||
|
async fn subscribe_blocks_ws() {
|
||||||
|
let (provider, _anvil) = crate::spawn_anvil_ws().await;
|
||||||
|
generic_subscribe_blocks_test(provider).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
#[cfg(feature = "ipc")]
|
||||||
|
async fn subscribe_blocks_ipc() {
|
||||||
|
let (provider, _anvil, _ipc) = crate::spawn_anvil_ipc().await;
|
||||||
|
generic_subscribe_blocks_test(provider).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(any(feature = "ws", feature = "ipc"))]
|
||||||
|
async fn generic_subscribe_blocks_test<M>(provider: M)
|
||||||
|
where
|
||||||
|
M: Middleware,
|
||||||
|
M::Provider: ethers_providers::PubsubClient,
|
||||||
|
{
|
||||||
|
let stream = provider.subscribe_blocks().await.unwrap();
|
||||||
|
let blocks = stream.take(3).collect::<Vec<_>>().await;
|
||||||
|
let block = provider.get_block(BlockNumber::Latest).await.unwrap().unwrap();
|
||||||
|
assert_eq!(&block, blocks.last().unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn send_tx_http() {
|
||||||
|
let (provider, anvil) = spawn_anvil();
|
||||||
|
generic_send_tx_test(provider, anvil.addresses()[0]).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
#[cfg(feature = "ws")]
|
||||||
|
async fn send_tx_ws() {
|
||||||
|
let (provider, anvil) = crate::spawn_anvil_ws().await;
|
||||||
|
generic_send_tx_test(provider, anvil.addresses()[0]).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
#[cfg(feature = "ipc")]
|
||||||
|
async fn send_tx_ipc() {
|
||||||
|
let (provider, anvil, _ipc) = crate::spawn_anvil_ipc().await;
|
||||||
|
generic_send_tx_test(provider, anvil.addresses()[0]).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn generic_send_tx_test<M: Middleware>(provider: M, who: Address) {
|
||||||
|
let tx = TransactionRequest::new().to(who).from(who);
|
||||||
|
let pending_tx = provider.send_transaction(tx, None).await.unwrap();
|
||||||
|
let tx_hash = *pending_tx;
|
||||||
|
let receipt = pending_tx.confirmations(3).await.unwrap().unwrap();
|
||||||
|
assert_eq!(receipt.transaction_hash, tx_hash);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "celo")]
|
#[cfg(feature = "celo")]
|
||||||
mod celo_tests {
|
mod celo_tests {
|
||||||
use super::*;
|
|
||||||
use ethers_core::types::{Randomness, H256};
|
use ethers_core::types::{Randomness, H256};
|
||||||
|
use ethers_providers::{Http, Middleware, Provider};
|
||||||
use futures_util::stream::StreamExt;
|
use futures_util::stream::StreamExt;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn get_block() {
|
async fn get_block() {
|
|
@ -1,4 +1,3 @@
|
||||||
#![cfg(not(target_arch = "wasm32"))]
|
|
||||||
use ethers_core::{
|
use ethers_core::{
|
||||||
types::{TransactionRequest, U256},
|
types::{TransactionRequest, U256},
|
||||||
utils::Anvil,
|
utils::Anvil,
|
||||||
|
@ -8,8 +7,8 @@ use std::convert::TryFrom;
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn txpool() {
|
async fn txpool() {
|
||||||
let geth = Anvil::new().block_time(20u64).spawn();
|
let anvil = Anvil::new().block_time(5u64).spawn();
|
||||||
let provider = Provider::<Http>::try_from(geth.endpoint()).unwrap();
|
let provider = Provider::<Http>::try_from(anvil.endpoint()).unwrap();
|
||||||
|
|
||||||
let account = provider.get_accounts().await.unwrap()[0];
|
let account = provider.get_accounts().await.unwrap()[0];
|
||||||
let value: u64 = 42;
|
let value: u64 = 42;
|
||||||
|
@ -17,13 +16,11 @@ async fn txpool() {
|
||||||
let tx = TransactionRequest::new().to(account).from(account).value(value).gas_price(gas_price);
|
let tx = TransactionRequest::new().to(account).from(account).value(value).gas_price(gas_price);
|
||||||
|
|
||||||
// send a few transactions
|
// send a few transactions
|
||||||
let mut txs = Vec::new();
|
|
||||||
for _ in 0..10 {
|
for _ in 0..10 {
|
||||||
let tx_hash = provider.send_transaction(tx.clone(), None).await.unwrap();
|
drop(provider.send_transaction(tx.clone(), None).await.unwrap());
|
||||||
txs.push(tx_hash);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// we gave a 20s block time, should be plenty for us to get the txpool's content
|
// we gave a 5s block time, should be plenty for us to get the txpool's content
|
||||||
let status = provider.txpool_status().await.unwrap();
|
let status = provider.txpool_status().await.unwrap();
|
||||||
assert_eq!(status.pending.as_u64(), 10);
|
assert_eq!(status.pending.as_u64(), 10);
|
||||||
assert_eq!(status.queued.as_u64(), 0);
|
assert_eq!(status.queued.as_u64(), 0);
|
|
@ -1,11 +1,10 @@
|
||||||
#![cfg(not(feature = "celo"))]
|
use ethers_core::types::Filter;
|
||||||
|
|
||||||
use ethers_providers::{Middleware, Provider, StreamExt, Ws};
|
use ethers_providers::{Middleware, Provider, StreamExt, Ws};
|
||||||
use futures_util::SinkExt;
|
use futures_util::SinkExt;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use tokio::net::{TcpListener, TcpStream};
|
use tokio::net::{TcpListener, TcpStream};
|
||||||
use tokio_tungstenite::{
|
use tokio_tungstenite::{
|
||||||
accept_async,
|
accept_async, connect_async,
|
||||||
tungstenite::{
|
tungstenite::{
|
||||||
self,
|
self,
|
||||||
protocol::{frame::coding::CloseCode, CloseFrame},
|
protocol::{frame::coding::CloseCode, CloseFrame},
|
||||||
|
@ -16,9 +15,6 @@ use tungstenite::protocol::Message;
|
||||||
|
|
||||||
const WS_ENDPOINT: &str = "127.0.0.1:9002";
|
const WS_ENDPOINT: &str = "127.0.0.1:9002";
|
||||||
|
|
||||||
use ethers_core::types::Filter;
|
|
||||||
use tokio_tungstenite::connect_async;
|
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn graceful_disconnect_on_ws_errors() {
|
async fn graceful_disconnect_on_ws_errors() {
|
||||||
// Spawn a fake Ws server that will drop our connection after a while
|
// Spawn a fake Ws server that will drop our connection after a while
|
|
@ -46,11 +46,8 @@ home = { version = "0.5.4", optional = true }
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
ethers-contract-derive = { version = "^1.0.0", path = "../ethers-contract/ethers-contract-derive" }
|
ethers-contract-derive = { version = "^1.0.0", path = "../ethers-contract/ethers-contract-derive" }
|
||||||
ethers-derive-eip712 = { version = "^1.0.0", path = "../ethers-core/ethers-derive-eip712" }
|
ethers-derive-eip712 = { version = "^1.0.0", path = "../ethers-core/ethers-derive-eip712" }
|
||||||
serde_json = { version = "1.0.64" }
|
|
||||||
tracing-subscriber = "0.3.16"
|
|
||||||
yubihsm = { version = "0.41.0", features = ["secp256k1", "usb", "mockhsm"] }
|
|
||||||
|
|
||||||
[target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies]
|
serde_json = { version = "1.0.64" }
|
||||||
yubihsm = { version = "0.41.0", features = ["secp256k1", "usb", "mockhsm"] }
|
yubihsm = { version = "0.41.0", features = ["secp256k1", "usb", "mockhsm"] }
|
||||||
tokio = { version = "1.18", default-features = false, features = ["macros", "rt"] }
|
tokio = { version = "1.18", default-features = false, features = ["macros", "rt"] }
|
||||||
tempfile = "3.4.0"
|
tempfile = "3.4.0"
|
||||||
|
|
|
@ -164,7 +164,7 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn parse_pk() {
|
fn parse_pk() {
|
||||||
let s = "6f142508b4eea641e33cb2a0161221105086a84584c74245ca463a49effea30b";
|
let s = "6f142508b4eea641e33cb2a0161221105086a84584c74245ca463a49effea30b";
|
||||||
let pk: Wallet<SigningKey> = s.parse().unwrap();
|
let _pk: Wallet<SigningKey> = s.parse().unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
|
|
|
@ -1082,6 +1082,7 @@ impl ArtifactOutput for MinimalCombinedArtifactsHardhatFallback {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
#[allow(clippy::extra_unused_type_parameters)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
//! Instantiate `Geth` with Clique enabled.
|
||||||
|
|
||||||
use ethers::{
|
use ethers::{
|
||||||
core::{rand::thread_rng, utils::Geth},
|
core::{rand::thread_rng, utils::Geth},
|
||||||
signers::LocalWallet,
|
signers::LocalWallet,
|
||||||
|
@ -5,7 +7,6 @@ use ethers::{
|
||||||
use eyre::Result;
|
use eyre::Result;
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
/// Shows how to instantiate a Geth with Clique enabled.
|
|
||||||
async fn main() -> Result<()> {
|
async fn main() -> Result<()> {
|
||||||
// Generate a random clique signer and set it on Geth.
|
// Generate a random clique signer and set it on Geth.
|
||||||
let data_dir = tempfile::tempdir().expect("should be able to create temp geth datadir");
|
let data_dir = tempfile::tempdir().expect("should be able to create temp geth datadir");
|
||||||
|
|
|
@ -38,15 +38,7 @@ async fn blocknative() {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn etherscan() {
|
async fn etherscan() {
|
||||||
let chain = Chain::Mainnet;
|
let client = Client::new_from_opt_env(Chain::Mainnet).unwrap();
|
||||||
let client = match Client::new_from_env(chain) {
|
|
||||||
Ok(client) => client,
|
|
||||||
Err(_) => Client::builder()
|
|
||||||
.chain(chain)
|
|
||||||
.expect("Mainnet is valid")
|
|
||||||
.build()
|
|
||||||
.expect("Mainnet is valid"),
|
|
||||||
};
|
|
||||||
let oracle = Etherscan::new(client).category(GasCategory::Fast);
|
let oracle = Etherscan::new(client).category(GasCategory::Fast);
|
||||||
match oracle.fetch().await {
|
match oracle.fetch().await {
|
||||||
Ok(gas_price) => println!("[Etherscan]: Gas price is {gas_price:?}"),
|
Ok(gas_price) => println!("[Etherscan]: Gas price is {gas_price:?}"),
|
||||||
|
|
|
@ -37,7 +37,7 @@ async fn main() -> Result<()> {
|
||||||
|
|
||||||
assert_eq!(curr_nonce, 0);
|
assert_eq!(curr_nonce, 0);
|
||||||
|
|
||||||
nonce_manager.send_transaction(tx, None).await?;
|
nonce_manager.send_transaction(tx, None).await?.await?.unwrap();
|
||||||
let next_nonce = nonce_manager.next().as_u64();
|
let next_nonce = nonce_manager.next().as_u64();
|
||||||
|
|
||||||
assert_eq!(next_nonce, 1);
|
assert_eq!(next_nonce, 1);
|
||||||
|
|
|
@ -1,9 +1,3 @@
|
||||||
#![warn(missing_debug_implementations, missing_docs, rust_2018_idioms, unreachable_pub)]
|
|
||||||
#![allow(rustdoc::broken_intra_doc_links)]
|
|
||||||
#![doc(test(
|
|
||||||
no_crate_inject,
|
|
||||||
attr(deny(warnings, rust_2018_idioms), allow(dead_code, unused_variables))
|
|
||||||
))]
|
|
||||||
//! # ethers-rs
|
//! # ethers-rs
|
||||||
//!
|
//!
|
||||||
//! A complete Ethereum and Celo Rust library.
|
//! A complete Ethereum and Celo Rust library.
|
||||||
|
|
|
@ -0,0 +1,59 @@
|
||||||
|
use crate::simple_storage::SimpleStorage;
|
||||||
|
use ethers::prelude::*;
|
||||||
|
use std::{sync::Arc, time::Duration};
|
||||||
|
|
||||||
|
static CELO_TESTNET_URL: &str = "https://alfajores-forno.celo-testnet.org";
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_send_transaction() {
|
||||||
|
// Celo testnet
|
||||||
|
let provider =
|
||||||
|
Provider::<Http>::try_from(CELO_TESTNET_URL).unwrap().interval(Duration::from_secs(3));
|
||||||
|
let chain_id = provider.get_chainid().await.unwrap().as_u64();
|
||||||
|
|
||||||
|
// Funded with https://celo.org/developers/faucet
|
||||||
|
// Please do not drain this account :)
|
||||||
|
let wallet = "d652abb81e8c686edba621a895531b1f291289b63b5ef09a94f686a5ecdd5db1"
|
||||||
|
.parse::<LocalWallet>()
|
||||||
|
.unwrap()
|
||||||
|
.with_chain_id(chain_id);
|
||||||
|
let client = SignerMiddleware::new(provider, wallet);
|
||||||
|
|
||||||
|
let balance_before = client.get_balance(client.address(), None).await.unwrap();
|
||||||
|
let tx = TransactionRequest::pay(client.address(), 100);
|
||||||
|
let _receipt = client.send_transaction(tx, None).await.unwrap().confirmations(3).await.unwrap();
|
||||||
|
let balance_after = client.get_balance(client.address(), None).await.unwrap();
|
||||||
|
assert!(balance_before > balance_after);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn deploy_and_call_contract() {
|
||||||
|
// Celo testnet
|
||||||
|
let provider =
|
||||||
|
Provider::<Http>::try_from(CELO_TESTNET_URL).unwrap().interval(Duration::from_secs(3));
|
||||||
|
let chain_id = provider.get_chainid().await.unwrap().as_u64();
|
||||||
|
|
||||||
|
// Funded with https://celo.org/developers/faucet
|
||||||
|
let wallet = "58ea5643a78c36926ad5128a6b0d8dfcc7fc705788a993b1c724be3469bc9697"
|
||||||
|
.parse::<LocalWallet>()
|
||||||
|
.unwrap()
|
||||||
|
.with_chain_id(chain_id);
|
||||||
|
let client = provider.with_signer(wallet);
|
||||||
|
let client = Arc::new(client);
|
||||||
|
|
||||||
|
let deploy_tx = SimpleStorage::deploy(client, ()).unwrap();
|
||||||
|
let contract = deploy_tx.send().await.unwrap();
|
||||||
|
|
||||||
|
let value: U256 = contract.value().call().await.unwrap();
|
||||||
|
assert_eq!(value, 0.into());
|
||||||
|
|
||||||
|
// make a state mutating transaction
|
||||||
|
// gas estimation costs are sometimes under-reported on celo,
|
||||||
|
// so we manually set it to avoid failures
|
||||||
|
let call = contract.set_value(1.into()).gas(100000);
|
||||||
|
let pending_tx = call.send().await.unwrap();
|
||||||
|
let _receipt = pending_tx.await.unwrap();
|
||||||
|
|
||||||
|
let value: U256 = contract.method("value", ()).unwrap().call().await.unwrap();
|
||||||
|
assert_eq!(value, 1.into());
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
//! Ethers live tests.
|
||||||
|
//!
|
||||||
|
//! If a feature or external binary is added, like Solc, please also update
|
||||||
|
//! `.github/workflows/ci.yml` at `job.live-test`.
|
||||||
|
|
||||||
|
#![cfg(not(target_arch = "wasm32"))]
|
||||||
|
|
||||||
|
#[cfg(feature = "celo")]
|
||||||
|
mod celo;
|
||||||
|
|
||||||
|
pub(crate) mod simple_storage {
|
||||||
|
ethers::contract::abigen!(SimpleStorage, "./tests/testdata/SimpleStorage.json");
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
{"abi":[{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"author","type":"address"},{"indexed":true,"internalType":"address","name":"oldAuthor","type":"address"},{"indexed":false,"internalType":"uint256","name":"oldValue","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"newValue","type":"uint256"}],"name":"ValueChanged","type":"event"},{"inputs":[],"name":"lastSender","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_value","type":"uint256"}],"name":"setValue","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"value","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"}],"bytecode":"608060405234801561001057600080fd5b50610155806100206000396000f3fe608060405234801561001057600080fd5b50600436106100415760003560e01c8063256fec88146100465780633fa4f24514610076578063552410771461008d575b600080fd5b600054610059906001600160a01b031681565b6040516001600160a01b0390911681526020015b60405180910390f35b61007f60015481565b60405190815260200161006d565b6100a061009b366004610106565b6100a2565b005b60005460015460408051918252602082018490526001600160a01b039092169133917f43a35cb4f6bbf56c64a97aba2d057d75ae7c2b008cfbbcf77c9bd7f2a525d969910160405180910390a3600155600080546001600160a01b03191633179055565b60006020828403121561011857600080fd5b503591905056fea26469706673582212202aa583b44008e0a0ecde172d743bed0508a31118ec816f86cd3551d73480e13d64736f6c63430008130033"}
|
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue