Skip to content

Commit

Permalink
smoother print statements/logging
Browse files Browse the repository at this point in the history
  • Loading branch information
leemount96 committed Jul 4, 2024
1 parent eb4473c commit f36525c
Show file tree
Hide file tree
Showing 14 changed files with 163 additions and 132 deletions.
16 changes: 14 additions & 2 deletions contracts/Liquidator.sol
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ contract Liquidator {
evc.enableController(msg.sender, params.vaultAddress);

// TODO: use swapper in correct way
// TODO: correctly use sub account instead of msg.sender
// TODO: correctly use evc account instead of msg.sender
multicallItems[0] = abi.encodeCall(
ISwapper.swap,
ISwapper.SwapParams({
Expand All @@ -86,7 +86,7 @@ contract Liquidator {
vaultIn: params.vaultAddress,
receiver: params.vaultAddress,
amountOut: 0,
data: params.swapData
data: injectReceiver(swapperAddress, params.swapData)
})
);

Expand Down Expand Up @@ -157,4 +157,16 @@ contract Liquidator {

return true;
}

// Need to understand this more
function injectReceiver(address receiver, bytes memory _payload) internal pure returns (bytes memory payload) {
payload = _payload;

// uint256 constant RECEIVER_OFFSET = 208;

assembly {
mstore(0, receiver)
mcopy(add(add(payload, 32), 208), 12, 20)
}
}
}
Binary file modified python/execute/__pycache__/liquidator.cpython-312.pyc
Binary file not shown.
58 changes: 31 additions & 27 deletions python/execute/liquidator.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,18 @@
import time
import os
import json
import threading

from monitor.monitor import Monitor
from monitor.position import Position

class Liquidator:
def __init__(self, liquidation_contract_address: str, monitor_instance: Monitor, run_with_monitor: bool = False):
def __init__(self, liquidator_contract_address: str, monitor_instance: Monitor, run_with_monitor: bool = False):
LIQUIDATOR_ABI_PATH = 'out/Liquidator.sol/Liquidator.json'
with open(LIQUIDATOR_ABI_PATH, 'r') as file:
liquidation_interface = json.load(file)
liquidator_interface = json.load(file)

liquidation_abi = liquidation_interface['abi']
liquidator_abi = liquidator_interface['abi']

load_dotenv()

Expand All @@ -23,19 +24,20 @@ def __init__(self, liquidation_contract_address: str, monitor_instance: Monitor,
w3 = Web3(Web3.HTTPProvider(rpc_url))

self.w3 = w3
self.liquidation_contract_address = liquidation_contract_address
self.liquidation_abi = liquidation_abi
self.liquidator_contract_address = liquidator_contract_address
self.liquidator_abi = liquidator_abi

self.liquidator_contract = w3.eth.contract(address=liquidation_contract_address, abi=liquidation_abi)
self.liquidator_contract = w3.eth.contract(address=liquidator_contract_address, abi=liquidator_abi)

self.monitor_instance = monitor_instance

if(run_with_monitor):
self.start()
self.monitor_listener_thread = threading.Thread(target=self.start)
self.monitor_listener_thread.start()

def start(self):
while True:
print("Checking for profitable liquidation opportunities to execute...\n")
print("Liquidator: Checking for profitable liquidation opportunities to execute...\n")
try:

profitable_liquidation = self.monitor_instance.position_list.pop_profitable_liquidation()
Expand All @@ -61,7 +63,8 @@ def start(self):
liquidator_private_key)

except Exception as e:
print("No profitable liquidation opportunities found.\n")
print("ERROR: Liquidator: No profitable liquidation opportunities found.\n")
print("ERROR: Liquidator: Error: ", e, "\n")
time.sleep(10)
continue

Expand All @@ -76,22 +79,23 @@ def execute_liquidation(self,
swap_data,
caller_public_key,
caller_private_key):
print(f"Trying to liquidate {violator_address} in vault {vault_address} with borrowed asset {borrowed_asset_address} and collateral asset {collateral_asset_address}...\n")
print(f"Liquidator Trying to liquidate {violator_address} in vault {vault_address} with borrowed asset {borrowed_asset_address} and collateral asset {collateral_asset_address}...\n")

#TODO: enable collateral & controller at EVC level for liquidation

try:
liquidation_tx = self.liquidator_contract.functions.liquidate({'vaultAddress': vault_address,
'violatorAddress': violator_address,
'borrowedAsset': borrowed_asset_address,
'colllateralAsset': collateral_asset_address,
'amountToRepay': amount_to_repay,
'expectedCollateral': expected_collateral,
'swapData': swap_data}).build_transaction({
'chainId': 1,
liquidation_tx = self.liquidator_contract.functions.liquidate((vault_address,
violator_address,
borrowed_asset_address,
collateral_asset_address,
amount_to_repay,
expected_collateral,
swap_data
)).build_transaction({
'chainId': 41337,
'gasPrice': self.w3.eth.gas_price,
'from': caller_public_key,
'nonce': self.w3.eth.get_transaction_count(caller_public_key)
'nonce': self.w3.eth.get_transaction_count(caller_public_key)
})

signed_tx = self.w3.eth.account.sign_transaction(liquidation_tx, caller_private_key)
Expand All @@ -100,26 +104,26 @@ def execute_liquidation(self,

tx_receipt = self.w3.eth.wait_for_transaction_receipt(tx_hash)

result = self.liquidator_contract.events.Liquidation().processReceipt(tx_receipt)
result = self.liquidator_contract.events.Liquidation().process_receipt(tx_receipt)

print("Liquidation successful.\n\n")
print("Liquidation details:\n")
print("Liquidator: Liquidation details:")
print(result[0]['args'])

print("Liquidator: Liquidation successful.")
return True
except Exception as e:

print("Liquidation failed, see error:\n\n")
print("ERROR: Liquidator: Liquidation failed, see error:")

print(e)
print("ERROR: Liquidator:", e, "\n")

return False

def test_contract_deployment(self):
print("Testing contract deployment...\n")
print("Liquidator: Testing contract deployment...\n")

print("Trying to get swapper address from liquidator...\n")
print("Swapper address: " + self.liquidator_contract.functions.swapperAddress().call())
print("Liquidator: Trying to get swapper address from liquidator...\n")
print("Liquidator: Swapper address: " + self.liquidator_contract.functions.swapperAddress().call())


# if __name__ == "__main__":
Expand Down
10 changes: 2 additions & 8 deletions python/liquidation_bot.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,7 @@ def __init__(self):
load_dotenv()

self.monitor = Monitor(0.1, 0.2, 0.2, 0.2, 1)
self.liquidator = Liquidator(os.getenv('LIQUIDATOR_ADDRESS'), self.monitor)

def start(self):
self.monitor.start()

#TODO: change to IO model for reading outputs from monitor to trigger liquidation
self.liquidator = Liquidator(os.getenv('LIQUIDATOR_ADDRESS'), self.monitor, True)

if __name__ == "__main__":
bot = LiquidationBot()
bot.start()
bot = LiquidationBot()
Binary file modified python/monitor/__pycache__/monitor.cpython-312.pyc
Binary file not shown.
Binary file modified python/monitor/__pycache__/position.cpython-312.pyc
Binary file not shown.
Binary file modified python/monitor/__pycache__/position_list.cpython-312.pyc
Binary file not shown.
Binary file modified python/monitor/__pycache__/profitability_calculator.cpython-312.pyc
Binary file not shown.
Binary file modified python/monitor/__pycache__/vault_list.cpython-312.pyc
Binary file not shown.
58 changes: 40 additions & 18 deletions python/monitor/monitor.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ def __init__(self, high_update_frequency, medium_update_frequency, other_update_

self.w3 = Web3(Web3.HTTPProvider(self.rpc_url))

self.start()

# Start the monitoring process
# Currently using a naive health score based "sorting" into groups, then simple monitoring at specific intervals
# TODO: Implement more sophisticated filtering/sorting
Expand All @@ -49,41 +51,61 @@ def start(self):
self.vault_creation_listener_thread = threading.Thread(target=self.start_vault_creation_listener)
self.vault_creation_listener_thread.start()

print("Successfully started monitoring threads!\n")
print("Monitor: Successfully started monitoring threads!\n")

# Scan for new positions every 5 minutes
def start_new_position_listener(self):
while True:
print("Scanning for new positions...\n")
self.position_list.scan_for_new_positions(self.w3.eth.block_number - 30) # scan 30 blocks behind = 6 minutes
time.sleep(self.new_position_scan_frequency * 60)
try:
print("Monitor: Scanning for new positions...\n")
self.position_list.scan_for_new_positions(self.w3.eth.block_number - 30) # scan 30 blocks behind = 6 minutes
time.sleep(self.new_position_scan_frequency * 60)
except Exception as e:
print("ERROR: Monitor: Error scanning for new positions: ", e)
time.sleep(self.new_position_scan_frequency * 60)

# Check high risk positions every 30 seconds
def start_high_risk_listener(self):
while True:
print("Checking high risk positions...\n")
self.position_list.update_high_risk_positions()
time.sleep(self.high_update_frequency * 60)
try:
print("Monitor: Checking high risk positions...\n")
self.position_list.update_high_risk_positions()
time.sleep(self.high_update_frequency * 60)
except Exception as e:
print("ERROR: Monitor: Error updating high risk positions: ", e)
time.sleep(self.high_update_frequency * 60)

# Check medium risk positions every 5 minutes
def start_medium_risk_listener(self):
while True:
print("Checking medium risk positions...\n")
self.position_list.update_medium_risk_positions()
time.sleep(self.medium_update_frequency * 60)

try:
print("Monitor: Checking medium risk positions...\n")
self.position_list.update_medium_risk_positions()
time.sleep(self.medium_update_frequency * 60)
except Exception as e:
print("ERROR: Monitor: Error updating medium risk positions: ", e)
time.sleep(self.medium_update_frequency * 60)

# Check other positions every 20 minutes
def start_other_position_listener(self):
while True:
print("Checking other positions...\n")
self.position_list.update_other_positions()
time.sleep(self.other_update_frequency * 60)

try:
print("Monitor: Checking other positions...\n")
self.position_list.update_other_positions()
time.sleep(self.other_update_frequency * 60)
except Exception as e:
print("ERROR: Monitor: Error updating other positions: ", e)
time.sleep(self.other_update_frequency * 60)

def start_vault_creation_listener(self):
while True:
print("Scanning for new vaults...\n")
self.vault_list.scan_for_new_vaults()
time.sleep(self.vault_scan_frequency * 60)
try:
print("Monitor: Scanning for new vaults...\n")
self.vault_list.scan_for_new_vaults()
time.sleep(self.vault_scan_frequency * 60)
except Exception as e:
print("ERROR: Monitor: Error scanning for new vaults: ", e)
time.sleep(self.vault_scan_frequency * 60)


# if __name__ == "__main__":
Expand Down
21 changes: 10 additions & 11 deletions python/monitor/position_list.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,13 +46,12 @@ def scan_for_new_positions(self, from_block_number: int, to_block_number: int =
continue

collaterals = self.evc.get_collaterals(borrower_address)
print("Collaterals: ", collaterals)

new_position = Position(self.vault_list.get_vault(vault_address), borrower_address, collateral_asset_address=collaterals[0])

self.all_positions[new_position_id] = new_position

print(f"New position found! Vault address: {vault_address}, borrower address: {borrower_address}")
print(f"Position List: New position found! Vault address: {vault_address}, borrower address: {borrower_address}\n")

if new_position.health_score <= 1:
self.high_risk_positions[new_position_id] = new_position
Expand All @@ -71,21 +70,20 @@ def update_high_risk_positions(self):
if pos.health_score > 1:
self.other_positions[id] = self.high_risk_positions.pop(id)

if pos.health_score < 1:
if pos.health_score < 1 and id not in self.profitable_liquidations_queue:
max_repay, expected_yield = pos.check_liquidation()
print(f"Possible liquidation found in vault {pos.vault.vault_address} for borrower {pos.borrower_address}...")
print(f"Max repay: {max_repay}, Expected yield: {expected_yield}")
print(f"Position List: Possible liquidation found in vault {pos.vault.vault_address} for borrower {pos.borrower_address}...\n")
print(f"Position List: Max repay: {max_repay}, Expected yield: {expected_yield}\n")

#TODO: add filter to exclude small size positions

profitable = check_if_liquidation_profitable(pos.vault.vault_address, pos.borrower_address, pos.vault.underlying_asset_address, self.vault_list.get_vault(pos.collateral_asset_address).underlying_asset_address, max_repay, expected_yield)

if profitable:
print(f"Position in vault {pos.vault.vault_address} is profitable to liquidate.")
print(f"Borrower: {pos.borrower_address}")
print(f"Max repay: {max_repay}, Expected yield: {expected_yield}")
if id not in self.profitable_liquidations_queue:
self.profitable_liquidations_queue.append(id)
print(f"Position List: Position in vault {pos.vault.vault_address} is profitable to liquidate.")
print(f"Position List: Borrower: {pos.borrower_address}")
print(f"Position List: Max repay: {max_repay}, Expected yield: {expected_yield}\n")
self.profitable_liquidations_queue.append(id)

# Update medium risk positions
# These are positions with a health score <= 1.15 that have potential to move to high risk
Expand Down Expand Up @@ -124,4 +122,5 @@ def get_position(self, position_id: int):
return self.all_positions[position_id]

def pop_profitable_liquidation(self):
return self.profitable_liquidations_queue.pop(0)
id = self.profitable_liquidations_queue.pop(0)
return self.all_positions[id]
8 changes: 4 additions & 4 deletions python/monitor/profitability_calculator.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,19 +66,19 @@ def get_1inch_quote(asset_in: str, asset_out: str, amount_in: int):
"amount": amount_in
}

print(f"Requesting 1inch quote for {amount_in} {asset_in} to {asset_out}")
print(f"Profit Calculator: Requesting 1inch quote for {amount_in} {asset_in} to {asset_out}\n")

response = requests.get(apiUrl, headers=headers, params=params)

if response.status_code == 200:
try:
response_json = response.json()
print("1inch response: ", response_json)
print("Profit Calculator: 1inch response: ", response_json, "\n")
return int(response_json['dstAmount'])
except ValueError as e:
print(f"Error decoding JSON: {e}")
print(f"Profit Calculator: Error decoding JSON: {e}\n")
return None
else:
print(f"API request failed with status code {response.status_code}: {response.text}")
print(f"Profit Calculator: API request failed with status code {response.status_code}: {response.text}\n")
return None

2 changes: 1 addition & 1 deletion python/monitor/vault_list.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ def scan_for_new_vaults(self):

self.vault_dict[vault_address] = new_vault

print(f"New vault found! Address: {vault_address}, underlying asset: {new_vault.underlying_asset_address}")
print(f"Vault List: New vault found! Address: {vault_address}, underlying asset: {new_vault.underlying_asset_address}\n")

self.latest_block_number = self.w3.eth.block_number

Expand Down
Loading

0 comments on commit f36525c

Please sign in to comment.