Skip to content

cartesi/honeypot

Repository files navigation

Honeypot DApp

The Honeypot DApp is a honeypot-like DApp whose pot is the balance of a pre-configured ERC-20 token kept by the actual DApp.

The Honeypot DApp may receive deposits from any source, which are kept in the DApp balance for further withdrawal by a predefined withdrawal address, only. No other account is allowed to withdraw the funds. Any withdrawal request coming from an address other than the withdrawal address will be discarded.

The DApp generates Reports for all operations. A Voucher for the withdrawal of the pot is generated only when a request for withdrawal is accepted.

The back-end of the DApp is written in C++ and relies on the Cartesi Rollups Low-level API to communicate with Cartesi Rollups.

This document covers how to build, deploy and run the back-end of the DApp. Additionaly, an external tool is suggested to act as a front-end to send requests to the DApp back-end.

For a simpler example of a low-level DApp, refer to the Echo Low-level DApp.

Configuring the application

The application comes configured to be run in a local environment by default. Such configuration is available at config/localhost/config.h, which defines:

  • CONFIG_ERC20_PORTAL_ADDRESS: byte representation of the address of the ERC-20 portal;
  • CONFIG_ERC20_CONTRACT_ADDRESS: byte representation of the address of the only ERC-20 contract accepted for deposits; In this case, it corresponds to 0x8cA1c547AE4C1D1bb8296a5854c2c50192118E02, which refers to SimpleERC20, the contract deployed locally via the common-contracts container.
  • CONFIG_ERC20_WITHDRAWAL_ADDRESS: byte representation of the only withdrawal address allowed. In this case, it corresponds to 0x70997970C51812dc3A010C7d01b50e0d17dc79C8 (account index 1 in the local Hardhat node).

Customizing the DApp for other networks

In order to configure the DApp for a network other than localhost, one needs to create a new configuration file (config.h) and place it in a separate directory.

For example, for the Sepolia network, the file structure would be:

config/
└── sepolia/
    └── config.h

File config.h must contain proper values for CONFIG_ERC20_PORTAL_ADDRESS, CONFIG_ERC20_CONTRACT_ADDRESS, and CONFIG_ERC20_WITHDRAWAL_ADDRESS.

Please refer to Deploying the DApp for more information about building and deploying the DApp on networks other than the local environment.

Building the application

To build the DApp for a local environment, simply execute:

docker buildx bake \
    --load

Running the application

To start the DApp locally, execute the following command:

docker compose up

On the other hand, to bring the DApp down along with its volumes, run the following command:

docker compose down -v

Notice that when running locally, the chain used is Hardhat. Due to a bug in this chain, it is normal to see the following warning in the logs. Do not bother, it is not a problem.

WARN background_process: eth_block_history::block_subscriber: `listen_and_broadcast` error `New block subscriber timeout: timed out`, retrying subscription

Interacting with the application

The DApp depends on external tools to perform deposits or send withdrawal requests to the DApp back-end. Foundry's command-line tool for performing Ethereum RPC calls, cast, is going to be used for this purpose in this documentation.

To install the Foundry development toolchain to have cast available, check the installation guide.

Gathering DApp data

A few Cartesi Rollups addresses are required for interactions with the Honeypot DApp.

In order to gather them, first start up the DApp.

For localhost, execute the following commands to extract the addresses from the deployment data:

export DAPP_ADDRESS=$(cat deployments/localhost/dapp.json | jq -r '.address')
export INPUT_BOX_ADDRESS=$(cat deployments/localhost/InputBox.json | jq -r '.address')
export ERC20_PORTAL_ADDRESS=$(cat deployments/localhost/ERC20Portal.json | jq -r '.address')
export ERC20_ADDRESS=$(cat common-contracts/deployments/localhost/SimpleERC20.json | jq -r '.address')

The following values are expected for the variables above:

  • $DAPP_ADDRESS: 0x70ac08179605AF2D9e75782b8DEcDD3c22aA4D0C;
  • $INPUT_BOX_ADDRESS: 0x59b22D57D4f067708AB0c00552767405926dc768;
  • $ERC20_PORTAL_ADDRESS: 0x9C21AEb2093C32DDbC53eEF24B873BDCd1aDa1DB;
  • $ERC20_ADDRESS: 0x8cA1c547AE4C1D1bb8296a5854c2c50192118E02.

Depositing funds

Any account can deposit funds into the pot. However, in order to do that, a proper allowance must be approved beforehand.

Approving allowances

In order to request an allowance to be approved, execute the following command from the current directory:

cast send $ERC20_ADDRESS \
    "approve(address,uint256)" \
        $ERC20_PORTAL_ADDRESS \
        $AMOUNT \
    --rpc-url $RPC_URL \
    --from $SIGNER_ADDRESS \
    --private-key $PRIVATE_KEY

Where:

  • $ERC20_ADDRESS is the hex representation of the ERC-20 contract address to be used;
  • $ERC20_PORTAL_ADDRESS is the hex representation of the ERC-20 Portal address, as explained in Gathering DApp data. In this case, $DAPP_ADDRESS, which is also the Rollups address, is the spender;
  • $AMOUNT is the amount of tokens to be requested in the allowance;
  • $RPC_URL is the URL of the RPC endpoint to be used;
  • $SIGNER_ADDRESS is the hex representation of the account address that will sign the transaction, thus approving the allowance;
  • $PRIVATE_KEY (only mandatory for non-local networks) is the private key associated with $SIGNER_ADDRESS.

For example, an allowance request coming from account address 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 (account index 0 in a local Hardhat node deployment) on localhost using SimpleERC20 (0x8cA1c547AE4C1D1bb8296a5854c2c50192118E02) as the ERC-20 contract would look like this:

cast send 0x8cA1c547AE4C1D1bb8296a5854c2c50192118E02 \
    "approve(address,uint256)" \
        0x9C21AEb2093C32DDbC53eEF24B873BDCd1aDa1DB \
        100000000000000000000 \
    --rpc-url http://localhost:8545 \
    --from 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266

For more information about cast send, simply type cast send --help or refer to Foundry's documentation.

For more information about function approve refer to the OpenZeppellin documentation.

Performing deposits

With an allowance in place, execute the following command from the current directory to perform a deposit:

cast send $ERC20_PORTAL_ADDRESS \
    "depositERC20Tokens(address,address,uint256,bytes)" \
        $ERC20_ADDRESS \
        $DAPP_ADDRESS \
        $AMOUNT \
        "" \
    --rpc-url $RPC_URL \
    --from $SIGNER_ADDRESS \
    --private-key $PRIVATE_KEY

Where:

  • $ERC20_PORTAL_ADDRESS is the hex representation of the ERC-20 Portal address, as explained in Gathering DApp data.
  • $ERC20_ADDRESS is the hex representation of the ERC-20 contract address to be used; It accepts any value, as long as properly ABI-encoded;
  • $DAPP_ADDRESS is the hex representation of the DApp address, as explained in Gathering DApp data;
  • $AMOUNT is the amount of $ERC20_ADDRESS to be deposited;
  • "" is a 'no-value' passed as parameter bytes, which corresponds to additional (layer-2) data to be parsed by the DApp;
  • $RPC_URL is the URL of the RPC endpoint to be used;
  • $SIGNER_ADDRESS is the hex representation of the account address that will sign the transaction, thus performing the deposit into the DApp;
  • $PRIVATE_KEY (only mandatory for non-local networks) is the private key associated with $SIGNER_ADDRESS.

Any deposit will be logged as a Report by the DApp.

For example, a deposit performed by account 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 on localhost using SimplerERC20 would be similar to this:

cast send 0x9C21AEb2093C32DDbC53eEF24B873BDCd1aDa1DB \
    "depositERC20Tokens(address,address,uint256,bytes)" \
        0x8cA1c547AE4C1D1bb8296a5854c2c50192118E02 \
        0x70ac08179605AF2D9e75782b8DEcDD3c22aA4D0C \
        100000000000000000000 \
        "" \
    --rpc-url http://localhost:8545 \
    --from 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266

Checking the pot balance

The pot balance may be retrieved at all times by sending an inspect-state request with a non-empty payload to the DApp as follows:

curl localhost:5005/inspect/

The response from such a request will contain the amount of tokens held at layer-2, which constitutes the pot balance. Here's a sample response from the DApp:

{
    "payload": "0x0000000000000000000000000000000000000000000000056bc75e2d63100000"
}

Notice that the pot balance may differ over time from the actual ERC-20 balance held by the DApp on layer-1. This is because when a withdrawal request is processed by the DApp, a corresponding voucher is emitted and the pot balance on layer-2 changes immediately. However, the DApp's ERC-20 balance on layer-1 will only change when that voucher is finalized and executed on layer-1.

Retrieving layer-1 balances

To check the layer-1 balance of any account address, including the DApp itself, simply execute the following command from the current directory:

cast call $ERC20_ADDRESS \
    "balanceOf(address)" \
        $ACCOUNT_ADDRESS \
    --rpc-url $RPC_URL

Where:

  • $ERC20_ADDRESS is the hex representation of the address of the ERC-20 contract to be used; and
  • $ACCOUNT_ADDRESS is the hex representation of the account address to be checked;
  • $RPC_URL is the URL of the RPC endpoint to be used.

The call above will return an hex representation of the balance.

For example, in a local environment, the DApp balance for SimpleERC20 may be retrieved as follows:

cast call 0x8cA1c547AE4C1D1bb8296a5854c2c50192118E02 \
    "balanceOf(address)" \
        0x70ac08179605AF2D9e75782b8DEcDD3c22aA4D0C \
    --rpc-url http://localhost:8545

Withdrawing the pot

In order to perform a withdrawal request, just send an empty input ("", in the example below) to the DApp as follows:

cast send $INPUT_BOX_ADDRESS \
    "addInput(address,bytes)" \
    $DAPP_ADDRESS \
        "" \
    --from $SIGNER_ADDRESS \
    --private-key $PRIVATE_KEY \
    --rpc-url $RPC_URL

Where:

  • $INPUT_BOX_ADDRESS is the hex representation of the InputBox address, as explained in Gathering DApp data;
  • $DAPP_ADDRESS is the hex representation of the DApp address, as explained in Gathering DApp data;
  • "" is the actual input;
  • $SIGNER_ADDRESS is the hex representation of the account address that will sign the withdrawal request;
  • $PRIVATE_KEY (only mandatory for non-local networks) is the private key associated with $SIGNER_ADDRESS;
  • $RPC_URL is the URL of the RPC endpoint to be used.

As repeatedly stated throughout this document, only withdrawal requests coming from the predefined withdrawal address will be fulfilled by the Honeypot DApp.

So, whenever $SIGNER_ADDRESS matches the correct withdrawal address, the request will be accepted and, as long as the pot balance is greater than zero, a Voucher will be generated as a result. Such voucher, when executed, will perform the transfer of all funds previously held by the DApp to the withdrawal address.

Any withdrawal request sent from a different address will be rejected, with a Report being generated accordingly.

In a local environment, a successful withdrawal request coming from withdrawal address 0x70997970C51812dc3A010C7d01b50e0d17dc79C8 would look like this:

cast send 0x59b22D57D4f067708AB0c00552767405926dc768 \
    "addInput(address,bytes)" \
        0x70ac08179605AF2D9e75782b8DEcDD3c22aA4D0C \
        "" \
    --from 0x70997970C51812dc3A010C7d01b50e0d17dc79C8 \
    --rpc-url http://localhost:8545

Deploying the DApp

Deploying a new Cartesi DApp to a blockchain requires creating a smart contract on that network, as well as running a Cartesi Validator Node for the DApp.

Building machine to deploy

The first step is to build the DApp's back-end machine for the target network, which will produce a hash that serves as a unique identifier.

In order to do that, you need to override build argument NETWORK, which defaults to localhost, by setting a value for *.args.NETWORK. This ensures that the configuration file related to the selected network is included during the build process (see Makefile).

For example, to build a machine to be deployed to Sepolia, proceed as follows:

docker buildx bake \
    machine \
    --load \
    --set *.args.NETWORK=sepolia

See documentation for more details about overriding target configurations for docker buildx bake.

Deploying DApp contract

Once the machine docker image is ready, it may be used to deploy a corresponding Rollups smart contract. This requires you to specify the account and RPC gateway to be used when submitting the deploy transaction on the target network, which can be done by defining the following environment variables:

export MNEMONIC=<user sequence of twelve words>
export RPC_URL=<https://your.rpc.gateway>

For example, to deploy to the Sepolia testnet using an Alchemy RPC node, you could execute:

export MNEMONIC=<user sequence of twelve words>
export RPC_URL=https://eth-sepolia.g.alchemy.com/v2/<API_KEY>

With that in place, you can submit a deploy transaction to the Cartesi DApp Factory contract on the target network by executing the following command:

docker compose \
    --env-file env.$NETWORK \
    -f deploy-testnet.yml \
    up

Here, env.$NETWORK specifies general parameters for the target network, like its name and chain ID. In the case of Sepolia, the command would be:

docker compose \
    --env-file env.sepolia \
    -f deploy-testnet.yml \
    up

This will create a file at ../deployments/$NETWORK/honeypot.json with the deployed contract's address. Once the command finishes, it is advisable to stop the docker compose and remove the volumes created when executing it.

docker compose \
    --env-file env.$NETWORK \
    -f deploy-testnet.yml \
    down -v

Running a validator node

With the DApp's smart contract deployed to the target network, a corresponding Cartesi Validator Node must also be instantiated to interact with it and handle the back-end logic of the DApp.

Aside from the environment variables defined before, the node will also need a secure websocket endpoint for the RPC gateway (WSS_URL).

For example, for Sepolia and Alchemy, you would set the following additional variable:

export WSS_URL=wss://eth-sepolia.g.alchemy.com/v2/<API_KEY>

Before running the Validator Node, a Cartesi Server Manager must be built specifying the target network being used, similarly to what was done before when building the machine.

For example, to build such a server for the Sepolia network, execute the following command:

docker buildx bake \
    --load \
    --set *.args.NETWORK=sepolia

Then, the node itself can be started by running docker compose as follows:

docker compose \
    --env-file env.$NETWORK \
    -f docker-compose-testnet.yml \
    up

Specifically for Sepolia, execute:

docker compose \
    --env-file env.sepolia \
    -f docker-compose-testnet.yml \
    up

Interacting with a deployed DApp

In order to interact with a deployed DApp, you will need to provide the addresses of a few contracts of interest. First of all, you will need the addresses of the Cartesi Rollups contracts InputBox and ERC20Portal for the associated network. These addresses are available in the @cartesi/rollups npm package, and also locally in the file ./deployments/$NETWORK/rollups.json. Besides that, the address of the DApp itself should be retrieved from the DApp deployment file ./deployments/$NETWORK/honeypot.json. Both deployment files are produced when deploying the DApp.

Finally, if using a SimpleERC20 contract deployed with the common-contracts container, you may retrieve the corresponding deployment address from file ./common-contracts/deployments/$NETWORK/SimpleERC20.json.

Putting everything together, for the Sepolia network you may define appropriate environment variables as follows:

export DAPP_ADDRESS=$(cat deployments/sepolia/honeypot.json | jq -r '.address')
export INPUT_BOX_ADDRESS=$(cat deployments/sepolia/rollups.json | jq -r '.contracts.InputBox.address')
export ERC20_PORTAL_ADDRESS=$(cat deployments/sepolia/rollups.json | jq -r '.contracts.ERC20Portal.address')
export ERC20_ADDRESS=$(cat common-contracts/deployments/sepolia/SimpleERC20.json | jq -r '.address')

With such configuration in place, one can interact with the DApp as explained at Interacting with the application.

Running the back-end in host mode

This application does not support host mode because it uses the Cartesi Rollup device driver, which is available only inside the Cartesi Machine.

Integration Tests

This repository comes with a set of integration tests meant to be executed on top of Honeypot DApp.

The tests are written in Typescript and use frameworks Mocha and Chai. They also use Foundry Cast as tool to interact with the DApp.

To run the tests locally, go to the tests directory and proceed as indicated in the tests README.