Mock smart contracts for writing Ethereum test suites

Overview

Automated test suite

Documentation Status

Mock smart contracts for writing Ethereum test suites

This package contains common Ethereum smart contracts to be used in automated test suites. This was created for Trading Strategy, but can be used for any other projects as well. As opposite to slower and messier mainnet forking test strategies, this project aims to explicit clean deployments and very fast test execution.

Smart contract support includes

  • ERC-20 token
  • SushiSwap: router, factory, pool (Uniswap v2, PancakeSwape, QuickSwap, Trader Joe and others are 99% Sushiswap compatible)
  • High-quality API documentation
  • Full type hinting support for optimal developer experience
  • (More integrations to come)

Table of contents

Precompiled ABI file distribution

This package primarly supports Python, Web3.p3 and Brownie developers. For other programming languages and frameworks, you can find precompiled Solidity smart contracts in abi folder.

These files are good to go with any framework:

  • Web3.js
  • Ethers.js
  • Hardhat
  • Truffle
  • Web3j

Each JSON file has abi and bytecode keys you need to deploy a contract.

Just download and embed in your project. The compiled source code files are mixture of MIT and GPL v2 license.

Python usage

The Python support is available as smart_contract_test_fixtures Python package.

The package depends only on web3.py and not others, like Brownie. It grabs popular ABI files with their bytecode and compilation artifacts so that the contracts are easily deployable on any Ethereum tester interface. No Ganache is needed and everything can be executed on faster eth-tester enginer.

[Read the full API documnetation](High-quality API documentation). For code examples please see below.

Prerequisites

ERC-20 token example

To use the package to deploy a simple ERC-20 token in pytest testing:

str: """User account.""" return web3.eth.accounts[1] @pytest.fixture() def user_2(web3) -> str: """User account.""" return web3.eth.accounts[2] def test_deploy_token(web3: Web3, deployer: str): """Deploy mock ERC-20.""" token = create_token(web3, deployer, "Hentai books token", "HENTAI", 100_000 * 10**18) assert token.functions.name().call() == "Hentai books token" assert token.functions.symbol().call() == "HENTAI" assert token.functions.totalSupply().call() == 100_000 * 10**18 assert token.functions.decimals().call() == 18 def test_tranfer_tokens_between_users(web3: Web3, deployer: str, user_1: str, user_2: str): """Transfer tokens between users.""" token = create_token(web3, deployer, "Telos EVM rocks", "TELOS", 100_000 * 10**18) # Move 10 tokens from deployer to user1 token.functions.transfer(user_1, 10 * 10**18).transact({"from": deployer}) assert token.functions.balanceOf(user_1).call() == 10 * 10**18 # Move 10 tokens from deployer to user1 token.functions.transfer(user_2, 6 * 10**18).transact({"from": user_1}) assert token.functions.balanceOf(user_1).call() == 4 * 10**18 assert token.functions.balanceOf(user_2).call() == 6 * 10**18">
import pytest
from web3 import Web3, EthereumTesterProvider

from smart_contracts_for_testing.token import create_token


@pytest.fixture
def tester_provider():
    return EthereumTesterProvider()


@pytest.fixture
def eth_tester(tester_provider):
    return tester_provider.ethereum_tester


@pytest.fixture
def web3(tester_provider):
    return Web3(tester_provider)


@pytest.fixture()
def deployer(web3) -> str:
    """Deploy account."""
    return web3.eth.accounts[0]


@pytest.fixture()
def user_1(web3) -> str:
    """User account."""
    return web3.eth.accounts[1]


@pytest.fixture()
def user_2(web3) -> str:
    """User account."""
    return web3.eth.accounts[2]


def test_deploy_token(web3: Web3, deployer: str):
    """Deploy mock ERC-20."""
    token = create_token(web3, deployer, "Hentai books token", "HENTAI", 100_000 * 10**18)
    assert token.functions.name().call() == "Hentai books token"
    assert token.functions.symbol().call() == "HENTAI"
    assert token.functions.totalSupply().call() == 100_000 * 10**18
    assert token.functions.decimals().call() == 18


def test_tranfer_tokens_between_users(web3: Web3, deployer: str, user_1: str, user_2: str):
    """Transfer tokens between users."""
    token = create_token(web3, deployer, "Telos EVM rocks", "TELOS", 100_000 * 10**18)

    # Move 10 tokens from deployer to user1
    token.functions.transfer(user_1, 10 * 10**18).transact({"from": deployer})
    assert token.functions.balanceOf(user_1).call() == 10 * 10**18

    # Move 10 tokens from deployer to user1
    token.functions.transfer(user_2, 6 * 10**18).transact({"from": user_1})
    assert token.functions.balanceOf(user_1).call() == 4 * 10**18
    assert token.functions.balanceOf(user_2).call() == 6 * 10**18

See full example.

For more information how to user Web3.py in testing, see Web3.py documentation.

Uniswap swap example

WETH path = [usdc.address, weth.address] # Path tell how the swap is routed # https://docs.uniswap.org/protocol/V2/reference/smart-contracts/router-02#swapexacttokensfortokens router.functions.swapExactTokensForTokens( usdc_amount_to_pay, 0, path, user_1, FOREVER_DEADLINE, ).transact({ "from": user_1 }) # Check the user_1 received ~0.284 ethers assert weth.functions.balanceOf(user_1).call() / 1e18 == pytest.approx(0.28488156127668085)">
import pytest
from web3 import Web3
from web3.contract import Contract

from smart_contracts_for_testing.uniswap_v2 import UniswapV2Deployment, deploy_trading_pair, FOREVER_DEADLINE


def test_swap(web3: Web3, deployer: str, user_1: str, uniswap_v2: UniswapV2Deployment, weth: Contract, usdc: Contract):
    """User buys WETH on Uniswap v2 using mock USDC."""

    # Create the trading pair and add initial liquidity
    deploy_trading_pair(
        web3,
        deployer,
        uniswap_v2,
        weth,
        usdc,
        10 * 10**18,  # 10 ETH liquidity
        17_000 * 10**18,  # 17000 USDC liquidity
    )

    router = uniswap_v2.router

    # Give user_1 500 dollars to buy ETH and approve it on the router
    usdc_amount_to_pay = 500 * 10**18
    usdc.functions.transfer(user_1, usdc_amount_to_pay).transact({"from": deployer})
    usdc.functions.approve(router.address, usdc_amount_to_pay).transact({"from": user_1})

    # Perform a swap USDC->WETH
    path = [usdc.address, weth.address]  # Path tell how the swap is routed
    # https://docs.uniswap.org/protocol/V2/reference/smart-contracts/router-02#swapexacttokensfortokens
    router.functions.swapExactTokensForTokens(
        usdc_amount_to_pay,
        0,
        path,
        user_1,
        FOREVER_DEADLINE,
    ).transact({
        "from": user_1
    })

    # Check the user_1 received ~0.284 ethers
    assert weth.functions.balanceOf(user_1).call() / 1e18 == pytest.approx(0.28488156127668085)

See the full example.

How to use the library in your Python project

Add smart_contract_test_fixtures as a development dependency:

Using Poetry:

poetry add -D smart_contract_test_fixtures

Development

This step will extract compiled smart contract from Sushiswap repository.

Requires

  • Node v14
  • npx
  • yarn
  • GNU Make
  • Unix shell

Make

To build the ABI distribution:

git submodule update --recursive --init
make all

See SushiSwap continuous integration files for more information.

Version history

See change log.

Discord

Join Discord for any questions.

Notes

Currently there is no Brownie support. To support Brownie, one would need to figure out how to import an existing Hardhat based project (Sushiswap) to Brownie project format.

License

MIT

Comments
  • Initial version of Aave v3 integration (issue #5)

    Initial version of Aave v3 integration (issue #5)

    This PR includes Aave v3 constants, event reading from blockchain and a Jupyter notebook for analyzing the data, and event documentation in eth_defi/aave_v3/README.md.

    The Aave v3 ABI JSON files can be generated with make aavev3, which copies them from the @aave/core-v3 NPM package to the working directory (Node.js required).

    opened by kennu 8
  • Investigate Uniswap v3 and getting historical price data in good format

    Investigate Uniswap v3 and getting historical price data in good format

    • Github and other searches to find Python (and JavaScript) open source examples for getting data out from Uniswap v3
    • Data structure how to store this data in the (SQL) database for historical queries

    Question that needs to be answered:

    • Given (chain id, Uniswap v3 deployment address)
    • What is the historical price for buy/sell (timestamp, trading pair, token0 quantity in) -> token1 quantity out

    Can be a single pool price at milestone 1, later expanded to cover auto routing and multi-hop trades.

    opened by miohtama 6
  • Get revert reason of any tx and especially for failed trades

    Get revert reason of any tx and especially for failed trades

    Uniswap trade analyzer should be able to tell why the trade failed

    • Too much slippage
    • Some internal Uniswap error
    • (There should be no other reasons if the tokens are not scam tokens)

    As a bonus, trade analyzer should able to tell if the trade was reverted because of slippage. Though not sure how we can pick up this from the transaction receipt, I believe is going to be quite hard. The ”real” EVM nodes do not store the revert reason for very long time (few hundreds of blocks) and one might need to replay the transaction.

    https://snakecharmers.ethereum.org/web3py-revert-reason-parsing/

    This would be a research task of doing some little trades with Ganache and see what kind of data we can get out of JSON-RPC for the revert reason -if any. And then do the transaction replay trick.

    priority: P2 
    opened by miohtama 3
  • Slippage and MEV preventation in trades

    Slippage and MEV preventation in trades

    Creaet a function that calculates amountIn and amountOut values for buy/sell with a specific slippage.

    See this question for details:

    https://ethereum.stackexchange.com/questions/99404/pancakeswap-anti-front-run

    First for Uniswap v2, as its dog eats dog frontrun battle at BNB Chain.

    priority: P1 
    opened by miohtama 3
  • Read token tax from a token

    Read token tax from a token

    Some tokens with ponzinomis have a token tax. This is especially popular on wild west blockchains like BNB Chain and PancakeSwap.

    Token tax is used to

    • Create deflanatory tokens
    • Create malicious honey pots for trading bots ("buy only") - effectively a very high tokex tax like 90% on sell

    An example trading pair and token with token tax is ELEPHANT-BUSD.

    • Buy has 10% tax
    • Sell has 10% tax
    image

    honeypot.is is an independent service to check the token tax. It does this by (likely) running Ganache mainnet fork and simulates the transactions. There is no API to ask this information.

    Step 1: Read the token tax with Python

    To figure out the amount of different token taxes (buy, sell, transfer) one needs to run a simulated transaction in Ganache.

    Here is a pseudo-Python code to do it:

    from eth_account.signers.local import LocalAccount
    from eth_typing import HexAddress
    from web3 import Web3
    
    from eth_defi.uniswap_v2.deployment import UniswapV2Deployment
    from tradeexecutor.utils import dataclass
    
    
    @dataclass
    class TokenTaxInfo:
        """Different token taxes we figured out."""
    
        #: Token in the question
        base_token: HexAddress
    
        #: Which token we traded against it
        quote_token: HexAddress
    
        #: How much % we lost of the token on buy
        buy_tax: float
    
        #: How much % we lose the token when we transfer between addresses
        transfer_tax:float
    
        #: How much % we lose the token when we sold it
        sell_tax: float
    
    
    def estimate_token_taxes(
            uniswap: UniswapV2Deployment,
            base_token: HexAddress,
            quote_token: HexAddress,
            buy_account: LocalAccount,
            sell_account: LocalAccount
        ) -> TokenTaxInfo:
        """Estimates different token taxes for a token by running Ganache simulations for it.
    
        :param uniswap:
            Uniswap deployment on a Ganache mainnet fork.
            Set up prior calling this function.
            See `ganache.py` and `test_ganache.py` for more details.
    
        :param base_token:
            The token of which tax properties we are figuring out.
    
        :param quote_token:
            Address of the quote token used for the trading pair. E.g. `BUDS`, `WBNB`
            Based on this information we can derive Uniswap trading pair address.
    
        :param buy_account:
            The account that does initial buy to measure the buy tax.
            This account must be loaded with gas money (ETH/BNB) and `quote_token`
            for a purchase.
    
        :param sell_account:
            The account that receives the token transfer and does the sell to measure the sell tax.
            This account must be loaded with gas money for the sell.
    
        :return:
            ToxTaxInfo tells us what we figure out about taxes.
            This can be later recorded to a database.
        """
    
        web3: Web3 = uniswap.web3
    
        # Figure out base_token/quote_token trading pair
        # Buy base_token with buy_account
        # Measure the loss as "buy tax"
        # Transfer tokens to sell_account
        # Measure the loss as "transfer tax"
        # Sell tokens
        # Measure the loss as "sell tax"
    
    
    

    The TokenTaxInfo info can be then stored in a database or similar, like SQL, JSON file repo and so on. It can be later retrieved when you want to trade tokens.

    Step 2: Making taxed tokens easily tradeable with Python

    Create a buy/sell_with_tax(token_tax_info: TokenTaxInfo, max_slippage) function that considers slippage and token tax and correctly handles trading these kinds of tokens.

    • Normally buying ELELPHANT-BUSD would revert
    • Normally selling a token tax token would fail with execution reverted: TransferHelper: TRANSFER_FROM_FAILED because there is a mismatch between allowance() and swap amount

    Create four test based on BSC mainnet fork using the existing Ganache fixtures

    • Naive buy of ELEPHANT fails
    • Taxed buy of ELEPHANT success, we can calculate the taxed amount after the buy (update analyse_trade to return this value)
    • Naive sell of ELEPHANT fails
    • Taxed sell of ELEPHANT success, we can calculate the taxed amount after the sell
    • Comment how this can be also considered in three-way trade (BUSD-BNB-ELEPHANT)
    priority: P1 
    opened by miohtama 1
  • Block data storage

    Block data storage

    • Add a method to efficiently store block headers on disk to save a lot of time and API calls if we need to resume after a crash
    • Parquet append method used
    • The storage method is chain reorganisation safe
    opened by miohtama 0
  • Price oracle implementation

    Price oracle implementation

    • Feature: generic price oracle implementation with configurable price function
    • Feature: time weighted average price (TWAP) price function for price oracle
    • Feature: price oracle implementation for Uniswap v2 pools
    • Feature: fetch_pair_details to get info on Uniswap v2 pairs
    opened by miohtama 0
  • Create mybinder env

    Create mybinder env

    Badge logo:

    [![Binder](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/tradingstrategy-ai/web3-ethereum-defi/master?labpath=docs/source/tutorials
    )
    
    opened by hieuh25 0
  • Handle chain reorg in event reader

    Handle chain reorg in event reader

    Current the event reader can't handle chain reorg situation, so it's likely to fail here: https://github.com/tradingstrategy-ai/web3-ethereum-defi/blob/df38b8a97176cc5b3dbc92783c0791c9603b0c5b/eth_defi/event_reader/reader.py#L148-L161

    bug enhancement priority: P2 size: M 
    opened by hieuh25 3
  • Aave deposit function

    Aave deposit function

    • [ ] Write a function that opens a loan position in Aave v3 by depositing any Aave v3 reserve token and receives aToken back
    • [ ] The function can live in eth_defi.aave_v3.loan module - The module should be modelled similar as e.g. Uniswap v2 swap_with_slippage_protection and deploy_trading_pair - Function name: deposit_in_aave - Inputs: HotWallet instance (assumed loaded with USDC), aave_deposit_address, token_address, amount - Outputs: tx_hash
    • [ ] The module must be sufficiently documented for autodoc according to the coding conventions
    • [ ] The code must be formatted to according to the coding conventions - there is is black that complains on open PRs if this is not the case
    • [ ] There must be integration test
      • Because there is no framework to set up Aave v3 smart contracts in Ethereum Tester environment, one must use Ganache mainnet fork for the test. Normally this is unpreferable, but creating Aave v3 test environment itself is large task.
      • There are some examples of using Ganache in tests in test_ganache and test_token_tax
      • The test can be called test_aave_deposit.py
      • A test case check that the 0) Hot wallet starts with USDC on Ethereum mainnet 1) deposit was correctly registered with the Aave reserves 2) you receive the corresponding Aave aUSDC token back in the wallet, with the correct amount
      • Because there is no automatic mechanism to fetch Aave reserves and addresses as a list, please use hardcoded and commented values as text fixtures for now for any Ethereum address, with links to their respective descriptions of what they are
    enhancement priority: P2 size: M 
    opened by miohtama 2
Owner
Trading Strategy
Algorithmic trading for decentralised markets
Trading Strategy
a socket mock framework - for all kinds of socket animals, web-clients included

mocket /mɔˈkɛt/ A socket mock framework for all kinds of socket animals, web-clients included - with gevent/asyncio/SSL support ...and then MicroPytho

Giorgio Salluzzo 249 Dec 14, 2022
Automatically mock your HTTP interactions to simplify and speed up testing

VCR.py ?? This is a Python version of Ruby's VCR library. Source code https://github.com/kevin1024/vcrpy Documentation https://vcrpy.readthedocs.io/ R

Kevin McCarthy 2.3k Jan 1, 2023
Automatically mock your HTTP interactions to simplify and speed up testing

VCR.py ?? This is a Python version of Ruby's VCR library. Source code https://github.com/kevin1024/vcrpy Documentation https://vcrpy.readthedocs.io/ R

Kevin McCarthy 1.8k Feb 7, 2021
Aioresponses is a helper for mock/fake web requests in python aiohttp package.

aioresponses Aioresponses is a helper to mock/fake web requests in python aiohttp package. For requests module there are a lot of packages that help u

null 402 Jan 6, 2023
a socket mock framework - for all kinds of socket animals, web-clients included

mocket /mɔˈkɛt/ A socket mock framework for all kinds of socket animals, web-clients included - with gevent/asyncio/SSL support ...and then MicroPytho

Giorgio Salluzzo 208 Jan 31, 2021
Mockoon is the easiest and quickest way to run mock APIs locally. No remote deployment, no account required, open source.

Mockoon Mockoon is the easiest and quickest way to run mock APIs locally. No remote deployment, no account required, open source. It has been built wi

mockoon 4.4k Dec 30, 2022
Thin-wrapper around the mock package for easier use with pytest

pytest-mock This plugin provides a mocker fixture which is a thin-wrapper around the patching API provided by the mock package: import os class UnixF

pytest-dev 1.5k Jan 5, 2023
Cornell record & replay mock server

Cornell: record & replay mock server Cornell makes it dead simple, via its record and replay features to perform end-to-end testing in a fast and isol

HiredScoreLabs 134 Sep 15, 2022
User-interest mock backend server implemnted using flask restful, and SQLAlchemy ORM confiugred with sqlite

Flask_Restful_SQLAlchemy_server User-interest mock backend server implemnted using flask restful, and SQLAlchemy ORM confiugred with sqlite. Backend b

Austin Weigel 1 Nov 17, 2022
a plugin for py.test that changes the default look and feel of py.test (e.g. progressbar, show tests that fail instantly)

pytest-sugar pytest-sugar is a plugin for pytest that shows failures and errors instantly and shows a progress bar. Requirements You will need the fol

Teemu 963 Dec 28, 2022
Pynguin, The PYthoN General UnIt Test geNerator is a test-generation tool for Python

Pynguin, the PYthoN General UnIt test geNerator, is a tool that allows developers to generate unit tests automatically.

Chair of Software Engineering II, Uni Passau 997 Jan 6, 2023
Ab testing - The using AB test to test of difference of conversion rate

Facebook recently introduced a new type of offer that is an alternative to the current type of bidding called maximum bidding he introduced average bidding.

null 5 Nov 21, 2022
A small automated test structure using python to test *.cpp codes

Get Started Insert C++ Codes Add Test Code Run Test Samples Check Coverages Insert C++ Codes you can easily add c++ files in /inputs directory there i

Alireza Zahiri 2 Aug 3, 2022
Yet another python home automation project. Because a smart light is more than just on or off

Automate home Yet another home automation project because a smart light is more than just on or off. Overview When talking about home automation there

Maja Massarini 62 Oct 10, 2022
Green is a clean, colorful, fast python test runner.

Green -- A clean, colorful, fast python test runner. Features Clean - Low redundancy in output. Result statistics for each test is vertically aligned.

Nathan Stocks 756 Dec 22, 2022
splinter - python test framework for web applications

splinter - python tool for testing web applications splinter is an open source tool for testing web applications using Python. It lets you automate br

Cobra Team 2.6k Dec 27, 2022
A test fixtures replacement for Python

factory_boy factory_boy is a fixtures replacement based on thoughtbot's factory_bot. As a fixtures replacement tool, it aims to replace static, hard t

FactoryBoy project 3k Jan 5, 2023
create custom test databases that are populated with fake data

About Generate fake but valid data filled databases for test purposes using most popular patterns(AFAIK). Current support is sqlite, mysql, postgresql

Emir Ozer 2.2k Jan 4, 2023
A test fixtures replacement for Python

factory_boy factory_boy is a fixtures replacement based on thoughtbot's factory_bot. As a fixtures replacement tool, it aims to replace static, hard t

FactoryBoy project 2.4k Feb 5, 2021