Skip to content

Commit

Permalink
Merge pull request #24 from BeanstalkFarms/fix/sk/ignored-withdraw
Browse files Browse the repository at this point in the history
Fixed net withdrawal being ignored
  • Loading branch information
soilking authored Sep 16, 2024
2 parents 545b19d + d080336 commit f185a1f
Show file tree
Hide file tree
Showing 11 changed files with 80 additions and 74 deletions.
1 change: 1 addition & 0 deletions dev.sh
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export PYTHONPATH=$PYTHONPATH:$(pwd)/src

# Configure dry run (test transactions)
# Provide comma separated hashes, or "all" to run through the whole collection
# Provide "seasons" to trigger the seasons monitor
if [ -n "$2" ]; then
export DRY_RUN="$2"
fi
Expand Down
2 changes: 1 addition & 1 deletion src/bots/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ async def update_discord_bot_name(name, bot):
next_name = pruned_name
# Note(funderberker): Is this rate limited?s
for guild in bot.current_guilds:
logging.info(f"Attempting to set nickname in guild with id {guild.id}")
# logging.info(f"Attempting to set nickname in guild with id {guild.id}")
await guild.me.edit(nick=next_name)
logging.info(f"Bot nickname changed to {next_name} in guild with id {guild.id}")
return next_name
Expand Down
2 changes: 1 addition & 1 deletion src/data_access/contracts/bean.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ def get_price_info(self):
Pricing data is returned as an array. See abi for structure.
"""
logging.info("Getting bean price...", exc_info=True)
# logging.info("Getting bean price...", exc_info=True)
raw_price_info = call_contract_function_with_retry(self.price_contract.functions.price())
return BeanClient.map_price_info(raw_price_info)

Expand Down
14 changes: 7 additions & 7 deletions src/data_access/contracts/eth_events.py
Original file line number Diff line number Diff line change
Expand Up @@ -366,7 +366,7 @@ def get_new_logs(self, dry_run=None, filters=None, get_all=False):
# Track which unique logs have already been processed from this event batch.
for entry in new_entries:
# There can be zero topics for dry run
if len(entry["topics"]) > 0:
if len(entry.get("topics", [])) > 0:
topic_hash = entry["topics"][0].hex()
# Do not process topics outside of this classes topics of interest.
if topic_hash not in self._events_dict:
Expand All @@ -377,14 +377,14 @@ def get_new_logs(self, dry_run=None, filters=None, get_all=False):
continue

# Print out entry.
logging.info(f"{self._event_client_type.name} entry:\n{str(entry)}\n")
# logging.info(f"{self._event_client_type.name} entry:\n{str(entry)}\n")

# Do not process the same txn multiple times.
txn_hash = entry["transactionHash"]
if txn_hash in txn_hash_set:
continue

logging.info(f"{self._event_client_type.name} processing {txn_hash.hex()} logs.")
# logging.info(f"{self._event_client_type.name} processing {txn_hash.hex()} logs.")

# Retrieve the full txn and txn receipt.
receipt = tools.util.get_txn_receipt_or_wait(self._web3, txn_hash)
Expand Down Expand Up @@ -431,10 +431,10 @@ def get_new_logs(self, dry_run=None, filters=None, get_all=False):
# Add all remaining txn logs to log map.
txn_hash_set.add(txn_hash)
txn_logs_list.append(TxnPair(txn_hash, decoded_logs))
logging.info(
f"Transaction: {txn_hash}\nAll txn logs of interest:\n"
f"{NEWLINE_CHAR.join([str(l) for l in decoded_logs])}"
)
# logging.info(
# f"Transaction: {txn_hash}\nAll txn logs of interest:\n"
# f"{NEWLINE_CHAR.join([str(l) for l in decoded_logs])}"
# )

return txn_logs_list

Expand Down
2 changes: 2 additions & 0 deletions src/data_access/contracts/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -359,6 +359,8 @@ def get_test_entries(dry_run=None):
if dry_run:
if dry_run[0] == 'all':
return dry_run_entries.entries
elif dry_run[0] == 'seasons':
return []
else:
entries = []
for i in range(len(dry_run)):
Expand Down
10 changes: 5 additions & 5 deletions src/data_access/subgraphs/beanstalk.py
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ def silo_assets_seasonal_changes(self, current_silo_assets=None, previous_silo_a
assets_changes = []
for i in range(len(previous_silo_assets)):
assets_changes.append(AssetChanges(previous_silo_assets[i], current_silo_assets[i]))
logging.info(f"assets_changes: {assets_changes}")
# logging.info(f"assets_changes: {assets_changes}")
return assets_changes

def seasons_stats(
Expand Down Expand Up @@ -309,14 +309,14 @@ def __init__(self, graph_seasons_response, season_index=0, season=None):
)
# List of each asset at the start of the season. Note that this is offset by 1 from subgraph data.
self.pre_assets = []
logging.info(
f'siloAssetHourlySnapshots: {graph_seasons_response["siloAssetHourlySnapshots"]}'
)
# logging.info(
# f'siloAssetHourlySnapshots: {graph_seasons_response["siloAssetHourlySnapshots"]}'
# )
for asset_season_snapshot in graph_seasons_response["siloAssetHourlySnapshots"]:
# Shift back by one season since asset amounts represent current/end of season values.
if int(asset_season_snapshot["season"]) == self.season - 1:
self.pre_assets.append(asset_season_snapshot)
logging.info(f"self.pre_assets: {self.pre_assets}")
# logging.info(f"self.pre_assets: {self.pre_assets}")
if "fieldHourlySnapshots" in graph_seasons_response:
self.temperature = float(
graph_seasons_response["fieldHourlySnapshots"][season_index]["temperature"]
Expand Down
4 changes: 2 additions & 2 deletions src/data_access/subgraphs/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,10 @@ def execute(client, query_str, max_tries=10):
try_count = 0
retry_delay = 1 # seconds
while not max_tries or try_count < max_tries:
logging.info(f"GraphQL query:" f'{query_str.replace(NEWLINE_CHAR, "").replace(" ", "")}')
# logging.info(f"GraphQL query:" f'{query_str.replace(NEWLINE_CHAR, "").replace(" ", "")}')
try:
result = client.execute(query)
logging.info(f"GraphQL result:{result}")
# logging.info(f"GraphQL result:{result}")
return result
except asyncio.TimeoutError:
logging.warning(
Expand Down
2 changes: 1 addition & 1 deletion src/data_access/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

def get_with_retries(request_url, max_tries=10, timeout=6):
"""Attempt a get call with error handling."""
logging.info(f"Attempting GET to {request_url}")
# logging.info(f"Attempting GET to {request_url}")
try_count = 0
while True:
try:
Expand Down
10 changes: 6 additions & 4 deletions src/monitors/barn.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,8 @@ def _handle_event_log(self, event_log):
"""Process a single event log for the Barn Raise."""
# Mint single.
if (
event_log.event in ["TransferSingle", "TransferBatch"]
event_log.address == FERTILIZER_ADDR
and event_log.event in ["TransferSingle", "TransferBatch"]
and event_log.args["from"] == NULL_ADDR
):
if event_log.event == "TransferSingle":
Expand All @@ -111,11 +112,11 @@ def _handle_event_log(self, event_log):
elif event_log.event == "TransferBatch":
amount = sum([int(value) for value in event_log.args.values])

weth_amount = token_to_float(
get_eth_sent(event_log.transactionHash, event_log.address, self._web3, event_log.logIndex), 18
wsteth_amount = token_to_float(
get_tokens_sent(WSTETH, event_log.transactionHash, event_log.address, event_log.logIndex), 18
)

event_str = f"🚛 Fertilizer Purchased - {round_num(amount, 0)} Fert for {round_num(weth_amount, 3)} WETH @ {round_num(self.barn_raise_client.get_humidity(), 1)}% Humidity"
event_str = f"🚛 Fertilizer Purchased - {round_num(amount, 0)} Fert for {round_num(wsteth_amount, 3)} wstETH @ {round_num(self.barn_raise_client.get_humidity(), 1)}% Humidity"
total_bought = self.beanstalk_graph_client.get_fertilizer_bought()

# The subgraph is slower to update, so may need to calculate total bought here.
Expand All @@ -135,3 +136,4 @@ def _handle_event_log(self, event_log):
# Empty line that does not get stripped.
event_str += "\n_ _"
self.message_function(event_str)
logging.info(f"\n\n\nfull barn message here {event_str}\n\n\n")
105 changes: 53 additions & 52 deletions src/monitors/beanstalk.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
from constants.addresses import *
from constants.config import *

from collections import defaultdict

class BeanstalkMonitor(Monitor):
"""Monitor the Beanstalk contract for events."""

Expand Down Expand Up @@ -66,32 +68,61 @@ def _handle_txn_logs(self, txn_hash, event_logs):
return
# Else handle txn logs individually using default strings.

# Prune *transfer* deposit logs. They are uninteresting clutter.
# Note that this assumes that a transfer event never includes a novel deposit.
remove_event_logs = get_logs_by_names(["RemoveDeposit", "RemoveDeposits"], event_logs)
deposit_event_logs = get_logs_by_names("AddDeposit", event_logs)
for remove_event_log in remove_event_logs:
for deposit_event_log in deposit_event_logs:
if deposit_event_log.args.get("token") == remove_event_log.args.get("token"):
# and deposit_event_log.args.get('amount') == \
# (remove_event_log.args.get('amount'))):
# Remove event log from event logs
try:
event_logs.remove(remove_event_log)
except ValueError:
pass
try:
event_logs.remove(deposit_event_log)
except ValueError:
pass
logging.info(
f"Ignoring a AddDeposit RemoveDeposit(s) pair {txn_hash.hex()}, possible transfer or silo migration"
)
# Determine net deposit/withdraw of each token
net_deposits = defaultdict(int)
silo_deposit_logs = get_logs_by_names(["AddDeposit", "RemoveDeposit", "RemoveDeposits"], event_logs)
for event_log in silo_deposit_logs:
sign = 1 if event_log.event == "AddDeposit" else -1
token_address = event_log.args.get("token")
token_amount_long = event_log.args.get("amount")
net_deposits[token_address] += sign * token_amount_long
event_logs.remove(event_log)

# logging.info(f"net token amounts {net_deposits}")
for token in net_deposits:
event_str = self.silo_event_str(token, net_deposits[token], txn_hash)
if event_str:
self.message_function(event_str)

for event_log in event_logs:
event_str = self.single_event_str(event_log)
if event_str:
self.message_function(event_str)

def silo_event_str(self, token_addr, net_amount, txn_hash):
"""Logs a Silo Deposit/Withdraw"""

event_str = ""

if net_amount > 0:
event_str += f"📥 Silo Deposit"
elif net_amount < 0:
event_str += f"📭 Silo Withdrawal"
else:
return ""

bean_price = self.bean_client.avg_bean_price()
token_info = get_erc20_info(token_addr)
amount = token_to_float(abs(net_amount), token_info.decimals)

# Use current bdv rather than the deposited bdv reported in the event
bdv = amount * self.beanstalk_client.get_bdv(token_info)

value = None
if bdv > 0:
value = bdv * bean_price

event_str += f" - {round_num_auto(amount, min_precision=0)} {token_info.symbol}"
# Some legacy events may not set BDV, skip valuation. Also do not value unripe assets.
if value is not None and not token_addr.startswith(UNRIPE_TOKEN_PREFIX):
event_str += f" ({round_num(value, 0, avoid_zero=True, incl_dollar=True)})"
event_str += f"\n{value_to_emojis(value)}"

event_str += f"\n<https://etherscan.io/tx/{txn_hash.hex()}>"
# Empty line that does not get stripped.
event_str += "\n_ _"
return event_str


def single_event_str(self, event_log):
"""Create a string representing a single event log.
Expand All @@ -103,39 +134,9 @@ def single_event_str(self, event_log):
event_str = ""
bean_price = self.bean_client.avg_bean_price()

# Ignore these events. They are uninteresting clutter.
# Ignore these events
if event_log.event in ["RemoveWithdrawal", "RemoveWithdrawals" "Plant", "Pick"]:
return ""

# Deposit & Withdraw events.
elif event_log.event in ["AddDeposit", "RemoveDeposit", "RemoveDeposits"]:
# Pull args from the event log.
token_address = event_log.args.get("token")
token_amount_long = event_log.args.get("amount") # AddDeposit, AddWithdrawal

_, _, token_symbol, decimals = get_erc20_info(token_address, web3=self._web3).parse()
amount = token_to_float(token_amount_long, decimals)

# Use current bdv rather than the deposited bdv reported in the event
bdv = amount * self.beanstalk_client.get_bdv(get_erc20_info(token_address))

value = None
if bdv > 0:
value = bdv * bean_price

if event_log.event in ["AddDeposit"]:
event_str += f"📥 Silo Deposit"
elif event_log.event in ["RemoveDeposit", "RemoveDeposits"]:
event_str += f"📭 Silo Withdrawal"
else:
return ""

event_str += f" - {round_num_auto(amount, min_precision=0)} {token_symbol}"
# Some legacy events may not set BDV, skip valuation. Also do not value unripe assets.
if value is not None and not token_address.startswith(UNRIPE_TOKEN_PREFIX):
event_str += f" ({round_num(value, 0, avoid_zero=True, incl_dollar=True)})"
event_str += f"\n{value_to_emojis(value)}"

# Sow event.
elif event_log.event in ["Sow", "Harvest"]:
# Pull args from the event log.
Expand Down
2 changes: 1 addition & 1 deletion src/monitors/seasons.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ def _wait_until_expected_sunrise(self):
Assumes sunrise timing cycle beings with Unix Epoch (1/1/1970 00:00:00 UTC).
This is not exact since we do not bother with syncing local and graph time.
"""
if self._dry_run:
if self._dry_run == ["seasons"]:
time.sleep(1)
return

Expand Down

0 comments on commit f185a1f

Please sign in to comment.