Skip to content

Commit

Permalink
unwind aave position with a balancer/aave flashloan and script with o…
Browse files Browse the repository at this point in the history
…dos and openocean integration
  • Loading branch information
0xdapper committed Mar 13, 2023
1 parent 8104dae commit 4b55956
Show file tree
Hide file tree
Showing 6 changed files with 355 additions and 0 deletions.
7 changes: 7 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,10 @@
[submodule "lib/solmate"]
path = lib/solmate
url = https://github.com/transmissions11/solmate
[submodule "lib/surl"]
path = lib/surl
url = https://github.com/memester-xyz/surl
[submodule "lib/solady"]
path = lib/solady
url = https://github.com/vectorized/solady
branch = v0.0.84
1 change: 1 addition & 0 deletions lib/solady
Submodule solady added at 2b69b6
1 change: 1 addition & 0 deletions lib/surl
Submodule surl added at 028c85
40 changes: 40 additions & 0 deletions odos.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// @ts-check
const axios = require("axios");

const getOdosSwapPath = async (tokenIn, amountIn, tokenOut, account) => {
const data = {
chainId: 43114,
gasPrice: 30,
inputTokens: [
{
amount: amountIn,
tokenAddress: tokenIn
}
],
outputTokens: [
{
proportion: 1,
tokenAddress: tokenOut,
}
],
slippageLimitPercent: 0.3,
userAddr: account
};
const res = await axios.post("https://api.odos.xyz/sor/quote", data);
// console.log("quote"); /
// console.log(res.data);
const res2 = await axios.post("https://api.odos.xyz/sor/assemble", { pathId: res.data.pathId, simulate: false, userAddr: account });
return res2.data;
}


const main = async () => {
const [tokenIn, amountIn, tokenOut, account] = process.argv.slice(-4);
// console.log({tokenIn, amountIn, tokenOut, account});

// console.log(res2.data);
const ret = await getOdosSwapPath(tokenIn, amountIn, tokenOut, account);
console.log(JSON.stringify(ret));
}

main();
90 changes: 90 additions & 0 deletions script/Unwind.s.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import {Unwind} from "src/Unwind.sol";
import {Script} from "forge-std/Script.sol";
import {ERC20} from "solmate/tokens/ERC20.sol";
import {Surl} from "surl/src/Surl.sol";
import {LibString} from "solady/utils/LibString.sol";
import {console} from "forge-std/console.sol";
import {Test} from "forge-std/Test.sol";
import {stdJson} from "forge-std/StdJson.sol";

contract UnwindScript is Script, Test {
ERC20 aAvaUSDC = ERC20(0x625E7708f30cA75bfd92586e17077590C60eb4cD);
address USDC = 0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E;
address USDT = 0x9702230A8Ea53601f5cD2dc00fDBc13d4dF4A8c7;
address openOcean = 0x6352a56caadC4F1E25CD6c75970Fa768A3304e64;

function run() external {
vm.startBroadcast();
Unwind unwind = new Unwind();
aAvaUSDC.approve(address(unwind), type(uint).max);
// Unwind unwind = Unwind(0xB7DEA8552B2555082fdd65455570f4F2cF669C99);
uint usdtAmount = 15600e6;
uint usdcAmount = 15750e6;

(address swapper, bytes memory swapData) = _getOdosSwapData(
USDC,
usdcAmount,
USDT,
address(unwind)
);
unwind.unwind(USDT, usdtAmount, 2, USDC, usdcAmount, swapper, swapData);
}

function _getOdosSwapData(
address _tokenIn,
uint _amountIn,
address _tokenOut,
address _receiver
) internal returns (address to, bytes memory ret) {
string[] memory cmd = new string[](6);
cmd[0] = "node";
cmd[1] = "odos.js";
cmd[2] = LibString.toHexStringChecksumed(_tokenIn);
cmd[3] = LibString.toString(_amountIn);
cmd[4] = LibString.toHexStringChecksumed(_tokenOut);
cmd[5] = LibString.toHexStringChecksumed(_receiver);
ret = vm.ffi(cmd);
to = stdJson.readAddress(string(ret), ".transaction.to");
ret = stdJson.readBytes(string(ret), ".transaction.data");

// console.log(to, LibString.toHexString(ret));
}

function _getOpenOceanSwapData(
address _tokenIn,
uint _amountIn,
address _tokenOut,
address _receiver
) internal returns (bytes memory ret) {
string memory url = _getSwapUrl(
_tokenIn,
_amountIn,
_tokenOut,
_receiver
);
uint status;
console.log(url);
(status, ret) = Surl.get(url);
require(status == 200, "Surl.get failed");
ret = stdJson.readBytes(string(ret), ".data.data");
}

function _getSwapUrl(
address _tokenIn,
uint _amountIn,
address _tokenOut,
address _receiver
) internal view returns (string memory url) {
url = "https://open-api.openocean.finance/v3/avax/swap_quote?inTokenAddress=";
url = string.concat(url, LibString.toHexStringChecksumed(_tokenIn));
url = string.concat(url, "&outTokenAddress=");
url = string.concat(url, LibString.toHexStringChecksumed(_tokenOut));
url = string.concat(url, "&amount=");
url = string.concat(url, LibString.toString(_amountIn / 1e6));
url = string.concat(
url,
"&gasPrice=100000000000&slippage=0.5&account="
);
url = string.concat(url, LibString.toHexStringChecksumed(_receiver));
}
}
216 changes: 216 additions & 0 deletions src/Unwind.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
import {SafeTransferLib} from "solmate/utils/SafeTransferLib.sol";
import {ERC20} from "solmate/tokens/ERC20.sol";
import {IPool} from "aave-v3-core/contracts/interfaces/IPool.sol";
import {DataTypes} from "aave-v3-core/contracts/protocol/libraries/types/DataTypes.sol";
import {IFlashLoanSimpleReceiver, IPoolAddressesProvider} from "aave-v3-core/contracts/flashloan/interfaces/IFlashLoanSimpleReceiver.sol";

using SafeTransferLib for ERC20;

interface IFlashLoanRecipient {
/**
* @dev When `flashLoan` is called on the Vault, it invokes the `receiveFlashLoan` hook on the recipient.
*
* At the time of the call, the Vault will have transferred `amounts` for `tokens` to the recipient. Before this
* call returns, the recipient must have transferred `amounts` plus `feeAmounts` for each token back to the
* Vault, or else the entire flash loan will revert.
*
* `userData` is the same value passed in the `IVault.flashLoan` call.
*/
function receiveFlashLoan(
address[] memory tokens,
uint256[] memory amounts,
uint256[] memory feeAmounts,
bytes memory userData
) external;
}

interface IVault {
function flashLoan(
IFlashLoanRecipient recipient,
address[] memory tokens,
uint256[] memory amounts,
bytes memory userData
) external;
}

contract Unwind is IFlashLoanRecipient, IFlashLoanSimpleReceiver {
error InsufficientOutput(address _token, uint _expected, uint _actual);

IVault constant vault = IVault(0xad68ea482860cd7077a5D0684313dD3a9BC70fbB);
IPool constant pool = IPool(0x794a61358D6845594F94dc1DB02A252b5b4814aD);

bool initiated = false;
address caller = address(0);

function unwind(
address _debtToken,
uint _debtToRepay,
uint _rateMode,
address _collToken,
uint _collToWithdraw,
address _swapper,
bytes calldata _swapData
) external initiate withCaller {
// _balancerFlashLoan(_debtToken, _debtToRepay);
_aaveV3FlashLoan(_debtToken, _debtToRepay);
}

function _aaveV3FlashLoan(address _debtToken, uint _debtToRepay) internal {
pool.flashLoanSimple(
address(this),
_debtToken,
_debtToRepay,
msg.data[4:],
0
);
}

function _balancerFlashLoan(
address _debtToken,
uint _debtToRepay
) internal {
address[] memory tokens = new address[](1);
tokens[0] = _debtToken;
uint[] memory amounts = new uint[](1);
amounts[0] = _debtToRepay;
vault.flashLoan(
IFlashLoanRecipient(this),
tokens,
amounts,
msg.data[4:]
);
}

function _afterFlashLoan(
address _flashBorrowedAsset,
uint _flashBorrowedAmount,
uint _flashBorrowFee,
address _borrowedFrom,
bytes calldata userData
) internal {
(
address _debtToken,
uint _debtToRepay,
uint _rateMode,
address _collToken,
uint _collToWithdraw,
address _swapper,
bytes memory _swapData
) = abi.decode(
userData,
(address, uint, uint, address, uint, address, bytes)
);

// repay debt with flashloaned assets
ERC20(_debtToken).safeApprove(address(pool), _debtToRepay);
pool.repay(_debtToken, _debtToRepay, _rateMode, caller);

// pull aToken from user, withdraw collateral, and transfer back the remainder
DataTypes.ReserveData memory assetReserveData = pool.getReserveData(
_collToken
);
ERC20 collateralAToken = ERC20(assetReserveData.aTokenAddress);
collateralAToken.safeTransferFrom(
caller,
address(this),
_collToWithdraw
);
pool.withdraw(_collToken, _collToWithdraw, address(this));
collateralAToken.safeTransfer(
caller,
collateralAToken.balanceOf(address(this))
);

// swap the collateral for debt token
uint totalRepay = _flashBorrowedAmount + _flashBorrowFee;
ERC20(_collToken).safeApprove(_swapper, _collToWithdraw);
(bool success, ) = _swapper.call(_swapData);
if (ERC20(_debtToken).balanceOf(address(this)) < totalRepay)
revert InsufficientOutput(
_debtToken,
totalRepay,
ERC20(_debtToken).balanceOf(address(this))
);

// repay the flashloan
ERC20(_debtToken).safeApprove(address(pool), totalRepay);
// ERC20(_debtToken).safeTransfer(_borrowedFrom, totalRepay);

// transfer back the remainders to the caller
ERC20(_debtToken).safeTransfer(
caller,
ERC20(_debtToken).balanceOf(address(this)) - totalRepay
);
ERC20(_collToken).safeTransfer(
caller,
ERC20(_collToken).balanceOf(address(this))
);
}

function receiveFlashLoan(
address[] memory tokens,
uint256[] memory amounts,
uint256[] memory feeAmounts,
bytes calldata userData
) external override onlyInitiated {
require(msg.sender == address(vault), "Unwind: unauthorized");
require(
tokens.length == amounts.length &&
tokens.length == feeAmounts.length,
"Unwind: invalid arrays"
);

_afterFlashLoan(
tokens[0],
amounts[0],
feeAmounts[0],
address(vault),
userData
);
}

function executeOperation(
address asset,
uint256 amount,
uint256 premium,
address initiator,
bytes calldata params
) external override onlyInitiated returns (bool) {
require(msg.sender == address(pool), "Unwind: unauthorized");
require(initiator == address(this), "Unwind: unauthorized");

_afterFlashLoan(asset, amount, premium, initiator, params);
return true;
}

function ADDRESSES_PROVIDER()
external
view
returns (IPoolAddressesProvider)
{
return pool.ADDRESSES_PROVIDER();
}

function POOL() external view returns (IPool) {
return pool;
}

modifier initiate() {
require(!initiated, "Unwind: already initiated");
initiated = true;
_;
initiated = false;
}

modifier onlyInitiated() {
require(initiated, "Unwind: not initiated");
_;
}

modifier withCaller() {
require(caller == address(0), "Unwind: already called");
caller = msg.sender;
_;
caller = address(0);
}
}

0 comments on commit 4b55956

Please sign in to comment.