Skip to content

Commit ccb43f0

Browse files
authored
Merge pull request #55 from paraswap/paraswap
ParaSwap adapter for collateral swaps
2 parents ca74be8 + 258a9be commit ccb43f0

21 files changed

+3158
-3
lines changed
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
// SPDX-License-Identifier: agpl-3.0
2+
pragma solidity 0.6.12;
3+
pragma experimental ABIEncoderV2;
4+
5+
import {SafeMath} from '../dependencies/openzeppelin/contracts/SafeMath.sol';
6+
import {IERC20} from '../dependencies/openzeppelin/contracts/IERC20.sol';
7+
import {IERC20Detailed} from '../dependencies/openzeppelin/contracts/IERC20Detailed.sol';
8+
import {SafeERC20} from '../dependencies/openzeppelin/contracts/SafeERC20.sol';
9+
import {Ownable} from '../dependencies/openzeppelin/contracts/Ownable.sol';
10+
import {ILendingPoolAddressesProvider} from '../interfaces/ILendingPoolAddressesProvider.sol';
11+
import {DataTypes} from '../protocol/libraries/types/DataTypes.sol';
12+
import {IPriceOracleGetter} from '../interfaces/IPriceOracleGetter.sol';
13+
import {IERC20WithPermit} from '../interfaces/IERC20WithPermit.sol';
14+
import {FlashLoanReceiverBase} from '../flashloan/base/FlashLoanReceiverBase.sol';
15+
16+
/**
17+
* @title BaseParaSwapAdapter
18+
* @notice Utility functions for adapters using ParaSwap
19+
* @author Jason Raymond Bell
20+
*/
21+
abstract contract BaseParaSwapAdapter is FlashLoanReceiverBase, Ownable {
22+
using SafeMath for uint256;
23+
using SafeERC20 for IERC20;
24+
using SafeERC20 for IERC20Detailed;
25+
using SafeERC20 for IERC20WithPermit;
26+
27+
struct PermitSignature {
28+
uint256 amount;
29+
uint256 deadline;
30+
uint8 v;
31+
bytes32 r;
32+
bytes32 s;
33+
}
34+
35+
// Max slippage percent allowed
36+
uint256 public constant MAX_SLIPPAGE_PERCENT = 3000; // 30%
37+
38+
IPriceOracleGetter public immutable ORACLE;
39+
40+
event Swapped(address indexed fromAsset, address indexed toAsset, uint256 fromAmount, uint256 receivedAmount);
41+
42+
constructor(
43+
ILendingPoolAddressesProvider addressesProvider
44+
) public FlashLoanReceiverBase(addressesProvider) {
45+
ORACLE = IPriceOracleGetter(addressesProvider.getPriceOracle());
46+
}
47+
48+
/**
49+
* @dev Get the price of the asset from the oracle denominated in eth
50+
* @param asset address
51+
* @return eth price for the asset
52+
*/
53+
function _getPrice(address asset) internal view returns (uint256) {
54+
return ORACLE.getAssetPrice(asset);
55+
}
56+
57+
/**
58+
* @dev Get the decimals of an asset
59+
* @return number of decimals of the asset
60+
*/
61+
function _getDecimals(IERC20Detailed asset) internal view returns (uint8) {
62+
uint8 decimals = asset.decimals();
63+
// Ensure 10**decimals won't overflow a uint256
64+
require(decimals <= 77, 'TOO_MANY_DECIMALS_ON_TOKEN');
65+
return decimals;
66+
}
67+
68+
/**
69+
* @dev Get the aToken associated to the asset
70+
* @return address of the aToken
71+
*/
72+
function _getReserveData(address asset) internal view returns (DataTypes.ReserveData memory) {
73+
return LENDING_POOL.getReserveData(asset);
74+
}
75+
76+
/**
77+
* @dev Pull the ATokens from the user
78+
* @param reserve address of the asset
79+
* @param reserveAToken address of the aToken of the reserve
80+
* @param user address
81+
* @param amount of tokens to be transferred to the contract
82+
* @param permitSignature struct containing the permit signature
83+
*/
84+
function _pullATokenAndWithdraw(
85+
address reserve,
86+
IERC20WithPermit reserveAToken,
87+
address user,
88+
uint256 amount,
89+
PermitSignature memory permitSignature
90+
) internal {
91+
// If deadline is set to zero, assume there is no signature for permit
92+
if (permitSignature.deadline != 0) {
93+
reserveAToken.permit(
94+
user,
95+
address(this),
96+
permitSignature.amount,
97+
permitSignature.deadline,
98+
permitSignature.v,
99+
permitSignature.r,
100+
permitSignature.s
101+
);
102+
}
103+
104+
// transfer from user to adapter
105+
reserveAToken.safeTransferFrom(user, address(this), amount);
106+
107+
// withdraw reserve
108+
require(
109+
LENDING_POOL.withdraw(reserve, amount, address(this)) == amount,
110+
'UNEXPECTED_AMOUNT_WITHDRAWN'
111+
);
112+
}
113+
114+
/**
115+
* @dev Emergency rescue for token stucked on this contract, as failsafe mechanism
116+
* - Funds should never remain in this contract more time than during transactions
117+
* - Only callable by the owner
118+
*/
119+
function rescueTokens(IERC20 token) external onlyOwner {
120+
token.safeTransfer(owner(), token.balanceOf(address(this)));
121+
}
122+
}
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
// SPDX-License-Identifier: agpl-3.0
2+
pragma solidity 0.6.12;
3+
pragma experimental ABIEncoderV2;
4+
5+
import {BaseParaSwapAdapter} from './BaseParaSwapAdapter.sol';
6+
import {PercentageMath} from '../protocol/libraries/math/PercentageMath.sol';
7+
import {IParaSwapAugustus} from '../interfaces/IParaSwapAugustus.sol';
8+
import {IParaSwapAugustusRegistry} from '../interfaces/IParaSwapAugustusRegistry.sol';
9+
import {ILendingPoolAddressesProvider} from '../interfaces/ILendingPoolAddressesProvider.sol';
10+
import {IERC20Detailed} from '../dependencies/openzeppelin/contracts/IERC20Detailed.sol';
11+
12+
/**
13+
* @title BaseParaSwapSellAdapter
14+
* @notice Implements the logic for selling tokens on ParaSwap
15+
* @author Jason Raymond Bell
16+
*/
17+
abstract contract BaseParaSwapSellAdapter is BaseParaSwapAdapter {
18+
using PercentageMath for uint256;
19+
20+
IParaSwapAugustusRegistry public immutable AUGUSTUS_REGISTRY;
21+
22+
constructor(
23+
ILendingPoolAddressesProvider addressesProvider,
24+
IParaSwapAugustusRegistry augustusRegistry
25+
) public BaseParaSwapAdapter(addressesProvider) {
26+
// Do something on Augustus registry to check the right contract was passed
27+
require(!augustusRegistry.isValidAugustus(address(0)));
28+
AUGUSTUS_REGISTRY = augustusRegistry;
29+
}
30+
31+
/**
32+
* @dev Swaps a token for another using ParaSwap
33+
* @param fromAmountOffset Offset of fromAmount in Augustus calldata if it should be overwritten, otherwise 0
34+
* @param swapCalldata Calldata for ParaSwap's AugustusSwapper contract
35+
* @param augustus Address of ParaSwap's AugustusSwapper contract
36+
* @param assetToSwapFrom Address of the asset to be swapped from
37+
* @param assetToSwapTo Address of the asset to be swapped to
38+
* @param amountToSwap Amount to be swapped
39+
* @param minAmountToReceive Minimum amount to be received from the swap
40+
* @return amountReceived The amount received from the swap
41+
*/
42+
function _sellOnParaSwap(
43+
uint256 fromAmountOffset,
44+
bytes memory swapCalldata,
45+
IParaSwapAugustus augustus,
46+
IERC20Detailed assetToSwapFrom,
47+
IERC20Detailed assetToSwapTo,
48+
uint256 amountToSwap,
49+
uint256 minAmountToReceive
50+
) internal returns (uint256 amountReceived) {
51+
require(AUGUSTUS_REGISTRY.isValidAugustus(address(augustus)), 'INVALID_AUGUSTUS');
52+
53+
{
54+
uint256 fromAssetDecimals = _getDecimals(assetToSwapFrom);
55+
uint256 toAssetDecimals = _getDecimals(assetToSwapTo);
56+
57+
uint256 fromAssetPrice = _getPrice(address(assetToSwapFrom));
58+
uint256 toAssetPrice = _getPrice(address(assetToSwapTo));
59+
60+
uint256 expectedMinAmountOut =
61+
amountToSwap
62+
.mul(fromAssetPrice.mul(10**toAssetDecimals))
63+
.div(toAssetPrice.mul(10**fromAssetDecimals))
64+
.percentMul(PercentageMath.PERCENTAGE_FACTOR - MAX_SLIPPAGE_PERCENT);
65+
66+
require(expectedMinAmountOut <= minAmountToReceive, 'MIN_AMOUNT_EXCEEDS_MAX_SLIPPAGE');
67+
}
68+
69+
uint256 balanceBeforeAssetFrom = assetToSwapFrom.balanceOf(address(this));
70+
require(balanceBeforeAssetFrom >= amountToSwap, 'INSUFFICIENT_BALANCE_BEFORE_SWAP');
71+
uint256 balanceBeforeAssetTo = assetToSwapTo.balanceOf(address(this));
72+
73+
address tokenTransferProxy = augustus.getTokenTransferProxy();
74+
assetToSwapFrom.safeApprove(tokenTransferProxy, 0);
75+
assetToSwapFrom.safeApprove(tokenTransferProxy, amountToSwap);
76+
77+
if (fromAmountOffset != 0) {
78+
// Ensure 256 bit (32 bytes) fromAmount value is within bounds of the
79+
// calldata, not overlapping with the first 4 bytes (function selector).
80+
require(fromAmountOffset >= 4 &&
81+
fromAmountOffset <= swapCalldata.length.sub(32),
82+
'FROM_AMOUNT_OFFSET_OUT_OF_RANGE');
83+
// Overwrite the fromAmount with the correct amount for the swap.
84+
// In memory, swapCalldata consists of a 256 bit length field, followed by
85+
// the actual bytes data, that is why 32 is added to the byte offset.
86+
assembly {
87+
mstore(add(swapCalldata, add(fromAmountOffset, 32)), amountToSwap)
88+
}
89+
}
90+
(bool success,) = address(augustus).call(swapCalldata);
91+
if (!success) {
92+
// Copy revert reason from call
93+
assembly {
94+
returndatacopy(0, 0, returndatasize())
95+
revert(0, returndatasize())
96+
}
97+
}
98+
require(assetToSwapFrom.balanceOf(address(this)) == balanceBeforeAssetFrom - amountToSwap, 'WRONG_BALANCE_AFTER_SWAP');
99+
amountReceived = assetToSwapTo.balanceOf(address(this)).sub(balanceBeforeAssetTo);
100+
require(amountReceived >= minAmountToReceive, 'INSUFFICIENT_AMOUNT_RECEIVED');
101+
102+
emit Swapped(
103+
address(assetToSwapFrom),
104+
address(assetToSwapTo),
105+
amountToSwap,
106+
amountReceived
107+
);
108+
}
109+
}

0 commit comments

Comments
 (0)