Skip to content

Commit

Permalink
Merge pull request #81 from lidofinance/develop
Browse files Browse the repository at this point in the history
Develop
  • Loading branch information
avsetsin authored Mar 1, 2022
2 parents c3f3918 + 5d116d9 commit e1a6560
Show file tree
Hide file tree
Showing 9 changed files with 55 additions and 49 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ docker-compose up
| KAFKA_TOPIC (required) | - | `None` | Kafka topic name (for msg receiving) |
| FLASHBOT_SIGNATURE (required) | - | `None` | Private key - Used to identify account in flashbot`s rpc (should NOT be equal to WALLET private key) |
| KAFKA_GROUP_PREFIX | - | `None` | Just for staging (staging-) |
| MAX_BUFFERED_ETHERS | 5000 ETH | `5000 ether` | Maximum amount of ETH in the buffer, after which the bot deposits at any gas |
| MAX_GAS_FEE | 100 GWEI | `100 gwei` | Bot will wait for a lower price. Treshold for gas_fee |
| GAS_FEE_PERCENTILE_1 | 20 | `20` | Percentile for first recommended fee calculation |
| GAS_FEE_PERCENTILE_DAYS_HISTORY_1 | 1 | `1` | Percentile for first recommended calculates from N days of the fee history |
Expand Down
2 changes: 2 additions & 0 deletions scripts/depositor.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
def main():
logger.info({'msg': 'Start up metrics service on port: 8080.'})
start_http_server(8080)

flashbot(web3, web3.eth.account.from_key(variables.FLASHBOT_SIGNATURE), FLASHBOTS_RPC[variables.WEB3_CHAIN_ID])

depositor_bot = DepositorBot()
depositor_bot.run_as_daemon()
61 changes: 33 additions & 28 deletions scripts/depositor_utils/depositor_bot.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from typing import List, Tuple

from brownie import web3, Wei, chain
from eth_account import Account
from hexbytes import HexBytes
from web3.exceptions import BlockNotFound
from web3.types import TxParams
Expand Down Expand Up @@ -58,6 +59,7 @@ def __init__(self):
'Depositor bot',
variables.NETWORK,
variables.MAX_GAS_FEE,
variables.MAX_BUFFERED_ETHERS,
variables.CONTRACT_GAS_LIMIT,
variables.GAS_FEE_PERCENTILE_1,
variables.GAS_FEE_PERCENTILE_DAYS_HISTORY_1,
Expand Down Expand Up @@ -158,13 +160,27 @@ def get_deposit_issues(self) -> List[str]:
ACCOUNT_BALANCE.set(0)
logger.info({'msg': 'Check account balance. No account provided.'})

current_gas_fee = web3.eth.get_block('pending').baseFeePerGas

# Lido contract buffered ether check
buffered_ether = LidoInterface.getBufferedEther(block_identifier=self._current_block.hash.hex())
logger.info({'msg': 'Call `getBufferedEther()`.', 'value': buffered_ether})
BUFFERED_ETHER.set(buffered_ether)

recommended_buffered_ether = self.gas_fee_strategy.get_recommended_buffered_ether_to_deposit(current_gas_fee)
logger.info({'msg': 'Recommended min buffered ether to deposit.', 'value': recommended_buffered_ether})
REQUIRED_BUFFERED_ETHER.set(recommended_buffered_ether)
if buffered_ether < recommended_buffered_ether:
logger.warning({'msg': self.LIDO_CONTRACT_HAS_NOT_ENOUGH_BUFFERED_ETHER, 'value': buffered_ether})
deposit_issues.append(self.LIDO_CONTRACT_HAS_NOT_ENOUGH_BUFFERED_ETHER)

is_high_buffer = buffered_ether >= variables.MAX_BUFFERED_ETHERS

# Gas price check
recommended_gas_fee = self.gas_fee_strategy.get_recommended_gas_fee((
(variables.GAS_FEE_PERCENTILE_DAYS_HISTORY_1, variables.GAS_FEE_PERCENTILE_1),
(variables.GAS_FEE_PERCENTILE_DAYS_HISTORY_2, variables.GAS_FEE_PERCENTILE_2),
))

current_gas_fee = web3.eth.get_block('pending').baseFeePerGas
), force = is_high_buffer)

GAS_FEE.labels('max_fee').set(variables.MAX_GAS_FEE)
GAS_FEE.labels('current_fee').set(current_gas_fee)
Expand All @@ -183,28 +199,18 @@ def get_deposit_issues(self) -> List[str]:
'max_fee': variables.MAX_GAS_FEE,
'current_fee': current_gas_fee,
'recommended_fee': recommended_gas_fee,
'buffered_ether': buffered_ether,
}
})
deposit_issues.append(self.GAS_FEE_HIGHER_THAN_RECOMMENDED)

# Security module check
can_deposit = DepositSecurityModuleInterface.canDeposit(block_identifier=self._current_block.hash.hex())
logger.info({'msg': 'Call `canDeposit()`.', 'value': can_deposit})
if not can_deposit:
logger.warning({'msg': self.DEPOSIT_SECURITY_ISSUE, 'value': can_deposit})
deposit_issues.append(self.DEPOSIT_SECURITY_ISSUE)

# Lido contract buffered ether check
buffered_ether = LidoInterface.getBufferedEther(block_identifier=self._current_block.hash.hex())
logger.info({'msg': 'Call `getBufferedEther()`.', 'value': buffered_ether})
BUFFERED_ETHER.set(buffered_ether)

recommended_buffered_ether = self.gas_fee_strategy.get_recommended_buffered_ether_to_deposit(current_gas_fee)
logger.info({'msg': 'Recommended min buffered ether to deposit.', 'value': recommended_buffered_ether})
REQUIRED_BUFFERED_ETHER.set(recommended_buffered_ether)
if buffered_ether < recommended_buffered_ether:
logger.warning({'msg': self.LIDO_CONTRACT_HAS_NOT_ENOUGH_BUFFERED_ETHER, 'value': buffered_ether})
deposit_issues.append(self.LIDO_CONTRACT_HAS_NOT_ENOUGH_BUFFERED_ETHER)

# Check that contract has unused operators keys
avail_keys = NodeOperatorsRegistryInterface.assignNextSigningKeys.call(1, {'from': LidoInterface.address})[0]
has_keys = bool(avail_keys)
Expand Down Expand Up @@ -256,11 +262,12 @@ def do_deposit(self):

logger.info({'msg': 'Creating tx in blockchain.'})

contract = web3.eth.contract(
address=DepositSecurityModuleInterface.address,
abi=DepositSecurityModuleInterface.abi,
)

try:
contract = web3.eth.contract(
address=DepositSecurityModuleInterface.address,
abi=DepositSecurityModuleInterface.abi,
)
func = contract.functions.depositBufferedEther(
self.deposit_root,
self.keys_op_index,
Expand All @@ -284,31 +291,29 @@ def do_deposit(self):
"data": func._encode_transaction_data()
}

from eth_account.account import Account
signer = Account.from_key(variables.ACCOUNT.private_key)
signer = Account.from_key(private_key=variables.ACCOUNT.private_key)

for i in range(10):
# Try to get in next 10 blocks
result = web3.flashbots.send_bundle(
[{"signer": signer, "transaction": tx}],
[{"signed_transaction": signer.sign_transaction(tx).rawTransaction}],
self._current_block.number + i
)

# We are waiting for `self._current_block.number + i` block number and get receipt by tx hash
result.wait()
rec = result.receipts()
if not rec:
raise Exception('No reception provided')
except Exception as error:
logger.error({'msg': f'Deposit failed.', 'error': str(error)})
DEPOSIT_FAILURE.inc()
else:

logger.info({'msg': 'Transaction executed.', 'value': {
'blockHash': rec[-1]['blockHash'].hex(),
'blockNumber': rec[-1]['blockNumber'],
'gasUsed': rec[-1]['gasUsed'],
'transactionHash': rec[-1]['transactionHash'].hex(),
}})
except Exception as error:
logger.error({'msg': f'Deposit failed.', 'error': str(error)})
DEPOSIT_FAILURE.inc()
else:
SUCCESS_DEPOSIT.inc()

logger.info({'msg': f'Deposit method end. Sleep for 1 minute.'})
Expand Down
1 change: 1 addition & 0 deletions scripts/pauser_utils/pause_bot.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ def __init__(self):
None,
None,
None,
None,
variables.KAFKA_TOPIC,
variables.ACCOUNT.address if variables.ACCOUNT else '0x0',
variables.CREATE_TRANSACTIONS,
Expand Down
13 changes: 9 additions & 4 deletions scripts/utils/gas_strategy.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,11 @@ def get_gas_fee_percentile(self, days: int, percentile: int) -> float:
gas_percentile = int(numpy.percentile(blocks_to_count_percentile, percentile))
return gas_percentile

def get_recommended_gas_fee(self, percentiles: Iterable[Tuple[int, int]]) -> float:
def get_recommended_gas_fee(self, percentiles: Iterable[Tuple[int, int]], force: bool = False) -> float:
"""Returns the recommended gas fee"""
if force:
return self.max_gas_fee

min_recommended_fee = self.max_gas_fee

for days, percentile in percentiles:
Expand All @@ -83,11 +86,13 @@ def get_recommended_gas_fee(self, percentiles: Iterable[Tuple[int, int]]) -> flo

def get_recommended_buffered_ether_to_deposit(self, gas_fee):
"""Returns suggested minimum buffered ether to deposit"""
apr = 0.049 # Protocol APR
apr = 0.044 # Protocol APR
# ether/14 days : select sum(tr.value)/1e18 from ethereum."transactions" as tr
# where tr.to = '\xae7ab96520DE3A18E5e111B5EaAb095312D7fE84'
# and tr.block_time >= '2021-12-01' and tr.block_time < '2021-12-15' and tr.value < 600*1e18;
a = 60 # ~ ether/hour
a = 24 # ~ ether/hour
keys_hour = a / 32
p = 32 * 10**18 * apr / 365 / 24 # ~ Profit in hour
c = 378300 # wei is constant for every deposit tx that should be paid
return sqrt(c * gas_fee * a / 32 / p) * 32 * 10**18
multiply_constant = 1.5 # we will get profit with constant from 1 to 2, but the most profitable will be 1.5
return sqrt(multiply_constant * c * gas_fee * keys_hour / p) * 32 * 10**18
1 change: 1 addition & 0 deletions scripts/utils/metrics.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
'name',
'network',
'max_gas_fee',
'max_buffered_ethers',
'contract_gas_limit',
'gas_fee_percentile_1',
'gas_fee_percentile_days_history_1',
Expand Down
2 changes: 2 additions & 0 deletions scripts/utils/variables.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@
MIN_PRIORITY_FEE = Wei(os.getenv('MIN_PRIORITY_FEE', '2 gwei'))
MAX_PRIORITY_FEE = Wei(os.getenv('MAX_PRIORITY_FEE', '10 gwei'))

MAX_BUFFERED_ETHERS = Wei(os.getenv('MAX_BUFFERED_ETHERS', '5000 ether'))

# Kafka secrets
KAFKA_BROKER_ADDRESS_1 = os.getenv('KAFKA_BROKER_ADDRESS_1')
KAFKA_USERNAME = os.getenv('KAFKA_USERNAME')
Expand Down
7 changes: 5 additions & 2 deletions tests/test_gas_srategy.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,10 @@ def test_recommended_buffered_ether():
assert 1 < buffered_ether / 10**18 < 100

buffered_ether = gas_fee_strategy.get_recommended_buffered_ether_to_deposit(50 * 10**9)
assert 400 < buffered_ether / 10**18 < 700
assert 350 < buffered_ether / 10**18 < 400

buffered_ether = gas_fee_strategy.get_recommended_buffered_ether_to_deposit(70 * 10**9)
assert 500 < buffered_ether / 10**18 < 1000
assert 400 < buffered_ether / 10**18 < 600

buffered_ether = gas_fee_strategy.get_recommended_buffered_ether_to_deposit(150 * 10**9)
assert 600 < buffered_ether / 10**18 < 700
16 changes: 1 addition & 15 deletions tests/utils/mock_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,18 +22,4 @@ def make_request(self, method: RPCEndpoint, params: Any) -> RPCResponse:
if result is not None:
return result[1]

infura_project_id = os.getenv('WEB3_INFURA_PROJECT_ID')
network = os.getenv('NETWORK')
prov = HTTPProvider(f'https://{network}.infura.io/v3/{infura_project_id}')
request_data = prov.encode_rpc_request(method, params)
raw_response = make_post_request(
prov.endpoint_uri,
request_data,
**prov.get_request_kwargs()
)
response = prov.decode_rpc_response(raw_response)

# print(method)
# print(f'({params}, {response}),')

return response
raise Exception('There is no mock for response')

0 comments on commit e1a6560

Please sign in to comment.