Skip to content

Commit

Permalink
Feature/debt swap (#118)
Browse files Browse the repository at this point in the history
* add debt swap adapter

* rewrite debt swap logic

* add testcases for debt swap

* add deploy scripts for debt swap adapter

* add fee and slippage in params

* add some comments to methods

* update debt swap adapter on goerli

* fix typo add missing init

* deploy debt swap adapter on mainnet

* add repay amounts return value
  • Loading branch information
thorseldon committed Jan 19, 2024
1 parent 636b8f6 commit 3bb61af
Show file tree
Hide file tree
Showing 20 changed files with 1,571 additions and 3 deletions.
437 changes: 437 additions & 0 deletions abis/UniswapV3DebtSwapAdapter.json

Large diffs are not rendered by default.

422 changes: 422 additions & 0 deletions contracts/adapters/UniswapV3DebtSwapAdapter.sol

Large diffs are not rendered by default.

18 changes: 18 additions & 0 deletions contracts/adapters/interfaces/IAaveFlashLoanReceiver.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// SPDX-License-Identifier: agpl-3.0
pragma solidity 0.8.4;

/**
* @title IAaveFlashLoanReceiver interface
* @notice Interface for the Aave fee IFlashLoanReceiver.
* @author Bend
* @dev implement this interface to develop a flashloan-compatible flashLoanReceiver contract
**/
interface IAaveFlashLoanReceiver {
function executeOperation(
address[] calldata assets,
uint256[] calldata amounts,
uint256[] calldata premiums,
address initiator,
bytes calldata params
) external returns (bool);
}
16 changes: 16 additions & 0 deletions contracts/adapters/interfaces/IAaveLendPool.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// SPDX-License-Identifier: agpl-3.0
pragma solidity 0.8.4;

interface IAaveLendPool {
function FLASHLOAN_PREMIUM_TOTAL() external view returns (uint256);

function flashLoan(
address receiverAddress,
address[] calldata assets,
uint256[] calldata amounts,
uint256[] calldata modes,
address onBehalfOf,
bytes calldata params,
uint16 referralCode
) external;
}
13 changes: 13 additions & 0 deletions contracts/adapters/interfaces/IAaveLendPoolAddressesProvider.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// SPDX-License-Identifier: agpl-3.0
pragma solidity 0.8.4;

/**
* @title IAaveLendPoolAddressesProvider contract
* @dev Main registry of addresses part of or connected to the aave protocol, including permissioned roles
* - Acting also as factory of proxies and admin of those, so with right to change its implementations
* - Owned by the Aave Governance
* @author Bend
**/
interface IAaveLendPoolAddressesProvider {
function getLendingPool() external view returns (address);
}
67 changes: 67 additions & 0 deletions contracts/adapters/interfaces/ISwapRouter.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.7.5;
pragma abicoder v2;

import {IUniswapV3SwapCallback} from "./IUniswapV3SwapCallback.sol";

/// @title Router token swapping functionality
/// @notice Functions for swapping tokens via Uniswap V3
interface ISwapRouter is IUniswapV3SwapCallback {
struct ExactInputSingleParams {
address tokenIn;
address tokenOut;
uint24 fee;
address recipient;
uint256 deadline;
uint256 amountIn;
uint256 amountOutMinimum;
uint160 sqrtPriceLimitX96;
}

/// @notice Swaps `amountIn` of one token for as much as possible of another token
/// @param params The parameters necessary for the swap, encoded as `ExactInputSingleParams` in calldata
/// @return amountOut The amount of the received token
function exactInputSingle(ExactInputSingleParams calldata params) external payable returns (uint256 amountOut);

struct ExactInputParams {
bytes path;
address recipient;
uint256 deadline;
uint256 amountIn;
uint256 amountOutMinimum;
}

/// @notice Swaps `amountIn` of one token for as much as possible of another along the specified path
/// @param params The parameters necessary for the multi-hop swap, encoded as `ExactInputParams` in calldata
/// @return amountOut The amount of the received token
function exactInput(ExactInputParams calldata params) external payable returns (uint256 amountOut);

struct ExactOutputSingleParams {
address tokenIn;
address tokenOut;
uint24 fee;
address recipient;
uint256 deadline;
uint256 amountOut;
uint256 amountInMaximum;
uint160 sqrtPriceLimitX96;
}

/// @notice Swaps as little as possible of one token for `amountOut` of another token
/// @param params The parameters necessary for the swap, encoded as `ExactOutputSingleParams` in calldata
/// @return amountIn The amount of the input token
function exactOutputSingle(ExactOutputSingleParams calldata params) external payable returns (uint256 amountIn);

struct ExactOutputParams {
bytes path;
address recipient;
uint256 deadline;
uint256 amountOut;
uint256 amountInMaximum;
}

/// @notice Swaps as little as possible of one token for `amountOut` of another along the specified path (reversed)
/// @param params The parameters necessary for the multi-hop swap, encoded as `ExactOutputParams` in calldata
/// @return amountIn The amount of the input token
function exactOutput(ExactOutputParams calldata params) external payable returns (uint256 amountIn);
}
21 changes: 21 additions & 0 deletions contracts/adapters/interfaces/IUniswapV3SwapCallback.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.5.0;

/// @title Callback for IUniswapV3PoolActions#swap
/// @notice Any contract that calls IUniswapV3PoolActions#swap must implement this interface
interface IUniswapV3SwapCallback {
/// @notice Called to `msg.sender` after executing a swap via IUniswapV3Pool#swap.
/// @dev In the implementation you must pay the pool tokens owed for the swap.
/// The caller of this method must be checked to be a UniswapV3Pool deployed by the canonical UniswapV3Factory.
/// amount0Delta and amount1Delta can both be 0 if no tokens were swapped.
/// @param amount0Delta The amount of token0 that was sent (negative) or must be received (positive) by the pool by
/// the end of the swap. If positive, the callback must send that amount of token0 to the pool.
/// @param amount1Delta The amount of token1 that was sent (negative) or must be received (positive) by the pool by
/// the end of the swap. If positive, the callback must send that amount of token1 to the pool.
/// @param data Any data passed through by the caller via the IUniswapV3PoolActions#swap call
function uniswapV3SwapCallback(
int256 amount0Delta,
int256 amount1Delta,
bytes calldata data
) external;
}
63 changes: 63 additions & 0 deletions contracts/mock/MockAaveLendPool.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// SPDX-License-Identifier: agpl-3.0
pragma solidity 0.8.4;

import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";

import {IAaveLendPool} from "../adapters/interfaces/IAaveLendPool.sol";
import {IAaveFlashLoanReceiver} from "../adapters/interfaces/IAaveFlashLoanReceiver.sol";

contract MockAaveLendPool is IAaveLendPool {
uint256 private _flashLoanPremiumTotal;

constructor() {
_flashLoanPremiumTotal = 9;
}

function FLASHLOAN_PREMIUM_TOTAL() public view override returns (uint256) {
return _flashLoanPremiumTotal;
}

struct FlashLoanLocalVars {
IAaveFlashLoanReceiver receiver;
uint256 i;
address currentAsset;
uint256 currentAmount;
uint256 currentPremium;
uint256 currentAmountPlusPremium;
}

function flashLoan(
address receiverAddress,
address[] calldata assets,
uint256[] calldata amounts,
uint256[] calldata, /*modes*/
address, /*onBehalfOf*/
bytes calldata params,
uint16 /*referralCode*/
) public override {
FlashLoanLocalVars memory vars;
vars.receiver = IAaveFlashLoanReceiver(receiverAddress);

uint256[] memory premiums = new uint256[](assets.length);

for (vars.i = 0; vars.i < assets.length; vars.i++) {
premiums[vars.i] = (amounts[vars.i] * _flashLoanPremiumTotal) / 10000;

IERC20(assets[vars.i]).transfer(receiverAddress, amounts[vars.i]);
}

require(
vars.receiver.executeOperation(assets, amounts, premiums, msg.sender, params),
"AaveLendPool: Flashloan execution failed"
);

for (vars.i = 0; vars.i < assets.length; vars.i++) {
vars.currentAsset = assets[vars.i];
vars.currentAmount = amounts[vars.i];
vars.currentPremium = premiums[vars.i];
vars.currentAmountPlusPremium = vars.currentAmount + vars.currentPremium;

IERC20(vars.currentAsset).transferFrom(receiverAddress, vars.currentAsset, vars.currentAmountPlusPremium);
}
}
}
16 changes: 16 additions & 0 deletions contracts/mock/MockAaveLendPoolAddressesProvider.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// SPDX-License-Identifier: agpl-3.0
pragma solidity 0.8.4;

import {IAaveLendPoolAddressesProvider} from "../adapters/interfaces/IAaveLendPoolAddressesProvider.sol";

contract MockAaveLendPoolAddressesProvider is IAaveLendPoolAddressesProvider {
address public lendingPool;

function setLendingPool(address lendingPool_) public {
lendingPool = lendingPool_;
}

function getLendingPool() public view override returns (address) {
return lendingPool;
}
}
63 changes: 63 additions & 0 deletions contracts/mock/MockUniswapV3SwapRouter.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// SPDX-License-Identifier: agpl-3.0
pragma solidity 0.8.4;

import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";

import {ISwapRouter} from "../adapters/interfaces/ISwapRouter.sol";

contract MockUniswapV3SwapRouter is ISwapRouter {
using SafeERC20 for IERC20;

uint256 public amountOutDeltaRatio;

function setSmountOutDeltaRatio(uint256 deltaRatio) external {
amountOutDeltaRatio = deltaRatio;
}

function exactInputSingle(ExactInputSingleParams calldata params)
external
payable
override
returns (uint256 amountOut)
{
params;

IERC20(params.tokenIn).safeTransferFrom(msg.sender, address(this), params.amountIn);

if (amountOutDeltaRatio > 0) {
amountOut = (params.amountOutMinimum * amountOutDeltaRatio) / 100;
} else {
amountOut = params.amountOutMinimum;
}
IERC20(params.tokenOut).safeTransfer(params.recipient, amountOut);

return amountOut;
}

function exactInput(ExactInputParams calldata params) external payable override returns (uint256 amountOut) {
params;
return 0;
}

function exactOutputSingle(ExactOutputSingleParams calldata params)
external
payable
override
returns (uint256 amountIn)
{
params;
return 0;
}

function exactOutput(ExactOutputParams calldata params) external payable override returns (uint256 amountIn) {
params;
return 0;
}

function uniswapV3SwapCallback(
int256 amount0Delta,
int256 amount1Delta,
bytes calldata data
) external override {}
}
6 changes: 6 additions & 0 deletions deployments/deployed-contracts-goerli.json
Original file line number Diff line number Diff line change
Expand Up @@ -162,5 +162,11 @@
"rateStrategyUSDT231211": {
"address": "0x0dd8CC6d1197fb76F185ad54d089AB19e6AFFA18",
"deployer": "0xafF5C36642385b6c7Aaf7585eC785aB2316b5db6"
},
"UniswapV3DebtSwapAdapterImpl": {
"address": "0xB953536E16aDE3C7e7f4dC68fcBe458F5A896cD0"
},
"UniswapV3DebtSwapAdapter": {
"address": "0x06a41eC387810a0dC9FE3042180D4617207C6E27"
}
}
7 changes: 7 additions & 0 deletions deployments/deployed-contracts-main.json
Original file line number Diff line number Diff line change
Expand Up @@ -163,5 +163,12 @@
"rateStrategyUSDT231211": {
"address": "0x9e987a69b59F9b09B0Ca2C666AC8526Aa972d836",
"deployer": "0x868964fa49a6fd6e116FE82c8f4165904406f479"
},
"UniswapV3DebtSwapAdapterImpl": {
"address": "0x1a1F40B9b07B480d9c53B99eCEF603c7bC931c87"
},
"UniswapV3DebtSwapAdapter": {
"address": "0x7DBFbd828c12442563e4037AF84B99C14e8EAcd1",
"deployer": "0x868964fa49a6fd6e116FE82c8f4165904406f479"
}
}
7 changes: 7 additions & 0 deletions helpers/contracts-deployments.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ import {
WrapperGatewayFactory,
MockerERC721Wrapper,
ChainlinkAggregatorHelperFactory,
UniswapV3DebtSwapAdapterFactory,
} from "../types";
import {
withSaveAndVerify,
Expand Down Expand Up @@ -674,3 +675,9 @@ export const deployWrapperGateway = async (id: string, verify?: boolean) => {
await rawInsertContractAddressInDb(id + "Impl", gatewayImpl.address);
return withSaveAndVerify(gatewayImpl, id, [], verify);
};

export const deployUniswapV3DebtSwapAdapter = async (verify?: boolean) => {
const adapterImpl = await new UniswapV3DebtSwapAdapterFactory(await getDeploySigner()).deploy();
await rawInsertContractAddressInDb(eContractid.UniswapV3DebtSwapAdapterImpl, adapterImpl.address);
return withSaveAndVerify(adapterImpl, eContractid.UniswapV3DebtSwapAdapter, [], verify);
};
13 changes: 13 additions & 0 deletions helpers/contracts-getters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ import {
MintableERC721,
WrapperGatewayFactory,
ChainlinkAggregatorHelperFactory,
UniswapV3DebtSwapAdapterFactory,
} from "../types";
import { IERC20DetailedFactory } from "../types/IERC20DetailedFactory";
import { IERC721DetailedFactory } from "../types/IERC721DetailedFactory";
Expand Down Expand Up @@ -484,3 +485,15 @@ export const getWrapperGateway = async (id: string, address?: tEthereumAddress)
address || (await getDb(DRE.network.name).get(`${id}`).value()).address,
await getDeploySigner()
);

export const getUniswapV3DebtSwapAdapter = async (address?: tEthereumAddress) =>
await UniswapV3DebtSwapAdapterFactory.connect(
address || (await getDb(DRE.network.name).get(`${eContractid.UniswapV3DebtSwapAdapter}`).value()).address,
await getDeploySigner()
);

export const getUniswapV3DebtSwapAdapterImpl = async (address?: tEthereumAddress) =>
await UniswapV3DebtSwapAdapterFactory.connect(
address || (await getDb(DRE.network.name).get(`${eContractid.UniswapV3DebtSwapAdapterImpl}`).value()).address,
await getDeploySigner()
);
2 changes: 2 additions & 0 deletions helpers/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,8 @@ export enum eContractid {
MockLoanRepaidInterceptor = "MockLoanRepaidInterceptor",
KodaGateway = "KodaGateway",
KodaGatewayImpl = "KodaGatewayImpl",
UniswapV3DebtSwapAdapter = "UniswapV3DebtSwapAdapter",
UniswapV3DebtSwapAdapterImpl = "UniswapV3DebtSwapAdapterImpl",
}

export enum ProtocolLoanState {
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
"prettier:abis": "prettier --write 'abis/**/*.json'",
"prettier:code": "prettier --write 'tasks/**/*.ts' 'contracts/**/*.sol' 'helpers/**/*.ts' 'test/**/*.ts' 'test/**/*.sol'",
"ci:test": "npm run compile && npm run test",
"ci:clean": "hardhat clean && rm -rf ./artifacts ./cache ./types",
"ci:clean": "hardhat clean && rm -rf ./artifacts ./cache ./cache_forge ./out ./types",
"bend:hardhat:dev:migration": "npm run compile && npm run hardhat -- bend:dev",
"bend:localhost:dev:migration": "npm run compile && npm run hardhat:localhost -- bend:dev",
"bend:localhost:full:migration": "npm run compile && npm run hardhat:localhost -- bend:mainnet",
Expand Down
4 changes: 4 additions & 0 deletions scripts/updateAbis.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ const miscContractList = ["UiPoolDataProvider", "BendProtocolDataProvider", "Wal

const interfacesContractList = ["IERC20Detailed", "IERC721Detailed", "IIncentivesController"];

const adaptersContractList = ["UniswapV3DebtSwapAdapter"];

const updateAbis = async (subDir, contractList) => {
contractList.forEach((contract) => {
const artifact = require(`../artifacts/contracts/${subDir}/${contract}.sol/${contract}.json`);
Expand All @@ -46,3 +48,5 @@ updateAbis("protocol", protocolContractList).then().catch();
updateAbis("misc", miscContractList).then().catch();

updateAbis("interfaces", interfacesContractList).then().catch();

updateAbis("adapters", adaptersContractList).then().catch();
Loading

0 comments on commit 3bb61af

Please sign in to comment.