-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
unwind aave position with a balancer/aave flashloan and script with o…
…dos and openocean integration
- Loading branch information
Showing
6 changed files
with
355 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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)); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} |