From 2efcc07909ea8b3260c7036cac6ab277243e766e Mon Sep 17 00:00:00 2001 From: btcookies <31353111+btcookies@users.noreply.github.com> Date: Mon, 7 Mar 2022 16:10:43 -0500 Subject: [PATCH] Deploy dev (#121) * feat: lock bvecvx every week at 23:50 UTC on Wednesday * pause eth tender * feat: earner keeper for ethereum network (#67) * add bbcvx strategy to blacklist for earn (#70) * add bbcvx strategy to blacklist for earn * chore: use strategy addr not vault' * chore: lock cvx before vote at 23:20 UTC (#71) * Add harvesting script for mstable strategies (#65) * bump web3.py version * add voter proxy abi; add harvestMta to keeperAcl * add mstable harvesting script * add test for mstable harvests * catch timeout while fetching fee history; use int for num_blocks * remove harvest_mstable script * move harvest_mta in conditional harvest * revert conditional harvest * run at 0:00 UTC Co-authored-by: brookstaylor * pause arb harvester for swapr upgrade (#74) * pause eth harvests til mstable ready (#75) * Chore/pause arb earns (#76) * pause eth tender (#64) Co-authored-by: brookstaylor * add swapr/weth strategy to earn blacklist arb Co-authored-by: Ayush Shukla * Make sure there's a buffer period before running harvestMta again (#72) * reduce harvestMta frequency * feat: conditional harvest tested for harvestMta * feat: check is_time_to_harvest in tests for mstable strategies Co-authored-by: brookstaylor * unpause eth harvester (#78) * Resume arbitrum harvests (#79) * enable arbitrum harvests * run arbitrum harvests at 14:30 * update eth earner to earn for all vaults (#86) * update eth earner to earn for all vaults * chore: exclude retired strategies from earns * take pending txs into consideration when assigning nonce (#73) * Harvest/tend migrated convex strategies (#87) * feat: add migrated convex strats * enable 1 off tender run to tend new convex strats Co-authored-by: brookstaylor * debug vault error * take 2 * blacklist yearn vault from earn calls * bump gas limit to 1.5m for eth earns * add tricrypto vault to invalid earn vaults * tend obtc at 14:30 * add badger/weth swapr lp deployer (#68) * reduce harvest frequency to 3/5 days (#91) * pause eth tender (#64) Co-authored-by: brookstaylor * reduce harvest frequency to 3/5 days * chore: suspend eth-tender bot * add chain to error logs * black formatting * add keeper address to error logging * update log string to match new frequency and gas limit * remove unused imports, log chain on error for earner * add keeper_address to earner Co-authored-by: Ayush Shukla * chore: remove swapr/weth strat from earn blacklist * feat: send chain and keeper address with failed tx logging * chore: add ops_deployer6 in arb vault owners (#92) * chore: run arb harvests at 18:10 * chore: run arb harvests at 18:34 (#93) * chore: run arb harvests at 19:14 * chore: use default arb rpc * feat: manually add bvecvx lp vault to list to earn, switch to default rpc for arb earns (#96) * Comment out bvecvx LP strat until whitelist (#97) * chore: start earning bvecvx lp strat * chore: pause bvecvx lp vault earns * chore: earn bvecvx lp * chore: pause bvecvx lp earns * Bug: fix eth harvest cadence so only harvesting every 4/5 days (#107) * fix harvest cadence so harvesting every 5 days * set min harvest time to 4 days * fix comment accuracy * refactor block offset into utils function, only use max_time_between_harvests * remove unused import * chore: fix typo, remove imports * anotha typo * align comments with cadence variables * pause negative rebases and pause centralized oracle propose (#108) * pause negative rebases and pause centralized oracle propose * feat: real test implemented * feat: smarter earn threshold using want eth price (#109) * feat: smarter earn threshold using want eth price * chore: move price func to utils, pass in chain, vault contract param * chore: fix naming, arb test * feat: handle polygon and arbitrum price getting * fix comment * chore: update return type comment * Quick fix earn (#110) * chore: denominate override in base 10, not wei * chore: earn new convex vaults * chore: reset earn sched to 10 after hour, all vaults (#112) * chore: run test tends at 15:35 * chore: pause eth harvests for subgraph upgrade (#115) * chore: suspend manual tends (#114) * chore: pause eth earner until convex strats migrated (#117) * Refactor chain references into enum (#116) * chore: reset earn sched to 10 after hour, all vaults * chore: refactor chain references into enum * chore: import utils get_abi for arb earn script * chore: get_node_url utils func, rename harvest eth * chore: base_currencies constant, remove unused imports * debug: check node url rebase * chore: remove debugging log * chore: update convex strats to new addresses (#119) * chore: update convex strats to new addresses * chore: unpause keepers, use flashbots rpc * bug: fix arbitrum abi directory * chore: revert flashbots rpc, run chainlink forwarder * chore: run ibbtc * chore: add ibbtc lp vault to earn list * chore: run bvecvx earn * chore: run ibbtc harvest * chore: run without flashbots * chore: turn off convex and ibbtc sushi earns * chore: harvest ibbtc * chore: run ibbtc harvest * chore: pause flashbots harvester * chore: reset harvests to regular cadence, add ibbtc * chore: harvest ibbtc daily * chore: update cadence" * add logging to make sure ibbtc checking * chore: log strategy address not contract obj * more logging * chore: black formatting, harvest ibbtc * feat: only earn if base fee < 150 gwei * chore: pause ren harvest * chore: fix poly rpc * chore: earn threshold 100E, don't harvest poly curve * chore: pause all earns and harvests * chore: pause bvecvx earner * chore: unpause harvests and earns on eth, use flashbots * chore: re enable ren harvest * chore: manual harvest again * chore: reset harvester script * chore: enable arbitrum harvests * chore: enable arb earns * Update eth_bvecvx_earner.yaml * Update eth_bvecvx_earner.yaml * chore: pause p,o,sbtc harvests * chore: pause bcvx for cvxcrv swap slippage issue * chore: pause ren harvests * chore: reduce ibbtc frequency to 3 days * chore: resume ren cycles * chore: pause bvecvx locking * Update arb_earner.yaml * Update arb_harvester.yaml * chore: restart bvecvx earner, change threshold to 20 eth * chore: add mim and frax vaults to earner keeper * chore: resume arb harvests and earns * chore: harvest bcvx * chore: suspend arb earns * chore: add container scanning * chore: enable arb earns * chore: check secrets on pr * chore: disable bad performing vaults * chore: update dockerfile to remove container vuln * chore: add git back to container * chore: pause bad vaults from harvest * feat: add ftm support for earns and harvests (#125) * feat: add ftm support for earns and harvests * chore: pause ftm keepers for now * chore: fix typo in kustomization.yaml * chore: pass contract objects instead of addresses to earn/harvest * chore: add abi to fantom dir * chore: add fantom to abi_dirs * chore: add ftmscan explorer name * chore: add new vault to keeper * chore: remove usdc/dai strat from active list * chore: handle 0 case issue * chore: let error log * chore: log balances * chore: try chainstack node for ftm * chore: fix pricing for ftm want, use default rpc * chore: handle harvesting ftm * fix: ibbtc collector test passing and sending tx successfully * fix: upgrade libsasl to remediate container vuln * chore: run at 3:45 * fix: get correct keeper address * chore: run at 5:07 * fix: use correct value for private key * chore: add new vaults to ftm keepers * Modularize repo (#134) * chore: modularize scripts/ dir * chore: modularize src/ and config/ files * chore: modularize scripts run for cron jobs * Add first units and GHA for testing (#137) * chore: move all integration tests to integration_tests package * chore: add gha config for tests; add tx test * chore: add more tests * chore: add test for hex gas * chore: copypaste brownie config from rewards * chore: add coverage action (#139) * Addresses (#140) * Create CODEOWNERS * chore: move address refs into constants file * chore: remove all non constant address refs * chore: fix monkeypatch paths * chore: move all hardcoded addresses to vars in constants file Co-authored-by: mitche50 * fix: update tricrypto address to tricrypto2; * chore: add dca ftm vaults and black formatting * fix: handle case when 0 strategy 0 vault * chore: pause bvecvx earner * chore: add flake8 run (#141) * chore: add flake8 run * chore: format earners * chore: removed redundant f strings * chore: reformat couple files; remove f strings * chore: reformat couple files; remove f strings vol2 * chore: more cleanups * chore: remove trailing whitespaces * chore: test cleanups * chore: disable F403 * chore: more import cleanups * chore: final flake cleanup * chore: final imports cleanup * Setup logging properly (#144) * chore: initialize all loggers properly * chore: cleanup remove unused imports * chore: deploy on push to main only (#143) * chore: deploy on push to main only * chore: update image tag to use sha for push event Co-authored-by: brookstaylor Co-authored-by: Ayush Shukla Co-authored-by: Andrew Mitchell Co-authored-by: Andrii Kulikov --- .coveragerc | 6 + .deploy/base/arb_earner.yaml | 2 +- .deploy/base/arb_harvester.yaml | 4 +- .deploy/base/cake_harvester.yaml | 2 +- .deploy/base/chainlink_forwarder.yaml | 18 +- .deploy/base/earner.yaml | 2 +- .deploy/base/eth_bvecvx_earner.yaml | 4 +- .deploy/base/eth_earner.yaml | 2 +- .deploy/base/eth_tender.yaml | 2 +- .deploy/base/flashbots_harvester.yaml | 2 +- .deploy/base/ftm_earner.yaml | 24 + .deploy/base/ftm_harvester.yaml | 25 + .deploy/base/general_harvester.yaml | 2 +- .deploy/base/ibbtc_fee_collector.yaml | 11 +- .deploy/base/kustomization.yaml | 2 + .deploy/base/propose_centralized_oracle.yaml | 16 +- .deploy/base/rebalancer.yaml | 2 +- .deploy/base/rebase.yaml | 14 +- .deploy/base/service_account.yaml | 16 +- .deploy/base/stability_executor.yaml | 2 +- .github/workflows/deploy_dev.yml | 17 +- .github/workflows/flake8_run.yml | 26 + .github/workflows/secrets_check.yml | 12 + .github/workflows/tests_run.yml | 43 ++ .gitignore | 2 + Dockerfile | 6 + abi/fantom/controller.json | 273 +++++++ abi/fantom/erc20.json | 222 ++++++ abi/fantom/keeper_acl.json | 228 ++++++ abi/fantom/oracle.json | 324 +++++++++ abi/fantom/registry.json | 347 +++++++++ abi/fantom/strategy.json | 672 ++++++++++++++++++ abi/fantom/vault.json | 536 ++++++++++++++ brownie-config.yaml | 205 +++++- config/constants.py | 164 ++++- config/enums.py | 1 + {tests => integration_tests}/__init__.py | 0 .../test_arbitrum_earner.py | 60 +- .../test_arbitrum_harvester.py | 42 +- .../test_eth_earner.py | 36 +- .../test_general_harvester.py | 64 +- integration_tests/test_ibbtc_fee_collect.py | 34 + .../test_mstable_harvests.py | 44 +- {tests => integration_tests}/test_oracle.py | 46 +- .../test_rebalance.py | 26 +- {tests => integration_tests}/test_rebase.py | 9 +- .../test_stability_execute.py | 26 +- {tests => integration_tests}/utils.py | 0 requirements-dev.txt | 4 + run_tests.sh | 2 +- scripts/approve_centralized_oracle.py | 12 +- scripts/arbitrum_earn.py | 21 +- scripts/arbitrum_harvest.py | 24 +- scripts/arbitrum_manual_harvest.py | 27 +- scripts/earn.py | 25 +- scripts/earn_locked_cvx.py | 32 +- scripts/eth_earn.py | 53 +- scripts/eth_tend.py | 77 -- scripts/forward_chainlink.py | 18 +- scripts/ftm_earn.py | 60 ++ scripts/ftm_harvest.py | 77 ++ scripts/harvest_cake.py | 45 -- scripts/harvest_eth.py | 128 +--- scripts/harvest_sushi.py | 41 -- scripts/ibbtc_fees.py | 20 +- scripts/one_time_harvests.py | 52 +- scripts/poly_harvest.py | 28 +- scripts/propose_centralized_oracle.py | 18 +- scripts/rebalance.py | 35 +- scripts/rebase.py | 17 +- scripts/stability_execute.py | 33 +- src/bsc/cake_harvester.py | 249 ------- src/earner.py | 97 +-- src/eth/rebalancer.py | 43 +- src/eth/stability_executor.py | 38 +- src/eth/sushi_harvester.py | 279 -------- src/eth/sushi_tender.py | 158 ---- src/general_harvester.py | 93 ++- src/ibbtc_fee_collector.py | 62 +- src/oracle.py | 107 ++- src/rebaser.py | 54 +- src/tx_utils.py | 27 +- src/utils.py | 87 ++- tests/test_cake.py | 108 --- tests/test_ibbtc_fee_collect.py | 42 -- tests/test_sushi.py | 191 ----- tests/test_tx_utils.py | 25 + 87 files changed, 3962 insertions(+), 2170 deletions(-) create mode 100644 .coveragerc create mode 100644 .deploy/base/ftm_earner.yaml create mode 100644 .deploy/base/ftm_harvester.yaml create mode 100644 .github/workflows/flake8_run.yml create mode 100644 .github/workflows/secrets_check.yml create mode 100644 .github/workflows/tests_run.yml create mode 100644 abi/fantom/controller.json create mode 100644 abi/fantom/erc20.json create mode 100644 abi/fantom/keeper_acl.json create mode 100644 abi/fantom/oracle.json create mode 100644 abi/fantom/registry.json create mode 100644 abi/fantom/strategy.json create mode 100644 abi/fantom/vault.json rename {tests => integration_tests}/__init__.py (100%) rename {tests => integration_tests}/test_arbitrum_earner.py (69%) rename {tests => integration_tests}/test_arbitrum_harvester.py (73%) rename {tests => integration_tests}/test_eth_earner.py (85%) rename {tests => integration_tests}/test_general_harvester.py (87%) create mode 100644 integration_tests/test_ibbtc_fee_collect.py rename {tests => integration_tests}/test_mstable_harvests.py (89%) rename {tests => integration_tests}/test_oracle.py (64%) rename {tests => integration_tests}/test_rebalance.py (83%) rename {tests => integration_tests}/test_rebase.py (92%) rename {tests => integration_tests}/test_stability_execute.py (84%) rename {tests => integration_tests}/utils.py (100%) create mode 100644 requirements-dev.txt delete mode 100644 scripts/eth_tend.py create mode 100644 scripts/ftm_earn.py create mode 100644 scripts/ftm_harvest.py delete mode 100644 scripts/harvest_cake.py delete mode 100644 scripts/harvest_sushi.py delete mode 100644 src/bsc/cake_harvester.py delete mode 100644 src/eth/sushi_harvester.py delete mode 100644 src/eth/sushi_tender.py delete mode 100644 tests/test_cake.py delete mode 100644 tests/test_ibbtc_fee_collect.py delete mode 100644 tests/test_sushi.py create mode 100644 tests/test_tx_utils.py diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 00000000..0a800587 --- /dev/null +++ b/.coveragerc @@ -0,0 +1,6 @@ +[run] +omit = + abi/* + tests/* + integration_tests/* + scripts/* diff --git a/.deploy/base/arb_earner.yaml b/.deploy/base/arb_earner.yaml index 099d6575..e07f796e 100644 --- a/.deploy/base/arb_earner.yaml +++ b/.deploy/base/arb_earner.yaml @@ -18,7 +18,7 @@ spec: - name: arb-earner-keeper image: IMAGE_NAME imagePullPolicy: IfNotPresent - command: ["python", "scripts/arbitrum_earn.py"] + command: ["python", "-m", "scripts.arbitrum_earn"] env: - name: LOG_LEVEL value: 'info' diff --git a/.deploy/base/arb_harvester.yaml b/.deploy/base/arb_harvester.yaml index 004b8005..af107f6b 100644 --- a/.deploy/base/arb_harvester.yaml +++ b/.deploy/base/arb_harvester.yaml @@ -5,7 +5,7 @@ metadata: labels: app: arb-harvester-keeper spec: - schedule: "5 1 * * *" + schedule: "35 20 * * *" startingDeadlineSeconds: 300 suspend: false jobTemplate: @@ -17,7 +17,7 @@ spec: - name: arb-harvester-keeper image: IMAGE_NAME imagePullPolicy: IfNotPresent - command: ["python", "scripts/arbitrum_harvest.py"] + command: ["python", "-m", "scripts.arbitrum_harvest"] env: - name: LOG_LEVEL value: 'info' diff --git a/.deploy/base/cake_harvester.yaml b/.deploy/base/cake_harvester.yaml index 5adcf7c2..76f6c100 100644 --- a/.deploy/base/cake_harvester.yaml +++ b/.deploy/base/cake_harvester.yaml @@ -18,7 +18,7 @@ spec: - name: cake-harvester-keeper image: IMAGE_NAME imagePullPolicy: IfNotPresent - command: ["python", "scripts/harvest_cake.py"] + command: ["python", "-m", "scripts.harvest_cake"] env: - name: LOG_LEVEL value: 'info' diff --git a/.deploy/base/chainlink_forwarder.yaml b/.deploy/base/chainlink_forwarder.yaml index bdb0adcd..ceacc93f 100644 --- a/.deploy/base/chainlink_forwarder.yaml +++ b/.deploy/base/chainlink_forwarder.yaml @@ -18,24 +18,8 @@ spec: - name: chainlink-forwarder-keeper image: IMAGE_NAME imagePullPolicy: IfNotPresent - command: ["python", "scripts/forward_chainlink.py"] + command: ["python", "-m", "scripts.forward_chainlink"] env: - name: LOG_LEVEL value: 'info' - - name: ETH_USD_CHAINLINK - value: '0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419' - - name: SUSHI_SUBGRAPH - value: 'https://api.thegraph.com/subgraphs/name/sushiswap/exchange' - - name: UNI_SUBGRAPH - value: 'https://api.thegraph.com/subgraphs/name/uniswap/uniswap-v2' - - name: SUSHI_PAIR - value: '0x9a13867048e01c663ce8ce2fe0cdae69ff9f35e3' - - name: UNI_PAIR - value: '0xe86204c4eddd2f70ee00ead6805f917671f56c52' - - name: CENTRALIZED_ORACLE - value: '0x73083058e0f61D3fc7814eEEDc39F9608B4546d7' - - name: CHAINLINK_FORWARDER - value: '0xB572f69edbfC946af11a1b3ef8D5c2f41D38a642' - - name: DIGG_BTC_CHAINLINK - value: '0x418a6c98cd5b8275955f08f0b8c1c6838c8b1685' restartPolicy: OnFailure diff --git a/.deploy/base/earner.yaml b/.deploy/base/earner.yaml index 7242de5d..823d55df 100644 --- a/.deploy/base/earner.yaml +++ b/.deploy/base/earner.yaml @@ -18,7 +18,7 @@ spec: - name: earner-keeper image: IMAGE_NAME imagePullPolicy: IfNotPresent - command: ["python", "scripts/earn.py"] + command: ["python", "-m", "scripts.earn"] env: - name: LOG_LEVEL value: 'info' diff --git a/.deploy/base/eth_bvecvx_earner.yaml b/.deploy/base/eth_bvecvx_earner.yaml index c6a8c312..1b1996cd 100644 --- a/.deploy/base/eth_bvecvx_earner.yaml +++ b/.deploy/base/eth_bvecvx_earner.yaml @@ -7,7 +7,7 @@ metadata: spec: schedule: "47 23 * * 3" startingDeadlineSeconds: 300 - suspend: false + suspend: true jobTemplate: spec: template: @@ -17,7 +17,7 @@ spec: - name: eth-bvecvx-earner-keeper image: IMAGE_NAME imagePullPolicy: IfNotPresent - command: ["python", "scripts/earn_locked_cvx.py"] + command: ["python", "-m", "scripts.earn_locked_cvx"] env: - name: LOG_LEVEL value: 'info' diff --git a/.deploy/base/eth_earner.yaml b/.deploy/base/eth_earner.yaml index c3e007cf..c36b3faf 100644 --- a/.deploy/base/eth_earner.yaml +++ b/.deploy/base/eth_earner.yaml @@ -18,7 +18,7 @@ spec: - name: eth-earner-keeper image: IMAGE_NAME imagePullPolicy: IfNotPresent - command: ["python", "scripts/eth_earn.py"] + command: ["python", "-m", "scripts.eth_earn"] env: - name: LOG_LEVEL value: 'info' diff --git a/.deploy/base/eth_tender.yaml b/.deploy/base/eth_tender.yaml index e7efcf38..ea736b85 100644 --- a/.deploy/base/eth_tender.yaml +++ b/.deploy/base/eth_tender.yaml @@ -18,7 +18,7 @@ spec: - name: eth-tender-keeper image: IMAGE_NAME imagePullPolicy: IfNotPresent - command: ["python", "scripts/eth_tend.py"] + command: ["python", "-m", "scripts.eth_tend"] env: - name: LOG_LEVEL value: "info" diff --git a/.deploy/base/flashbots_harvester.yaml b/.deploy/base/flashbots_harvester.yaml index 45e43bff..f06cb983 100644 --- a/.deploy/base/flashbots_harvester.yaml +++ b/.deploy/base/flashbots_harvester.yaml @@ -18,7 +18,7 @@ spec: - name: flashbots-harvester-keeper image: IMAGE_NAME imagePullPolicy: IfNotPresent - command: ["python", "scripts/harvest_eth.py"] + command: ["python", "-m", "scripts.harvest_eth"] env: - name: LOG_LEVEL value: "info" diff --git a/.deploy/base/ftm_earner.yaml b/.deploy/base/ftm_earner.yaml new file mode 100644 index 00000000..f2a67ec0 --- /dev/null +++ b/.deploy/base/ftm_earner.yaml @@ -0,0 +1,24 @@ +apiVersion: batch/v1beta1 +kind: CronJob +metadata: + name: ftm-earner-keeper + labels: + app: ftm-earner-keeper +spec: + schedule: "*/10 * * * *" + startingDeadlineSeconds: 300 + suspend: false + jobTemplate: + spec: + template: + spec: + serviceAccountName: ftm-earner-keeper + containers: + - name: ftm-earner-keeper + image: IMAGE_NAME + imagePullPolicy: IfNotPresent + command: ["python", "-m", "scripts.ftm_earn"] + env: + - name: LOG_LEVEL + value: 'info' + restartPolicy: OnFailure diff --git a/.deploy/base/ftm_harvester.yaml b/.deploy/base/ftm_harvester.yaml new file mode 100644 index 00000000..c344088d --- /dev/null +++ b/.deploy/base/ftm_harvester.yaml @@ -0,0 +1,25 @@ +apiVersion: batch/v1beta1 +kind: CronJob +metadata: + name: ftm-harvester-keeper + labels: + app: ftm-harvester-keeper +spec: + schedule: "35 20 * * *" + startingDeadlineSeconds: 300 + suspend: true + jobTemplate: + spec: + template: + spec: + serviceAccountName: ftm-harvester-keeper + containers: + - name: ftm-harvester-keeper + image: IMAGE_NAME + imagePullPolicy: IfNotPresent + command: ["python", "-m", "scripts.ftm_harvest"] + env: + - name: LOG_LEVEL + value: 'info' + restartPolicy: OnFailure + backoffLimit: 3 diff --git a/.deploy/base/general_harvester.yaml b/.deploy/base/general_harvester.yaml index 2acb1879..3908258a 100644 --- a/.deploy/base/general_harvester.yaml +++ b/.deploy/base/general_harvester.yaml @@ -18,7 +18,7 @@ spec: - name: general-harvester-keeper image: IMAGE_NAME imagePullPolicy: IfNotPresent - command: ["python", "scripts/poly_harvest.py"] + command: ["python", "-m", "scripts.poly_harvest"] env: - name: LOG_LEVEL value: 'info' diff --git a/.deploy/base/ibbtc_fee_collector.yaml b/.deploy/base/ibbtc_fee_collector.yaml index 6b55a7fb..fcc72d19 100644 --- a/.deploy/base/ibbtc_fee_collector.yaml +++ b/.deploy/base/ibbtc_fee_collector.yaml @@ -5,8 +5,7 @@ metadata: labels: app: ibbtc-fee-collector-keeper spec: - # cron to run every 6 AM EST every day "0 10 * * *" - schedule: "47 20 * * *" + schedule: "20 17 * * *" startingDeadlineSeconds: 300 suspend: false jobTemplate: @@ -18,14 +17,8 @@ spec: - name: ibbtc-fee-collector-keeper image: IMAGE_NAME imagePullPolicy: IfNotPresent - command: ["python", "scripts/ibbtc_fees.py"] + command: ["python", "-m", "scripts.ibbtc_fees"] env: - name: LOG_LEVEL value: 'info' - - name: IBBTC_CORE_ADDRESS - value: '0x2A8facc9D49fBc3ecFf569847833C380A13418a8' - - name: BTC_ETH_CHAINLINK - value: '0xdeb288F737066589598e9214E782fa5A8eD689e8' - - name: ETH_USD_CHAINLINK - value: '0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419' restartPolicy: OnFailure diff --git a/.deploy/base/kustomization.yaml b/.deploy/base/kustomization.yaml index 269c951e..24c621e0 100644 --- a/.deploy/base/kustomization.yaml +++ b/.deploy/base/kustomization.yaml @@ -8,6 +8,8 @@ resources: - earner.yaml - eth_bvecvx_earner.yaml - eth_earner.yaml + - ftm_earner.yaml + - ftm_harvester.yaml - general_harvester.yaml - flashbots_harvester.yaml - ibbtc_fee_collector.yaml diff --git a/.deploy/base/propose_centralized_oracle.yaml b/.deploy/base/propose_centralized_oracle.yaml index 95084ee5..a8efac7c 100644 --- a/.deploy/base/propose_centralized_oracle.yaml +++ b/.deploy/base/propose_centralized_oracle.yaml @@ -18,22 +18,8 @@ spec: - name: propose-centralized-oracle-keeper image: IMAGE_NAME imagePullPolicy: IfNotPresent - command: ["python", "scripts/propose_centralized_oracle.py"] + command: ["python", "-m", "scripts.propose_centralized_oracle"] env: - name: LOG_LEVEL value: 'info' - - name: ETH_USD_CHAINLINK - value: '0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419' - - name: SUSHI_SUBGRAPH - value: 'https://api.thegraph.com/subgraphs/name/sushiswap/exchange' - - name: UNI_SUBGRAPH - value: 'https://api.thegraph.com/subgraphs/name/uniswap/uniswap-v2' - - name: SUSHI_PAIR - value: '0x9a13867048e01c663ce8ce2fe0cdae69ff9f35e3' - - name: UNI_PAIR - value: '0xe86204c4eddd2f70ee00ead6805f917671f56c52' - - name: CENTRALIZED_ORACLE - value: '0x73083058e0f61D3fc7814eEEDc39F9608B4546d7' - - name: CHAINLINK_FORWARDER - value: '0xB572f69edbfC946af11a1b3ef8D5c2f41D38a642' restartPolicy: OnFailure diff --git a/.deploy/base/rebalancer.yaml b/.deploy/base/rebalancer.yaml index 8b71665d..84391598 100644 --- a/.deploy/base/rebalancer.yaml +++ b/.deploy/base/rebalancer.yaml @@ -18,7 +18,7 @@ spec: - name: rebalancer-keeper image: IMAGE_NAME imagePullPolicy: IfNotPresent - command: ["python", "scripts/rebalance.py"] + command: ["python", "-m", "scripts.rebalance"] env: - name: LOG_LEVEL value: 'info' diff --git a/.deploy/base/rebase.yaml b/.deploy/base/rebase.yaml index 5928bf67..09971fff 100644 --- a/.deploy/base/rebase.yaml +++ b/.deploy/base/rebase.yaml @@ -18,22 +18,10 @@ spec: - name: rebase-keeper image: IMAGE_NAME imagePullPolicy: IfNotPresent - command: ["python", "scripts/rebase.py"] + command: ["python", "-m", "scripts.rebase"] env: - name: LOG_LEVEL value: 'info' - - name: ETH_USD_CHAINLINK - value: '0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419' - - name: DIGG_TOKEN_ADDRESS - value: '0x798D1bE841a82a273720CE31c822C61a67a601C3' - - name: DIGG_ORCHESTRATOR_ADDRESS - value: '0xbd5d9451e004fc495f105ceab40d6c955e4192ba' - - name: DIGG_POLICY_ADDRESS - value: '0x327a78D13eA74145cc0C63E6133D516ad3E974c3' - - name: UNIV2_DIGG_WBTC_ADDRESS - value: '0xe86204c4eddd2f70ee00ead6805f917671f56c52' - - name: SUSHI_DIGG_WBTC_ADDRESS - value: '0x9a13867048e01c663ce8ce2fe0cdae69ff9f35e3' - name: GAS_LIMIT value: '12000000' restartPolicy: OnFailure diff --git a/.deploy/base/service_account.yaml b/.deploy/base/service_account.yaml index 5ca4e69c..c8a354aa 100644 --- a/.deploy/base/service_account.yaml +++ b/.deploy/base/service_account.yaml @@ -102,4 +102,18 @@ metadata: name: eth-earner-keeper annotations: eks.amazonaws.com/role-arn: arn:aws:iam::342684350154:role/eth-earner-keeper ---- \ No newline at end of file +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: ftm-harvester-keeper + annotations: + eks.amazonaws.com/role-arn: arn:aws:iam::342684350154:role/ftm-harvester-keeper +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: ftm-earner-keeper + annotations: + eks.amazonaws.com/role-arn: arn:aws:iam::342684350154:role/ftm-earner-keeper +--- diff --git a/.deploy/base/stability_executor.yaml b/.deploy/base/stability_executor.yaml index eb9afc08..409f85d7 100644 --- a/.deploy/base/stability_executor.yaml +++ b/.deploy/base/stability_executor.yaml @@ -18,7 +18,7 @@ spec: - name: stability-executor-keeper image: IMAGE_NAME imagePullPolicy: IfNotPresent - command: ["python", "scripts/stability_execute.py"] + command: ["python", "-m", "scripts.stability_execute"] env: - name: LOG_LEVEL value: 'info' diff --git a/.github/workflows/deploy_dev.yml b/.github/workflows/deploy_dev.yml index 2a77be7a..1920691a 100644 --- a/.github/workflows/deploy_dev.yml +++ b/.github/workflows/deploy_dev.yml @@ -1,16 +1,14 @@ -name: Pull Request - Build and Deploy to Development Environment -# Code probably works, but may not. -# Pushing to development environment allows for validation. +name: Build and deploy keepers on: - pull_request: + push: branches: - main env: TARGET_ENVIRONMENT: 'dev' MANIFEST_RESPOSITORY: 'badger-finance/badger-kube-manifests' - IMAGE_NAME: '/badger/${{ github.event.repository.name }}:${{ github.event.pull_request.head.sha }}' + IMAGE_NAME: '/badger/${{ github.event.repository.name }}:${{ github.event.after }}' jobs: build: @@ -32,6 +30,15 @@ jobs: run: | docker build -t ${{ steps.login-ecr.outputs.registry }}${{ env.IMAGE_NAME }} . docker push ${{ steps.login-ecr.outputs.registry }}${{ env.IMAGE_NAME }} + - name: Run Trivy vulnerability scanner + uses: aquasecurity/trivy-action@master + with: + image-ref: '${{ steps.login-ecr.outputs.registry }}${{ env.IMAGE_NAME }}' + format: 'table' + exit-code: '1' + ignore-unfixed: true + vuln-type: 'os,library' + severity: 'CRITICAL,HIGH' validate_manifest: name: Run Manifest Validation diff --git a/.github/workflows/flake8_run.yml b/.github/workflows/flake8_run.yml new file mode 100644 index 00000000..a8bde9df --- /dev/null +++ b/.github/workflows/flake8_run.yml @@ -0,0 +1,26 @@ +name: flake8 Lint + +on: + push: + branches: + - development + - main + pull_request: + + +jobs: + flake8-lint: + runs-on: ubuntu-latest + name: Lint + steps: + - name: Check out source repository + uses: actions/checkout@v2 + - name: Set up Python environment + uses: actions/setup-python@v1 + with: + python-version: "3.9" + - name: flake8 Lint + uses: py-actions/flake8@v2 + with: + ignore: "E402,W504,W503,F405,F403" + max-line-length: "100" diff --git a/.github/workflows/secrets_check.yml b/.github/workflows/secrets_check.yml new file mode 100644 index 00000000..5bd8823c --- /dev/null +++ b/.github/workflows/secrets_check.yml @@ -0,0 +1,12 @@ +name: Secrets Check +on: [pull_request] +jobs: + detect-secrets: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: detect-secrets + uses: reviewdog/action-detect-secrets@master + with: + github_token: ${{ secrets.GH_TOKEN }} + reporter: github-pr-review \ No newline at end of file diff --git a/.github/workflows/tests_run.yml b/.github/workflows/tests_run.yml new file mode 100644 index 00000000..71fbc207 --- /dev/null +++ b/.github/workflows/tests_run.yml @@ -0,0 +1,43 @@ +on: + push: + branches: + - develop + - main + pull_request: + +name: main workflow + +env: + ENV: TEST + +jobs: + tests: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + + - name: Setup Node.js + uses: actions/setup-node@v1 + + - name: npm install + run: npm install + + - name: Install Ganache + run: npm install -g ganache-cli@6.10.2 + + - name: Setup Python 3.9 + uses: actions/setup-python@v2 + with: + python-version: 3.9.5 + + - name: Install Requirements + run: pip install -r requirements.txt && pip install -r requirements-dev.txt + + - name: Run Tests + run: brownie test --cov-report=xml --cov=. + + - uses: codecov/codecov-action@v1 + with: + fail_ci_if_error: false + token: ${{ secrets.CODECOV_TOKEN }} diff --git a/.gitignore b/.gitignore index ed8cae20..368efd76 100644 --- a/.gitignore +++ b/.gitignore @@ -135,3 +135,5 @@ package.json package-lock.json hardhat.config.js yarn.lock + +.idea/ diff --git a/Dockerfile b/Dockerfile index 79bf2ca0..cfe13abc 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,6 +2,12 @@ FROM python:3.9 WORKDIR /keepers +RUN apt-get remove libexpat1 libexpat1-dev -y +RUN apt-get remove libsasl2-2 libsasl2-modules-db -y +RUN apt-get update -y +RUN apt-get install libexpat1>=2.2.10-2+deb11u1 -y +RUN apt-get install libsasl2-2>=2.1.27+dfsg-2.1+deb11u1 -y +RUN apt-get install git -y COPY requirements.txt . RUN pip install -r requirements.txt diff --git a/abi/fantom/controller.json b/abi/fantom/controller.json new file mode 100644 index 00000000..b5599e8e --- /dev/null +++ b/abi/fantom/controller.json @@ -0,0 +1,273 @@ +[ + { + "inputs": [ + { "internalType": "address", "name": "_token", "type": "address" }, + { "internalType": "address", "name": "_strategy", "type": "address" } + ], + "name": "approveStrategy", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "", "type": "address" }, + { "internalType": "address", "name": "", "type": "address" } + ], + "name": "approvedStrategies", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_token", "type": "address" } + ], + "name": "balanceOf", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "", "type": "address" }, + { "internalType": "address", "name": "", "type": "address" } + ], + "name": "converters", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_token", "type": "address" }, + { "internalType": "uint256", "name": "_amount", "type": "uint256" } + ], + "name": "earn", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_strategy", "type": "address" }, + { "internalType": "address", "name": "_token", "type": "address" }, + { "internalType": "uint256", "name": "parts", "type": "uint256" } + ], + "name": "getExpectedReturn", + "outputs": [ + { "internalType": "uint256", "name": "expected", "type": "uint256" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "governance", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_strategy", "type": "address" }, + { "internalType": "address", "name": "_token", "type": "address" } + ], + "name": "inCaseStrategyTokenGetStuck", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_token", "type": "address" }, + { "internalType": "uint256", "name": "_amount", "type": "uint256" } + ], + "name": "inCaseTokensGetStuck", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_governance", "type": "address" }, + { "internalType": "address", "name": "_strategist", "type": "address" }, + { "internalType": "address", "name": "_keeper", "type": "address" }, + { "internalType": "address", "name": "_rewards", "type": "address" } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "keeper", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "max", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "onesplit", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_token", "type": "address" }, + { "internalType": "address", "name": "_strategy", "type": "address" } + ], + "name": "revokeStrategy", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "rewards", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_input", "type": "address" }, + { "internalType": "address", "name": "_output", "type": "address" }, + { "internalType": "address", "name": "_converter", "type": "address" } + ], + "name": "setConverter", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_governance", "type": "address" } + ], + "name": "setGovernance", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_keeper", "type": "address" } + ], + "name": "setKeeper", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_onesplit", "type": "address" } + ], + "name": "setOneSplit", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_rewards", "type": "address" } + ], + "name": "setRewards", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "_split", "type": "uint256" } + ], + "name": "setSplit", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_strategist", "type": "address" } + ], + "name": "setStrategist", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_token", "type": "address" }, + { "internalType": "address", "name": "_strategy", "type": "address" } + ], + "name": "setStrategy", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_token", "type": "address" }, + { "internalType": "address", "name": "_vault", "type": "address" } + ], + "name": "setVault", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "split", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [{ "internalType": "address", "name": "", "type": "address" }], + "name": "strategies", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "strategist", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [{ "internalType": "address", "name": "", "type": "address" }], + "name": "vaults", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_token", "type": "address" }, + { "internalType": "uint256", "name": "_amount", "type": "uint256" } + ], + "name": "withdraw", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_token", "type": "address" } + ], + "name": "withdrawAll", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +] diff --git a/abi/fantom/erc20.json b/abi/fantom/erc20.json new file mode 100644 index 00000000..06b572dd --- /dev/null +++ b/abi/fantom/erc20.json @@ -0,0 +1,222 @@ +[ + { + "constant": true, + "inputs": [], + "name": "name", + "outputs": [ + { + "name": "", + "type": "string" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_spender", + "type": "address" + }, + { + "name": "_value", + "type": "uint256" + } + ], + "name": "approve", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "totalSupply", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_from", + "type": "address" + }, + { + "name": "_to", + "type": "address" + }, + { + "name": "_value", + "type": "uint256" + } + ], + "name": "transferFrom", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "decimals", + "outputs": [ + { + "name": "", + "type": "uint8" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_owner", + "type": "address" + } + ], + "name": "balanceOf", + "outputs": [ + { + "name": "balance", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "symbol", + "outputs": [ + { + "name": "", + "type": "string" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_to", + "type": "address" + }, + { + "name": "_value", + "type": "uint256" + } + ], + "name": "transfer", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_owner", + "type": "address" + }, + { + "name": "_spender", + "type": "address" + } + ], + "name": "allowance", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "payable": true, + "stateMutability": "payable", + "type": "fallback" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "name": "spender", + "type": "address" + }, + { + "indexed": false, + "name": "value", + "type": "uint256" + } + ], + "name": "Approval", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "from", + "type": "address" + }, + { + "indexed": true, + "name": "to", + "type": "address" + }, + { + "indexed": false, + "name": "value", + "type": "uint256" + } + ], + "name": "Transfer", + "type": "event" + } +] diff --git a/abi/fantom/keeper_acl.json b/abi/fantom/keeper_acl.json new file mode 100644 index 00000000..f1809d43 --- /dev/null +++ b/abi/fantom/keeper_acl.json @@ -0,0 +1,228 @@ +[ + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "bytes32", + "name": "previousAdminRole", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "bytes32", + "name": "newAdminRole", + "type": "bytes32" + } + ], + "name": "RoleAdminChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + } + ], + "name": "RoleGranted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + } + ], + "name": "RoleRevoked", + "type": "event" + }, + { + "inputs": [], + "name": "DEFAULT_ADMIN_ROLE", + "outputs": [{ "internalType": "bytes32", "name": "", "type": "bytes32" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "EARNER_ROLE", + "outputs": [{ "internalType": "bytes32", "name": "", "type": "bytes32" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "HARVESTER_ROLE", + "outputs": [{ "internalType": "bytes32", "name": "", "type": "bytes32" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "TENDER_ROLE", + "outputs": [{ "internalType": "bytes32", "name": "", "type": "bytes32" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "strategy", "type": "address" } + ], + "name": "deposit", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "sett", "type": "address" } + ], + "name": "earn", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "bytes32", "name": "role", "type": "bytes32" } + ], + "name": "getRoleAdmin", + "outputs": [{ "internalType": "bytes32", "name": "", "type": "bytes32" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "bytes32", "name": "role", "type": "bytes32" }, + { "internalType": "uint256", "name": "index", "type": "uint256" } + ], + "name": "getRoleMember", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "bytes32", "name": "role", "type": "bytes32" } + ], + "name": "getRoleMemberCount", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "bytes32", "name": "role", "type": "bytes32" }, + { "internalType": "address", "name": "account", "type": "address" } + ], + "name": "grantRole", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "strategy", "type": "address" } + ], + "name": "harvest", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "strategy", "type": "address" } + ], + "name": "harvestNoReturn", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "bytes32", "name": "role", "type": "bytes32" }, + { "internalType": "address", "name": "account", "type": "address" } + ], + "name": "hasRole", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "initialAdmin_", "type": "address" } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "bytes32", "name": "role", "type": "bytes32" }, + { "internalType": "address", "name": "account", "type": "address" } + ], + "name": "renounceRole", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "bytes32", "name": "role", "type": "bytes32" }, + { "internalType": "address", "name": "account", "type": "address" } + ], + "name": "revokeRole", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "strategy", "type": "address" } + ], + "name": "tend", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +] + diff --git a/abi/fantom/oracle.json b/abi/fantom/oracle.json new file mode 100644 index 00000000..b4e106be --- /dev/null +++ b/abi/fantom/oracle.json @@ -0,0 +1,324 @@ +[ + { + "inputs": [ + { "internalType": "address", "name": "_aggregator", "type": "address" }, + { + "internalType": "address", + "name": "_accessController", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "int256", + "name": "current", + "type": "int256" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "roundId", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "updatedAt", + "type": "uint256" + } + ], + "name": "AnswerUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "roundId", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "address", + "name": "startedBy", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "startedAt", + "type": "uint256" + } + ], + "name": "NewRound", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + } + ], + "name": "OwnershipTransferRequested", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "inputs": [], + "name": "acceptOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "accessController", + "outputs": [ + { + "internalType": "contract AccessControllerInterface", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "aggregator", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_aggregator", "type": "address" } + ], + "name": "confirmAggregator", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "decimals", + "outputs": [{ "internalType": "uint8", "name": "", "type": "uint8" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "description", + "outputs": [{ "internalType": "string", "name": "", "type": "string" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "_roundId", "type": "uint256" } + ], + "name": "getAnswer", + "outputs": [{ "internalType": "int256", "name": "", "type": "int256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint80", "name": "_roundId", "type": "uint80" } + ], + "name": "getRoundData", + "outputs": [ + { "internalType": "uint80", "name": "roundId", "type": "uint80" }, + { "internalType": "int256", "name": "answer", "type": "int256" }, + { "internalType": "uint256", "name": "startedAt", "type": "uint256" }, + { "internalType": "uint256", "name": "updatedAt", "type": "uint256" }, + { "internalType": "uint80", "name": "answeredInRound", "type": "uint80" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "_roundId", "type": "uint256" } + ], + "name": "getTimestamp", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "latestAnswer", + "outputs": [{ "internalType": "int256", "name": "", "type": "int256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "latestRound", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "latestRoundData", + "outputs": [ + { "internalType": "uint80", "name": "roundId", "type": "uint80" }, + { "internalType": "int256", "name": "answer", "type": "int256" }, + { "internalType": "uint256", "name": "startedAt", "type": "uint256" }, + { "internalType": "uint256", "name": "updatedAt", "type": "uint256" }, + { "internalType": "uint80", "name": "answeredInRound", "type": "uint80" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "latestTimestamp", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [{ "internalType": "uint16", "name": "", "type": "uint16" }], + "name": "phaseAggregators", + "outputs": [ + { + "internalType": "contract AggregatorV2V3Interface", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "phaseId", + "outputs": [{ "internalType": "uint16", "name": "", "type": "uint16" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_aggregator", "type": "address" } + ], + "name": "proposeAggregator", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "proposedAggregator", + "outputs": [ + { + "internalType": "contract AggregatorV2V3Interface", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint80", "name": "_roundId", "type": "uint80" } + ], + "name": "proposedGetRoundData", + "outputs": [ + { "internalType": "uint80", "name": "roundId", "type": "uint80" }, + { "internalType": "int256", "name": "answer", "type": "int256" }, + { "internalType": "uint256", "name": "startedAt", "type": "uint256" }, + { "internalType": "uint256", "name": "updatedAt", "type": "uint256" }, + { "internalType": "uint80", "name": "answeredInRound", "type": "uint80" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "proposedLatestRoundData", + "outputs": [ + { "internalType": "uint80", "name": "roundId", "type": "uint80" }, + { "internalType": "int256", "name": "answer", "type": "int256" }, + { "internalType": "uint256", "name": "startedAt", "type": "uint256" }, + { "internalType": "uint256", "name": "updatedAt", "type": "uint256" }, + { "internalType": "uint80", "name": "answeredInRound", "type": "uint80" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_accessController", + "type": "address" + } + ], + "name": "setController", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [{ "internalType": "address", "name": "_to", "type": "address" }], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "version", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + } + ] + diff --git a/abi/fantom/registry.json b/abi/fantom/registry.json new file mode 100644 index 00000000..cd30da28 --- /dev/null +++ b/abi/fantom/registry.json @@ -0,0 +1,347 @@ +[ + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "string", + "name": "key", + "type": "string" + } + ], + "name": "AddKey", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "string", + "name": "version", + "type": "string" + } + ], + "name": "AddVersion", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "author", + "type": "address" + }, + { + "indexed": false, + "internalType": "string", + "name": "version", + "type": "string" + }, + { + "indexed": false, + "internalType": "address", + "name": "vault", + "type": "address" + }, + { + "indexed": false, + "internalType": "enum BadgerRegistry.VaultStatus", + "name": "status", + "type": "uint8" + } + ], + "name": "DemoteVault", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "author", + "type": "address" + }, + { + "indexed": false, + "internalType": "string", + "name": "version", + "type": "string" + }, + { + "indexed": false, + "internalType": "address", + "name": "vault", + "type": "address" + } + ], + "name": "NewVault", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "author", + "type": "address" + }, + { + "indexed": false, + "internalType": "string", + "name": "version", + "type": "string" + }, + { + "indexed": false, + "internalType": "address", + "name": "vault", + "type": "address" + }, + { + "indexed": false, + "internalType": "enum BadgerRegistry.VaultStatus", + "name": "status", + "type": "uint8" + } + ], + "name": "PromoteVault", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "author", + "type": "address" + }, + { + "indexed": false, + "internalType": "string", + "name": "version", + "type": "string" + }, + { + "indexed": false, + "internalType": "address", + "name": "vault", + "type": "address" + } + ], + "name": "RemoveVault", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "string", + "name": "key", + "type": "string" + }, + { + "indexed": false, + "internalType": "address", + "name": "at", + "type": "address" + } + ], + "name": "Set", + "type": "event" + }, + { + "inputs": [ + { "internalType": "string", "name": "version", "type": "string" }, + { "internalType": "address", "name": "vault", "type": "address" } + ], + "name": "add", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "string", "name": "version", "type": "string" } + ], + "name": "addVersions", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [{ "internalType": "string", "name": "", "type": "string" }], + "name": "addresses", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "string", "name": "version", "type": "string" }, + { "internalType": "address", "name": "vault", "type": "address" }, + { + "internalType": "enum BadgerRegistry.VaultStatus", + "name": "status", + "type": "uint8" + } + ], + "name": "demote", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "devGovernance", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [{ "internalType": "string", "name": "key", "type": "string" }], + "name": "get", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "string", "name": "version", "type": "string" }, + { + "internalType": "enum BadgerRegistry.VaultStatus", + "name": "status", + "type": "uint8" + } + ], + "name": "getFilteredProductionVaults", + "outputs": [ + { "internalType": "address[]", "name": "", "type": "address[]" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getProductionVaults", + "outputs": [ + { + "components": [ + { "internalType": "string", "name": "version", "type": "string" }, + { + "internalType": "enum BadgerRegistry.VaultStatus", + "name": "status", + "type": "uint8" + }, + { "internalType": "address[]", "name": "list", "type": "address[]" } + ], + "internalType": "struct BadgerRegistry.VaultData[]", + "name": "", + "type": "tuple[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "string", "name": "version", "type": "string" }, + { "internalType": "address", "name": "author", "type": "address" } + ], + "name": "getVaults", + "outputs": [ + { "internalType": "address[]", "name": "", "type": "address[]" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "governance", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "newGovernance", "type": "address" } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "name": "keys", + "outputs": [{ "internalType": "string", "name": "", "type": "string" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "string", "name": "version", "type": "string" }, + { "internalType": "address", "name": "vault", "type": "address" }, + { + "internalType": "enum BadgerRegistry.VaultStatus", + "name": "status", + "type": "uint8" + } + ], + "name": "promote", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "string", "name": "version", "type": "string" }, + { "internalType": "address", "name": "vault", "type": "address" } + ], + "name": "remove", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "string", "name": "key", "type": "string" }, + { "internalType": "address", "name": "at", "type": "address" } + ], + "name": "set", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "newDev", "type": "address" } + ], + "name": "setDev", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_newGov", "type": "address" } + ], + "name": "setGovernance", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "name": "versions", + "outputs": [{ "internalType": "string", "name": "", "type": "string" }], + "stateMutability": "view", + "type": "function" + } +] diff --git a/abi/fantom/strategy.json b/abi/fantom/strategy.json new file mode 100644 index 00000000..62d2e056 --- /dev/null +++ b/abi/fantom/strategy.json @@ -0,0 +1,672 @@ +[ + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "harvested", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "blockNumber", + "type": "uint256" + } + ], + "name": "Harvest", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "Paused", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "controller", + "type": "address" + } + ], + "name": "SetController", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "governance", + "type": "address" + } + ], + "name": "SetGovernance", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "performanceFeeGovernance", + "type": "uint256" + } + ], + "name": "SetPerformanceFeeGovernance", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "performanceFeeStrategist", + "type": "uint256" + } + ], + "name": "SetPerformanceFeeStrategist", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "strategist", + "type": "address" + } + ], + "name": "SetStrategist", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "withdrawalFee", + "type": "uint256" + } + ], + "name": "SetWithdrawalFee", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "tended", + "type": "uint256" + } + ], + "name": "Tend", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "blockNumber", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "timestamp", + "type": "uint256" + } + ], + "name": "TreeDistribution", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "Unpaused", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "Withdraw", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "balance", + "type": "uint256" + } + ], + "name": "WithdrawAll", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "WithdrawOther", + "type": "event" + }, + { + "inputs": [], + "name": "CHEF", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "MAX_BPS", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "MAX_FEE", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "SUSHISWAP_ROUTER", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "SUSHI_TOKEN", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_governance", "type": "address" }, + { "internalType": "address", "name": "_strategist", "type": "address" }, + { "internalType": "address", "name": "_controller", "type": "address" }, + { "internalType": "address", "name": "_keeper", "type": "address" }, + { "internalType": "address", "name": "_guardian", "type": "address" } + ], + "name": "__BaseStrategy_init", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "badgerTree", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "balanceOf", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "balanceOfPool", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_token", "type": "address" } + ], + "name": "balanceOfToken", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "balanceOfWant", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "baseStrategyVersion", + "outputs": [{ "internalType": "string", "name": "", "type": "string" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "checkPendingReward", + "outputs": [ + { "internalType": "uint256", "name": "", "type": "uint256" }, + { "internalType": "uint256", "name": "", "type": "uint256" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "controller", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "deposit", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "getName", + "outputs": [{ "internalType": "string", "name": "", "type": "string" }], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [], + "name": "getProtectedTokens", + "outputs": [ + { "internalType": "address[]", "name": "", "type": "address[]" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "governance", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "guardian", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "harvest", + "outputs": [ + { "internalType": "uint256", "name": "harvested", "type": "uint256" } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "ibBTC_TOKEN", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "ibBTC_WBTC_LP", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_governance", "type": "address" }, + { "internalType": "address", "name": "_strategist", "type": "address" }, + { "internalType": "address", "name": "_controller", "type": "address" }, + { "internalType": "address", "name": "_keeper", "type": "address" }, + { "internalType": "address", "name": "_guardian", "type": "address" }, + { + "internalType": "address[3]", + "name": "_wantConfig", + "type": "address[3]" + }, + { + "internalType": "uint256[3]", + "name": "_feeConfig", + "type": "uint256[3]" + } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "isTendable", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "keeper", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "lpComponent", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "pause", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "paused", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "performanceFeeGovernance", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "performanceFeeStrategist", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "pid", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "reward", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_controller", "type": "address" } + ], + "name": "setController", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_governance", "type": "address" } + ], + "name": "setGovernance", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_guardian", "type": "address" } + ], + "name": "setGuardian", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_keeper", "type": "address" } + ], + "name": "setKeeper", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_performanceFeeGovernance", + "type": "uint256" + } + ], + "name": "setPerformanceFeeGovernance", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_performanceFeeStrategist", + "type": "uint256" + } + ], + "name": "setPerformanceFeeStrategist", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [{ "internalType": "uint256", "name": "_s", "type": "uint256" }], + "name": "setSlippageTolerance", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_strategist", "type": "address" } + ], + "name": "setStrategist", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "_withdrawalFee", "type": "uint256" } + ], + "name": "setWithdrawalFee", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "_threshold", "type": "uint256" } + ], + "name": "setWithdrawalMaxDeviationThreshold", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "sl", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "strategist", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "tend", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "uniswap", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "unpause", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "version", + "outputs": [{ "internalType": "string", "name": "", "type": "string" }], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [], + "name": "wBTC_TOKEN", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "wETH_TOKEN", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "want", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "_amount", "type": "uint256" } + ], + "name": "withdraw", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "withdrawAll", + "outputs": [ + { "internalType": "uint256", "name": "balance", "type": "uint256" } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_asset", "type": "address" } + ], + "name": "withdrawOther", + "outputs": [ + { "internalType": "uint256", "name": "balance", "type": "uint256" } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "withdrawalFee", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "withdrawalMaxDeviationThreshold", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + } +] + diff --git a/abi/fantom/vault.json b/abi/fantom/vault.json new file mode 100644 index 00000000..4d3b1c39 --- /dev/null +++ b/abi/fantom/vault.json @@ -0,0 +1,536 @@ +[ + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "Approval", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "timestamp", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "blockNumber", + "type": "uint256" + } + ], + "name": "FullPricePerShareUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "Paused", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "Transfer", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "Unpaused", + "type": "event" + }, + { + "inputs": [ + { "internalType": "address", "name": "owner", "type": "address" }, + { "internalType": "address", "name": "spender", "type": "address" } + ], + "name": "allowance", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "spender", "type": "address" }, + { "internalType": "uint256", "name": "amount", "type": "uint256" } + ], + "name": "approve", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "account", "type": "address" } + ], + "name": "approveContractAccess", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [{ "internalType": "address", "name": "", "type": "address" }], + "name": "approved", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "available", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "balance", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "account", "type": "address" } + ], + "name": "balanceOf", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [{ "internalType": "address", "name": "", "type": "address" }], + "name": "blockLock", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "controller", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "decimals", + "outputs": [{ "internalType": "uint8", "name": "", "type": "uint8" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "spender", "type": "address" }, + { + "internalType": "uint256", + "name": "subtractedValue", + "type": "uint256" + } + ], + "name": "decreaseAllowance", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "_amount", "type": "uint256" }, + { "internalType": "bytes32[]", "name": "proof", "type": "bytes32[]" } + ], + "name": "deposit", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "_amount", "type": "uint256" } + ], + "name": "deposit", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "bytes32[]", "name": "proof", "type": "bytes32[]" } + ], + "name": "depositAll", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "depositAll", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_recipient", "type": "address" }, + { "internalType": "uint256", "name": "_amount", "type": "uint256" } + ], + "name": "depositFor", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_recipient", "type": "address" }, + { "internalType": "uint256", "name": "_amount", "type": "uint256" }, + { "internalType": "bytes32[]", "name": "proof", "type": "bytes32[]" } + ], + "name": "depositFor", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "earn", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "getPricePerFullShare", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "governance", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "guardian", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "guestList", + "outputs": [ + { + "internalType": "contract BadgerGuestListAPI", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "reserve", "type": "address" }, + { "internalType": "uint256", "name": "amount", "type": "uint256" } + ], + "name": "harvest", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "spender", "type": "address" }, + { "internalType": "uint256", "name": "addedValue", "type": "uint256" } + ], + "name": "increaseAllowance", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_token", "type": "address" }, + { "internalType": "address", "name": "_controller", "type": "address" }, + { "internalType": "address", "name": "_governance", "type": "address" }, + { "internalType": "address", "name": "_keeper", "type": "address" }, + { "internalType": "address", "name": "_guardian", "type": "address" }, + { "internalType": "bool", "name": "_overrideTokenName", "type": "bool" }, + { "internalType": "string", "name": "_namePrefix", "type": "string" }, + { "internalType": "string", "name": "_symbolPrefix", "type": "string" } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "keeper", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "max", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "min", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "name", + "outputs": [{ "internalType": "string", "name": "", "type": "string" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "pause", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "paused", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "account", "type": "address" } + ], + "name": "revokeContractAccess", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_controller", "type": "address" } + ], + "name": "setController", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_governance", "type": "address" } + ], + "name": "setGovernance", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_guardian", "type": "address" } + ], + "name": "setGuardian", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_guestList", "type": "address" } + ], + "name": "setGuestList", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_keeper", "type": "address" } + ], + "name": "setKeeper", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "_min", "type": "uint256" } + ], + "name": "setMin", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_strategist", "type": "address" } + ], + "name": "setStrategist", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "strategist", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "symbol", + "outputs": [{ "internalType": "string", "name": "", "type": "string" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "token", + "outputs": [ + { + "internalType": "contract IERC20Upgradeable", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "totalSupply", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "trackFullPricePerShare", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "recipient", "type": "address" }, + { "internalType": "uint256", "name": "amount", "type": "uint256" } + ], + "name": "transfer", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "sender", "type": "address" }, + { "internalType": "address", "name": "recipient", "type": "address" }, + { "internalType": "uint256", "name": "amount", "type": "uint256" } + ], + "name": "transferFrom", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "unpause", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "version", + "outputs": [{ "internalType": "string", "name": "", "type": "string" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "_shares", "type": "uint256" } + ], + "name": "withdraw", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "withdrawAll", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +] diff --git a/brownie-config.yaml b/brownie-config.yaml index 2b7148e2..765f3a10 100644 --- a/brownie-config.yaml +++ b/brownie-config.yaml @@ -1,14 +1,203 @@ -autofetch_sources: True -dotenv: .env +live: + - name: Ethereum + networks: + - name: Mainnet (Infura) + chainid: 1 + id: mainnet + host: https://spring-delicate-paper.quiknode.pro/$QUIKNODE_ID/ + explorer: https://api.etherscan.io/api + multicall2: "0x5BA1e12693Dc8F9c48aAD8770482f4739bEeD696" + - name: Ropsten (Infura) + chainid: 3 + id: ropsten + host: https://ropsten.infura.io/v3/$WEB3_INFURA_PROJECT_ID + explorer: https://api-ropsten.etherscan.io/api + multicall2: "0x5BA1e12693Dc8F9c48aAD8770482f4739bEeD696" + - name: Rinkeby (Infura) + chainid: 4 + id: rinkeby + host: https://rinkeby.infura.io/v3/$WEB3_INFURA_PROJECT_ID + explorer: https://api-rinkeby.etherscan.io/api + multicall2: "0x5BA1e12693Dc8F9c48aAD8770482f4739bEeD696" + - name: Goerli (Infura) + chainid: 5 + id: goerli + host: https://goerli.infura.io/v3/$WEB3_INFURA_PROJECT_ID + explorer: https://api-goerli.etherscan.io/api + multicall2: "0x5BA1e12693Dc8F9c48aAD8770482f4739bEeD696" + - name: Kovan (Infura) + chainid: 42 + id: kovan + host: https://kovan.infura.io/v3/$WEB3_INFURA_PROJECT_ID + explorer: https://api-kovan.etherscan.io/api + multicall2: "0x5BA1e12693Dc8F9c48aAD8770482f4739bEeD696" + - name: Ethereum Classic + networks: + - name: Mainnet + chainid: 61 + id: etc + host: https://www.ethercluster.com/etc + explorer: https://blockscout.com/etc/mainnet/api + - name: Kotti + chainid: 6 + id: kotti + host: https://www.ethercluster.com/kotti + explorer: https://blockscout.com/etc/kotti/api + - name: Binance Smart Chain + networks: + - name: Testnet + chainid: 97 + id: bsc-test + host: https://data-seed-prebsc-1-s1.binance.org:8545 + explorer: https://api-testnet.bscscan.com/api + - name: Mainnet + chainid: 56 + id: bsc-main + host: https://bsc-dataseed.binance.org + explorer: https://api.bscscan.com/api + - name: Fantom Opera + networks: + - name: Testnet + chainid: 0xfa2 + id: ftm-test + host: https://rpc.testnet.fantom.network + explorer: https://explorer.testnet.fantom.network + - name: Mainnet + chainid: 250 + id: ftm-main + host: https://rpcapi.fantom.network + explorer: https://api.ftmscan.com/api + - name: Polygon + networks: + - name: Mainnet (Infura) + chainid: 137 + id: polygon-main + host: https://polygon-mainnet.infura.io/v3/$WEB3_INFURA_PROJECT_ID + explorer: https://api.polygonscan.com/api + multicall2: "0xc8E51042792d7405184DfCa245F2d27B94D013b6" + - name: Mumbai Testnet (Infura) + chainid: 80001 + id: polygon-test + host: https://polygon-mumbai.infura.io/v3/$WEB3_INFURA_PROJECT_ID + explorer: https://api-testnet.polygonscan.com/api + multicall2: "0x6842E0412AC1c00464dc48961330156a07268d14" + - name: XDai + networks: + - name: Mainnet + chainid: 100 + id: xdai-main + host: https://xdai.poanetwork.dev + explorer: https://blockscout.com/xdai/mainnet/api + - name: Testnet + chainid: 77 + id: xdai-test + host: https://sokol.poa.network + explorer: https://blockscout.com/poa/sokol/api + - name: Arbitrum + networks: + - name: Mainnet + chainid: 42161 + id: arbitrum-main + host: https://arb-mainnet.g.alchemy.com/v2/$ALCHEMY_ID + explorer: https://api.arbiscan.io/api + multicall2: "0x5B5CFE992AdAC0C9D48E05854B2d91C73a003858" -networks: - default: hardhat-fork - bsc-fork: - explorer: https://www.bscscan.com/ +development: + - name: Ganache-CLI + id: development cmd: ganache-cli + host: http://127.0.0.1 cmd_settings: + port: 8545 + gas_limit: 12000000 + accounts: 10 + evm_version: istanbul + mnemonic: brownie + - name: Geth Dev + id: geth-dev + cmd: ethnode + host: http://127.0.0.1 + cmd_settings: + port: 8545 + - name: Hardhat + id: hardhat + cmd: npx hardhat node + host: http://127.0.0.1 + cmd_settings: + port: 8545 + - name: Hardhat (Mainnet Fork) + id: hardhat-fork + cmd: npx hardhat node + host: http://127.0.0.1 + timeout: 120 + cmd_settings: + port: 8545 + fork: mainnet + - name: Ganache-CLI (Mainnet Fork) + id: mainnet-fork + cmd: ganache-cli + host: http://127.0.0.1 + timeout: 120 + cmd_settings: + port: 8545 + gas_limit: 12000000 accounts: 10 + evm_version: istanbul mnemonic: brownie - fork: https://bsc-dataseed1.binance.org - chain_id: 56 + fork: mainnet + - name: Ganache-CLI (BSC-Mainnet Fork) + id: bsc-main-fork + cmd: ganache-cli + host: http://127.0.0.1 + timeout: 120 + cmd_settings: + port: 8545 gas_limit: 12000000 + accounts: 10 + evm_version: istanbul + mnemonic: brownie + fork: bsc-main + - name: Ganache-CLI (FTM-Mainnet Fork) + id: ftm-main-fork + cmd: ganache-cli + host: http://127.0.0.1 + timeout: 120 + cmd_settings: + port: 8545 + gas_limit: 12000000 + accounts: 10 + evm_version: istanbul + mnemonic: brownie + fork: ftm-main + - name: Ganache-CLI (Polygon-Mainnet Fork) + id: polygon-main-fork + cmd: ganache-cli + host: http://127.0.0.1 + timeout: 120 + cmd_settings: + port: 8545 + gas_limit: 20000000 + accounts: 10 + evm_version: istanbul + mnemonic: brownie + fork: polygon-main + - name: Ganache-CLI (XDai-Mainnet Fork) + id: xdai-main-fork + cmd: ganache-cli + host: http://127.0.0.1 + timeout: 120 + cmd_settings: + port: 8545 + gas_limit: 20000000 + accounts: 10 + evm_version: istanbul + mnemonic: brownie + fork: xdai-main + - name: Hardhat (Arbitrum Fork) + id: hardhat-arbitrum-fork + cmd: npx hardhat node + host: http://127.0.0.1 + timeout: 120 + cmd_settings: + port: 8545 + fork: arbitrum-main diff --git a/config/constants.py b/config/constants.py index ef28aaa8..6a313337 100644 --- a/config/constants.py +++ b/config/constants.py @@ -1,18 +1,46 @@ -import os -import sys +from config.enums import Currency +from config.enums import Network -sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "./"))) +ETH_DIGG_BTC_CHAINLINK = "0x418a6c98cd5b8275955f08f0b8c1c6838c8b1685" +ETH_ETH_USD_CHAINLINK = "0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419" +ETH_BTC_ETH_CHAINLINK = "0xdeb288F737066589598e9214E782fa5A8eD689e8" +IBBTC_CORE_ADDRESS = "0x2A8facc9D49fBc3ecFf569847833C380A13418a8" +WETH = "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" +XSUSHI = "0x8798249c2e607446efb7ad49ec89dd1865ff4272" +MSTABLE_VOTER_PROXY = "0x10D96b1Fd46Ce7cE092aA905274B8eD9d4585A6E" +MTA = "0xa3bed4e1c75d00fa6f4e5e6922db7261b5e9acd2" -from enums import Network, Currency +DIGG = "0x798D1bE841a82a273720CE31c822C61a67a601C3" +DIGG_ORCHESTRATOR = "0xbd5d9451e004fc495f105ceab40d6c955e4192ba" +DIGG_POLICY = "0x327a78D13eA74145cc0C63E6133D516ad3E974c3" +UNIV2_DIGG_WBTC = "0xe86204c4eddd2f70ee00ead6805f917671f56c52" +SUSHI_DIGG_WBTC = "0x9a13867048e01c663ce8ce2fe0cdae69ff9f35e3" +DIGG_CENTRALIZED_ORACLE = "0x73083058e0f61D3fc7814eEEDc39F9608B4546d7" +DIGG_CHAINLINK_FORWARDER = "0xB572f69edbfC946af11a1b3ef8D5c2f41D38a642" +UNI_SUBGRAPH = "https://api.thegraph.com/subgraphs/name/uniswap/uniswap-v2" +SUSHI_SUBGRAPH = "https://api.thegraph.com/subgraphs/name/sushiswap/exchange" + +ETH_KEEPER_ACL = "0x711A339c002386f9db409cA55b6A35a604aB6cF6" +ETH_REWARDS_MANAGER = "0x5B60952481Eb42B66bdfFC3E049025AC5b91c127" ETH_BVECVX_STRATEGY = "0x3ff634ce65cDb8CC0D569D6d1697c41aa666cEA9" ETH_RETIRED_CVX_STRATEGY = "0x87fB47c2B9EB41d362BAb44F5Ec81514b6b1de13" -ETH_TRICRYPTO_STRATEGY = "0x05eC4356e1acd89CC2d16adC7415c8c95E736AC1" +ETH_TRICRYPTO_STRATEGY = "0x647eeb5C5ED5A71621183f09F6CE8fa66b96827d" ETH_HARVEST_STRATEGY = "0xaaE82E3c89e15E6F26F60724f115d5012363e030" ETH_UNI_DIGG_STRATEGY = "0xadc8d7322f2E284c1d9254170dbe311E9D3356cf" ETH_BBADGER_STRATEGY = "0x75b8E21BD623012Efb3b69E1B562465A68944eE6" - +ETH_CVX_HELPER_STRATEGY = "0xBCee2c6CfA7A4e29892c3665f464Be5536F16D95" +ETH_CVX_CRV_HELPER_STRATEGY = "0x826048381d65a65DAa51342C51d464428d301896" +ETH_IBBTC_CRV_STRATEGY = "0x6D4BA00Fd7BB73b5aa5b3D6180c6f1B0c89f70D1" +ETH_TBTC_CRV_STRATEGY = "0xAB73Ec65a1Ef5a2e5b56D5d6F36Bee4B2A1D3FFb" +ETH_RENBTC_CRV_STRATEGY = "0x61e16b46F74aEd8f9c2Ec6CB2dCb2258Bdfc7071" +ETH_SLP_DIGG_WBTC_STRATEGY = "0xaa8dddfe7DFA3C3269f1910d89E4413dD006D08a" +ETH_SLP_BADGER_WBTC_STRATEGY = "0x3a494D79AA78118795daad8AeFF5825C6c8dF7F1" +ETH_STABILIZE_STRATEGY = "0xA6af1B913E205B8E9B95D3B30768c0989e942316" +ETH_HBTC_CRV_STRATEGY = "0xf4146A176b09C664978e03d28d07Db4431525dAd" +ETH_MBTC_HBTC_STRATEGY = "0x54D06A0E1cE55a7a60Ee175AbCeaC7e363f603f3" +ETH_IBMBTC_STRATEGY = "0xd409C506742b7f76f164909025Ab29A47e06d30A" ETH_BVECVX_VAULT = "0xfd05D3C7fe2924020620A8bE4961bBaA747e6305" ETH_YVWBTC_VAULT = "0x4b92d19c11435614CD49Af1b589001b7c08cD4D5" @@ -24,37 +52,107 @@ ETH_TBTC_VAULT = "0xb9D076fDe463dbc9f915E5392F807315Bf940334" ETH_PBTC_VAULT = "0x55912D0Cf83B75c492E761932ABc4DB4a5CB1b17" ETH_BBTC_VAULT = "0x5Dce29e92b1b939F8E8C60DcF15BDE82A85be4a9" +ETH_FRAX_CRV_VAULT = "0x15cBC4ac1e81c97667780fE6DAdeDd04a6EEB47B" +ETH_MIM_CRV_VAULT = "0x19E4d89e0cB807ea21B8CEF02df5eAA99A110dA5" + + +FTM_KEEPER_ACL = "0x0680b32b52C5ca8C731490c0C576337058f39337" +FTM_GAS_ORACLE = "0xf4766552D15AE4d256Ad41B6cf2933482B0680dc" +FTM_REGISTRY = "0xFda7eB6f8b7a9e9fCFd348042ae675d1d652454f" + +FTM_SMM_USDC_DAI_STRATEGY = "0x89e48a9eb3f6018cb612e923bf190ef475787c0a" +FTM_SMM_BOO_XBOO_STRATEGY = "0x60129B2b762952Dfe8b21f40ee8aa3B2A4623546" +FTM_SMM_WFTM_SEX_STRATEGY = "0x99f4db590ee266011631985589AEde1EBbDDc137" +FTM_SMM_SOLID_SOLIDSEX_STRATEGY = "0x7AfB2E386b7990507009f81B3c486c8C596501a4" +FTM_SMM_WEVE_USDC_STRATEGY = "0xd6479e4477a41EadB0fc0DAB31a992efc7EFdAcd" +FTM_SMM_WBTC_RENBTC_STRATEGY = "0xc645D9ab319E442b31e22ECf9E48f3D5913B8a01" +FTM_SMM_WFTM_RENBTC_STRATEGY = "0xE143aA25Eec81B4Fc952b38b6Bca8D2395481377" +FTM_SMM_WFTM_CRV_STRATEGY = "0x8bfc9dF1228D73BcaE1B553D0e472d5D4447D971" +FTM_SMM_USDC_MIM_STRATEGY = "0x5631Baa4C636Dd5Fe3Dbdcd6e31c41f2385D3405" + +FTM_SMM_USDC_DAI_VAULT = "0x5deaB57a0aF330F268d4D2D029a1CE6549F11DAD" +FTM_SMM_BOO_XBOO_VAULT = "0x7dD26f47e1C8a060EDC1999D8781dDE90A4C33A9" +FTM_SMM_WFTM_SEX_VAULT = "0x7cc6049a125388B51c530e51727A87aE101f6417" +FTM_SMM_SOLID_SOLIDSEX_VAULT = "0xC7cBF5a24caBA375C09cc824481F5508c644dF28" +FTM_SMM_WEVE_USDC_VAULT = "0xd9770deC6fdA576450e66f0c441B6b8755F7184A" +FTM_SMM_WBTC_RENBTC_VAULT = "0x7637eFAa11d29a5b34e717ab447833bF143f2383" +FTM_SMM_WFTM_RENBTC_VAULT = "0x9a28eeFB2c5F8311f37c39314D78Be85C54B5da6" +FTM_SMM_WFTM_CRV_VAULT = "0x4145a9B8B7bdC76F14e65c26f9c71e23B9069b25" +FTM_SMM_USDC_MIM_VAULT = "0x1AaF7f8154704D80E2380B3DbC967FD0486016e0" + +FTM_STRATEGIES = [ + FTM_SMM_BOO_XBOO_STRATEGY, + FTM_SMM_WFTM_SEX_STRATEGY, + FTM_SMM_SOLID_SOLIDSEX_STRATEGY, + FTM_SMM_WEVE_USDC_STRATEGY, + FTM_SMM_WBTC_RENBTC_STRATEGY, + FTM_SMM_WFTM_RENBTC_STRATEGY, + FTM_SMM_WFTM_CRV_STRATEGY, + FTM_SMM_USDC_MIM_STRATEGY, +] +FTM_VAULTS = [ + FTM_SMM_BOO_XBOO_VAULT, + FTM_SMM_WFTM_SEX_VAULT, + FTM_SMM_SOLID_SOLIDSEX_VAULT, + FTM_SMM_WEVE_USDC_VAULT, + FTM_SMM_WBTC_RENBTC_VAULT, + FTM_SMM_WFTM_RENBTC_VAULT, + FTM_SMM_WFTM_CRV_VAULT, + FTM_SMM_USDC_MIM_VAULT, +] + +ARB_KEEPER_ACL = "0x265820F3779f652f2a9857133fDEAf115b87db4B" +ARB_ETH_USD_CHAINLINK = "0x639Fe6ab55C921f74e7fac1ee960C0B6293ba612" +ARB_VAULT_OWNER_1 = "0xc388750A661cC0B99784bAB2c55e1F38ff91643b" + +ARB_SWAPR_SWAPR_WETH_STRATEGY = "0x85386C3cE0679b035a9F8F17f531C076d0b35954" +ARB_SWAPR_WBTC_WETH_STRATEGY = "0x43942cEae98CC7485B48a37fBB1aa5035e1c8B46" +ARB_SLP_WBTC_WETH_STRATEGY = "0xA6827f0f14D0B83dB925B616d820434697328c22" +ARB_SLP_WETH_SUSHI_STRATEGY = "0x86f772C82914f5bFD168f99e208d0FC2C371e9C2" -ARB_SWAPR_WETH_STRATEGY = "0x85386C3cE0679b035a9F8F17f531C076d0b35954" +ARB_SLP_WBTC_WETH_VAULT = "0xFc13209cAfE8fb3bb5fbD929eC9F11a39e8Ac041" +ARB_SLP_WETH_SUSHI_VAULT = "0xe774d1fb3133b037aa17d39165b8f45f444f632d" + +POLY_OLD_STRATEGY_1 = "0xDb0C3118ef1acA6125200139BEaCc5D675F37c9C" +POLY_OLD_STRATEGY_2 = "0xF8F02D0d41C79a1973f65A440C98acAc7eAA8Dc1" +POLY_MATIC_USD_CHAINLNK = "0xAB594600376Ec9fD91F8e885dADF0CE036862dE0" +POLY_KEEPER_ACL = "0x46fa8817624eea8052093eab8e3fdf0e2e0443b2" + +REGISTRY = "0xFda7eB6f8b7a9e9fCFd348042ae675d1d652454f" + +OPS_DEPLOYER_2 = "0xeE8b29AA52dD5fF2559da2C50b1887ADee257556" +OPS_EXECUTOR_4 = "0xbb2281ca5b4d07263112604d1f182ad0ab26a252" +OPS_DEPLOYER_3 = "0x283c857ba940a61828d9f4c09e3fcee2e7aef3f7" +OPS_DEPLOYER_6 = "0x7c1D678685B9d2F65F1909b9f2E544786807d46C" MULTICHAIN_CONFIG = { Network.Polygon: { - "gas_oracle": "0xAB594600376Ec9fD91F8e885dADF0CE036862dE0", - "keeper_acl": "0x46fa8817624eea8052093eab8e3fdf0e2e0443b2", - "vault_owner": ["0xeE8b29AA52dD5fF2559da2C50b1887ADee257556"], - "registry": "0xFda7eB6f8b7a9e9fCFd348042ae675d1d652454f", + "gas_oracle": POLY_MATIC_USD_CHAINLNK, + "keeper_acl": POLY_KEEPER_ACL, + "vault_owner": [OPS_DEPLOYER_2], + "registry": REGISTRY, "earn": {"invalid_strategies": []}, }, Network.Arbitrum: { - "gas_oracle": "0x639Fe6ab55C921f74e7fac1ee960C0B6293ba612", - "keeper_acl": "0x265820F3779f652f2a9857133fDEAf115b87db4B", + "gas_oracle": ARB_ETH_USD_CHAINLINK, + "keeper_acl": ARB_KEEPER_ACL, "vault_owner": [ - "0xeE8b29AA52dD5fF2559da2C50b1887ADee257556", - "0xbb2281ca5b4d07263112604d1f182ad0ab26a252", - "0x283c857ba940a61828d9f4c09e3fcee2e7aef3f7", - "0xc388750A661cC0B99784bAB2c55e1F38ff91643b", - "0x7c1D678685B9d2F65F1909b9f2E544786807d46C", + OPS_DEPLOYER_2, + OPS_EXECUTOR_4, + OPS_DEPLOYER_3, + ARB_VAULT_OWNER_1, + OPS_DEPLOYER_6, ], - "registry": "0xFda7eB6f8b7a9e9fCFd348042ae675d1d652454f", + "registry": REGISTRY, "earn": {"invalid_strategies": []}, "harvest": {"invalid_strategies": []}, }, Network.Ethereum: { - "gas_oracle": "0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419", - "keeper_acl": "0x711A339c002386f9db409cA55b6A35a604aB6cF6", - "vault_owner": ["0xeE8b29AA52dD5fF2559da2C50b1887ADee257556"], - "registry": "0xFda7eB6f8b7a9e9fCFd348042ae675d1d652454f", - "rewards_manager": "0x5B60952481Eb42B66bdfFC3E049025AC5b91c127", + "gas_oracle": ETH_ETH_USD_CHAINLINK, + "keeper_acl": ETH_KEEPER_ACL, + "vault_owner": [OPS_DEPLOYER_2], + "registry": REGISTRY, + "rewards_manager": ETH_REWARDS_MANAGER, "earn": { "invalid_strategies": [ ETH_BVECVX_STRATEGY, @@ -66,23 +164,41 @@ ] }, }, + Network.Fantom: { + "gas_oracle": FTM_GAS_ORACLE, + "keeper_acl": FTM_KEEPER_ACL, + "vault_owner": [], + "registry": FTM_REGISTRY, + "earn": {"invalid_strategies": []}, + "harvest": {"invalid_strategies": []}, + }, } NODE_URL_SECRET_NAMES = { Network.Ethereum: {"name": "quiknode/eth-node-url", "key": "NODE_URL"}, Network.Polygon: {"name": "quiknode/poly-node-url", "key": "NODE_URL"}, + Network.Fantom: {"name": "chainstack/ftm-url", "key": "NODE_URL"}, } ABI_DIRS = { Network.Ethereum: "eth", Network.Polygon: "poly", Network.Arbitrum: "arbitrum", + Network.Fantom: "fantom", } BASE_CURRENCIES = { Network.Ethereum: Currency.Eth, Network.Arbitrum: Currency.Eth, Network.Polygon: Currency.Matic, + Network.Fantom: Currency.Ftm, +} + +GAS_LIMITS = { + Network.Ethereum: 6_000_000, + Network.Polygon: 1_000_000, + Network.Arbitrum: 3_000_000, + Network.Fantom: 3_000_000, } EARN_PCT_THRESHOLD = 0.01 diff --git a/config/enums.py b/config/enums.py index 2d04d771..96b1a623 100644 --- a/config/enums.py +++ b/config/enums.py @@ -19,6 +19,7 @@ class Currency(str, Enum): Matic = "matic" Usd = "usd" Btc = "btc" + Ftm = "ftm" def __str__(self): return self.value diff --git a/tests/__init__.py b/integration_tests/__init__.py similarity index 100% rename from tests/__init__.py rename to integration_tests/__init__.py diff --git a/tests/test_arbitrum_earner.py b/integration_tests/test_arbitrum_earner.py similarity index 69% rename from tests/test_arbitrum_earner.py rename to integration_tests/test_arbitrum_earner.py index abaf307b..bf032aed 100644 --- a/tests/test_arbitrum_earner.py +++ b/integration_tests/test_arbitrum_earner.py @@ -1,40 +1,27 @@ import logging -import os -import pytest -from brownie import accounts, Contract, web3 from decimal import Decimal + +import pytest +from brownie import Contract +from brownie import accounts +from brownie import web3 from hexbytes import HexBytes from web3 import contract -from src.earner import Earner -from src.utils import get_abi, get_last_harvest_times, hours, get_secret -from tests.utils import test_address, test_key -from config.constants import EARN_OVERRIDE_THRESHOLD, EARN_PCT_THRESHOLD +from config.constants import ARB_ETH_USD_CHAINLINK +from config.constants import ARB_KEEPER_ACL +from config.constants import ARB_SLP_WBTC_WETH_STRATEGY +from config.constants import ARB_SLP_WBTC_WETH_VAULT +from config.constants import ARB_SLP_WETH_SUSHI_STRATEGY +from config.constants import ARB_SLP_WETH_SUSHI_VAULT +from config.constants import EARN_OVERRIDE_THRESHOLD from config.enums import Network +from integration_tests.utils import test_address +from integration_tests.utils import test_key +from src.earner import Earner +from src.utils import get_abi -logger = logging.getLogger("test-eth-earner") - -ETH_USD_CHAINLINK = web3.toChecksumAddress("0x639Fe6ab55C921f74e7fac1ee960C0B6293ba612") -KEEPER_ACL = web3.toChecksumAddress("0x265820F3779f652f2a9857133fDEAf115b87db4B") - -WBTC_WETH_SLP_STRATEGY = web3.toChecksumAddress( - "0xA6827f0f14D0B83dB925B616d820434697328c22" -) # WBTC-WETH-SLP -WETH_SUSHI_SLP_STRATEGY = web3.toChecksumAddress( - "0x86f772C82914f5bFD168f99e208d0FC2C371e9C2" -) # WETH-SUSHI-SLP -WBTC_WETH_SLP_VAULT = web3.toChecksumAddress( - "0xFc13209cAfE8fb3bb5fbD929eC9F11a39e8Ac041" -) -WETH_SUSHI_SLP_VAULT = web3.toChecksumAddress( - "0xe774d1fb3133b037aa17d39165b8f45f444f632d" -) - - -# def mock_get_last_harvest_times(web3, keeper_acl, start_block): -# return get_last_harvest_times( -# web3, keeper_acl, start_block, etherscan_key=os.getenv("ETHERSCAN_TOKEN") -# ) +logger = logging.getLogger(__name__) def mock_send_discord( @@ -53,9 +40,6 @@ def mock_send_discord( def mock_fns(monkeypatch): # TODO: Ideally should find a way to mock get_secret monkeypatch.setattr("src.earner.send_success_to_discord", mock_send_discord) - # monkeypatch.setattr( - # "src.general_harvester.get_last_harvest_times", mock_get_last_harvest_times - # ) @pytest.fixture @@ -72,7 +56,7 @@ def keeper_address() -> str: def setup_keeper_acl(keeper_address): keeper_acl = Contract.from_abi( "KeeperAccessControl", - KEEPER_ACL, + ARB_KEEPER_ACL, get_abi(Network.Ethereum, "keeper_acl"), ) earner_key = keeper_acl.EARNER_ROLE() @@ -103,18 +87,18 @@ def earner(keeper_address, keeper_key) -> Earner: return Earner( chain=Network.Arbitrum, web3=web3, - keeper_acl=KEEPER_ACL, + keeper_acl=ARB_KEEPER_ACL, keeper_address=keeper_address, keeper_key=keeper_key, - base_oracle_address=ETH_USD_CHAINLINK, + base_oracle_address=ARB_ETH_USD_CHAINLINK, ) @pytest.mark.parametrize( "strategy,vault", [ - (WBTC_WETH_SLP_STRATEGY, WBTC_WETH_SLP_VAULT), - (WETH_SUSHI_SLP_STRATEGY, WETH_SUSHI_SLP_VAULT), + (ARB_SLP_WBTC_WETH_STRATEGY, ARB_SLP_WBTC_WETH_VAULT), + (ARB_SLP_WETH_SUSHI_STRATEGY, ARB_SLP_WETH_SUSHI_VAULT), ], indirect=True, ) diff --git a/tests/test_arbitrum_harvester.py b/integration_tests/test_arbitrum_harvester.py similarity index 73% rename from tests/test_arbitrum_harvester.py rename to integration_tests/test_arbitrum_harvester.py index aa4c0477..445e9f20 100644 --- a/tests/test_arbitrum_harvester.py +++ b/integration_tests/test_arbitrum_harvester.py @@ -1,26 +1,21 @@ -import os -import pytest -from brownie import accounts, Contract, web3 from decimal import Decimal + +import pytest +from brownie import Contract +from brownie import accounts +from brownie import web3 from hexbytes import HexBytes from web3 import contract -from src.general_harvester import GeneralHarvester -from src.utils import get_abi, get_last_harvest_times, hours, get_secret -from tests.utils import test_address, test_key +from config.constants import ARB_ETH_USD_CHAINLINK +from config.constants import ARB_KEEPER_ACL +from config.constants import ARB_SLP_WBTC_WETH_STRATEGY +from config.constants import ARB_SLP_WETH_SUSHI_STRATEGY from config.enums import Network - -ETH_USD_CHAINLINK = web3.toChecksumAddress("0x639Fe6ab55C921f74e7fac1ee960C0B6293ba612") -KEEPER_ACL = web3.toChecksumAddress("0x265820F3779f652f2a9857133fDEAf115b87db4B") - -WBTC_WETH_SLP_STRATEGY = "0xA6827f0f14D0B83dB925B616d820434697328c22" # WBTC-WETH-SLP -WETH_SUSHI_SLP_STRATEGY = "0x86f772C82914f5bFD168f99e208d0FC2C371e9C2" # WETH-SUSHI-SLP - - -# def mock_get_last_harvest_times(web3, keeper_acl, start_block): -# return get_last_harvest_times( -# web3, keeper_acl, start_block, etherscan_key=os.getenv("ETHERSCAN_TOKEN") -# ) +from integration_tests.utils import test_address +from integration_tests.utils import test_key +from src.general_harvester import GeneralHarvester +from src.utils import get_abi def mock_send_discord( @@ -41,9 +36,6 @@ def mock_fns(monkeypatch): monkeypatch.setattr( "src.general_harvester.send_success_to_discord", mock_send_discord ) - # monkeypatch.setattr( - # "src.general_harvester.get_last_harvest_times", mock_get_last_harvest_times - # ) @pytest.fixture @@ -60,7 +52,7 @@ def keeper_address() -> str: def setup_keeper_acl(keeper_address): keeper_acl = Contract.from_abi( "KeeperAccessControl", - KEEPER_ACL, + ARB_KEEPER_ACL, get_abi(Network.Ethereum, "keeper_acl"), ) harvester_key = keeper_acl.HARVESTER_ROLE() @@ -83,15 +75,15 @@ def harvester(keeper_address, keeper_key) -> GeneralHarvester: return GeneralHarvester( chain=Network.Arbitrum, web3=web3, - keeper_acl=KEEPER_ACL, + keeper_acl=ARB_KEEPER_ACL, keeper_address=keeper_address, keeper_key=keeper_key, - base_oracle_address=ETH_USD_CHAINLINK, + base_oracle_address=ARB_ETH_USD_CHAINLINK, ) @pytest.mark.parametrize( - "strategy", [WBTC_WETH_SLP_STRATEGY, WETH_SUSHI_SLP_STRATEGY], indirect=True + "strategy", [ARB_SLP_WBTC_WETH_STRATEGY, ARB_SLP_WETH_SUSHI_STRATEGY], indirect=True ) @pytest.mark.require_network("hardhat-arbitrum-fork") def test_harvest(keeper_address, harvester, strategy): diff --git a/tests/test_eth_earner.py b/integration_tests/test_eth_earner.py similarity index 85% rename from tests/test_eth_earner.py rename to integration_tests/test_eth_earner.py index ff985830..485cb8a4 100644 --- a/tests/test_eth_earner.py +++ b/integration_tests/test_eth_earner.py @@ -1,28 +1,22 @@ import logging -import os -import pytest -from brownie import accounts, Contract, web3 from decimal import Decimal + +import pytest +from brownie import Contract +from brownie import accounts +from brownie import web3 from hexbytes import HexBytes -from web3 import contract -from src.earner import Earner -from src.utils import ( - get_abi, - get_last_harvest_times, - hours, - get_secret, - get_strategies_and_vaults, -) -from tests.utils import test_address, test_key -from config.constants import ( - EARN_OVERRIDE_THRESHOLD, - EARN_PCT_THRESHOLD, - MULTICHAIN_CONFIG, -) +from config.constants import EARN_OVERRIDE_THRESHOLD +from config.constants import MULTICHAIN_CONFIG from config.enums import Network +from integration_tests.utils import test_address +from integration_tests.utils import test_key +from src.earner import Earner +from src.utils import get_abi +from src.utils import get_strategies_and_vaults -logger = logging.getLogger("test-eth-earner") +logger = logging.getLogger(__name__) def mock_send_discord( @@ -91,9 +85,9 @@ def test_earn(keeper_address, earner): and 0 after. """ accounts[0].transfer(keeper_address, "10 ether") - STRATEGIES, VAULTS = get_strategies_and_vaults(web3, Network.Ethereum) + strategies, vaults = get_strategies_and_vaults(web3, Network.Ethereum) - for strategy, vault in zip(STRATEGIES, VAULTS): + for strategy, vault in zip(strategies, vaults): strategy_name = strategy.functions.getName().call() diff --git a/tests/test_general_harvester.py b/integration_tests/test_general_harvester.py similarity index 87% rename from tests/test_general_harvester.py rename to integration_tests/test_general_harvester.py index 3369b4bc..82f5ee29 100644 --- a/tests/test_general_harvester.py +++ b/integration_tests/test_general_harvester.py @@ -1,21 +1,26 @@ import os -import pytest -from brownie import accounts, Contract, web3 from decimal import Decimal + +import pytest +from brownie import Contract +from brownie import accounts +from brownie import web3 from hexbytes import HexBytes from web3 import contract +from config.constants import ETH_CVX_CRV_HELPER_STRATEGY +from config.constants import ETH_HBTC_CRV_STRATEGY +from config.constants import ETH_SLP_DIGG_WBTC_STRATEGY from config.constants import MULTICHAIN_CONFIG -from src.general_harvester import GeneralHarvester -from src.utils import ( - get_abi, - get_last_harvest_times, - hours, - get_secret, - seconds_to_blocks, -) -from tests.utils import test_address, test_key +from config.constants import XSUSHI from config.enums import Network +from integration_tests.utils import test_address +from integration_tests.utils import test_key +from src.general_harvester import GeneralHarvester +from src.utils import get_abi +from src.utils import get_last_harvest_times +from src.utils import hours +from src.utils import seconds_to_blocks ETH_USD_CHAINLINK = web3.toChecksumAddress( MULTICHAIN_CONFIG[Network.Ethereum]["gas_oracle"] @@ -24,15 +29,6 @@ REWARDS_MANAGER = web3.toChecksumAddress( MULTICHAIN_CONFIG[Network.Ethereum]["rewards_manager"] ) -CVX_HELPER_STRATEGY = web3.toChecksumAddress( - "0xBCee2c6CfA7A4e29892c3665f464Be5536F16D95" -) -CVX_CRV_HELPER_STRATEGY = web3.toChecksumAddress( - "0x826048381d65a65DAa51342C51d464428d301896" -) -HBTC_STRATEGY = web3.toChecksumAddress("0xf4146A176b09C664978e03d28d07Db4431525dAd") -DIGG_STRATEGY = web3.toChecksumAddress("0xaa8dddfe7DFA3C3269f1910d89E4413dD006D08a") -XSUSHI = "0x8798249c2e607446efb7ad49ec89dd1865ff4272" def mock_get_last_harvest_times(web3, keeper_acl, start_block): @@ -105,7 +101,7 @@ def setup_rewards_manager(keeper_address): @pytest.fixture def strategy() -> contract: return web3.eth.contract( - address=CVX_CRV_HELPER_STRATEGY, + address=ETH_CVX_CRV_HELPER_STRATEGY, abi=get_abi(Network.Ethereum, "strategy"), ) @@ -113,7 +109,7 @@ def strategy() -> contract: @pytest.fixture def rewards_manager_strategy() -> contract: return web3.eth.contract( - address=DIGG_STRATEGY, + address=ETH_SLP_DIGG_WBTC_STRATEGY, abi=get_abi(Network.Ethereum, "strategy"), ) @@ -121,7 +117,7 @@ def rewards_manager_strategy() -> contract: @pytest.fixture def btc_strategy() -> contract: return web3.eth.contract( - address=HBTC_STRATEGY, abi=get_abi(Network.Ethereum, "strategy") + address=ETH_HBTC_CRV_STRATEGY, abi=get_abi(Network.Ethereum, "strategy") ) @@ -213,7 +209,7 @@ def test_harvest(keeper_address, harvester, strategy): def test_btc_profit_est(harvester, btc_strategy): - want = web3.eth.contract( + web3.eth.contract( address=btc_strategy.functions.want().call(), abi=get_abi(Network.Ethereum, "erc20"), ) @@ -222,25 +218,25 @@ def test_btc_profit_est(harvester, btc_strategy): @pytest.mark.require_network("hardhat-fork") def test_is_time_to_harvest(web3, chain, keeper_address, harvester, strategy): - strategy_name = strategy.functions.getName().call() + strategy.functions.getName().call() accounts[0].transfer(keeper_address, "10 ether") # Strategy should be harvestable at this point chain.sleep(hours(121)) chain.mine(1) - assert harvester.is_time_to_harvest(strategy) == True + assert harvester.is_time_to_harvest(strategy) is True harvester.harvest(strategy) # Strategy shouldn't be harvestable - assert harvester.is_time_to_harvest(strategy) == False + assert harvester.is_time_to_harvest(strategy) is False # Should only be able to harvest after 120 hours chain.sleep(hours(72)) chain.mine(1) - assert harvester.is_time_to_harvest(strategy) == False + assert harvester.is_time_to_harvest(strategy) is False chain.sleep(hours(49)) chain.mine(1) - assert harvester.is_time_to_harvest(strategy) == True + assert harvester.is_time_to_harvest(strategy) is True harvester.harvest(strategy) @@ -265,25 +261,25 @@ def test_is_time_to_harvest_rewards_manager( start_block=harvester.web3.eth.block_number - seconds_to_blocks(hours(120)), etherscan_key=os.getenv("ETHERSCAN_TOKEN"), ) - strategy_name = rewards_manager_strategy.functions.getName().call() + rewards_manager_strategy.functions.getName().call() accounts[0].transfer(keeper_address, "10 ether") # Strategy should be harvestable at this point chain.sleep(hours(121)) chain.mine(1) - assert harvester.is_time_to_harvest(rewards_manager_strategy) == True + assert harvester.is_time_to_harvest(rewards_manager_strategy) is True harvester.harvest_rewards_manager(rewards_manager_strategy) assert harvester.last_harvest_times[rewards_manager_strategy.address] # Strategy shouldn't be harvestable - assert harvester.is_time_to_harvest(rewards_manager_strategy) == False + assert harvester.is_time_to_harvest(rewards_manager_strategy) is False # Should only be able to harvest after 120 hours chain.sleep(hours(72)) chain.mine(1) - assert harvester.is_time_to_harvest(rewards_manager_strategy) == False + assert harvester.is_time_to_harvest(rewards_manager_strategy) is False chain.sleep(hours(49)) chain.mine(1) - assert harvester.is_time_to_harvest(rewards_manager_strategy) == True + assert harvester.is_time_to_harvest(rewards_manager_strategy) is True harvester.harvest_rewards_manager(rewards_manager_strategy) diff --git a/integration_tests/test_ibbtc_fee_collect.py b/integration_tests/test_ibbtc_fee_collect.py new file mode 100644 index 00000000..ca234e64 --- /dev/null +++ b/integration_tests/test_ibbtc_fee_collect.py @@ -0,0 +1,34 @@ +import pytest +from brownie import * + +import integration_tests.utils as test_utils +from src.ibbtc_fee_collector import ibBTCFeeCollector + + +@pytest.mark.require_network("mainnet-fork") +def test_correct_network(): + pass + + +@pytest.fixture +def collector() -> ibBTCFeeCollector: + return ibBTCFeeCollector( + keeper_address=test_utils.test_address, + keeper_key=test_utils.test_key, + web3="http://127.0.0.1:8545", + ) + + +def test_collect(collector, mocker): + """ + Check if the contract should be harvestable, then call the harvest function + + If the strategy should be harvested then claimable rewards should be positive before + and 0 after. If not then claimable rewards should be the same before and after + calling harvest + """ + success_message = mocker.patch("src.ibbtc_fee_collector.send_success_to_discord") + accounts[0].transfer(test_utils.test_address, "5 ether") + + collector.collect_fees() + assert success_message.called diff --git a/tests/test_mstable_harvests.py b/integration_tests/test_mstable_harvests.py similarity index 89% rename from tests/test_mstable_harvests.py rename to integration_tests/test_mstable_harvests.py index 12da8e42..86ff0c3d 100644 --- a/tests/test_mstable_harvests.py +++ b/integration_tests/test_mstable_harvests.py @@ -1,27 +1,33 @@ import os -import pytest -from brownie import accounts, interface, Contract, web3 from decimal import Decimal + +import pytest +from brownie import Contract +from brownie import accounts +from brownie import web3 from hexbytes import HexBytes from web3 import contract +from config.constants import ETH_IBMBTC_STRATEGY +from config.constants import ETH_MBTC_HBTC_STRATEGY +from config.constants import MSTABLE_VOTER_PROXY +from config.constants import MTA from config.constants import MULTICHAIN_CONFIG -from src.general_harvester import GeneralHarvester -from src.utils import get_abi, get_last_harvest_times, hours -from tests.utils import test_address, test_key from config.enums import Network +from integration_tests.utils import test_address +from integration_tests.utils import test_key +from src.general_harvester import GeneralHarvester +from src.utils import get_abi +from src.utils import get_last_harvest_times +from src.utils import hours ETH_USD_CHAINLINK = web3.toChecksumAddress( MULTICHAIN_CONFIG[Network.Ethereum]["gas_oracle"] ) KEEPER_ACL = web3.toChecksumAddress(MULTICHAIN_CONFIG[Network.Ethereum]["keeper_acl"]) -MSTABLE_VOTER_PROXY = "0x10D96b1Fd46Ce7cE092aA905274B8eD9d4585A6E" -MSTABLE_STRATEGIES = [ - "0x54D06A0E1cE55a7a60Ee175AbCeaC7e363f603f3", # mBTC/hBTC mstable - "0xd409C506742b7f76f164909025Ab29A47e06d30A", # ibmBTC mstable -] -MTA = "0xa3bed4e1c75d00fa6f4e5e6922db7261b5e9acd2" + +MSTABLE_STRATEGIES = [ETH_MBTC_HBTC_STRATEGY, ETH_IBMBTC_STRATEGY] def mock_get_last_harvest_times(web3, keeper_acl, start_block): @@ -201,30 +207,30 @@ def test_conditional_mstable_harvest( chain.sleep(hours(121)) chain.mine(1) # Strategy should be harvestable at this point - assert harvester.is_time_to_harvest(voter_proxy) == True + assert harvester.is_time_to_harvest(voter_proxy) is True for strategy in strategies: - assert harvester.is_time_to_harvest(strategy["contract"]) == True + assert harvester.is_time_to_harvest(strategy["contract"]) is True harvester.harvest_mta(voter_proxy) for strategy in strategies: harvester.harvest(strategy["contract"]) # Strategy shouldn't be harvestable - assert harvester.is_time_to_harvest(voter_proxy) == False + assert harvester.is_time_to_harvest(voter_proxy) is False for strategy in strategies: - assert harvester.is_time_to_harvest(strategy["contract"]) == False + assert harvester.is_time_to_harvest(strategy["contract"]) is False chain.sleep(hours(72)) chain.mine(1) - assert harvester.is_time_to_harvest(voter_proxy) == False + assert harvester.is_time_to_harvest(voter_proxy) is False for strategy in strategies: - assert harvester.is_time_to_harvest(strategy["contract"]) == False + assert harvester.is_time_to_harvest(strategy["contract"]) is False # Strategy should be harvestable again after 120 hours chain.sleep(hours(49)) chain.mine(1) - assert harvester.is_time_to_harvest(voter_proxy) == True + assert harvester.is_time_to_harvest(voter_proxy) is True for strategy in strategies: - assert harvester.is_time_to_harvest(strategy["contract"]) == True + assert harvester.is_time_to_harvest(strategy["contract"]) is True harvester.harvest_mta(voter_proxy) for strategy in strategies: diff --git a/tests/test_oracle.py b/integration_tests/test_oracle.py similarity index 64% rename from tests/test_oracle.py rename to integration_tests/test_oracle.py index e68bec50..48f4dab2 100644 --- a/tests/test_oracle.py +++ b/integration_tests/test_oracle.py @@ -1,35 +1,19 @@ -from typing import Tuple +import logging + import pytest -from decimal import Decimal from brownie import * -from web3 import Web3 -import logging -import requests -import os +from config.constants import DIGG_CENTRALIZED_ORACLE +from config.constants import DIGG_CHAINLINK_FORWARDER +from config.constants import ETH_DIGG_BTC_CHAINLINK +from config.enums import Network +from integration_tests.utils import mock_send_discord +from integration_tests.utils import test_address +from integration_tests.utils import test_key from src.oracle import Oracle from src.utils import get_abi -from tests.utils import test_address, test_key, mock_send_discord -from config.enums import Network -logger = logging.getLogger("test-oracle") - -os.environ[ - "DISCORD_WEBHOOK_URL" -] = "https://discord.com/api/webhooks/838956838636093491/OXDBt7dz6nn_AghoD5W8yhVjw7udBO6noHU8JNzbyAZMDgszvWAIJm9gAUikAdxTd03c" # os.getenv("TEST_DISCORD_WEBHOOK_URL") -os.environ["ETH_USD_CHAINLINK"] = "0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419" -os.environ["GAS_LIMIT"] = "1000000" -os.environ[ - "UNI_SUBGRAPH" -] = "https://api.thegraph.com/subgraphs/name/uniswap/uniswap-v2" -os.environ["UNI_PAIR"] = "0xe86204c4eddd2f70ee00ead6805f917671f56c52" -os.environ[ - "SUSHI_SUBGRAPH" -] = "https://api.thegraph.com/subgraphs/name/sushiswap/exchange" -os.environ["SUSHI_PAIR"] = "0x9a13867048e01c663ce8ce2fe0cdae69ff9f35e3" -os.environ["CENTRALIZED_ORACLE"] = "0x73083058e0f61D3fc7814eEEDc39F9608B4546d7" -os.environ["CHAINLINK_FORWARDER"] = "0xB572f69edbfC946af11a1b3ef8D5c2f41D38a642" -os.environ["DIGG_BTC_CHAINLINK"] = "0x418a6c98cd5b8275955f08f0b8c1c6838c8b1685" +logger = logging.getLogger(__name__) def mock_send_error(tx_type: str, error: Exception): @@ -50,7 +34,7 @@ def keeper_address() -> str: def setup_centralized_oracle(keeper_address): centralized_oracle = Contract.from_abi( "CentralizedOracle", - os.getenv("CENTRALIZED_ORACLE"), + DIGG_CENTRALIZED_ORACLE, get_abi(Network.Ethereum, "digg_centralized_oracle"), ) oracle_role = centralized_oracle.ORACLE_ROLE() @@ -64,7 +48,7 @@ def setup_centralized_oracle(keeper_address): def setup_chainlink_oracle(keeper_address): chainlink_oracle = Contract.from_abi( "ChainlinkOracle", - os.getenv("CHAINLINK_FORWARDER"), + DIGG_CHAINLINK_FORWARDER, get_abi(Network.Ethereum, "chainlink_forwarder"), ) owner = chainlink_oracle.owner() @@ -110,11 +94,11 @@ def test_chainlink_forwarder(oracle): def test_is_negative_rebase(oracle): digg_btc_oracle = Contract.from_abi( "DiggBtcOracle", - os.getenv("DIGG_BTC_CHAINLINK"), + ETH_DIGG_BTC_CHAINLINK, get_abi(Network.Ethereum, "oracle"), ) price = digg_btc_oracle.latestAnswer() if price < 95000000: - assert oracle.is_negative_rebase() == True + assert oracle.is_negative_rebase() is True else: - assert oracle.is_negative_rebase() == False + assert oracle.is_negative_rebase() is False diff --git a/tests/test_rebalance.py b/integration_tests/test_rebalance.py similarity index 83% rename from tests/test_rebalance.py rename to integration_tests/test_rebalance.py index ecb58cfb..7b0e2dfe 100644 --- a/tests/test_rebalance.py +++ b/integration_tests/test_rebalance.py @@ -5,16 +5,18 @@ from brownie import accounts, Contract, web3 from web3 import contract +from config.constants import ( + ETH_ETH_USD_CHAINLINK, + ETH_KEEPER_ACL, + ETH_STABILIZE_STRATEGY, +) from src.utils import get_abi -from tests.utils import test_address, test_key +from integration_tests.utils import test_address, test_key from src.eth.rebalancer import Rebalancer from config.enums import Network -ETH_USD_CHAINLINK = "0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419" -KEEPER_ACL = "0x711A339c002386f9db409cA55b6A35a604aB6cF6" -STABILIZE_STRAT = "0xA6af1B913E205B8E9B95D3B30768c0989e942316" -logger = logging.getLogger("test_rebalance") +logger = logging.getLogger(__name__) def mock_send_discord( @@ -56,14 +58,16 @@ def keeper_address() -> str: @pytest.fixture(autouse=True) def patch_rebalancer(monkeypatch): monkeypatch.setattr("src.eth.rebalancer.send_success_to_discord", mock_send_discord) - monkeypatch.setattr("tests.test_rebalance.web3.eth.fee_history", mock_fee_history) + monkeypatch.setattr( + "integration_tests.test_rebalance.web3.eth.fee_history", mock_fee_history + ) @pytest.fixture(autouse=True) def setup_keeper_acl(keeper_address): keeper_acl = Contract.from_abi( "KeeperAccessControl", - KEEPER_ACL, + ETH_KEEPER_ACL, get_abi(Network.Ethereum, "keeper_acl"), ) harvester_key = keeper_acl.HARVESTER_ROLE() @@ -77,7 +81,7 @@ def setup_keeper_acl(keeper_address): def setup_stability_vault(keeper_address, setup_keeper_acl): stability_strategy = Contract.from_abi( "StabilizeStrategyDiggV1", - STABILIZE_STRAT, + ETH_STABILIZE_STRATEGY, get_abi(Network.Ethereum, "stability_strat"), ) governance = stability_strategy.governance() @@ -88,7 +92,7 @@ def setup_stability_vault(keeper_address, setup_keeper_acl): @pytest.fixture def strategy() -> contract: return web3.eth.contract( - address=STABILIZE_STRAT, abi=get_abi(Network.Ethereum, "stability_strat") + address=ETH_STABILIZE_STRATEGY, abi=get_abi(Network.Ethereum, "stability_strat") ) @@ -96,10 +100,10 @@ def strategy() -> contract: def rebalancer(keeper_address, keeper_key) -> Rebalancer: return Rebalancer( web3=web3, - keeper_acl=KEEPER_ACL, + keeper_acl=ETH_KEEPER_ACL, keeper_address=keeper_address, keeper_key=keeper_key, - base_oracle_address=ETH_USD_CHAINLINK, + base_oracle_address=ETH_ETH_USD_CHAINLINK, use_flashbots=False, ) diff --git a/tests/test_rebase.py b/integration_tests/test_rebase.py similarity index 92% rename from tests/test_rebase.py rename to integration_tests/test_rebase.py index d1e0682f..635c750b 100644 --- a/tests/test_rebase.py +++ b/integration_tests/test_rebase.py @@ -1,13 +1,10 @@ -from typing import Tuple +import os + import pytest -from decimal import Decimal from brownie import * -from web3 import Web3 -import requests -import os +from integration_tests.utils import * from src.rebaser import Rebaser -from tests.utils import * os.environ["DISCORD_WEBHOOK_URL"] = os.getenv("TEST_DISCORD_WEBHOOK_URL") os.environ["ETH_USD_CHAINLINK"] = "0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419" diff --git a/tests/test_stability_execute.py b/integration_tests/test_stability_execute.py similarity index 84% rename from tests/test_stability_execute.py rename to integration_tests/test_stability_execute.py index 236d9ac2..6cc2bb79 100644 --- a/tests/test_stability_execute.py +++ b/integration_tests/test_stability_execute.py @@ -5,16 +5,17 @@ from brownie import accounts, Contract, web3 from web3 import contract +from config.constants import ( + ETH_ETH_USD_CHAINLINK, + ETH_KEEPER_ACL, + ETH_STABILIZE_STRATEGY, +) from src.utils import get_abi -from tests.utils import test_address, test_key +from integration_tests.utils import test_address, test_key from src.eth.stability_executor import StabilityExecutor from config.enums import Network -ETH_USD_CHAINLINK = "0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419" -KEEPER_ACL = "0x711A339c002386f9db409cA55b6A35a604aB6cF6" -STABILIZE_STRAT = "0xA6af1B913E205B8E9B95D3B30768c0989e942316" - -logger = logging.getLogger("test_rebalance") +logger = logging.getLogger(__name__) def mock_send_discord( @@ -59,7 +60,8 @@ def patch_stability_executor(monkeypatch): "src.eth.stability_executor.send_success_to_discord", mock_send_discord ) monkeypatch.setattr( - "tests.test_stability_execute.web3.eth.fee_history", mock_fee_history + "integration_tests.test_stability_execute.web3.eth.fee_history", + mock_fee_history, ) @@ -67,7 +69,7 @@ def patch_stability_executor(monkeypatch): def setup_keeper_acl(keeper_address): keeper_acl = Contract.from_abi( "KeeperAccessControl", - KEEPER_ACL, + ETH_KEEPER_ACL, get_abi(Network.Ethereum, "keeper_acl"), ) harvester_key = keeper_acl.HARVESTER_ROLE() @@ -81,7 +83,7 @@ def setup_keeper_acl(keeper_address): def setup_stability_vault(keeper_address): stability_strategy = Contract.from_abi( "StabilizeStrategyDiggV1", - STABILIZE_STRAT, + ETH_STABILIZE_STRATEGY, get_abi(Network.Ethereum, "stability_strat"), ) governance = stability_strategy.governance() @@ -92,7 +94,7 @@ def setup_stability_vault(keeper_address): @pytest.fixture def strategy() -> contract: return web3.eth.contract( - address=STABILIZE_STRAT, abi=get_abi(Network.Ethereum, "stability_strat") + address=ETH_STABILIZE_STRATEGY, abi=get_abi(Network.Ethereum, "stability_strat") ) @@ -100,10 +102,10 @@ def strategy() -> contract: def stability_executor(keeper_address, keeper_key) -> StabilityExecutor: return StabilityExecutor( web3=web3, - keeper_acl=KEEPER_ACL, + keeper_acl=ETH_KEEPER_ACL, keeper_address=keeper_address, keeper_key=keeper_key, - base_oracle_address=ETH_USD_CHAINLINK, + base_oracle_address=ETH_ETH_USD_CHAINLINK, use_flashbots=False, ) diff --git a/tests/utils.py b/integration_tests/utils.py similarity index 100% rename from tests/utils.py rename to integration_tests/utils.py diff --git a/requirements-dev.txt b/requirements-dev.txt new file mode 100644 index 00000000..99c26ca4 --- /dev/null +++ b/requirements-dev.txt @@ -0,0 +1,4 @@ +eth-brownie +responses==0.16.0 +pytest-mock==3.6.1 +pytest-cov==3.0.0 diff --git a/run_tests.sh b/run_tests.sh index b0c9818c..181976e1 100755 --- a/run_tests.sh +++ b/run_tests.sh @@ -17,7 +17,7 @@ failing_tests="failures:" for i in "${eth_tests[@]}" do - if brownie test tests/"$i".py --network=hardhat-fork; then + if brownie test integration_tests/"$i".py --network=hardhat-fork; then continue else failing_tests="$failing_tests $i" diff --git a/scripts/approve_centralized_oracle.py b/scripts/approve_centralized_oracle.py index 23099896..ebb2d295 100644 --- a/scripts/approve_centralized_oracle.py +++ b/scripts/approve_centralized_oracle.py @@ -1,19 +1,15 @@ import logging -import os -import sys -from time import sleep, time +from time import time -sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../src"))) - -from oracle import Oracle -from utils import get_secret +from src.oracle import Oracle +from src.utils import get_secret logging.basicConfig(level=logging.INFO) if __name__ == "__main__": - logger = logging.getLogger() + logger = logging.getLogger(__name__) logger.info(f"INVOKED AT {time()}") keeper_key = get_secret("keepers/rebaser/keeper-pk", "KEEPER_KEY") diff --git a/scripts/arbitrum_earn.py b/scripts/arbitrum_earn.py index 3820dd8b..6933c6a5 100644 --- a/scripts/arbitrum_earn.py +++ b/scripts/arbitrum_earn.py @@ -1,22 +1,15 @@ -import json import logging -import os -import sys -from time import sleep -from web3 import Web3, contract -sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../src"))) -sys.path.insert( - 0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../config")) -) +from web3 import Web3 -from earner import Earner -from utils import get_secret, get_strategies_and_vaults, get_abi -from constants import MULTICHAIN_CONFIG -from enums import Network +from config.constants import MULTICHAIN_CONFIG +from config.enums import Network +from src.earner import Earner +from src.utils import get_secret +from src.utils import get_strategies_and_vaults logging.basicConfig(level=logging.INFO) -logger = logging.getLogger("script") +logger = logging.getLogger(__name__) def safe_earn(earner, vault, strategy): diff --git a/scripts/arbitrum_harvest.py b/scripts/arbitrum_harvest.py index ddaad952..f40b8d3b 100644 --- a/scripts/arbitrum_harvest.py +++ b/scripts/arbitrum_harvest.py @@ -1,27 +1,17 @@ import logging -import os -import sys import time -from pathlib import Path -from web3 import Web3 -sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../src"))) -sys.path.insert( - 0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../config")) -) +from web3 import Web3 -from enums import Network -from general_harvester import GeneralHarvester -from utils import get_abi, get_secret, get_strategies_from_registry -from constants import MULTICHAIN_CONFIG +from config.constants import MULTICHAIN_CONFIG +from config.enums import Network +from src.general_harvester import GeneralHarvester +from src.utils import get_secret +from src.utils import get_strategies_from_registry logging.basicConfig(level=logging.INFO) -logger = logging.getLogger(Path(__file__).name) +logger = logging.getLogger(__name__) -# strategies = { -# "0x86f772C82914f5bFD168f99e208d0FC2C371e9C2", # WETH-SUSHI-SLP -# "0xA6827f0f14D0B83dB925B616d820434697328c22", # WBTC-WETH-SLP -# } # TODO: Add conditional harvest logic def safe_harvest(harvester, strategy) -> str: diff --git a/scripts/arbitrum_manual_harvest.py b/scripts/arbitrum_manual_harvest.py index 6014ffdd..8645be63 100644 --- a/scripts/arbitrum_manual_harvest.py +++ b/scripts/arbitrum_manual_harvest.py @@ -1,27 +1,20 @@ import logging -import os -import sys import time -from eth_account.account import Account -from pathlib import Path -from web3 import Web3 -sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../src"))) -sys.path.insert( - 0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../config")) -) +from web3 import Web3 -from enums import Network -from general_harvester import GeneralHarvester -from utils import get_abi, get_secret, get_node_url -from constants import MULTICHAIN_CONFIG +from config.constants import ARB_SWAPR_WBTC_WETH_STRATEGY +from config.constants import MULTICHAIN_CONFIG +from config.enums import Network +from src.general_harvester import GeneralHarvester +from src.utils import get_abi +from src.utils import get_node_url +from src.utils import get_secret logging.basicConfig(level=logging.INFO) -logger = logging.getLogger(Path(__file__).name) +logger = logging.getLogger(__name__) -strategies = { - "0x43942cEae98CC7485B48a37fBB1aa5035e1c8B46", # WBTC WETH SWAPR -} +strategies = {ARB_SWAPR_WBTC_WETH_STRATEGY} def safe_harvest(harvester, strategy_name, strategy) -> str: diff --git a/scripts/earn.py b/scripts/earn.py index 8e9eb58c..a5f491ae 100644 --- a/scripts/earn.py +++ b/scripts/earn.py @@ -1,24 +1,19 @@ -import json import logging -import os -import sys -from time import sleep -from web3 import Web3, contract -sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../src"))) -sys.path.insert( - 0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../config")) -) +from web3 import Web3 -from constants import MULTICHAIN_CONFIG -from earner import Earner -from enums import Network -from utils import get_secret, get_strategies_and_vaults, get_node_url +from config.constants import MULTICHAIN_CONFIG +from config.constants import POLY_OLD_STRATEGY +from config.enums import Network +from src.earner import Earner +from src.utils import get_node_url +from src.utils import get_secret +from src.utils import get_strategies_and_vaults logging.basicConfig(level=logging.INFO) -logger = logging.getLogger("script") +logger = logging.getLogger(__name__) -INVALID_STRATS = ["0xDb0C3118ef1acA6125200139BEaCc5D675F37c9C"] +INVALID_STRATS = [POLY_OLD_STRATEGY] def safe_earn(earner, sett_name, vault, strategy): diff --git a/scripts/earn_locked_cvx.py b/scripts/earn_locked_cvx.py index 4a424c68..bebaa30d 100644 --- a/scripts/earn_locked_cvx.py +++ b/scripts/earn_locked_cvx.py @@ -1,26 +1,18 @@ -import json import logging -import os -import sys -from time import sleep -from web3 import Web3, contract - -sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../src"))) -sys.path.insert( - 0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../config")) -) - -from enums import Network -from earner import Earner -from utils import get_secret, get_abi, get_node_url -from constants import ( - MULTICHAIN_CONFIG, - ETH_BVECVX_STRATEGY, - ETH_BVECVX_VAULT, -) + +from web3 import Web3 + +from config.constants import ETH_BVECVX_STRATEGY +from config.constants import ETH_BVECVX_VAULT +from config.constants import MULTICHAIN_CONFIG +from config.enums import Network +from src.earner import Earner +from src.utils import get_abi +from src.utils import get_node_url +from src.utils import get_secret logging.basicConfig(level=logging.INFO) -logger = logging.getLogger("script") +logger = logging.getLogger(__name__) if __name__ == "__main__": diff --git a/scripts/eth_earn.py b/scripts/eth_earn.py index 1d2f47df..f6892fa0 100644 --- a/scripts/eth_earn.py +++ b/scripts/eth_earn.py @@ -1,34 +1,29 @@ -import json import logging -import os -import sys -from time import sleep -from web3 import Web3, contract - -sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../src"))) -sys.path.insert( - 0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../config")) -) - -from earner import Earner -from utils import get_secret, get_strategy_from_vault, get_abi, get_node_url -from tx_utils import get_latest_base_fee -from constants import ( - MULTICHAIN_CONFIG, - ETH_YVWBTC_VAULT, - ETH_TRICRYPTO_VAULT, - ETH_BVECVX_CVX_LP_VAULT, - ETH_IBBTC_CRV_LP_VAULT, - ETH_IBBTC_SUSHI_VAULT, - ETH_SBTC_VAULT, - ETH_TBTC_VAULT, - ETH_PBTC_VAULT, - ETH_BBTC_VAULT, -) -from enums import Network + +from web3 import Web3 + +from config.constants import ETH_BBTC_VAULT +from config.constants import ETH_BVECVX_CVX_LP_VAULT +from config.constants import ETH_FRAX_CRV_VAULT +from config.constants import ETH_IBBTC_CRV_LP_VAULT +from config.constants import ETH_IBBTC_SUSHI_VAULT +from config.constants import ETH_MIM_CRV_VAULT +from config.constants import ETH_PBTC_VAULT +from config.constants import ETH_SBTC_VAULT +from config.constants import ETH_TBTC_VAULT +from config.constants import ETH_TRICRYPTO_VAULT +from config.constants import ETH_YVWBTC_VAULT +from config.constants import MULTICHAIN_CONFIG +from config.enums import Network +from src.earner import Earner +from src.tx_utils import get_latest_base_fee +from src.utils import get_abi +from src.utils import get_node_url +from src.utils import get_secret +from src.utils import get_strategy_from_vault logging.basicConfig(level=logging.INFO) -logger = logging.getLogger("script") +logger = logging.getLogger(__name__) INVALID_VAULTS = [ ETH_YVWBTC_VAULT, @@ -66,6 +61,8 @@ def safe_earn(earner, sett_name, vault, strategy): ) vault_addresses.append(ETH_BVECVX_CVX_LP_VAULT) vault_addresses.append(ETH_IBBTC_CRV_LP_VAULT) + vault_addresses.append(ETH_FRAX_CRV_VAULT) + vault_addresses.append(ETH_MIM_CRV_VAULT) for address in vault_addresses: if address not in INVALID_VAULTS: diff --git a/scripts/eth_tend.py b/scripts/eth_tend.py deleted file mode 100644 index 0f934a85..00000000 --- a/scripts/eth_tend.py +++ /dev/null @@ -1,77 +0,0 @@ -import logging -import os -import sys -import time -from eth_account.account import Account -from flashbots import flashbot -from pathlib import Path -from web3 import Web3 - -sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../src"))) -sys.path.insert( - 0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../config")) -) - -from general_harvester import GeneralHarvester -from utils import get_abi, get_secret, get_node_url -from enums import Network - -logging.basicConfig(level=logging.INFO) -logger = logging.getLogger(Path(__file__).name) - -ETH_USD_CHAINLINK = "0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419" -KEEPER_ACL = "0x711A339c002386f9db409cA55b6A35a604aB6cF6" - -STRATEGIES = { - # New strategies - "native.renCrv": "0x61e16b46F74aEd8f9c2Ec6CB2dCb2258Bdfc7071", - "native.sbtcCrv": "0xCce0D2d1Eb2310F7e67e128bcFE3CE870A3D3a3d", - "native.tbtcCrv": "0xAB73Ec65a1Ef5a2e5b56D5d6F36Bee4B2A1D3FFb", - "native.hbtcCrv": "0x8c26D9B6B80684CC642ED9eb1Ac1729Af3E819eE", - "native.pbtcCrv": "0xA9A646668Df5Cec5344941646F5c6b269551e53D", - "native.obtcCrv": "0x5dd69c6D81f0a403c03b99C5a44Ef2D49b66d388", - "native.bbtcCrv": "0xF2F3AB09E2D8986fBECbBa59aE838a5418a6680c", - "native.tricrypto2": "0x647eeb5C5ED5A71621183f09F6CE8fa66b96827d", -} - - -def safe_tend(harvester, strategy_name, strategy) -> str: - logger.info(f"strategy address at {strategy.address}") - try: - harvester.tend(strategy) - return "Success!" - except Exception as e: - logger.error(f"Error running {strategy_name} tend: {e}") - - -if __name__ == "__main__": - # Load secrets - keeper_key = get_secret("keepers/rebaser/keeper-pk", "KEEPER_KEY") - keeper_address = get_secret("keepers/rebaser/keeper-address", "KEEPER_ADDRESS") - node_url = get_node_url(Network.Ethereum) - - web3 = Web3(Web3.HTTPProvider(node_url)) - - harvester = GeneralHarvester( - web3=web3, - keeper_acl=KEEPER_ACL, - keeper_address=keeper_address, - keeper_key=keeper_key, - base_oracle_address=ETH_USD_CHAINLINK, - use_flashbots=False, - ) - - for strategy_address in STRATEGIES.values(): - strategy = web3.eth.contract( - address=web3.toChecksumAddress(strategy_address), - abi=get_abi(Network.Ethereum, "strategy"), - ) - - if strategy.functions.isTendable().call(): - strategy_name = strategy.functions.getName().call() - - logger.info(f"+-----Tending {strategy_name}-----+") - safe_tend(harvester, strategy_name, strategy) - - # Sleep for 2 blocks in between tends - time.sleep(30) diff --git a/scripts/forward_chainlink.py b/scripts/forward_chainlink.py index 2b4662d1..8bcbc020 100644 --- a/scripts/forward_chainlink.py +++ b/scripts/forward_chainlink.py @@ -1,23 +1,17 @@ import logging -import os -import sys -from time import sleep, time +from time import time -sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../src"))) -sys.path.insert( - 0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../config")) -) - -from enums import Network -from oracle import Oracle -from utils import get_secret, get_node_url +from config.enums import Network +from src.oracle import Oracle +from src.utils import get_node_url +from src.utils import get_secret logging.basicConfig(level=logging.INFO) if __name__ == "__main__": - logger = logging.getLogger() + logger = logging.getLogger(__name__) logger.info(f"INVOKED AT {time()}") keeper_key = get_secret("keepers/rebaser/keeper-pk", "KEEPER_KEY") diff --git a/scripts/ftm_earn.py b/scripts/ftm_earn.py new file mode 100644 index 00000000..45df060f --- /dev/null +++ b/scripts/ftm_earn.py @@ -0,0 +1,60 @@ +import logging + +from web3 import Web3 + +from config.constants import FTM_VAULTS +from config.constants import MULTICHAIN_CONFIG +from config.enums import Network +from src.earner import Earner +from src.utils import get_secret +from src.utils import get_strategy_from_vault + +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger(__name__) + + +def safe_earn(earner, vault, strategy): + try: + sett_name = strategy.functions.getName().call() + logger.info(f"+-----Earning {sett_name}-----+") + earner.earn(vault, strategy, sett_name=sett_name) + except Exception as e: + logger.error(f"Error running earn: {e}") + + +if __name__ == "__main__": + for chain in [Network.Fantom]: + node_url = "https://rpc.ftm.tools/" + node = Web3(Web3.HTTPProvider(node_url)) + + keeper_key = get_secret("keepers/rebaser/keeper-pk", "KEEPER_KEY") + keeper_address = get_secret("keepers/rebaser/keeper-address", "KEEPER_ADDRESS") + discord_url = get_secret( + "keepers/harvester/fantom/info-webhook", "DISCORD_WEBHOOK_URL" + ) + + earner = Earner( + chain=chain, + keeper_acl=MULTICHAIN_CONFIG.get(chain).get("keeper_acl"), + keeper_address=keeper_address, + keeper_key=keeper_key, + web3=node, + base_oracle_address=MULTICHAIN_CONFIG.get(chain).get("gas_oracle"), + discord_url=discord_url, + ) + strategies = [] + vaults = [] + for vault_address in FTM_VAULTS: + strategy, vault = get_strategy_from_vault(node, chain, vault_address) + strategies.append(strategy) + vaults.append(vault) + + for strategy, vault in zip(strategies, vaults): + if ( + strategy.address + not in MULTICHAIN_CONFIG[chain]["earn"]["invalid_strategies"] + ): + sett_name = strategy.functions.getName().call() + logger.info(f"+-----Earning {sett_name}-----+") + earner.earn(vault, strategy, sett_name=sett_name) + # safe_earn(earner, vault, strategy) diff --git a/scripts/ftm_harvest.py b/scripts/ftm_harvest.py new file mode 100644 index 00000000..b6ab6755 --- /dev/null +++ b/scripts/ftm_harvest.py @@ -0,0 +1,77 @@ +import logging +import time + +from web3 import Web3 + +from config.constants import FTM_VAULTS +from config.constants import MULTICHAIN_CONFIG +from config.enums import Network +from src.general_harvester import GeneralHarvester +from src.utils import get_secret +from src.utils import get_strategy_from_vault + +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger(__name__) + + +def safe_harvest(harvester, strategy) -> str: + try: + strategy_name = strategy.functions.getName().call() + logger.info(f"+-----Harvesting {strategy_name} {strategy.address}-----+") + harvester.harvest(strategy) + return "Success!" + except Exception as e: + logger.error(f"Error running harvest: {e}") + logger.info("Trying to run harvestNoReturn") + try: + harvester.harvest_no_return(strategy) + return "Success!" + except Exception as e: + logger.error(f"Error running harvestNoReturn: {e}") + + logger.info("Tend first, then harvest") + try: + harvester.tend_then_harvest(strategy) + return "Success!" + except Exception as e: + logger.error(f"Error running tend_then_harvest: {e}") + + +if __name__ == "__main__": + # Load secrets + keeper_key = get_secret("keepers/rebaser/keeper-pk", "KEEPER_KEY") + keeper_address = get_secret("keepers/rebaser/keeper-address", "KEEPER_ADDRESS") + node_url = "https://rpc.ftm.tools/" + discord_url = get_secret( + "keepers/harvester/fantom/info-webhook", "DISCORD_WEBHOOK_URL" + ) + + web3 = Web3(Web3.HTTPProvider(node_url)) + + harvester = GeneralHarvester( + web3=web3, + chain=Network.Fantom, + keeper_acl=MULTICHAIN_CONFIG[Network.Fantom]["keeper_acl"], + keeper_address=keeper_address, + keeper_key=keeper_key, + base_oracle_address=MULTICHAIN_CONFIG[Network.Fantom]["gas_oracle"], + use_flashbots=False, + discord_url=discord_url, + ) + strategies = [] + for vault_address in FTM_VAULTS: + strategy, _ = get_strategy_from_vault(web3, Network.Fantom, vault_address) + strategies.append(strategy) + + for strategy in strategies: + if ( + strategy.address + not in MULTICHAIN_CONFIG[Network.Fantom]["harvest"]["invalid_strategies"] + ): + # safe_harvest(harvester, strategy) + strategy_name = strategy.functions.getName().call() + logger.info(f"+-----Harvesting {strategy_name} {strategy.address}-----+") + harvester.harvest(strategy) + + # Sleep for a few blocks in between harvests + time.sleep(30) diff --git a/scripts/harvest_cake.py b/scripts/harvest_cake.py deleted file mode 100644 index 8e4beadb..00000000 --- a/scripts/harvest_cake.py +++ /dev/null @@ -1,45 +0,0 @@ -import logging -import os -import sys - -sys.path.insert( - 0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../src/bsc")) -) - -from cake_harvester import CakeHarvester -from utils import get_secret - -logging.basicConfig(level=logging.INFO) - -BBADGER_BTCB_STRATEGY = "0x2A842e01724F10d093aE8a46A01e66DbCf3C7373" -BDIGG_BTCB_STRATEGY = "0xC8C53A293edca5a0146d713b9b95b0cd0a2e5ca4" -BNB_BTCB_STRATEGY = "0x120BB9F87bAB3C49b89c7745eDC07FED50786534" - - -def safe_harvest(harvester, sett_name, strategy): - try: - harvester.harvest(sett_name, strategy) - except Exception as e: - logging.error(f"Error running {sett_name} harvest: {e}") - - -if __name__ == "__main__": - - logger = logging.getLogger() - - keeper_key = get_secret("keepers/rebaser/keeper-pk", "KEEPER_KEY") - keeper_address = get_secret("keepers/rebaser/keeper-address", "KEEPER_ADDRESS") - node_url = get_secret("quiknode/bsc-node-url", "BSC_NODE_URL") - - harvester = CakeHarvester( - keeper_address=keeper_address, keeper_key=keeper_key, web3=node_url - ) - - logger.info("+-----Harvesting BBADGER BTCB LP-----+") - safe_harvest(harvester, "BBADGER BTCB LP", BBADGER_BTCB_STRATEGY) - - # logger.info("+-----Harvesting BDIGG BTCB LP-----+") - # safe_harvest(harvester, "BDIGG BTCB LP", BDIGG_BTCB_STRATEGY) - - logger.info("+-----Harvesting BNB BTCB LP-----+") - safe_harvest(harvester, "BNB BTCB LP", BNB_BTCB_STRATEGY) diff --git a/scripts/harvest_eth.py b/scripts/harvest_eth.py index 3d68a67b..2ac38d61 100644 --- a/scripts/harvest_eth.py +++ b/scripts/harvest_eth.py @@ -1,94 +1,53 @@ import logging -import os -import sys import time + from eth_account.account import Account from flashbots import flashbot -from pathlib import Path from web3 import Web3 -sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../src"))) -sys.path.insert( - 0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../config")) -) - -from enums import Network -from constants import MULTICHAIN_CONFIG -from general_harvester import GeneralHarvester -from utils import ( - get_abi, - get_secret, - hours, - get_last_harvest_times, - seconds_to_blocks, - get_node_url, -) -from tx_utils import get_latest_base_fee +from config.constants import ETH_BVECVX_STRATEGY +from config.constants import ETH_CVX_CRV_HELPER_STRATEGY +from config.constants import ETH_ETH_USD_CHAINLINK +from config.constants import ETH_IBBTC_CRV_STRATEGY +from config.constants import ETH_KEEPER_ACL +from config.constants import ETH_RENBTC_CRV_STRATEGY +from config.constants import ETH_SLP_BADGER_WBTC_STRATEGY +from config.constants import ETH_SLP_DIGG_WBTC_STRATEGY +from config.constants import ETH_TBTC_CRV_STRATEGY +from config.constants import ETH_TRICRYPTO_STRATEGY +from config.constants import MULTICHAIN_CONFIG +from config.enums import Network +from src.general_harvester import GeneralHarvester +from src.tx_utils import get_latest_base_fee +from src.utils import get_abi +from src.utils import get_last_harvest_times +from src.utils import get_secret +from src.utils import hours +from src.utils import seconds_to_blocks logging.basicConfig(level=logging.INFO) -logger = logging.getLogger(Path(__file__).name) +logger = logging.getLogger(__name__) HOURS_24 = hours(24) HOURS_72 = hours(72) HOURS_96 = hours(96) HOURS_120 = hours(120) -ETH_USD_CHAINLINK = "0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419" -KEEPER_ACL = "0x711A339c002386f9db409cA55b6A35a604aB6cF6" - -CVX_HELPER_STRATEGY = "0xBCee2c6CfA7A4e29892c3665f464Be5536F16D95" -CVX_CRV_HELPER_STRATEGY = "0x826048381d65a65DAa51342C51d464428d301896" -IBBTC_CRV_STRATEGY = "0x6D4BA00Fd7BB73b5aa5b3D6180c6f1B0c89f70D1" - -MSTABLE_VOTER_PROXY = "0x10D96b1Fd46Ce7cE092aA905274B8eD9d4585A6E" - strategies = [ - IBBTC_CRV_STRATEGY, - "0xBCee2c6CfA7A4e29892c3665f464Be5536F16D95", # CVX_HELPER_STRATEGY - "0x826048381d65a65DAa51342C51d464428d301896", # CVX_CRV_HELPER_STRATEGY - "0x8c26D9B6B80684CC642ED9eb1Ac1729Af3E819eE", # HBTC_CRV_STRATEGY - "0xA9A646668Df5Cec5344941646F5c6b269551e53D", # PBTC_CRV_STRATEGY - "0x5dd69c6D81f0a403c03b99C5a44Ef2D49b66d388", # OBTC_CRV_STRATEGY - "0xF2F3AB09E2D8986fBECbBa59aE838a5418a6680c", # BBTC_CRV_STRATEGY - "0x647eeb5C5ED5A71621183f09F6CE8fa66b96827d", # TRICRYPTO_CRV_STRATEGY - "0x61e16b46F74aEd8f9c2Ec6CB2dCb2258Bdfc7071", # native.renCrv - "0xCce0D2d1Eb2310F7e67e128bcFE3CE870A3D3a3d", # native.sbtcCrv - "0xAB73Ec65a1Ef5a2e5b56D5d6F36Bee4B2A1D3FFb", # native.tbtcCrv - "0xaaE82E3c89e15E6F26F60724f115d5012363e030", # harvest.renCrv - "0x7A56d65254705B4Def63c68488C0182968C452ce", # native.sushiWbtcEth - "0x3a494D79AA78118795daad8AeFF5825C6c8dF7F1", # native.sushiBadgerWbtc - "0xf4146A176b09C664978e03d28d07Db4431525dAd", # experimental.sushiIBbtcWbtc - # "0xA6af1B913E205B8E9B95D3B30768c0989e942316", # experimental.digg - "0x3ff634ce65cDb8CC0D569D6d1697c41aa666cEA9", # locked cvx strategy + ETH_IBBTC_CRV_STRATEGY, + ETH_CVX_CRV_HELPER_STRATEGY, + ETH_BVECVX_STRATEGY, + ETH_TRICRYPTO_STRATEGY, + ETH_TBTC_CRV_STRATEGY, + ETH_RENBTC_CRV_STRATEGY, ] -rewards_manager_strategies = { - "0xaa8dddfe7DFA3C3269f1910d89E4413dD006D08a", # native.sushiDiggWbtc - "0x3a494D79AA78118795daad8AeFF5825C6c8dF7F1", # native.sushiBadgerWbtc - # "0x4a8651F2edD68850B944AD93f2c67af817F39F62", # native.digg - # "0xadc8d7322f2E284c1d9254170dbe311E9D3356cf", # native.uniDiggWbtc - # "0x95826C65EB1f2d2F0EDBb7EcB176563B61C60bBf", # native.uniBadgerWbtc - # "0x75b8E21BD623012Efb3b69E1B562465A68944eE6", # native.badger -} - -mstable_strategies = { - "0x54D06A0E1cE55a7a60Ee175AbCeaC7e363f603f3", # mBTC/hBTC mstable - "0xd409C506742b7f76f164909025Ab29A47e06d30A", # ibmBTC mstable -} +rewards_manager_strategies = {ETH_SLP_BADGER_WBTC_STRATEGY, ETH_SLP_DIGG_WBTC_STRATEGY} def conditional_harvest(harvester, strategy_name, strategy) -> str: latest_base_fee = get_latest_base_fee(harvester.web3) logger.info(f"Checking harvests for {strategy_name} {strategy.address}") - # ibbtc exception - if ( - strategy.address == IBBTC_CRV_STRATEGY - and harvester.is_time_to_harvest(strategy, HOURS_24) - and latest_base_fee < int(150e9) - ): - logger.info(f"Been longer than 24 hours and base fee < 150 for {strategy_name}") - res = safe_harvest(harvester, strategy_name, strategy) - logger.info(res) # regular thresholds for rest of vaults if harvester.is_time_to_harvest(strategy, HOURS_96) and latest_base_fee < int(80e9): logger.info(f"Been longer than 96 hours and base fee < 80 for {strategy_name}") @@ -130,12 +89,12 @@ def conditional_harvest_mta(harvester, voter_proxy) -> str: if harvester.is_time_to_harvest(voter_proxy, HOURS_96) and latest_base_fee < int( 80e9 ): - logger.info(f"Been longer than 96 hours and base fee < 80 since harvestMta") + logger.info("Been longer than 96 hours and base fee < 80 since harvestMta") res = safe_harvest_mta(harvester, voter_proxy) logger.info(res) elif harvester.is_time_to_harvest(voter_proxy) and latest_base_fee < int(150e9): logger.info( - f"Been longer than 120 hours harvest no matter what since harvestMta" + "Been longer than 120 hours harvest no matter what since harvestMta" ) res = safe_harvest_mta(harvester, voter_proxy) logger.info(res) @@ -193,10 +152,10 @@ def safe_harvest_mta(harvester, voter_proxy) -> str: harvester = GeneralHarvester( web3=web3, - keeper_acl=KEEPER_ACL, + keeper_acl=ETH_KEEPER_ACL, keeper_address=keeper_address, keeper_key=keeper_key, - base_oracle_address=ETH_USD_CHAINLINK, + base_oracle_address=ETH_ETH_USD_CHAINLINK, use_flashbots=False, discord_url=discord_url, ) @@ -220,29 +179,6 @@ def safe_harvest_mta(harvester, voter_proxy) -> str: abi=get_abi(harvester.chain, "rewards_manager"), ) - # Mstable harvests - # Call harvestMta before harvesting strategies - voter_proxy = web3.eth.contract( - address=web3.toChecksumAddress(MSTABLE_VOTER_PROXY), - abi=get_abi(Network.Ethereum, "mstable_voter_proxy"), - ) - conditional_harvest_mta(harvester, voter_proxy) - # Sleep for 2 blocks before harvesting - time.sleep(30) - - # TODO: Check if it's fine if harvestMta and harvests go out of sync - for strategy_address in mstable_strategies: - strategy = web3.eth.contract( - address=web3.toChecksumAddress(strategy_address), - abi=get_abi(Network.Ethereum, "strategy"), - ) - strategy_name = strategy.functions.getName().call() - - conditional_harvest(harvester, strategy_name, strategy) - - # Sleep for 2 blocks in between harvests - time.sleep(30) - # This should be done after mstable since it removes keeper acl harvest times harvester.last_harvest_times = get_last_harvest_times( harvester.web3, diff --git a/scripts/harvest_sushi.py b/scripts/harvest_sushi.py deleted file mode 100644 index 77fa5f77..00000000 --- a/scripts/harvest_sushi.py +++ /dev/null @@ -1,41 +0,0 @@ -import logging -import os -import sys -from time import sleep - -sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../src"))) - -from sushi_harvester import SushiHarvester - -logging.basicConfig(level=logging.INFO) - -BADGER_WBTC_STRATEGY = "0x3a494D79AA78118795daad8AeFF5825C6c8dF7F1" -DIGG_WBTC_STRATEGY = "0xaa8dddfe7DFA3C3269f1910d89E4413dD006D08a" -ETH_WBTC_STRATEGY = "0x7A56d65254705B4Def63c68488C0182968C452ce" - - -def safe_harvest(harvester, sett_name, strategy): - try: - harvester.harvest(sett_name, strategy) - except Exception as e: - logging.error(f"Error running {sett_name} harvest: {e}") - - -if __name__ == "__main__": - - logger = logging.getLogger() - - while True: - - harvester = SushiHarvester() - - logger.info("+-----Harvesting BADGER WBTC LP-----+") - safe_harvest(harvester, "BADGER WBTC LP", BADGER_WBTC_STRATEGY) - - logger.info("+-----Harvesting DIGG WBTC LP-----+") - safe_harvest(harvester, "DIGG WBTC LP", DIGG_WBTC_STRATEGY) - - logger.info("+-----Harvesting ETH WBTC LP-----+") - safe_harvest(harvester, "ETH WBTC LP", ETH_WBTC_STRATEGY) - - sleep(30 * 60) diff --git a/scripts/ibbtc_fees.py b/scripts/ibbtc_fees.py index de7ddf11..59575fe5 100644 --- a/scripts/ibbtc_fees.py +++ b/scripts/ibbtc_fees.py @@ -1,26 +1,20 @@ import logging -import os -import sys -sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../src"))) -sys.path.insert( - 0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../config")) -) - -from enums import Network -from ibbtc_fee_collector import ibBTCFeeCollector -from utils import get_secret, get_node_url +from config.enums import Network +from src.ibbtc_fee_collector import ibBTCFeeCollector +from src.utils import get_node_url +from src.utils import get_secret logging.basicConfig(level=logging.INFO) if __name__ == "__main__": - logger = logging.getLogger() + logger = logging.getLogger(__name__) - keeper_key = get_node_url(Network.Ethereum) + keeper_key = get_secret("keepers/rebaser/keeper-pk", "KEEPER_KEY") keeper_address = get_secret("keepers/rebaser/keeper-address", "KEEPER_ADDRESS") - node_url = get_secret("price-bots/infura-url", "INFURA_URL") + node_url = get_node_url(Network.Ethereum) collector = ibBTCFeeCollector( keeper_address=keeper_address, keeper_key=keeper_key, web3=node_url diff --git a/scripts/one_time_harvests.py b/scripts/one_time_harvests.py index 1fc1bf55..a1de3ba9 100644 --- a/scripts/one_time_harvests.py +++ b/scripts/one_time_harvests.py @@ -1,48 +1,22 @@ import logging -import os -import sys import time + from eth_account.account import Account from flashbots import flashbot -from pathlib import Path from web3 import Web3 -sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../src"))) -sys.path.insert( - 0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../config")) -) - -from enums import Network -from general_harvester import GeneralHarvester -from utils import get_abi, get_secret, get_node_url +from config.constants import ETH_ETH_USD_CHAINLINK +from config.constants import ETH_KEEPER_ACL +from config.enums import Network +from src.general_harvester import GeneralHarvester +from src.utils import get_abi +from src.utils import get_node_url +from src.utils import get_secret logging.basicConfig(level=logging.INFO) -logger = logging.getLogger(Path(__file__).name) - -ETH_USD_CHAINLINK = "0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419" -KEEPER_ACL = "0x711A339c002386f9db409cA55b6A35a604aB6cF6" - -strategies = { - # "0xBCee2c6CfA7A4e29892c3665f464Be5536F16D95", # CVX_HELPER_STRATEGY - # "0x826048381d65a65DAa51342C51d464428d301896", # CVX_CRV_HELPER_STRATEGY - # "0xff26f400e57bf726822eacbb64fa1c52f1f27988", # HBTC_CRV_STRATEGY - # "0x1C1fD689103bbFD701b3B7D41A3807F12814033D", # PBTC_CRV_STRATEGY - # "0x2bb864cdb4856ab2d148c5ca52dd7ccec126d138", # OBTC_CRV_STRATEGY - # "0x4f3e7a4566320b2709fd1986f2e9f84053d3e2a0", # BBTC_CRV_STRATEGY - # "0x05ec4356e1acd89cc2d16adc7415c8c95e736ac1", # TRICRYPTO_CRV_STRATEGY - # "0x6582a5b139fc1c6360846efdc4440d51aad4df7b", # native.renCrv - # "0xf1ded284e891943b3e9c657d7fc376b86164ffc2", # native.sbtcCrv - # "0x522bb024c339a12be1a47229546f288c40b62d29", # native.tbtcCrv - # "0x95826C65EB1f2d2F0EDBb7EcB176563B61C60bBf", # native.uniBadgerWbtc - # "0xaaE82E3c89e15E6F26F60724f115d5012363e030", # harvest.renCrv - # "0x7A56d65254705B4Def63c68488C0182968C452ce", # native.sushiWbtcEth - # "0x3a494D79AA78118795daad8AeFF5825C6c8dF7F1", # native.sushiBadgerWbtc - # "0xaa8dddfe7DFA3C3269f1910d89E4413dD006D08a", # native.sushiDiggWbtc - # "0xf4146A176b09C664978e03d28d07Db4431525dAd", # experimental.sushiIBbtcWbtc - "0x61e16b46F74aEd8f9c2Ec6CB2dCb2258Bdfc7071", # native.renCrv - "0x647eeb5C5ED5A71621183f09F6CE8fa66b96827d", # TRICRYPTO_CRV_STRATEGY - "0x6D4BA00Fd7BB73b5aa5b3D6180c6f1B0c89f70D1" # crv ibbtc -} +logger = logging.getLogger(__name__) + +strategies = {} def safe_harvest(harvester, strategy_name, strategy) -> str: @@ -85,10 +59,10 @@ def safe_harvest(harvester, strategy_name, strategy) -> str: harvester = GeneralHarvester( web3=web3, - keeper_acl=KEEPER_ACL, + keeper_acl=ETH_KEEPER_ACL, keeper_address=keeper_address, keeper_key=keeper_key, - base_oracle_address=ETH_USD_CHAINLINK, + base_oracle_address=ETH_ETH_USD_CHAINLINK, use_flashbots=False, ) diff --git a/scripts/poly_harvest.py b/scripts/poly_harvest.py index 7b78ba69..589ed465 100644 --- a/scripts/poly_harvest.py +++ b/scripts/poly_harvest.py @@ -1,28 +1,24 @@ -import json import logging -import os -import sys from time import sleep -from web3 import Web3, contract -from web3.middleware import geth_poa_middleware -sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../src"))) -sys.path.insert( - 0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../config")) -) +from web3 import Web3 +from web3.middleware import geth_poa_middleware -from enums import Network -from general_harvester import GeneralHarvester -from utils import get_abi, get_secret, get_strategies_from_registry, get_node_url -from constants import MULTICHAIN_CONFIG +from config.constants import MULTICHAIN_CONFIG +from config.constants import POLY_OLD_STRATEGY_1 +from config.constants import POLY_OLD_STRATEGY_2 +from config.enums import Network +from src.general_harvester import GeneralHarvester +from src.utils import get_secret +from src.utils import get_strategies_from_registry logging.basicConfig(level=logging.INFO) -logger = logging.getLogger("script") +logger = logging.getLogger(__name__) INVALID_STRATS = [ - "0xDb0C3118ef1acA6125200139BEaCc5D675F37c9C", - "0xF8F02D0d41C79a1973f65A440C98acAc7eAA8Dc1", + POLY_OLD_STRATEGY_1, + POLY_OLD_STRATEGY_2, ] diff --git a/scripts/propose_centralized_oracle.py b/scripts/propose_centralized_oracle.py index a34bff2f..639d3d2c 100644 --- a/scripts/propose_centralized_oracle.py +++ b/scripts/propose_centralized_oracle.py @@ -1,23 +1,17 @@ import logging -import os -import sys -from time import sleep, time +from time import time -sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../src"))) -sys.path.insert( - 0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../config")) -) - -from enums import Network -from oracle import Oracle -from utils import get_secret, get_node_url +from config.enums import Network +from src.oracle import Oracle +from src.utils import get_node_url +from src.utils import get_secret logging.basicConfig(level=logging.INFO) if __name__ == "__main__": - logger = logging.getLogger() + logger = logging.getLogger(__name__) logger.info(f"INVOKED AT {time()}") keeper_key = get_secret("keepers/rebaser/keeper-pk", "KEEPER_KEY") diff --git a/scripts/rebalance.py b/scripts/rebalance.py index abb14a12..773533ff 100644 --- a/scripts/rebalance.py +++ b/scripts/rebalance.py @@ -1,32 +1,23 @@ import logging -import os -import sys + from eth_account.account import Account -from flashbots import flashbot -from pathlib import Path from web3 import Web3 -sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../src"))) -sys.path.insert( - 0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../config")) -) -sys.path.insert( - 0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../src/eth")) -) - -from utils import get_secret, get_abi, get_node_url -from enums import Network -from rebalancer import Rebalancer +from config.constants import ETH_ETH_USD_CHAINLINK +from config.constants import ETH_KEEPER_ACL +from config.constants import ETH_STABILIZE_STRATEGY +from config.enums import Network +from src.eth.rebalancer import Rebalancer +from src.utils import get_abi +from src.utils import get_node_url +from src.utils import get_secret logging.basicConfig(level=logging.INFO) -ETH_USD_CHAINLINK = "0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419" -KEEPER_ACL = "0x711A339c002386f9db409cA55b6A35a604aB6cF6" -STABILIZE_STRAT = "0xA6af1B913E205B8E9B95D3B30768c0989e942316" if __name__ == "__main__": - logger = logging.getLogger("script") + logger = logging.getLogger(__name__) # Load secrets keeper_key = get_secret("keepers/rebaser/keeper-pk", "KEEPER_KEY") @@ -40,15 +31,15 @@ web3 = Web3(Web3.HTTPProvider(node_url)) strategy = web3.eth.contract( - address=STABILIZE_STRAT, abi=get_abi(Network.Ethereum, "stability_strat") + address=ETH_STABILIZE_STRATEGY, abi=get_abi(Network.Ethereum, "stability_strat") ) rebalancer = Rebalancer( web3=web3, - keeper_acl=KEEPER_ACL, + keeper_acl=ETH_KEEPER_ACL, keeper_address=keeper_address, keeper_key=keeper_key, - base_oracle_address=ETH_USD_CHAINLINK, + base_oracle_address=ETH_ETH_USD_CHAINLINK, use_flashbots=False, ) diff --git a/scripts/rebase.py b/scripts/rebase.py index f97dec64..09a32da6 100644 --- a/scripts/rebase.py +++ b/scripts/rebase.py @@ -1,16 +1,9 @@ import logging -import os -import sys -from time import sleep -sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../src"))) -sys.path.insert( - 0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../config")) -) - -from enums import Network -from rebaser import Rebaser -from utils import get_secret, get_node_url +from config.enums import Network +from src.rebaser import Rebaser +from src.utils import get_node_url +from src.utils import get_secret logging.basicConfig(level=logging.INFO) @@ -24,7 +17,7 @@ def safe_rebase(harvester, sett_name, strategy): if __name__ == "__main__": - logger = logging.getLogger() + logger = logging.getLogger(__name__) keeper_key = get_secret("keepers/rebaser/keeper-pk", "KEEPER_KEY") keeper_address = get_secret("keepers/rebaser/keeper-address", "KEEPER_ADDRESS") diff --git a/scripts/stability_execute.py b/scripts/stability_execute.py index f52309cd..0538938c 100644 --- a/scripts/stability_execute.py +++ b/scripts/stability_execute.py @@ -1,31 +1,22 @@ import logging -import os -import sys + from eth_account.account import Account -from flashbots import flashbot from web3 import Web3 -sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../src"))) -sys.path.insert( - 0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../src/eth")) -) -sys.path.insert( - 0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../config")) -) - -from utils import get_secret, get_abi -from stability_executor import StabilityExecutor -from enums import Network +from config.constants import ETH_ETH_USD_CHAINLINK +from config.constants import ETH_KEEPER_ACL +from config.constants import ETH_STABILIZE_STRATEGY +from config.enums import Network +from src.eth.stability_executor import StabilityExecutor +from src.utils import get_abi +from src.utils import get_secret logging.basicConfig(level=logging.INFO) -ETH_USD_CHAINLINK = "0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419" -KEEPER_ACL = "0x711A339c002386f9db409cA55b6A35a604aB6cF6" -STABILIZE_STRAT = "0xA6af1B913E205B8E9B95D3B30768c0989e942316" if __name__ == "__main__": - logger = logging.getLogger("script") + logger = logging.getLogger(__name__) # Load secrets keeper_key = get_secret("keepers/rebaser/keeper-pk", "KEEPER_KEY") @@ -39,15 +30,15 @@ web3 = Web3(Web3.HTTPProvider(node_url)) strategy = web3.eth.contract( - address=STABILIZE_STRAT, abi=get_abi(Network.Ethereum, "stability_strat") + address=ETH_STABILIZE_STRATEGY, abi=get_abi(Network.Ethereum, "stability_strat") ) stability_executor = StabilityExecutor( web3=web3, - keeper_acl=KEEPER_ACL, + keeper_acl=ETH_KEEPER_ACL, keeper_address=keeper_address, keeper_key=keeper_key, - base_oracle_address=ETH_USD_CHAINLINK, + base_oracle_address=ETH_ETH_USD_CHAINLINK, use_flashbots=False, ) diff --git a/src/bsc/cake_harvester.py b/src/bsc/cake_harvester.py deleted file mode 100644 index 7515ddbd..00000000 --- a/src/bsc/cake_harvester.py +++ /dev/null @@ -1,249 +0,0 @@ -from decimal import Decimal -from dotenv import load_dotenv -from hexbytes import HexBytes -import json -import logging -import os -import sys -from time import sleep -from web3 import Web3, contract, exceptions - -sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../"))) - -from harvester import IHarvester -from utils import send_error_to_discord, send_success_to_discord, confirm_transaction - -load_dotenv() - -logging.basicConfig(level=logging.INFO) - -GAS_MULTIPLIER = 1.2 # use 20% more gas than node reporting -HARVEST_THRESHOLD = 2 # bnb amount rewards must exceed to claim, reward_amt (BNB) > HARVEST_THRESHOLD (BNB) -CAKE_BNB_CHAINLINK = "0xcB23da9EA243f53194CBc2380A6d4d9bC046161f" -BNB_USD_CHAINLINK = "0x0567F2323251f0Aab15c8dFb1967E4e8A7D42aeE" -CAKE_ADDRESS = "0x0e09fabb73bd3ade0a17ecc321fd13a19e81ce82" -CAKE_CHEF = "0x73feaa1eE314F8c655E354234017bE2193C9E24E" - - -class CakeHarvester(IHarvester): - def __init__( - self, - keeper_address=os.getenv("KEEPER_ADDRESS"), - keeper_key=os.getenv("KEEPER_KEY"), - web3=Web3(Web3.HTTPProvider(os.getenv("ETH_NODE_URL"))), - ): - self.logger = logging.getLogger() - self.web3 = Web3(Web3.HTTPProvider(web3)) - self.keeper_key = keeper_key - self.keeper_address = keeper_address - self.bnb_usd_oracle = self.web3.eth.contract( - address=self.web3.toChecksumAddress(BNB_USD_CHAINLINK), - abi=self.__get_abi("oracle"), - ) - self.cake_bnb_oracle = self.web3.eth.contract( - address=self.web3.toChecksumAddress(CAKE_BNB_CHAINLINK), - abi=self.__get_abi("oracle"), - ) - self.cake = self.web3.eth.contract( - address=self.web3.toChecksumAddress(CAKE_ADDRESS), - abi=self.__get_abi("cake"), - ) - self.cake_decimals = self.cake.functions.decimals().call() - self.chef = self.web3.eth.contract( - address=CAKE_CHEF, abi=self.__get_abi("chef") - ) - - def __get_abi(self, contract_id: str): - with open(f"./abi/bsc/{contract_id}.json") as f: - return json.load(f) - - def harvest( - self, - sett_name: str, - strategy_address: str, - ): - """Orchestration function that harvests outstanding Cake awards. - - Args: - sett_name (str) - strategy_address (str) - - Raises: - ValueError: If the keeper isn't whitelisted, throw an error and alert user. - """ - strategy = self.web3.eth.contract( - address=self.web3.toChecksumAddress(strategy_address), - abi=self.__get_abi("strategy"), - ) - - if not self.__is_keeper_whitelisted(strategy): - raise ValueError(f"Keeper is not whitelisted for {sett_name}") - - pool_id = strategy.functions.wantPid().call() - - claimable_rewards = self.get_harvestable_rewards_amount( - pool_id=pool_id, strategy_address=strategy_address - ) - self.logger.info(f"claimable rewards: {claimable_rewards}") - - current_price_bnb = self.get_current_rewards_price() - self.logger.info(f"current rewards price per token (BNB): {current_price_bnb}") - - should_harvest = self.is_profitable(claimable_rewards, current_price_bnb) - self.logger.info(f"Should we harvest: {should_harvest}") - - if should_harvest: - bnb_usd_price = Decimal( - self.bnb_usd_oracle.functions.latestRoundData().call()[1] / 10 ** 8 - ) - - self.__process_harvest( - strategy=strategy, - sett_name=sett_name, - overrides={ - "from": self.keeper_address, - "gas_limit": 12000000, - "allow_revert": True, - }, - harvested=claimable_rewards * current_price_bnb * bnb_usd_price, - ) - - def get_harvestable_rewards_amount( - self, - pool_id: int = None, - strategy_address: str = None, - ) -> Decimal: - """Get integer amount of outstanding awards waiting to be harvested. - - Args: - pool_id (int, optional): Pancake swap liquidity pool id. Defaults to None. - strategy_address (str, optional): Defaults to None. - - Returns: - Decimal: Integer amont of outstanding awards available for harvest. - """ - harvestable_amt = ( - self.chef.functions.pendingCake(pool_id, strategy_address).call() - / 10 ** self.cake_decimals - ) - harvestable_amt += ( - self.chef.functions.pendingCake(0, strategy_address).call() - / 10 ** self.cake_decimals - ) - return Decimal(harvestable_amt) - - def get_current_rewards_price(self) -> Decimal: - """Get price of Cake in BNB. - - Returns: - Decimal: Price per Cake denominated in BNB - """ - return Decimal( - self.cake_bnb_oracle.functions.latestRoundData().call()[1] - / 10 ** self.cake_decimals - ) - - def is_profitable(self, amount: Decimal, price_per: Decimal) -> bool: - """Checks if harvesting is profitable based on amount of awards and cost to harvest. - - Args: - amount (Decimal): Integer amount of Cake available for harvest - price_per (Decimal): Price per Cake in BNB - - Returns: - bool: True if we should harvest based on amount / cost, False otherwise - """ - bnb_amount_of_rewards = amount * price_per - return bnb_amount_of_rewards >= HARVEST_THRESHOLD - - def __is_keeper_whitelisted(self, strategy: contract) -> bool: - """Checks if the bot we're using is whitelisted for the strategy. - - Args: - strategy (contract) - - Returns: - bool: True if our bot is whitelisted to make function calls to strategy, - False otherwise. - """ - return strategy.functions.keeper().call() == self.keeper_address - - def __process_harvest( - self, - strategy: contract = None, - sett_name: str = None, - overrides: dict = None, - harvested: Decimal = None, - ): - """Private function to create, broadcast, confirm tx on bsc and then send - transaction to Discord for monitoring - - Args: - strategy (contract, optional): Defaults to None. - sett_name (str, optional): Defaults to None. - overrides (dict, optional): Dictionary settings for transaction. Defaults to None. - harvested (Decimal, optional): Amount of Cake harvested. Defaults to None. - """ - tx_hash = HexBytes(0) - try: - tx_hash = self.__send_harvest_tx(strategy, overrides) - succeeded, _ = confirm_transaction(self.web3, tx_hash) - if succeeded: - gas_price_of_tx = self.__get_gas_price_of_tx(tx_hash) - send_success_to_discord( - tx_hash, sett_name, gas_price_of_tx, harvested, "Harvest", "BSC" - ) - elif tx_hash: - send_error_to_discord(sett_name, "Harvest", tx_hash=tx_hash) - except Exception as e: - self.logger.error(f"Error processing harvest tx: {e}") - tx_hash = "invalid" if tx_hash == HexBytes(0) else tx_hash - error = e - send_error_to_discord(sett_name, "Harvest", tx_hash=tx_hash, error=error) - - def __send_harvest_tx(self, contract: contract, overrides: dict) -> HexBytes: - """Sends transaction to BSC node for confirmation. - - Args: - contract (contract) - overrides (dict) - - Raises: - Exception: If we have an issue sending transaction (unable to communicate with - node, etc.) we log the error and return a tx_hash of 0x00. - - Returns: - HexBytes: Transaction hash for transaction that was sent. - """ - try: - tx = contract.functions.harvest().buildTransaction( - { - "nonce": self.web3.eth.get_transaction_count(self.keeper_address), - "gasPrice": self._CakeHarvester__get_gas_price(), - "from": self.keeper_address, - } - ) - signed_tx = self.web3.eth.account.sign_transaction( - tx, private_key=self.keeper_key - ) - tx_hash = self.web3.eth.send_raw_transaction(signed_tx.rawTransaction) - except Exception as e: - self.logger.error(f"Error sending harvest tx: {e}") - tx_hash = HexBytes(0) - raise Exception - finally: - return tx_hash - - def __get_gas_price(self): - return int(self.web3.eth.gas_price * GAS_MULTIPLIER) - - def __get_gas_price_of_tx(self, tx_hash: HexBytes) -> Decimal: - tx = self.web3.eth.get_transaction(tx_hash) - - total_gas_used = Decimal(tx.get("gas", 0)) - gas_price_bnb = Decimal(tx.get("gasPrice", 0) / 10 ** 18) - bnb_usd = Decimal( - self.bnb_usd_oracle.functions.latestRoundData().call()[1] / 10 ** 8 - ) - - return total_gas_used * gas_price_bnb * bnb_usd diff --git a/src/earner.py b/src/earner.py index e31c27f9..cfcb3ff2 100644 --- a/src/earner.py +++ b/src/earner.py @@ -1,31 +1,27 @@ -from decimal import Decimal -from hexbytes import HexBytes -import json import logging import os -import requests -import sys import traceback -from time import sleep from typing import Tuple -from web3 import Web3, contract, exceptions - -sys.path.insert( - 0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../config")) -) -sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "./"))) - -from utils import ( - confirm_transaction, - send_error_to_discord, - send_success_to_discord, - get_abi, - get_hash_from_failed_tx_error, - get_token_price, -) -from tx_utils import get_priority_fee, get_effective_gas_price, get_gas_price_of_tx -from constants import EARN_OVERRIDE_THRESHOLD, EARN_PCT_THRESHOLD -from enums import Network, Currency + +import requests +from hexbytes import HexBytes +from web3 import Web3 +from web3 import contract + +from config.constants import BASE_CURRENCIES +from config.constants import EARN_OVERRIDE_THRESHOLD +from config.constants import EARN_PCT_THRESHOLD +from config.constants import ETH_BVECVX_STRATEGY +from config.enums import Network +from src.tx_utils import get_effective_gas_price +from src.tx_utils import get_gas_price_of_tx +from src.tx_utils import get_priority_fee +from src.utils import confirm_transaction +from src.utils import get_abi +from src.utils import get_hash_from_failed_tx_error +from src.utils import get_token_price +from src.utils import send_error_to_discord +from src.utils import send_success_to_discord logging.basicConfig(level=logging.INFO) @@ -33,8 +29,9 @@ Network.Ethereum: 1_500_000, Network.Polygon: 1_000_000, Network.Arbitrum: 3_000_000, + Network.Fantom: 1_500_000, } -EARN_EXCEPTIONS = {} +EARN_EXCEPTIONS = {ETH_BVECVX_STRATEGY: 20} class Earner: @@ -48,7 +45,7 @@ def __init__( web3: Web3 = None, discord_url: str = None, ): - self.logger = logging.getLogger("earner") + self.logger = logging.getLogger(__name__) self.chain = chain self.web3 = web3 self.keeper_key = keeper_key @@ -101,39 +98,55 @@ def get_balances( want (contract): want web3 contract object Returns: - Tuple[float, float]: want in vault denominated in eth, want in strat denominated in eth + Tuple[float, float]: want in vault denominated in selected currenct, want in strat + denominated in selected currency. """ - price_per_want_eth = get_token_price(want.address, Currency.Eth, self.chain) - self.logger.info(f"price per want: {price_per_want_eth}") + currency = BASE_CURRENCIES[self.chain] + if self.chain == Network.Fantom: + price_per_want = get_token_price( + want.address, currency, self.chain, use_staging=True + ) + else: + price_per_want = get_token_price(want.address, currency, self.chain) + + self.logger.info(f"price per want: {price_per_want} {currency}") + want_decimals = want.functions.decimals().call() vault_balance = want.functions.balanceOf(vault.address).call() strategy_balance = strategy.functions.balanceOf().call() - vault_balance_eth = price_per_want_eth * vault_balance / 10 ** want_decimals - strategy_balance_eth = ( - price_per_want_eth * strategy_balance / 10 ** want_decimals - ) + vault_balance = price_per_want * vault_balance / 10 ** want_decimals + strategy_balance = price_per_want * strategy_balance / 10 ** want_decimals - return vault_balance_eth, strategy_balance_eth + return vault_balance, strategy_balance def should_earn( self, override_threshold: int, vault_balance: int, strategy_balance: int ) -> bool: # Always allow earn on first run - if strategy_balance == 0 and vault_balance > 0: - self.logger.info("No strategy balance, earn") - return True + self.logger.info( + {"strategy_balance": strategy_balance, "vault_balance": vault_balance} + ) + if strategy_balance == 0: + if vault_balance == 0: + self.logger.info("No strategy balance or vault balance") + return False + else: + self.logger.info("No strategy balance, earn") + return True # Earn if deposits have accumulated over a static threshold if vault_balance >= override_threshold: self.logger.info( - f"Vault balance of {vault_balance} over earn threshold override of {override_threshold}" + f"Vault balance of {vault_balance} " + f"over earn threshold override of {override_threshold}" ) return True # Earn if deposits have accumulated over % threshold if vault_balance / strategy_balance > EARN_PCT_THRESHOLD: self.logger.info( - f"Vault balance of {vault_balance} and strategy balance of {strategy_balance} over standard % threshold of {EARN_PCT_THRESHOLD}" + f"Vault balance of {vault_balance} and strategy balance " + f"of {strategy_balance} over standard % threshold of {EARN_PCT_THRESHOLD}" ) return True @@ -143,7 +156,9 @@ def should_earn( "vault_balance": vault_balance, "strategy_balance": strategy_balance, "override_threshold": override_threshold, - "vault_to_strategy_ratio": vault_balance / strategy_balance, + "vault_to_strategy_ratio": vault_balance / strategy_balance + if strategy_balance > 0 + else 0, } ) return False @@ -242,7 +257,7 @@ def __get_gas_price(self) -> int: gas_price = self.web3.toWei(int(response.get("fast") * 1.1), "gwei") elif self.chain == Network.Ethereum: gas_price = get_effective_gas_price(self.web3) - elif self.chain == Network.Arbitrum: + elif self.chain in [Network.Arbitrum, Network.Fantom]: gas_price = int(1.1 * self.web3.eth.gas_price) return gas_price diff --git a/src/eth/rebalancer.py b/src/eth/rebalancer.py index 6246a4c6..47333035 100644 --- a/src/eth/rebalancer.py +++ b/src/eth/rebalancer.py @@ -1,34 +1,25 @@ -from decimal import Decimal -from hexbytes import HexBytes import logging import os -import sys -from time import sleep -from web3 import Web3, contract, exceptions - -sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../"))) -sys.path.insert( - 0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../../config")) -) - -from enums import Network -from utils import ( - confirm_transaction, - send_error_to_discord, - send_success_to_discord, - get_abi, -) -from tx_utils import ( - get_gas_price_of_tx, - get_effective_gas_price, - get_priority_fee, -) +from decimal import Decimal + +from hexbytes import HexBytes +from web3 import Web3 +from web3 import contract + +from config.constants import DIGG +from config.enums import Network +from src.tx_utils import get_effective_gas_price +from src.tx_utils import get_gas_price_of_tx +from src.tx_utils import get_priority_fee +from src.utils import confirm_transaction +from src.utils import get_abi +from src.utils import send_error_to_discord +from src.utils import send_success_to_discord logging.basicConfig(level=logging.INFO) GAS_LIMIT = 1000000 MAX_GAS_PRICE = int(200e9) # 200 gwei -DIGG_TOKEN = "0x798D1bE841a82a273720CE31c822C61a67a601C3" NUM_FLASHBOTS_BUNDLES = 6 @@ -43,7 +34,7 @@ def __init__( base_oracle_address: str = os.getenv("ETH_USD_CHAINLINK"), use_flashbots=False, ): - self.logger = logging.getLogger("rebalancer") + self.logger = logging.getLogger(__name__) self.chain = chain self.web3 = web3 self.keeper_key = keeper_key @@ -57,7 +48,7 @@ def __init__( abi=get_abi(self.chain, "oracle"), ) self.digg = self.web3.eth.contract( - address=self.web3.toChecksumAddress(DIGG_TOKEN), + address=self.web3.toChecksumAddress(DIGG), abi=get_abi(self.chain, "erc20"), ) diff --git a/src/eth/stability_executor.py b/src/eth/stability_executor.py index 33d4f3f0..418a22b8 100644 --- a/src/eth/stability_executor.py +++ b/src/eth/stability_executor.py @@ -1,27 +1,19 @@ -from decimal import Decimal -from hexbytes import HexBytes import logging import os -import sys -from web3 import Web3, contract - -sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../"))) -sys.path.insert( - 0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../../config")) -) - -from utils import ( - confirm_transaction, - send_error_to_discord, - send_success_to_discord, - get_abi, -) -from tx_utils import ( - get_gas_price_of_tx, - get_effective_gas_price, - get_priority_fee, -) -from enums import Network +from decimal import Decimal + +from hexbytes import HexBytes +from web3 import Web3 +from web3 import contract + +from config.enums import Network +from src.tx_utils import get_effective_gas_price +from src.tx_utils import get_gas_price_of_tx +from src.tx_utils import get_priority_fee +from src.utils import confirm_transaction +from src.utils import get_abi +from src.utils import send_error_to_discord +from src.utils import send_success_to_discord logging.basicConfig(level=logging.INFO) @@ -41,7 +33,7 @@ def __init__( base_oracle_address: str = os.getenv("ETH_USD_CHAINLINK"), use_flashbots=False, ): - self.logger = logging.getLogger("stability-executor") + self.logger = logging.getLogger(__name__) self.chain = chain self.web3 = web3 self.keeper_key = keeper_key diff --git a/src/eth/sushi_harvester.py b/src/eth/sushi_harvester.py deleted file mode 100644 index 1701022c..00000000 --- a/src/eth/sushi_harvester.py +++ /dev/null @@ -1,279 +0,0 @@ -from decimal import Decimal -from dotenv import load_dotenv -from hexbytes import HexBytes -import json -import logging -import os -import requests -import sys -from time import sleep -from web3 import Web3, contract, exceptions - -sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../"))) - -from harvester import IHarvester -from utils import confirm_transaction, send_error_to_discord, send_success_to_discord - -load_dotenv() - -logging.basicConfig(level=logging.INFO) - -ETH_USD_CHAINLINK = "0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419" -SUSHI_ETH_CHAINLINK = "0xe572CeF69f43c2E488b33924AF04BDacE19079cf" -WBTC_ETH_STRATEGY = "0x7A56d65254705B4Def63c68488C0182968C452ce" -WBTC_DIGG_STRATEGY = "0xaa8dddfe7DFA3C3269f1910d89E4413dD006D08a" -WBTC_BADGER_STRATEGY = "0x3a494D79AA78118795daad8AeFF5825C6c8dF7F1" -SUSHI_ADDRESS = "0x6b3595068778dd592e39a122f4f5a5cf09c90fe2" -XSUSHI_ADDRESS = "0x8798249c2E607446EfB7Ad49eC89dD1865Ff4272" -FEE_THRESHOLD = 0.01 # ratio of gas cost to harvest amount we're ok with - - -class SushiHarvester(IHarvester): - def __init__( - self, - keeper_address=os.getenv("KEEPER_ADDRESS"), - keeper_key=os.getenv("KEEPER_KEY"), - web3=Web3(Web3.HTTPProvider(os.getenv("ETH_NODE_URL"))), - ): - self.logger = logging.getLogger() - self.web3 = web3 - self.keeper_key = keeper_key - self.keeper_address = keeper_address - self.eth_usd_oracle = self.web3.eth.contract( - address=self.web3.toChecksumAddress(ETH_USD_CHAINLINK), - abi=self.__get_abi("oracle"), - ) - self.sushi_eth_oracle = self.web3.eth.contract( - address=self.web3.toChecksumAddress(SUSHI_ETH_CHAINLINK), - abi=self.__get_abi("oracle"), - ) - self.sushi = self.web3.eth.contract( - address=self.web3.toChecksumAddress(SUSHI_ADDRESS), - abi=self.__get_abi("sushi"), - ) - self.xsushi = self.web3.eth.contract( - address=self.web3.toChecksumAddress(XSUSHI_ADDRESS), - abi=self.__get_abi("xsushi"), - ) - self.sushi_decimals = self.sushi.functions.decimals().call() - - def __get_abi(self, contract_id: str): - with open(f"./abi/eth/{contract_id}.json") as f: - return json.load(f) - - def harvest( - self, - sett_name: str, - strategy_address: str, - ): - """Orchestration function that harvests outstanding Sushi awards. - - Args: - sett_name (str) - strategy_address (str) - - Raises: - ValueError: If the keeper isn't whitelisted, throw an error and alert user. - """ - strategy = self.web3.eth.contract( - address=self.web3.toChecksumAddress(strategy_address), - abi=self.__get_abi("strategy"), - ) - - if not self.__is_keeper_whitelisted(strategy): - raise ValueError(f"Keeper is not whitelisted for {sett_name}") - - pool_id = strategy.functions.pid().call() - - claimable_rewards = self.get_harvestable_rewards_amount( - pool_id=pool_id, strategy_address=strategy_address - ) - self.logger.info(f"claimable rewards: {claimable_rewards}") - - current_price_eth = self.get_current_rewards_price() - self.logger.info(f"current rewards price per token (ETH): {current_price_eth}") - - gas_fee = self.estimate_gas_fee(strategy) - - should_harvest = self.is_profitable( - claimable_rewards, current_price_eth, gas_fee - ) - self.logger.info(f"Should we harvest: {should_harvest}") - - if should_harvest: - eth_usd_price = Decimal( - self.eth_usd_oracle.functions.latestRoundData().call()[1] / 10 ** 8 - ) - - self.__process_harvest( - strategy=strategy, - sett_name=sett_name, - overrides={ - "from": self.keeper_address, - "gas_limit": 12000000, - "allow_revert": True, - }, - harvested=claimable_rewards * current_price_eth * eth_usd_price, - ) - - def get_harvestable_rewards_amount( - self, - pool_id: int = None, - strategy_address: str = None, - ) -> Decimal: - """Get integer amount of outstanding awards waiting to be harvested. - - Args: - pool_id (int, optional): Sushi swap liquidity pool id. Defaults to None. - strategy_address (str, optional): Defaults to None. - - Returns: - Decimal: Integer amont of outstanding awards available for harvest. - """ - harvestable_amt = ( - self.xsushi.functions.balanceOf(strategy_address).call() - / 10 ** self.sushi_decimals - ) - return Decimal(harvestable_amt) - - def get_current_rewards_price(self) -> Decimal: - """Get price of Sushi in ETH. - - Returns: - Decimal: Price per Sushi denominated in ETH - """ - ratio = ( - self.sushi.functions.balanceOf(self.xsushi.address).call() - / self.xsushi.functions.totalSupply().call() - ) - return Decimal( - ( - self.sushi_eth_oracle.functions.latestRoundData().call()[1] - / 10 ** self.sushi_decimals - ) - * ratio - ) - - def is_profitable( - self, amount: Decimal, price_per: Decimal, gas_fee: Decimal - ) -> bool: - """Checks if harvesting is profitable based on amount of awards and cost to harvest. - - Args: - amount (Decimal): Integer amount of Sushi available for harvest - price_per (Decimal): Price per Sushi in ETH - gas_fee (Decimal): gas fee in wei - - Returns: - bool: True if we should harvest based on amount / cost, False otherwise - """ - gas_fee_ether = self.web3.fromWei(gas_fee, "ether") - fee_percent_of_claim = ( - 1 if amount * price_per == 0 else gas_fee_ether / (amount * price_per) - ) - self.logger.info( - f"Fee as percent of harvest: {round(fee_percent_of_claim * 100, 2)}%" - ) - return fee_percent_of_claim <= FEE_THRESHOLD - - def __is_keeper_whitelisted(self, strategy: contract) -> bool: - """Checks if the bot we're using is whitelisted for the strategy. - - Args: - strategy (contract) - - Returns: - bool: True if our bot is whitelisted to make function calls to strategy, - False otherwise. - """ - return strategy.functions.keeper().call() == self.keeper_address - - def __process_harvest( - self, - strategy: contract = None, - sett_name: str = None, - overrides: dict = None, - harvested: Decimal = None, - ): - """Private function to create, broadcast, confirm tx on eth and then send - transaction to Discord for monitoring - - Args: - strategy (contract, optional): Defaults to None. - sett_name (str, optional): Defaults to None. - overrides (dict, optional): Dictionary settings for transaction. Defaults to None. - harvested (Decimal, optional): Amount of Sushi harvested. Defaults to None. - """ - error = None - try: - tx_hash = self.__send_harvest_tx(strategy, overrides) - succeeded, _ = confirm_transaction(self.web3, tx_hash) - if succeeded: - gas_price_of_tx = self.__get_gas_price_of_tx(tx_hash) - send_success_to_discord( - tx_hash, sett_name, gas_price_of_tx, harvested, "Harvest" - ) - elif tx_hash: - send_error_to_discord(sett_name, "Harvest", tx_hash=tx_hash) - except Exception as e: - self.logger.error(f"Error processing harvest tx: {e}") - error = e - send_error_to_discord(sett_name, "Harvest", error=error) - - def __send_harvest_tx(self, contract: contract, overrides: dict) -> HexBytes: - """Sends transaction to ETH node for confirmation. - - Args: - contract (contract) - overrides (dict) - - Raises: - Exception: If we have an issue sending transaction (unable to communicate with - node, etc.) we log the error and return a tx_hash of 0x00. - - Returns: - HexBytes: Transaction hash for transaction that was sent. - """ - try: - tx = contract.functions.harvest().buildTransaction( - { - "nonce": self.web3.eth.get_transaction_count(self.keeper_address), - "gasPrice": self.__get_gas_price(), - "gas": 12000000, - "from": self.keeper_address, - } - ) - signed_tx = self.web3.eth.account.sign_transaction( - tx, private_key=self.keeper_key - ) - tx_hash = self.web3.eth.send_raw_transaction(signed_tx.rawTransaction) - except Exception as e: - self.logger.error(f"Error in sending harvest tx: {e}") - tx_hash = HexBytes(0) - raise Exception - finally: - return tx_hash - - def estimate_gas_fee(self, strategy: contract) -> Decimal: - current_gas_price = self.__get_gas_price() - estimated_gas_to_harvest = strategy.functions.harvest().estimateGas( - {"from": strategy.functions.keeper().call()} - ) - return Decimal(current_gas_price * estimated_gas_to_harvest) - - def __get_gas_price(self) -> int: - response = requests.get( - "https://www.gasnow.org/api/v3/gas/price?utm_source=BadgerKeeper" - ) - return int(response.json().get("data").get("rapid") * 1.1) - - def __get_gas_price_of_tx(self, tx_hash: HexBytes) -> Decimal: - tx = self.web3.eth.get_transaction(tx_hash) - - total_gas_used = Decimal(tx.get("gas", 0)) - gas_price_eth = Decimal(tx.get("gasPrice", 0) / 10 ** 18) - eth_usd = Decimal( - self.eth_usd_oracle.functions.latestRoundData().call()[1] / 10 ** 8 - ) - - return total_gas_used * gas_price_eth * eth_usd diff --git a/src/eth/sushi_tender.py b/src/eth/sushi_tender.py deleted file mode 100644 index 0eb5eb10..00000000 --- a/src/eth/sushi_tender.py +++ /dev/null @@ -1,158 +0,0 @@ -from decimal import Decimal -from hexbytes import HexBytes -import os -from src.eth.sushi_harvester import SushiHarvester -import sys -from web3 import contract - -sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../"))) - -from src.utils import send_success_to_discord, send_error_to_discord - -CHEF = "0xc2EdaD668740f1aA35E4D8f227fB8E17dcA888Cd" - - -class SushiTender(SushiHarvester): - def tend( - self, - sett_name: str, - strategy_address: str, - ): - """Orchestration function that tends outstanding Sushi awards. - - Args: - sett_name (str) - strategy_address (str) - - Raises: - ValueError: If the keeper isn't whitelisted, throw an error and alert user. - """ - strategy = self.web3.eth.contract( - address=self.web3.toChecksumAddress(strategy_address), - abi=self._SushiHarvester__get_abi("strategy"), - ) - - if not self._SushiHarvester__is_keeper_whitelisted(strategy): - raise ValueError(f"Keeper is not whitelisted for {sett_name}") - - pool_id = strategy.functions.pid().call() - - claimable_rewards = self.get_tendable_rewards_amount( - pool_id=pool_id, strategy_address=strategy_address - ) - self.logger.info(f"claimable rewards: {claimable_rewards}") - - current_price_eth = self.get_current_rewards_price() - self.logger.info(f"current rewards price per token (ETH): {current_price_eth}") - - gas_fee = self.estimate_gas_fee(strategy) - - should_tend = self.is_profitable(claimable_rewards, current_price_eth, gas_fee) - self.logger.info(f"Should we tend: {should_tend}") - - if should_tend: - eth_usd_price = Decimal( - self.eth_usd_oracle.functions.latestRoundData().call()[1] / 10 ** 8 - ) - - self.__process_tend( - strategy=strategy, - sett_name=sett_name, - overrides={ - "from": self.keeper_address, - "gas_limit": 12000000, - "allow_revert": True, - }, - tended=claimable_rewards * current_price_eth * eth_usd_price, - ) - - def get_tendable_rewards_amount(self, pool_id: int, strategy_address: str): - chef = self.web3.eth.contract( - address=self.web3.toChecksumAddress(CHEF), - abi=self._SushiHarvester__get_abi("chef"), - ) - claimable_rewards = ( - chef.functions.pendingSushi(pool_id, strategy_address).call() - / 10 ** self.sushi_decimals - ) - return Decimal(claimable_rewards) - - def get_current_rewards_price(self) -> Decimal: - """Get price of Sushi in ETH. - - Returns: - Decimal: Price per Sushi denominated in ETH - """ - return Decimal( - self.sushi_eth_oracle.functions.latestRoundData().call()[1] - / 10 ** self.sushi_decimals - ) - - def __process_tend( - self, - strategy: contract, - sett_name: str, - overrides: dict, - tended: Decimal, - ): - """Private function to create, broadcast, confirm tx on eth and then send - transaction to Discord for monitoring - - Args: - strategy (contract, optional): Defaults to None. - sett_name (str, optional): Defaults to None. - overrides (dict, optional): Dictionary settings for transaction. Defaults to None. - tended (Decimal, optional): Amount of Sushi tended. Defaults to None. - """ - error = None - try: - tx_hash = self.__send_tend_tx(strategy, overrides) - succeeded, _ = self.confirm_transaction(self.web3, tx_hash) - except Exception as e: - self.logger.error(f"Error processing tend tx: {e}") - tx_hash = "invalid" if tx_hash == HexBytes(0) else tx_hash - succeeded = False - error = e - finally: - succeeded, _ = self.confirm_transaction(self.web3, tx_hash) - if succeeded: - gas_price_of_tx = self._SushiHarvester__get_gas_price_of_tx(tx_hash) - send_success_to_discord( - tx_hash, sett_name, gas_price_of_tx, tended, "Tend" - ) - elif tx_hash: - send_error_to_discord(sett_name, "Tend", tx_hash=tx_hash) - - def __send_tend_tx(self, contract: contract, overrides: dict) -> HexBytes: - """Sends transaction to ETH node for confirmation. - - Args: - contract (contract) - overrides (dict) - - Raises: - Exception: If we have an issue sending transaction (unable to communicate with - node, etc.) we log the error and return a tx_hash of 0x00. - - Returns: - HexBytes: Transaction hash for transaction that was sent. - """ - try: - tx = contract.functions.tend().buildTransaction( - { - "nonce": self.web3.eth.get_transaction_count(self.keeper_address), - "gasPrice": self._SushiHarvester__get_gas_price(), - "gas": 12000000, - "from": self.keeper_address, - } - ) - signed_tx = self.web3.eth.account.sign_transaction( - tx, private_key=self.keeper_key - ) - tx_hash = self.web3.eth.send_raw_transaction(signed_tx.rawTransaction) - except Exception as e: - self.logger.error(f"Error in sending tend tx: {e}") - tx_hash = HexBytes(0) - raise Exception - finally: - return tx_hash diff --git a/src/general_harvester.py b/src/general_harvester.py index e92d7ffd..f850f8ca 100644 --- a/src/general_harvester.py +++ b/src/general_harvester.py @@ -1,42 +1,36 @@ import logging import os -import requests -import sys from decimal import Decimal -from hexbytes import HexBytes from time import sleep -from web3 import Web3, contract, exceptions - -sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "./"))) -sys.path.insert( - 0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../config")) -) - -from constants import MULTICHAIN_CONFIG, BASE_CURRENCIES -from enums import Network, Currency -from harvester import IHarvester -from utils import ( - confirm_transaction, - hours, - send_error_to_discord, - send_success_to_discord, - get_abi, - get_hash_from_failed_tx_error, - get_last_harvest_times, - seconds_to_blocks, -) -from tx_utils import get_priority_fee, get_effective_gas_price, get_gas_price_of_tx + +import requests +from hexbytes import HexBytes +from web3 import Web3 +from web3 import contract + +from config.constants import BASE_CURRENCIES +from config.constants import GAS_LIMITS +from config.constants import MULTICHAIN_CONFIG +from config.enums import Network +from src.harvester import IHarvester +from src.tx_utils import get_effective_gas_price +from src.tx_utils import get_gas_price_of_tx +from src.tx_utils import get_priority_fee +from src.utils import confirm_transaction +from src.utils import get_abi +from src.utils import get_hash_from_failed_tx_error +from src.utils import get_last_harvest_times +from src.utils import get_token_price +from src.utils import hours +from src.utils import seconds_to_blocks +from src.utils import send_error_to_discord +from src.utils import send_success_to_discord logging.basicConfig(level=logging.INFO) MAX_TIME_BETWEEN_HARVESTS = hours(120) HARVEST_THRESHOLD = 0.0005 # min ratio of want to total vault AUM required to harvest -GAS_LIMITS = { - Network.Ethereum: 6_000_000, - Network.Polygon: 1_000_000, - Network.Arbitrum: 3_000_000, -} NUM_FLASHBOTS_BUNDLES = 6 @@ -52,7 +46,7 @@ def __init__( use_flashbots: bool = False, discord_url: str = None, ): - self.logger = logging.getLogger("harvester") + self.logger = logging.getLogger(__name__) self.chain = chain self.web3 = web3 self.keeper_key = keeper_key @@ -86,12 +80,13 @@ def is_time_to_harvest( harvest_interval_threshold: int = MAX_TIME_BETWEEN_HARVESTS, ) -> bool: """Calculates the time between harvests for the supplied strategy and returns true if - it has been longer than the supplied harvest_interval_threshold which is measured in seconds. + it has been longer than the supplied harvest_interval_threshold which is measured in seconds Args: strategy (contract): Vault strategy web3 contract object - harvest_interval_threshold (int, optional): Amount of time in seconds that is acceptable to not - have harvested within. Defaults to MAX_TIME_BETWEEN_HARVESTS. + harvest_interval_threshold (int, optional): + Amount of time in seconds that is acceptable to not have harvested within. + Defaults to MAX_TIME_BETWEEN_HARVESTS. Returns: bool: True if time since last harvest is > harvest_interval_threshold, else False @@ -127,7 +122,7 @@ def harvest( # TODO: update for ACL if not self.__is_keeper_whitelisted("harvest"): - raise ValueError(f"Keeper ACL is not whitelisted for calling harvest") + raise ValueError("Keeper ACL is not whitelisted for calling harvest") want_address = strategy.functions.want().call() want = self.web3.eth.contract( @@ -169,7 +164,7 @@ def harvest_no_return( # TODO: update for ACL if not self.__is_keeper_whitelisted("harvestNoReturn"): raise ValueError( - f"Keeper ACL is not whitelisted for calling harvestNoReturn" + "Keeper ACL is not whitelisted for calling harvestNoReturn" ) want_address = strategy.functions.want().call() @@ -235,7 +230,7 @@ def harvest_mta( ): # TODO: update for ACL if not self.__is_keeper_whitelisted("harvestMta"): - raise ValueError(f"Keeper ACL is not whitelisted for calling harvestMta") + raise ValueError("Keeper ACL is not whitelisted for calling harvestMta") gas_fee = self.estimate_gas_fee(voter_proxy.address, function="harvestMta") self.logger.info(f"estimated gas cost: {gas_fee}") @@ -250,7 +245,7 @@ def tend(self, strategy: contract): strategy_name = strategy.functions.getName().call() # TODO: update for ACL if not self.__is_keeper_whitelisted("tend"): - raise ValueError(f"Keeper ACL is not whitelisted for calling tend") + raise ValueError("Keeper ACL is not whitelisted for calling tend") # TODO: figure out how to handle profit estimation # current_price_eth = self.get_current_rewards_price() @@ -279,13 +274,14 @@ def estimate_harvest_amount(self, strategy: contract) -> Decimal: ) # call badger api to get prices currency = BASE_CURRENCIES[self.chain] - chain = self.chain - prices = requests.get( - f"https://api.badger.finance/v2/prices?currency={currency}&chain={chain}" - ).json() - # Price of want token in ETH - price_per_want = prices.get(want.address) - self.logger.info(f"price per want: {price_per_want}") + if self.chain == Network.Fantom: + price_per_want = get_token_price( + want.address, currency, self.chain, use_staging=True + ) + else: + price_per_want = get_token_price(want.address, currency, self.chain) + + self.logger.info(f"price per want: {price_per_want} {currency}") self.logger.info(f"want gained: {want_gained}") if type(want_gained) is list: want_gained = 0 @@ -372,7 +368,8 @@ def __process_harvest( self.web3, tx_hash, max_block=max_target_block ) if succeeded: - # If successful, update last harvest harvest time to make sure we don't double harvest + # If successful, update last harvest harvest + # time to make sure we don't double harvest self.update_last_harvest_time(strategy.address) gas_price_of_tx = get_gas_price_of_tx( self.web3, self.base_usd_oracle, tx_hash, self.chain @@ -435,7 +432,7 @@ def __process_harvest_mta( ) self.logger.info(f"got gas price of tx: {gas_price_of_tx}") send_success_to_discord( - tx_type=f"Harvest MTA", + tx_type="Harvest MTA", tx_hash=tx_hash, gas_cost=gas_price_of_tx, chain=self.chain, @@ -443,7 +440,7 @@ def __process_harvest_mta( ) elif tx_hash != HexBytes(0): send_success_to_discord( - tx_type=f"Harvest MTA", + tx_type="Harvest MTA", tx_hash=tx_hash, chain=self.chain, url=self.discord_url, @@ -669,7 +666,7 @@ def __get_effective_gas_price(self) -> int: if self.chain == Network.Polygon: response = requests.get("https://gasstation-mainnet.matic.network").json() gas_price = self.web3.toWei(int(response.get("fast") * 1.1), "gwei") - elif self.chain == Network.Arbitrum: + elif self.chain in [Network.Arbitrum, Network.Fantom]: gas_price = int(1.1 * self.web3.eth.gas_price) # Estimated gas price + buffer elif self.chain == Network.Ethereum: diff --git a/src/ibbtc_fee_collector.py b/src/ibbtc_fee_collector.py index 2e4d40e6..90cc94f1 100644 --- a/src/ibbtc_fee_collector.py +++ b/src/ibbtc_fee_collector.py @@ -1,35 +1,25 @@ -from decimal import Decimal -from enum import Enum -from hexbytes import HexBytes import json import logging import os -import requests -import sys -import time - -sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../src"))) -sys.path.insert( - 0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../config")) -) - -from enums import Network -from utils import ( - get_secret, - hours, - confirm_transaction, - get_hash_from_failed_tx_error, - send_success_to_discord, - send_oracle_error_to_discord, -) -from tx_utils import get_priority_fee, get_effective_gas_price, get_gas_price_of_tx -from web3 import Web3, contract, exceptions - -IBBTC_CORE_ADDRESS = "0x2A8facc9D49fBc3ecFf569847833C380A13418a8" -BTC_ETH_CHAINLINK = "0xdeb288F737066589598e9214E782fa5A8eD689e8" +from decimal import Decimal + +from hexbytes import HexBytes +from web3 import Web3 + +from config.constants import ETH_BTC_ETH_CHAINLINK +from config.constants import ETH_ETH_USD_CHAINLINK +from config.constants import GAS_LIMITS +from config.constants import IBBTC_CORE_ADDRESS +from config.enums import Network +from src.tx_utils import get_effective_gas_price +from src.tx_utils import get_gas_price_of_tx +from src.tx_utils import get_priority_fee +from src.utils import confirm_transaction +from src.utils import get_hash_from_failed_tx_error +from src.utils import send_oracle_error_to_discord +from src.utils import send_success_to_discord FEE_THRESHOLD = 0.01 # ratio of gas cost to harvest amount we're ok with -MAX_GAS_PRICE = int(200e9) class ibBTCFeeCollector: @@ -39,20 +29,20 @@ def __init__( keeper_key=os.getenv("KEEPER_KEY"), web3=os.getenv("ETH_NODE_URL"), ): - self.logger = logging.getLogger("ibBTC-fee-collector") + self.logger = logging.getLogger(__name__) self.web3 = Web3(Web3.HTTPProvider(web3)) # get secret here self.keeper_key = keeper_key # get secret here self.keeper_address = keeper_address # get secret here self.eth_usd_oracle = self.web3.eth.contract( - address=self.web3.toChecksumAddress(os.getenv("ETH_USD_CHAINLINK")), + address=self.web3.toChecksumAddress(ETH_ETH_USD_CHAINLINK), abi=self.__get_abi("oracle"), ) self.btc_eth_oracle = self.web3.eth.contract( - address=self.web3.toChecksumAddress(os.getenv("BTC_ETH_CHAINLINK")), + address=self.web3.toChecksumAddress(ETH_BTC_ETH_CHAINLINK), abi=self.__get_abi("oracle"), ) self.ibbtc = self.web3.eth.contract( - address=self.web3.toChecksumAddress(os.getenv("IBBTC_CORE_ADDRESS")), + address=self.web3.toChecksumAddress(IBBTC_CORE_ADDRESS), abi=self.__get_abi("ibbtc_core"), ) @@ -70,7 +60,7 @@ def collect_fees(self): # Collect fees if profitable if is_profitable: - self.logger.info(f"Collecting profitable, beginning transaction submission") + self.logger.info("Collecting profitable, beginning transaction submission") self.__process_collection() else: @@ -135,15 +125,19 @@ def __send_collection_tx(self) -> HexBytes: options = { "nonce": self.web3.eth.get_transaction_count(self.keeper_address), "from": self.keeper_address, + "gas": GAS_LIMITS[Network.Ethereum], "maxPriorityFeePerGas": get_priority_fee(self.web3), - "maxFeePerGas": MAX_GAS_PRICE, + "maxFeePerGas": get_effective_gas_price(self.web3), } + tx_hash = HexBytes(0) try: tx = self.ibbtc.functions.collectFee().buildTransaction(options) signed_tx = self.web3.eth.account.sign_transaction( tx, private_key=self.keeper_key ) - tx_hash = self.web3.eth.send_raw_transaction(signed_tx.rawTransaction) + tx_hash = signed_tx.hash + + self.web3.eth.send_raw_transaction(signed_tx.rawTransaction) except ValueError as e: self.logger.error(f"Error in sending collection tx: {e}") tx_hash = get_hash_from_failed_tx_error( diff --git a/src/oracle.py b/src/oracle.py index 5a104b85..cc10d628 100644 --- a/src/oracle.py +++ b/src/oracle.py @@ -1,34 +1,34 @@ -from datetime import datetime, timezone -from decimal import Decimal -from hexbytes import HexBytes -from traceback import format_exc import json import logging import os +from datetime import datetime +from datetime import timezone +from traceback import format_exc + import requests -import sys -from web3 import Web3, contract, exceptions - -sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../src"))) -sys.path.insert( - 0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../config")) -) - -from enums import Network -from utils import ( - get_abi, - get_secret, - hours, - confirm_transaction, - get_hash_from_failed_tx_error, - send_success_to_discord, - send_oracle_error_to_discord, -) -from tx_utils import get_priority_fee, get_gas_price_of_tx, get_effective_gas_price +from hexbytes import HexBytes +from web3 import Web3 + +from config.constants import DIGG_CENTRALIZED_ORACLE +from config.constants import DIGG_CHAINLINK_FORWARDER +from config.constants import ETH_DIGG_BTC_CHAINLINK +from config.constants import ETH_ETH_USD_CHAINLINK +from config.constants import SUSHI_DIGG_WBTC +from config.constants import SUSHI_SUBGRAPH +from config.constants import UNIV2_DIGG_WBTC +from config.constants import UNI_SUBGRAPH +from config.enums import Network +from src.tx_utils import get_effective_gas_price +from src.tx_utils import get_gas_price_of_tx +from src.tx_utils import get_priority_fee +from src.utils import confirm_transaction +from src.utils import get_abi +from src.utils import get_hash_from_failed_tx_error +from src.utils import send_oracle_error_to_discord +from src.utils import send_success_to_discord # push report to centralizedOracle REPORT_TIME_UTC = {"hour": 18, "minute": 30, "second": 0, "microsecond": 0} -WETH_ADDRESS = "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" GAS_LIMIT = 200_000 NEGATIVE_THRESHOLD = 0.95 @@ -40,30 +40,30 @@ def __init__( keeper_key=os.getenv("KEEPER_KEY"), web3=os.getenv("ETH_NODE_URL"), ): - self.logger = logging.getLogger("oracle") + self.logger = logging.getLogger(__name__) self.web3 = Web3(Web3.HTTPProvider(web3)) self.keeper_key = keeper_key self.keeper_address = keeper_address self.eth_usd_oracle = self.web3.eth.contract( - address=self.web3.toChecksumAddress(os.getenv("ETH_USD_CHAINLINK")), + address=self.web3.toChecksumAddress(ETH_ETH_USD_CHAINLINK), abi=get_abi(Network.Ethereum, "oracle"), ) self.centralized_oracle = self.web3.eth.contract( - address=self.web3.toChecksumAddress(os.getenv("CENTRALIZED_ORACLE")), + address=self.web3.toChecksumAddress(DIGG_CENTRALIZED_ORACLE), abi=get_abi(Network.Ethereum, "digg_centralized_oracle"), ) self.chainlink_forwarder = self.web3.eth.contract( - address=self.web3.toChecksumAddress(os.getenv("CHAINLINK_FORWARDER")), + address=self.web3.toChecksumAddress(DIGG_CHAINLINK_FORWARDER), abi=get_abi(Network.Ethereum, "chainlink_forwarder"), ) self.digg_btc_chainlink = self.web3.eth.contract( - address=self.web3.toChecksumAddress(os.getenv("DIGG_BTC_CHAINLINK")), + address=self.web3.toChecksumAddress(ETH_DIGG_BTC_CHAINLINK), abi=get_abi(Network.Ethereum, "oracle"), ) def is_negative_rebase(self): price = self.digg_btc_chainlink.functions.latestAnswer().call() - self.logger.info(f"price: [{price} - {price / 10**8}]") + self.logger.info(f"price: [{price} - {price / 10 ** 8}]") return price / 10 ** 8 < NEGATIVE_THRESHOLD def propose_centralized_report_push(self): @@ -167,15 +167,12 @@ def get_digg_twap_centralized(self) -> int: """Calculates 24 hour TWAP for digg based on sushi and uni wbtc / digg pools Returns: - [int]: average of 24 hour TWAP for sushi and uni wbtc / digg pools time 10^18 (digg decimal places) + [int]: average of 24 hour TWAP for sushi and + uni wbtc / digg pools time 10^18 (digg decimal places) """ - uni_twap_data = self.send_twap_query( - "uni", os.getenv("UNI_SUBGRAPH"), os.getenv("UNI_PAIR") - ) - sushi_twap_data = self.send_twap_query( - "sushi", os.getenv("SUSHI_SUBGRAPH"), os.getenv("SUSHI_PAIR") - ) + uni_twap_data = self.send_twap_query("uni", UNI_SUBGRAPH, UNIV2_DIGG_WBTC) + sushi_twap_data = self.send_twap_query("sushi", SUSHI_SUBGRAPH, SUSHI_DIGG_WBTC) uni_prices = [ float(x["reserve0"]) / float(x["reserve1"]) @@ -216,18 +213,18 @@ def send_twap_query(self, exchange: str, url: str, pair: str) -> dict: time_id = "hourStartUnix" if exchange == "uni" else "date" query = f""" - {{ - pairHourDatas(where: + {{ + pairHourDatas(where: {{ pair: \"{pair}\" {time_id}_gte: {yesterday_timestamp} {time_id}_lte: {today_timestamp} }} - ) - {{ - id + ) + {{ + id {time_id} - reserve0 + reserve0 reserve1 }} }} @@ -263,24 +260,6 @@ def _get_today_report_datetime(self): ) return today - def request_uma_report(self): - price_identifier = "DIGGBTC".encode("utf-8") - today_timestamp = round(self._get_today_report_datetime().timestamp()) - ancillary_data = HexBytes(0) - currency = WETH_ADDRESS - reward = 0 - - """ - function requestPrice( - bytes32 identifier, - uint256 timestamp, - bytes memory ancillaryData, - IERC20 currency, - uint256 reward - ) external virtual returns (uint256 totalBond); - """ - pass - def get_digg_twap_uma(self) -> float: return 0 @@ -300,15 +279,15 @@ def __process_chainlink_tx(self): ) self.logger.info(f"got gas price of tx: ${gas_price_of_tx}") send_success_to_discord( - tx_type=f"Chainlink Forwarder", + tx_type="Chainlink Forwarder", tx_hash=tx_hash, gas_cost=gas_price_of_tx, ) elif tx_hash != HexBytes(0): - send_success_to_discord(tx_type=f"Chainlink Forwarder", tx_hash=tx_hash) + send_success_to_discord(tx_type="Chainlink Forwarder", tx_hash=tx_hash) except Exception as e: self.logger.error(f"Error processing chainlink tx: {e}") - send_oracle_error_to_discord(tx_type=f"Chainlink Forwarder", error=e) + send_oracle_error_to_discord(tx_type="Chainlink Forwarder", error=e) def __send_chainlink_tx(self) -> HexBytes: """Sends transaction to ETH node for confirmation. diff --git a/src/rebaser.py b/src/rebaser.py index 8921e40b..5d0d3926 100644 --- a/src/rebaser.py +++ b/src/rebaser.py @@ -1,30 +1,26 @@ -from decimal import Decimal -from enum import Enum -from hexbytes import HexBytes import json import logging import os -import requests -import sys import time -sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../src"))) -sys.path.insert( - 0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../config")) -) - -from enums import Network -from utils import ( - get_secret, - hours, - confirm_transaction, - get_hash_from_failed_tx_error, - send_success_to_discord, - send_rebase_to_discord, - send_rebase_error_to_discord, -) -from tx_utils import get_gas_price_of_tx, get_priority_fee, get_effective_gas_price -from web3 import Web3, contract, exceptions +from hexbytes import HexBytes +from web3 import Web3 + +from config.constants import DIGG +from config.constants import DIGG_ORCHESTRATOR +from config.constants import DIGG_POLICY +from config.constants import ETH_ETH_USD_CHAINLINK +from config.constants import SUSHI_DIGG_WBTC +from config.constants import UNIV2_DIGG_WBTC +from config.enums import Network +from src.tx_utils import get_effective_gas_price +from src.tx_utils import get_gas_price_of_tx +from src.tx_utils import get_priority_fee +from src.utils import confirm_transaction +from src.utils import get_hash_from_failed_tx_error +from src.utils import hours +from src.utils import send_rebase_error_to_discord +from src.utils import send_rebase_to_discord MAX_GAS_PRICE = int(1000e9) # 1000 gwei @@ -36,32 +32,32 @@ def __init__( keeper_key=os.getenv("KEEPER_KEY"), web3=os.getenv("ETH_NODE_URL"), ): - self.logger = logging.getLogger() + self.logger = logging.getLogger(__name__) self.web3 = Web3(Web3.HTTPProvider(web3)) # get secret here self.keeper_key = keeper_key # get secret here self.keeper_address = keeper_address # get secret here self.eth_usd_oracle = self.web3.eth.contract( - address=self.web3.toChecksumAddress(os.getenv("ETH_USD_CHAINLINK")), + address=self.web3.toChecksumAddress(ETH_ETH_USD_CHAINLINK), abi=self.__get_abi("oracle"), ) self.digg_token = self.web3.eth.contract( - address=self.web3.toChecksumAddress(os.getenv("DIGG_TOKEN_ADDRESS")), + address=self.web3.toChecksumAddress(DIGG), abi=self.__get_abi("digg_token"), ) self.digg_orchestrator = self.web3.eth.contract( - address=self.web3.toChecksumAddress(os.getenv("DIGG_ORCHESTRATOR_ADDRESS")), + address=self.web3.toChecksumAddress(DIGG_ORCHESTRATOR), abi=self.__get_abi("digg_orchestrator"), ) self.digg_policy = self.web3.eth.contract( - address=self.web3.toChecksumAddress(os.getenv("DIGG_POLICY_ADDRESS")), + address=self.web3.toChecksumAddress(DIGG_POLICY), abi=self.__get_abi("digg_policy"), ) self.uni_pair = self.web3.eth.contract( - address=self.web3.toChecksumAddress(os.getenv("UNIV2_DIGG_WBTC_ADDRESS")), + address=self.web3.toChecksumAddress(UNIV2_DIGG_WBTC), abi=self.__get_abi("univ2_pair"), ) self.sushi_pair = self.web3.eth.contract( - address=self.web3.toChecksumAddress(os.getenv("SUSHI_DIGG_WBTC_ADDRESS")), + address=self.web3.toChecksumAddress(SUSHI_DIGG_WBTC), abi=self.__get_abi("sushi_pair"), ) diff --git a/src/tx_utils.py b/src/tx_utils.py index e94daf07..02315a23 100644 --- a/src/tx_utils.py +++ b/src/tx_utils.py @@ -1,17 +1,15 @@ -from decimal import Decimal -from hexbytes import HexBytes +# TODO: Move this module as a shared functionality to badger utils lib import logging -import os -import sys -from web3 import Web3, contract, exceptions +from decimal import Decimal -sys.path.insert( - 0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../config")) -) +from hexbytes import HexBytes +from web3 import Web3 +from web3 import contract +from web3 import exceptions -from enums import Network +from config.enums import Network -logger = logging.getLogger("tx-utils") +logger = logging.getLogger(__name__) def get_gas_price_of_tx( @@ -69,7 +67,8 @@ def get_latest_base_fee( def get_effective_gas_price(web3: Web3) -> int: - # TODO: Currently using max fee (per gas) that can be used for this tx. Maybe use base + priority (for average). + # TODO: Currently using max fee (per gas) that can be used for this tx. + # TODO: Maybe use base + priority (for average). base_fee = get_latest_base_fee(web3) logger.info(f"latest base fee: {base_fee}") @@ -92,8 +91,10 @@ def get_priority_fee( Args: web3 (Web3): Web3 object num_blocks (int, optional): Number of historic blocks to look at. Defaults to 4. - percentiles (int, optional): Percentile of transactions in blocks to use to analyze fees. Defaults to 70. - default_reward (int, optional): If call fails, what default reward to use in gwei. Defaults to 10e9. + percentiles (int, optional): Percentile of transactions + in blocks to use to analyze fees. Defaults to 70. + default_reward (int, optional): If call fails, + what default reward to use in gwei. Defaults to 10e9. Returns: int: [description] diff --git a/src/utils.py b/src/utils.py index 1c212f93..f9d7c485 100644 --- a/src/utils.py +++ b/src/utils.py @@ -1,30 +1,27 @@ -import boto3 import base64 -from botocore.exceptions import ClientError -from decimal import Decimal -from discord import Webhook, RequestsWebhookAdapter, Embed -from hexbytes import HexBytes import json import logging -from web3 import Web3, contract, exceptions -import requests -import sys -import os +from decimal import Decimal -sys.path.insert( - 0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../config")) -) +import boto3 +import requests +from botocore.exceptions import ClientError +from discord import Embed +from discord import RequestsWebhookAdapter +from discord import Webhook +from hexbytes import HexBytes +from web3 import Web3 +from web3 import contract +from web3 import exceptions -from constants import ( - MULTICHAIN_CONFIG, - SECONDS_IN_A_DAY, - BLOCKS_IN_A_DAY, - ABI_DIRS, - NODE_URL_SECRET_NAMES, -) -from enums import Network +from config.constants import ABI_DIRS +from config.constants import BLOCKS_IN_A_DAY +from config.constants import MULTICHAIN_CONFIG +from config.constants import NODE_URL_SECRET_NAMES +from config.constants import SECONDS_IN_A_DAY +from config.enums import Network -logger = logging.getLogger("utils") +logger = logging.getLogger(__name__) def get_secret( @@ -36,10 +33,12 @@ def get_secret( secret_key (str): Dict key value to use to access secret value region_name (str, optional): AWS region name for secret. Defaults to "us-west-1". Raises: - e: DecryptionFailureException - Secrets Manager can't decrypt the protected secret text using the provided KMS key. + e: DecryptionFailureException - Secrets Manager can't decrypt + the protected secret text using the provided KMS key. e: InternalServiceErrorException - An error occurred on the server side. e: InvalidParameterException - You provided an invalid value for a parameter. - e: InvalidRequestException - You provided a parameter value that is not valid for the current state of the resource. + e: InvalidRequestException - You provided a parameter value + that is not valid for the current state of the resource. e: ResourceNotFoundException - We can't find the resource that you asked for. Returns: str: secret value @@ -71,7 +70,8 @@ def get_secret( raise e else: # Decrypts secret using the associated KMS CMK. - # Depending on whether the secret is a string or binary, one of these fields will be populated. + # Depending on whether the secret is a string + # or binary, one of these fields will be populated. if "SecretString" in get_secret_value_response: return json.loads(get_secret_value_response["SecretString"]).get(secret_key) else: @@ -239,14 +239,14 @@ def send_rebase_to_discord(tx_hash: HexBytes, gas_cost: Decimal = None): # "inline": True, # } embed = Embed( - title=f"**Badger Rebaser Report**", + title="**Badger Rebaser Report**", description=f"{status} Rebase", ) for field in fields: embed.add_field( name=field.get("name"), value=field.get("value"), inline=field.get("inline") ) - webhook.send(embed=embed, username=f"Rebaser") + webhook.send(embed=embed, username="Rebaser") def send_rebase_error_to_discord(error: Exception): @@ -255,11 +255,11 @@ def send_rebase_error_to_discord(error: Exception): adapter=RequestsWebhookAdapter(), ) embed = Embed( - title=f"**Badger Rebaser Report**", - description=f"Failed Rebase", + title="**Badger Rebaser Report**", + description="Failed Rebase", ) - embed.add_field(name="Error sending rebase tx", value=f"{error}", inline=False) - webhook.send(embed=embed, username=f"Rebaser") + embed.add_field(name="Error sending rebase tx", value="{error}", inline=False) + webhook.send(embed=embed, username="Rebaser") def send_oracle_error_to_discord(tx_type: str, error: Exception): @@ -278,7 +278,8 @@ def send_oracle_error_to_discord(tx_type: str, error: Exception): def confirm_transaction( web3: Web3, tx_hash: HexBytes, timeout: int = 120, max_block: int = None ) -> tuple[bool, str]: - """Waits for transaction to appear within a given timeframe or before a given block (if specified), and then times out. + """Waits for transaction to appear within + a given timeframe or before a given block (if specified), and then times out. Args: web3 (Web3): Web3 instance @@ -351,6 +352,9 @@ def get_explorer(chain: str, tx_hash: HexBytes) -> tuple: elif chain == Network.Arbitrum: explorer_name = "Arbiscan" explorer_url = f"https://arbiscan.io/tx/{tx_hash.hex()}" + elif chain == Network.Fantom: + explorer_name = "Ftmscan" + explorer_url = f"https://ftmscan.com/tx/{tx_hash.hex()}" return (explorer_name, explorer_url) @@ -358,13 +362,15 @@ def get_explorer(chain: str, tx_hash: HexBytes) -> tuple: def get_last_harvest_times( web3: Web3, keeper_acl: contract, start_block: int = 0, etherscan_key: str = None ): - """Fetches the latest harvest timestamps of strategies from Etherscan API which occur after `start_block`. + """Fetches the latest harvest timestamps + of strategies from Etherscan API which occur after `start_block`. NOTE: Temporary function until Harvested events are emitted from all strategies. Args: web3 (Web3): Web3 node instance. keeper_acl (contract): Keeper ACL web3 contract instance. - start_block (int, optional): Minimum block number to start fetching harvest timestamps from. Defaults to 0. + start_block (int, optional): + Minimum block number to start fetching harvest timestamps from. Defaults to 0. Returns: dict: Dictionary of strategy addresses and their latest harvest timestamps. @@ -482,10 +488,17 @@ def seconds_to_blocks(seconds: int) -> int: return seconds / SECONDS_IN_A_DAY * BLOCKS_IN_A_DAY -def get_token_price(token_address: str, currency: str, chain: str) -> int: - prices = requests.get( - f"https://api.badger.finance/v2/prices?currency={currency}&chain={chain}" - ).json() +def get_token_price( + token_address: str, currency: str, chain: str, use_staging: bool = False +) -> int: + if use_staging: + prices = requests.get( + f"https://staging-api.badger.finance/v2/prices?currency={currency}&chain={chain}" + ).json() + else: + prices = requests.get( + f"https://api.badger.finance/v2/prices?currency={currency}&chain={chain}" + ).json() token_price = prices.get(token_address, 0) return token_price diff --git a/tests/test_cake.py b/tests/test_cake.py deleted file mode 100644 index df97708f..00000000 --- a/tests/test_cake.py +++ /dev/null @@ -1,108 +0,0 @@ -from typing import Tuple -import pytest -from decimal import Decimal -from brownie import * -from web3 import Web3 -import requests -import os - -from src.bsc.cake_harvester import CakeHarvester -from tests.utils import * - -os.environ["DISCORD_WEBHOOK_URL"] = os.getenv("TEST_DISCORD_WEBHOOK_URL") - - -@pytest.mark.require_network("bsc-fork") -def test_correct_network(): - pass - - -@pytest.fixture -def bbadger_btcb_strategy() -> Tuple[str, str, Contract]: - strategy_address = "0x2A842e01724F10d093aE8a46A01e66DbCf3C7373" - return (strategy_address, "BBADGER BTCB LP", get_strategy(strategy_address, "bsc")) - - -@pytest.fixture -def bdigg_btcb_strategy() -> Tuple[str, str, Contract]: - strategy_address = "0xC8C53A293edca5a0146d713b9b95b0cd0a2e5ca4" - return (strategy_address, "BDIGG BTCB LP", get_strategy(strategy_address, "bsc")) - - -@pytest.fixture -def bnb_btcb_strategy() -> Tuple[str, str, Contract]: - strategy_address = "0x120BB9F87bAB3C49b89c7745eDC07FED50786534" - return (strategy_address, "BNB BTCB LP", get_strategy(strategy_address, "bsc")) - - -@pytest.fixture -def harvester() -> CakeHarvester: - return CakeHarvester( - keeper_address=test_address, - keeper_key=test_key, - web3=Web3(Web3.HTTPProvider("http://127.0.0.1:8545")), - ) - - -def test_harvest( - harvester, bbadger_btcb_strategy, bdigg_btcb_strategy, bnb_btcb_strategy -): - """ - Check if the contract should be harvestable, then call the harvest function - - If the strategy should be harvested then claimable rewards should be positive before - and 0 after. If not then claimable rewards should be the same before and after - calling harvest - """ - accounts[0].transfer(test_address, "1 ether") - - def run_harvest(strategy_address, strategy_name, strategy): - before_claimable = harvester.get_harvestable_rewards_amount( - pool_id=strategy.wantPid(), strategy_address=strategy_address - ) - current_price_bnb = harvester.get_current_rewards_price() - should_harvest = harvester.is_profitable(before_claimable, current_price_bnb) - - print(strategy_name, "should_harvest:", should_harvest) - - harvester.harvest(strategy_name, strategy_address) - after_claimable = harvester.get_harvestable_rewards_amount( - pool_id=strategy.wantPid(), strategy_address=strategy_address - ) - return (before_claimable, after_claimable, should_harvest) - - for (strategy_address, strategy_name, strategy) in [ - bbadger_btcb_strategy, - bdigg_btcb_strategy, - bnb_btcb_strategy, - ]: - (before_claimable, after_claimable, should_harvest) = run_harvest( - strategy_address, strategy_name, strategy - ) - - assert (should_harvest and before_claimable != 0 and after_claimable == 0) or ( - before_claimable == after_claimable and not should_harvest - ) - - -def test_get_current_rewards_price(harvester): - test_price = harvester.get_current_rewards_price() - - r = requests.get( - "https://api.coingecko.com/api/v3/simple/price?ids=pancakeswap-token&vs_currencies=bnb" - ) - data = r.json() - coin_gecko_price = data["pancakeswap-token"]["bnb"] - - assert round(float(test_price), 3) == round(coin_gecko_price, 3) - - -def test_is_profitable(harvester): - res = harvester.is_profitable(Decimal(100), Decimal(2)) - assert res - - res = harvester.is_profitable(Decimal(1), Decimal(2)) - assert res - - res = harvester.is_profitable(Decimal(0.9), Decimal(2)) - assert not res diff --git a/tests/test_ibbtc_fee_collect.py b/tests/test_ibbtc_fee_collect.py deleted file mode 100644 index d95cd354..00000000 --- a/tests/test_ibbtc_fee_collect.py +++ /dev/null @@ -1,42 +0,0 @@ -from typing import Tuple -import pytest -from decimal import Decimal -from brownie import * -from web3 import Web3 -import requests -import os - -from src.ibbtc_fee_collector import ibBTCFeeCollector -from tests.utils import * - -os.environ["IBBTC_CORE_ADDRESS"] = "0x2A8facc9D49fBc3ecFf569847833C380A13418a8" -os.environ["BTC_ETH_CHAINLINK"] = "0xdeb288F737066589598e9214E782fa5A8eD689e8" -os.environ["ETH_USD_CHAINLINK"] = "0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419" - - -@pytest.mark.require_network("mainnet-fork") -def test_correct_network(): - pass - - -@pytest.fixture -def collector() -> ibBTCFeeCollector: - return ibBTCFeeCollector( - keeper_address=test_address, - keeper_key=test_key, - web3="http://127.0.0.1:8545", - ) - - -def test_collect(collector): - """ - Check if the contract should be harvestable, then call the harvest function - - If the strategy should be harvested then claimable rewards should be positive before - and 0 after. If not then claimable rewards should be the same before and after - calling harvest - """ - accounts[0].transfer(test_address, "1 ether") - - # TODO: mock call to secretsmanager and have a real assert - assert collector.collect_fees() == {} diff --git a/tests/test_sushi.py b/tests/test_sushi.py deleted file mode 100644 index 2bf3af96..00000000 --- a/tests/test_sushi.py +++ /dev/null @@ -1,191 +0,0 @@ -from typing import Tuple -import pytest -from decimal import Decimal -from brownie import * -from web3 import Web3 -import requests -import os - -from src.eth.sushi_harvester import SushiHarvester -from src.eth.sushi_tender import SushiTender -from src.utils import get_abi -from tests.utils import * -from config.enums import Network - -os.environ["DISCORD_WEBHOOK_URL"] = os.getenv("TEST_DISCORD_WEBHOOK_URL") - - -@pytest.mark.require_network("mainnet-fork") -def test_correct_network(): - pass - - -@pytest.fixture -def badger_wbtc_strategy() -> Tuple[str, str, Contract]: - strategy_address = "0x3a494D79AA78118795daad8AeFF5825C6c8dF7F1" - return ( - strategy_address, - "BADGER WBTC LP", - get_strategy(strategy_address, Network.Ethereum), - ) - - -@pytest.fixture -def digg_wbtc_strategy() -> Tuple[str, str, Contract]: - strategy_address = "0xaa8dddfe7DFA3C3269f1910d89E4413dD006D08a" - return ( - strategy_address, - "DIGG WBTC LP", - get_strategy(strategy_address, Network.Ethereum), - ) - - -@pytest.fixture -def eth_wbtc_strategy() -> Tuple[str, str, Contract]: - strategy_address = "0x7A56d65254705B4Def63c68488C0182968C452ce" - return ( - strategy_address, - "ETH WBTC LP", - get_strategy(strategy_address, Network.Ethereum), - ) - - -@pytest.fixture -def harvester() -> SushiHarvester: - return SushiHarvester( - keeper_address=test_address, - keeper_key=test_key, - web3=Web3(Web3.HTTPProvider("http://127.0.0.1:8545")), - ) - - -@pytest.fixture -def tender() -> SushiTender: - return SushiTender( - keeper_address=test_address, - keeper_key=test_key, - web3=Web3(Web3.HTTPProvider("http://127.0.0.1:8545")), - ) - - -def test_harvest( - harvester, badger_wbtc_strategy, digg_wbtc_strategy, eth_wbtc_strategy -): - """ - Check if the contract should be harvestable, then call the harvest function - - If the strategy should be harvested then claimable rewards should be positive before - and 0 after. If not then claimable rewards should be the same before and after - calling harvest - """ - accounts[0].transfer(test_address, "1 ether") - - def run_harvest(strategy_address, strategy_name, strategy): - before_claimable = harvester.get_harvestable_rewards_amount( - pool_id=strategy.pid(), strategy_address=strategy_address - ) - current_price_eth = harvester.get_current_rewards_price() - gas_fee = harvester.estimate_gas_fee( - harvester.web3.eth.contract( - address=strategy_address, - abi=get_abi(Network.Ethereum, "strategy"), - ) - ) - should_harvest = harvester.is_profitable( - before_claimable, current_price_eth, gas_fee - ) - - print(strategy_name, "should_harvest:", should_harvest) - - harvester.harvest(strategy_name, strategy_address) - after_claimable = harvester.get_harvestable_rewards_amount( - pool_id=strategy.pid(), strategy_address=strategy_address - ) - return (before_claimable, after_claimable, should_harvest) - - for (strategy_address, strategy_name, strategy) in [ - badger_wbtc_strategy, - digg_wbtc_strategy, - eth_wbtc_strategy, - ]: - (before_claimable, after_claimable, should_harvest) = run_harvest( - strategy_address, strategy_name, strategy - ) - - assert (should_harvest and before_claimable != 0 and after_claimable == 0) or ( - before_claimable == after_claimable and not should_harvest - ) - - -def test_tend(tender, badger_wbtc_strategy, digg_wbtc_strategy, eth_wbtc_strategy): - """ - Check if the contract should be tendable, then call the tend function - - If the strategy should be tended then claimable rewards should be positive before - and 0 after. If not then claimable rewards should be the same before and after - calling tend - """ - accounts[0].transfer(test_address, "1 ether") - - def run_tend(strategy_address, strategy_name, strategy): - before_claimable = tender.get_tendable_rewards_amount( - pool_id=strategy.pid(), strategy_address=strategy_address - ) - current_price_eth = tender.get_current_rewards_price() - gas_fee = tender.estimate_gas_fee( - tender.web3.eth.contract( - address=strategy_address, - abi=get_abi(Network.Ethereum, "strategy"), - ) - ) - should_tend = tender.is_profitable(before_claimable, current_price_eth, gas_fee) - - print(strategy_name, "should_tend:", should_tend) - - tender.tend(strategy_name, strategy_address) - after_claimable = tender.get_tendable_rewards_amount( - pool_id=strategy.pid(), strategy_address=strategy_address - ) - return (before_claimable, after_claimable, should_tend) - - for (strategy_address, strategy_name, strategy) in [ - badger_wbtc_strategy, - digg_wbtc_strategy, - eth_wbtc_strategy, - ]: - (before_claimable, after_claimable, should_tend) = run_tend( - strategy_address, strategy_name, strategy - ) - - assert (should_tend and before_claimable != 0 and after_claimable == 0) or ( - before_claimable == after_claimable and not should_tend - ) - - -def test_get_current_rewards_price(harvester): - test_price = harvester.get_current_rewards_price() - - r = requests.get( - f"https://api.coingecko.com/api/v3/simple/price?ids=xsushi&vs_currencies=eth" - ) - data = r.json() - coin_gecko_price = data["xsushi"]["eth"] - - assert round(float(test_price), 3) == round(coin_gecko_price, 3) - - -def test_is_profitable(harvester): - res = harvester.is_profitable( - Decimal(1), Decimal(2), Decimal(web3.toWei(10000000, "gwei")) - ) - assert res - - res = harvester.is_profitable( - Decimal(1), Decimal(1), Decimal(web3.toWei(10000000, "gwei")) - ) - assert res - - res = harvester.is_profitable( - Decimal(1), Decimal(1), Decimal(web3.toWei(11000000, "gwei")) - ) - assert not res diff --git a/tests/test_tx_utils.py b/tests/test_tx_utils.py new file mode 100644 index 00000000..9b004e93 --- /dev/null +++ b/tests/test_tx_utils.py @@ -0,0 +1,25 @@ +from unittest.mock import MagicMock + +from src.tx_utils import get_latest_base_fee + + +def test_get_latest_base_fee(): + base_fee = 1213 + web3 = MagicMock( + eth=MagicMock(get_block=MagicMock(return_value={"baseFeePerGas": base_fee})) + ) + assert get_latest_base_fee(web3) == base_fee + + +def test_get_latest_base_fee_no_fee(): + default_fee = int(100e9) + web3 = MagicMock(eth=MagicMock(get_block=MagicMock(return_value={}))) + assert get_latest_base_fee(web3) == default_fee + + +def test_get_latest_base_fee_hex_fee(): + hex_gas = "0x64" + web3 = MagicMock( + eth=MagicMock(get_block=MagicMock(return_value={"baseFeePerGas": hex_gas})) + ) + assert get_latest_base_fee(web3) == int(hex_gas, 0)