Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

In Progress: Bot improvements #98

Open
wants to merge 3 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 81 additions & 7 deletions helpers/sett/SnapshotManager.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
Controller,
interface,
chain,
Transaction,
Contract
)
from tabulate import tabulate
from rich.console import Console
Expand All @@ -20,8 +22,15 @@
StrategySushiDiggWbtcLpOptimizerResolver,
StrategyDiggLpMetaFarmResolver,
)
from helpers.utils import digg_shares_to_initial_fragments, val
from helpers.utils import (
digg_shares_to_initial_fragments,
val,
send_transaction_to_discord,
)
from scripts.systems.badger_system import BadgerSystem
from datetime import datetime
import time
from decimal import Decimal

console = Console()

Expand Down Expand Up @@ -172,41 +181,106 @@ def init_resolver(self, name):
if name == "StrategyPancakeLpOptimizer":
return StrategyBasePancakeResolver(self)

def settTend(self, overrides, confirm=True):
def settTend(self, overrides: dict, confirm: bool = True) -> Transaction:
user = overrides["from"].address
trackedUsers = {"user": user}
before = self.snap(trackedUsers)
tx = self.strategy.tend(overrides)
after = self.snap(trackedUsers)
if confirm:
self.resolver.confirm_tend(before, after, tx)

def settTendViaManager(self, strategy, overrides, confirm=True):
return tx

def settTendViaManager(
self,
strategy: Contract,
overrides: dict,
confirm: bool = True
) -> Transaction:
user = overrides["from"].address
trackedUsers = {"user": user}
before = self.snap(trackedUsers)
tx = self.badger.badgerRewardsManager.tend(strategy, overrides)
after = self.snap(trackedUsers)
if confirm:
self.resolver.confirm_tend(before, after, tx)

def settHarvestViaManager(self, strategy, overrides, confirm=True):
return tx

def settTendAndProcessTx(
self,
overrides: dict,
confirm: bool = True,
tended: Decimal = None
):
tx = self.settTend(overrides, confirm)
self.confirmTransaction(tx, tended)

def settTendViaManagerAndProcessTx(
self,
strategy: Contract,
overrides: dict,
confirm: bool = True,
tended: Decimal = None
):
tx = self.settTendViaManager(strategy, overrides, confirm)
self.confirmTransaction(tx, tended)

def settHarvestViaManager(
self,
strategy: Contract,
overrides: dict,
confirm: bool = True
) -> Transaction:
user = overrides["from"].address
trackedUsers = {"user": user}
before = self.snap(trackedUsers)
tx = self.badger.badgerRewardsManager.harvest(strategy, overrides)
after = self.snap(trackedUsers)
if confirm:
self.resolver.confirm_harvest(before, after, tx)
return tx

def settHarvest(self, overrides, confirm=True):
def settHarvest(self, overrides: dict, confirm: bool = True) -> Transaction:
user = overrides["from"].address
trackedUsers = {"user": user}
before = self.snap(trackedUsers)
tx = self.strategy.harvest(overrides)
after = self.snap(trackedUsers)
if confirm:
self.resolver.confirm_harvest(before, after, tx)
return tx

def settHarvestViaManagerAndProcessTx(
self,
strategy: Contract,
overrides: dict,
confirm: bool = True,
harvested: Decimal = None
):
tx = self.settHarvestViaManager(strategy, overrides, confirm)
self.confirmTransaction(tx, harvested)

def settHarvestAndProcessTx(
self,
overrides: dict,
confirm: bool = True,
harvested: Decimal = None
):
tx = self.settHarvest(overrides, confirm)
self.confirmTransaction(tx, harvested)

def confirmTransaction(self, tx: Transaction, amount: Decimal):
success = True
if tx.error() == None and tx.revert_msg == None:
console.print(f"Transaction succeded!")
else:
# something went wrong
console.print(f"ERROR: harvest errored or reverted.")
console.print(f"Error: {tx.error()}")
console.print(f"Revert: {tx.revert_msg}")
success = False

send_transaction_to_discord(tx, self.strategy.getName(), amount=amount, success=success)

def settDeposit(self, amount, overrides, confirm=True):
user = overrides["from"].address
Expand Down
39 changes: 39 additions & 0 deletions helpers/utils.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
import time
from brownie import *
from decimal import Decimal
from dotenv import load_dotenv
from rich.console import Console
from tabulate import tabulate
import requests
from discord import Webhook, RequestsWebhookAdapter, Embed
import os
console = Console()
load_dotenv()

# Assert approximate integer
def approx(actual, expected, percentage_threshold):
Expand Down Expand Up @@ -131,3 +137,36 @@ def snapSharesMatchForToken(snap, otherSnap, tokenKey):
if shares != otherShares:
return False
return True

def send_transaction_to_discord(
tx: Transaction,
strategy_name: str,
amount: Decimal = None,
success: bool = True
):
webhook = Webhook.from_url(os.getenv("DISCORD_WEBHOOK_URL"), adapter=RequestsWebhookAdapter())
etherscan_url = f"https://etherscan.io/tx/{tx.txid}"
if success:
embed = Embed(
title="**Keeper transaction SUCCESS**"
)
else:
embed = Embed(
title="**Keeper transaction FAILED**"
)
if "harvest" in tx.fn_name:
embed.add_field(
name="Keeper Action", value=f"Harvest ${str(round(amount))} for {strategy_name}.", inline=False
)
embed.add_field(
name="Etherscan Transaction", value=f"{etherscan_url}", inline=False
)
webhook.send(embed=embed, username="Sushi Harvester")
elif "tend" in tx.fn_name:
embed.add_field(
name="Keeper Action", value=f"Tend ${str(round(amount))} for {strategy_name}.", inline=False
)
embed.add_field(
name="Etherscan Transaction", value=f"{etherscan_url}", inline=False
)
webhook.send(embed=embed, username="Sushi Tender")
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ colorama==0.4.3
commonmark==0.9.1
contextlib2==0.6.0
cytoolz==0.11.0
discord.py==1.7.1
distlib==0.3.0
distro==1.4.0
dotmap==1.3.23
Expand Down
157 changes: 157 additions & 0 deletions scripts/keeper/harvest_crv.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
from helpers.sett.SnapshotManager import SnapshotManager
from config.keeper import keeper_config
from helpers.utils import tx_wait, val
from brownie import *
from helpers.gas_utils import gas_strategies
from brownie.network.gas.strategies import (
GasNowStrategy,
ExponentialScalingStrategy,
SimpleGasStrategy,
)
from rich.console import Console
from scripts.systems.badger_system import BadgerSystem, connect_badger
from tabulate import tabulate
from decimal import Decimal

gas_strategies.set_default_for_active_chain()

console = Console()

CRV_USD_CHAINLINK = "0xCd627aA160A6fA45Eb793D19Ef54f5062F20f33f"
CRV_ETH_CHAINLINK = "0x8a12Be339B0cD1829b91Adc01977caa5E9ac121e"
REN_CRV_STRATEGY = "0x444B860128B7Bf8C0e864bDc3b7a36a940db7D88"
SBTC_CRV_STRATEGY = "0x3Efc97A8e23f463e71Bf28Eb19690d097797eb17"
TBTC_CRV_STRATEGY = "0xE2fA197eAA5C726426003074147a08beaA59403B"

XSUSHI_TOKEN = "0x8798249c2E607446EfB7Ad49eC89dD1865Ff4272"


def harvest_ren_crv(badger: BadgerSystem = None):
harvest(
badger=badger,
strategy_address=REN_CRV_STRATEGY,
rewards_price_feed_address=CRV_ETH_CHAINLINK,
key="native.renCrv",
)


def harvest_sbtc_crv(badger: BadgerSystem = None):
harvest(
badger=badger,
strategy_address=SBTC_CRV_STRATEGY,
rewards_price_feed_address=CRV_ETH_CHAINLINK,
key="native.sbtcCrv",
)


def harvest_tbtc_crv(badger: BadgerSystem = None):
harvest(
badger=badger,
strategy_address=TBTC_CRV_STRATEGY,
rewards_price_feed_address=CRV_ETH_CHAINLINK,
key="native.tbtcCrv",
)


def harvest(
badger: BadgerSystem = None,
strategy_address: str = None,
rewards_price_feed_address: str = None,
key: str = None,
):
strategy = Contract.from_explorer(strategy_address)
keeper_address = strategy.keeper.call()

crv = Contract.from_explorer(strategy.crv.call())
gauge = Contract.from_explorer(strategy.gauge())

decimals = 18

claimable_rewards = get_harvestable_amount(
decimals=crv.decimals(),
strategy=strategy,
gauge=gauge,
)
console.print(f"claimable rewards: {claimable_rewards}")

current_price_eth = get_current_price(
price_feed_address=rewards_price_feed_address,
decimals=crv.decimals()
)
console.print(f"current rewards price per token (ETH): {current_price_eth}")

gas_fee = estimate_gas_fee(strategy, keeper_address)
console.print(f"estimated gas fee to harvest: {gas_fee}")

should_harvest = is_profitable(claimable_rewards, current_price_eth, gas_fee)
console.print(f"Should we harvest: {should_harvest}")

if should_harvest and badger:
# we should actually take the snapshot and claim here
snap = SnapshotManager(badger, key)
before = snap.snap()
keeper = accounts.at(keeper_address)
crv_usd_oracle = Contract.from_explorer(CRV_USD_CHAINLINK)
crv_usd_price = Decimal(crv_usd_oracle.latestRoundData.call()[1] / 10 ** 8)

if strategy.keeper() == badger.badgerRewardsManager:
snap.settHarvestViaManagerAndProcessTx(
strategy=strategy,
overrides={"from": keeper, "gas_limit": 2000000, "allow_revert": True},
confirm=False,
harvested=claimable_rewards * crv_usd_price,
)
else:
snap.settHarvestAndProcessTx(
overrides={"from": keeper, "gas_limit": 2000000, "allow_revert": True},
confirm=False,
harvested=claimable_rewards * crv_usd_price,
)


def get_harvestable_amount(
decimals: int,
strategy: Contract = None,
gauge: Contract = None
) -> Decimal:
harvestable_amt = gauge.claimable_tokens.call(strategy.address) / 10 ** decimals
return Decimal(harvestable_amt)


def get_current_price(
price_feed_address: str,
decimals: int,
) -> Decimal:
price_feed = Contract.from_explorer(price_feed_address)
return Decimal(price_feed.latestRoundData.call()[1] / 10 ** decimals)


def estimate_gas_fee(strategy: Contract, keeper: str) -> Decimal:
estimated_gas_to_harvest = strategy.harvest.estimate_gas({"from": keeper})
current_gas_price = GasNowStrategy("fast").get_gas_price() / 10 ** 18
return Decimal(current_gas_price * estimated_gas_to_harvest)


def is_profitable(amount: Decimal, price_per: Decimal, gas_fee: Decimal) -> bool:
fee_percent_of_claim = (
1 if amount * price_per == 0 else gas_fee / (amount * price_per)
)
console.print(f"Fee as percent of harvest: {round(fee_percent_of_claim * 100, 2)}%")
return fee_percent_of_claim <= 0.05


def main():
badger = connect_badger(load_keeper=True)
# skip = keeper_config.get_active_chain_skipped_setts("harvest")
# console.print(badger.getAllSettIds())

# harvest_all(badger, skip)

console.print("harvesting ren crv")
harvest_ren_crv(badger=badger)
console.print("-------------------")
console.print("harvesting sbtc crv")
harvest_sbtc_crv(badger=badger)
console.print("-------------------")
console.print("harvesting tbtc crv")
harvest_tbtc_crv(badger=badger)
Loading