This project aims to write scripts that fetch information about LSD arbitrage opportunities on the Goerli testnet and a smart contract that executes the arbitrage. Executing arbitrage in this context means taking validators that are associated with the open index (the default index for earning yield) and transferring them to the user's own index in exchange for a user's validator, where the yield is boosted by managing the dETH and validators.
To find arbitrage opportunities, the script compares the yields of validators in the open index to the yields of validators in the user's index, and only considers validators that have a higher yield in the open index and require less dETH to isolate (transfer to the user's index) than their counterparts in the user's index. The script then calculates the total dETH required to isolate all identified validators and the total dETH gained by returning the user's validators to the open index. If the total dETH gained is greater than the total dETH required, an arbitrage opportunity is deemed to exist and the validators are eligible for transfer.
To fetch information about the validators and their yields, the script queries the LSD and Stakehouse subgraphs using GraphQL. It also interacts with the ISavETHManager
and ArbitrageExecutor
contracts to execute the arbitrage by isolating the validators and returning the user's validators to the open index.
ArbitrageExecuter Smart Contract
To run the script, run the following command:
python3 arbitrage.py
The following dependencies are required to run the script:
- aiohttp
- aiographql
- asyncio
- dotenv
- json
- os
- web3
Install by running the following command:
pip3 install -r requirements.txt
The following environment variables must be set:
-
INFURA_URL
: The URL of the Ethereum node to be used for contract interactions. -
PRIVATE_key
: The private key used to generate the Ethereum account that will execute the arbitrage. -
MNEMONIC
: The mnemonic used to generate the Ethereum account for the truffle goerli deployment. -
ACCOUNT
: The Ethereum account address that truffle will use to deploy the contracts. -
PROJECT_ID
: The project ID for the Infura project. Used to create the truffle goerli connection in truffle-config.js.
The following constants are used in the script:
LSD_SUBGRAPH_URL
: The URL of the LSD subgraph.SH_SUBGRAPH_URL
: The URL of the Stakehouse subgraph.SAV_ETH_MANAGER_COMPILED_JSON
: The compiled JSON file for theISavETHManager
contract.ARBITRAGE_EXECUTOR_COMPILED_JSON
: The compiled JSON file for theArbitrageExecutor
contract.ARBITRAGE_EXECUTOR_CONTRACT_ADDRESS
: The contract address of theArbitrageExecutor
contract.DETH_CONTRACT_ADDRESS
: The contract address of the dETH contract.GAS_LIMIT
: The maximum amount of gas that can be used for a contract transaction.SAV_ETH_MANAGER_CONTRACT_ADDRESS
: The contract address of theISavETHManager
contract.DETH_COMPILED_JSON
: The compiled JSON file for the dETH contract.
This class is used to get the LSD validators from the LSD subgraph.
This is the constructor of the LsdValidators
class. It takes in a subgraph_url
and creates a GraphQLClient
object.
This method queries the GraphQL API for all the LSD validators that have a status
of "MINTED_DERIVATIVES" and a currentIndex
of the currentIndex
variable. If no status
is provided, it returns all validators with the specified currentIndex
. It returns a generator object.
currentIndex
: ThecurrentIndex
of the validator.status
: Thestatus
of the validator.
- A generator object.
This class is used to get the StakehouseAccounts from the Stakehouse subgraph.
This is the constructor of the Stakehouses
class. It takes in a subgraph_url
and creates a GraphQLClient
object.
This method queries the GraphQL API for all the StakehouseAccounts that have a stakeHouse
of the validator_ids
. It returns a generator object.
validator_ids
: Thevalidator_ids
.
- A generator object.
This method queries the GraphQL API for all the LSD validators that have a stakeHouse
of the user_address
. It returns a generator object.
user_address
: Theuser_address
.
- A generator object.
This method queries the GraphQL API for all the details of the validators. It returns a list of dictionaries containing the details of the validators.
validators
: The list of validators.order_by
: The field to order the results by.order_direction
: The direction to order the results in.
- A list of dictionaries containing the details of the validators.
This class is used to interact with the ISavETHManager
contract.
This is the constructor of the ISavETHManager
class. It takes in a web3_url
and creates a Web3
object. It also sets the contract address and ABI for the ISavETHManager
contract.
web3_url
: Theweb3_url
to connect to.
This method calculates the amount of dETH required for isolation for a validator with the given validator_id
. It returns an integer representing the amount of dETH required.
validator_id
: Thevalidator_id
of the validator.
- An integer representing the amount of dETH required.
This method approves the amount of dETH required for isolation for a validator for the saveETHManager to spend. It returns the transaction hash.
deth_amount
: Thedeth_amount
required for isolation of the validatos.
- The transaction hash of the approval.
This method gets the index of the user using one of the validator IDs of the user. It returns an integer representing the index of the user.
user_validator_id
: Theuser_validator_id
of the user.
- An integer representing the index of the user.
.
This class is used to interact with the ArbitrageExecutor
contract.
This is the constructor of the Arbitrage
class. It takes in a web3_url
and creates a Web3
object. It also sets the contract address and ABI for the ArbitrageExecutor
contract.
web3_url
: Theweb3_url
to connect to.
async execute_arbitrage(self, open_index_validator_ids, user_validator_ids, open_index_stakehouse_ids, user_stakehouse_ids, deth_required_to_isolate, deth_gained_for_returning)
This method executes the arbitrage by calling the executeArbitrage
function on the ArbitrageExecutor
contract. It takes in the open_index_validator_ids
, user_validator_ids
, open_index_stakehouse_ids
, user_stakehouse_ids
, deth_required_to_isolate
, and deth_gained_for_returning
and sends a transaction to the contract to execute the arbitrage.
-
open_index_validator_ids
: Theopen_index_validator_ids
of the validators in the open index. -
user_validator_ids
: Theuser_validator_ids
of the validators in the user's index. -
user_stakehouse_ids
: Theuser_stakehouse_ids
of the validators in the user's index. -
deth_required_to_isolate
: Thedeth_required_to_isolate
required to isolate the validators from the open index. -
deth_gained_for_returning
: Thedeth_gained_for_returning
gained for returning the validators to the open index.
- The transaction hash of the executed arbitrage.
This is the main function of the script. It loads the environment variables and sets some initial variables such as the web3_url
, open_index_id
, user_index
, and user_address
. It then creates instances of the LsdValidators
, ISavETHManager
, Stakehouses
, and Arbitrage
classes.
It then queries the GraphQL API for all the LSD validators in the open index and the user's index. It also gets the metadata for the validators in the open index and the user's index.
The main
function continues by looping through the validators in the open index and the user's index, and calculating the dETH required for isolation for both. If the dETH required for isolation for the open index validator is less than the dETH required for isolation for the user's validator and the reported yield for the open index validator is greater than the reported yield for the user's validator, it adds them to the list of validators to be arbitraged.
This is done to find the arbitrage opportunities, as the validators with a higher reported yield and a lower dETH required for isolation are more likely to provide a higher return on investment.
Finally, the main
function calls the execute_arbitrage
method of the Arbitrage
class to execute the arbitrage. It passes in the validator IDs and stakehouse IDs of the validators in the open index and the user's index, as well as the deth_required_to_isolate
and deth_gained_for_returning
calculated earlier.
The execute_arbitrage
method returns the transaction hash of the executed arbitrage, which is then printed to the console.
This script fetches information about LSD arbitrage opportunities on the Goerli testnet and writes a smart contract that executes the arbitrage. It does so by querying the GraphQL API for validators in the open index and the user's index, calculating the dETH required for isolation for each validator, and finding arbitrage opportunities by comparing the dETH required for isolation and the reported yield for each validator. It then executes the arbitrage by calling the appropriate smart contract functions.