Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

1delta proxy functions for Aave #185

Merged
merged 2 commits into from
Jan 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions docs/source/api/one_delta/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ This is Python documentation for high-level `1delta protocol <https://1delta.io/
Functionality includes

- Opening and closing short positions, utilizing Aave v3 lending pool.
- Supply and withdraw collateral to/from Aave v3 lending pool.

Getting started

Expand All @@ -23,5 +24,6 @@ Getting started
eth_defi.one_delta.deployment
eth_defi.one_delta.position
eth_defi.one_delta.price
eth_defi.one_delta.lending
eth_defi.one_delta.utils

137 changes: 137 additions & 0 deletions eth_defi/one_delta/lending.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
"""1delta proxy functions to interact with lending pool.

- Supply collateral to lending pool
- Withdraw collateral from lending pool
"""

from web3.contract.contract import Contract, ContractFunction

from eth_defi.aave_v3.constants import MAX_AMOUNT, AaveV3InterestRateMode
from eth_defi.aave_v3.deployment import AaveV3Deployment
from eth_defi.one_delta.deployment import OneDeltaDeployment


def supply(
one_delta_deployment: OneDeltaDeployment,
*,
token: Contract,
amount: int,
wallet_address: str,
) -> ContractFunction:
"""Supply collateral to Aave

:param one_delta_deployment: 1delta deployment
:param token: collateral token contract proxy
:param collateral_amount: amount of collateral to be supplied
:param wallet_address: wallet address of the user
:return: multicall contract function to supply collateral
"""

calls = _build_supply_multicall(
one_delta_deployment=one_delta_deployment,
token=token,
amount=amount,
wallet_address=wallet_address,
)
return one_delta_deployment.broker_proxy.functions.multicall(calls)


def _build_supply_multicall(
one_delta_deployment,
*,
token: Contract,
amount: int,
wallet_address: str,
) -> list[str]:
"""Build multicall to supply collateral to Aave

:param one_delta_deployment: 1delta deployment
:param token: collateral token contract proxy
:param collateral_amount: amount of collateral to be supplied
:param wallet_address: wallet address of the user
:return: list of encoded ABI calls
"""
call_transfer = one_delta_deployment.flash_aggregator.encodeABI(
fn_name="transferERC20In",
args=[
token.address,
amount,
],
)

call_deposit = one_delta_deployment.flash_aggregator.encodeABI(
fn_name="deposit",
args=[
token.address,
wallet_address,
],
)

return [call_transfer, call_deposit]


def withdraw(
one_delta_deployment: OneDeltaDeployment,
*,
token: Contract,
atoken: Contract,
amount: int,
wallet_address: str,
) -> ContractFunction:
"""Withdraw collateral from Aave

:param one_delta_deployment: 1delta deployment
:param token: collateral token contract proxy
:param atoken: aToken contract proxy
:param collateral_amount: amount of collateral to be withdrawn
:param wallet_address: wallet address of the user
:return: multicall contract function to withdraw collateral
"""

calls = _build_withdraw_multicall(
one_delta_deployment=one_delta_deployment,
token=token,
atoken=atoken,
amount=amount,
wallet_address=wallet_address,
)
return one_delta_deployment.broker_proxy.functions.multicall(calls)


def _build_withdraw_multicall(
one_delta_deployment,
*,
token: Contract,
atoken: Contract,
amount: int,
wallet_address: str,
) -> list[str]:
"""Build multicall to withdraw collateral from Aave

:param one_delta_deployment: 1delta deployment
:param token: collateral token contract proxy
:param atoken: aToken contract proxy
:param collateral_amount: amount of collateral to be withdrawn, use MAX_AMOUNT to withdraw all collateral
:param wallet_address: wallet address of the user
:return: list of encoded ABI calls
"""
if amount == MAX_AMOUNT:
# use MAX_AMOUNT to make sure the whole balane is swept
call_transfer = one_delta_deployment.flash_aggregator.encodeABI(
fn_name="transferERC20AllIn",
args=[atoken.address],
)
else:
call_transfer = one_delta_deployment.flash_aggregator.encodeABI(
fn_name="transferERC20In",
args=[atoken.address, amount],
)

call_withdraw = one_delta_deployment.flash_aggregator.encodeABI(
fn_name="withdraw",
args=[
token.address,
wallet_address,
],
)
return [call_transfer, call_withdraw]
54 changes: 18 additions & 36 deletions eth_defi/one_delta/position.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@
from eth_defi.aave_v3.deployment import AaveV3Deployment
from eth_defi.one_delta.constants import Exchange, TradeOperation, TradeType
from eth_defi.one_delta.deployment import OneDeltaDeployment
from eth_defi.one_delta.lending import (
_build_supply_multicall,
_build_withdraw_multicall,
)
from eth_defi.one_delta.utils import encode_path


Expand Down Expand Up @@ -92,22 +96,6 @@ def open_short_position(
:return: multicall contract function to supply collateral and open the short position
"""

call_transfer = one_delta_deployment.flash_aggregator.encodeABI(
fn_name="transferERC20In",
args=[
collateral_token.address,
collateral_amount,
],
)

call_deposit = one_delta_deployment.flash_aggregator.encodeABI(
fn_name="deposit",
args=[
collateral_token.address,
wallet_address,
],
)

path = encode_path(
path=[
borrow_token.address,
Expand All @@ -128,8 +116,14 @@ def open_short_position(
],
)

calls = [call_transfer, call_deposit, call_swap]
if do_supply is False:
if do_supply is True:
calls = _build_supply_multicall(
one_delta_deployment=one_delta_deployment,
token=collateral_token,
amount=collateral_amount,
wallet_address=wallet_address,
) + [call_swap]
else:
calls = [call_swap]

return one_delta_deployment.broker_proxy.functions.multicall(calls)
Expand Down Expand Up @@ -190,25 +184,13 @@ def close_short_position(
if withdraw_collateral_amount == 0:
calls = [call_swap]
else:
if withdraw_collateral_amount == MAX_AMOUNT:
call_transfer = one_delta_deployment.flash_aggregator.encodeABI(
fn_name="transferERC20AllIn",
args=[atoken.address],
)
else:
call_transfer = one_delta_deployment.flash_aggregator.encodeABI(
fn_name="transferERC20In",
args=[atoken.address, withdraw_collateral_amount],
)

call_withdraw = one_delta_deployment.flash_aggregator.encodeABI(
fn_name="withdraw",
args=[
collateral_token.address,
wallet_address,
],
calls = [call_swap] + _build_withdraw_multicall(
one_delta_deployment=one_delta_deployment,
token=collateral_token,
atoken=atoken,
amount=withdraw_collateral_amount,
wallet_address=wallet_address,
)
calls = [call_swap, call_transfer, call_withdraw]

return one_delta_deployment.broker_proxy.functions.multicall(calls)

Expand Down
131 changes: 131 additions & 0 deletions tests/one_delta/test_one_delta_lending.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
"""Test 1delta lending functions using forked Polygon."""
import logging
import os
import shutil

import flaky
import pytest
from eth_account import Account
from eth_account.signers.local import LocalAccount
from eth_typing import HexAddress, HexStr

from eth_defi.aave_v3.constants import MAX_AMOUNT
from eth_defi.hotwallet import HotWallet
from eth_defi.one_delta.deployment import OneDeltaDeployment
from eth_defi.one_delta.deployment import fetch_deployment as fetch_1delta_deployment
from eth_defi.one_delta.lending import supply, withdraw
from eth_defi.one_delta.position import approve
from eth_defi.provider.anvil import fork_network_anvil, mine
from eth_defi.provider.multi_provider import create_multi_provider_web3
from eth_defi.token import fetch_erc20_details
from eth_defi.trace import assert_transaction_success_with_explanation

from .utils import _execute_tx, _print_current_balances

# https://docs.pytest.org/en/latest/how-to/skipping.html#skip-all-test-functions-of-a-class-or-module
pytestmark = pytest.mark.skipif(
(os.environ.get("JSON_RPC_POLYGON") is None) or (shutil.which("anvil") is None),
reason="Set JSON_RPC_POLYGON env install anvil command to run these tests",
)

logger = logging.getLogger(__name__)


def test_one_delta_supply(
web3,
hot_wallet,
one_delta_deployment,
aave_v3_deployment,
usdc,
ausdc,
weth,
vweth,
):
"""Test supply to Aave via 1delta proxy"""
for fn in approve(
one_delta_deployment=one_delta_deployment,
collateral_token=usdc.contract,
borrow_token=weth.contract,
atoken=ausdc.contract,
vtoken=vweth.contract,
aave_v3_deployment=aave_v3_deployment,
):
_execute_tx(web3, hot_wallet, fn)

wallet_original_balance = 100_000 * 10**6
usdc_supply_amount = 10_000 * 10**6

supply_fn = supply(
one_delta_deployment=one_delta_deployment,
token=usdc.contract,
amount=usdc_supply_amount,
wallet_address=hot_wallet.address,
)
_execute_tx(web3, hot_wallet, supply_fn, 500_000)

assert usdc.contract.functions.balanceOf(hot_wallet.address).call() == pytest.approx(wallet_original_balance - usdc_supply_amount)
assert ausdc.contract.functions.balanceOf(hot_wallet.address).call() == pytest.approx(usdc_supply_amount)


def test_one_delta_withdraw(
web3,
hot_wallet,
one_delta_deployment,
aave_v3_deployment,
usdc,
ausdc,
weth,
vweth,
):
"""Test withdraw from Aave via 1delta proxy"""
for fn in approve(
one_delta_deployment=one_delta_deployment,
collateral_token=usdc.contract,
borrow_token=weth.contract,
atoken=ausdc.contract,
vtoken=vweth.contract,
aave_v3_deployment=aave_v3_deployment,
):
_execute_tx(web3, hot_wallet, fn)

wallet_original_balance = 100_000 * 10**6
usdc_supply_amount = 10_000 * 10**6

supply_fn = supply(
one_delta_deployment=one_delta_deployment,
token=usdc.contract,
amount=usdc_supply_amount,
wallet_address=hot_wallet.address,
)
_execute_tx(web3, hot_wallet, supply_fn, 500_000)

assert usdc.contract.functions.balanceOf(hot_wallet.address).call() == pytest.approx(wallet_original_balance - usdc_supply_amount)
assert ausdc.contract.functions.balanceOf(hot_wallet.address).call() == pytest.approx(usdc_supply_amount)

# test partial withdrawal
usdc_partial_withdraw_amount = 4_000 * 10**6

withdraw_fn = withdraw(
one_delta_deployment=one_delta_deployment,
token=usdc.contract,
atoken=ausdc.contract,
amount=usdc_partial_withdraw_amount,
wallet_address=hot_wallet.address,
)
_execute_tx(web3, hot_wallet, withdraw_fn, 500_000)

assert usdc.contract.functions.balanceOf(hot_wallet.address).call() == pytest.approx(wallet_original_balance - usdc_supply_amount + usdc_partial_withdraw_amount)
assert ausdc.contract.functions.balanceOf(hot_wallet.address).call() == pytest.approx(usdc_supply_amount - usdc_partial_withdraw_amount)

# test full withdrawal
withdraw_fn = withdraw(
one_delta_deployment=one_delta_deployment,
token=usdc.contract,
atoken=ausdc.contract,
amount=MAX_AMOUNT,
wallet_address=hot_wallet.address,
)
_execute_tx(web3, hot_wallet, withdraw_fn, 500_000)

assert usdc.contract.functions.balanceOf(hot_wallet.address).call() == pytest.approx(wallet_original_balance)
assert ausdc.contract.functions.balanceOf(hot_wallet.address).call() == 0