Skip to content

Commit 8605312

Browse files
authored
Aave V2 Wrap Adapter (#124)
* add AaveV2WrapV2Adapter * add calldata tests * add integration tests * review changes
1 parent 23549b5 commit 8605312

File tree

6 files changed

+536
-1
lines changed

6 files changed

+536
-1
lines changed

contracts/interfaces/external/aave-v2/IAToken.sol

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,6 @@
1818
pragma solidity 0.6.10;
1919

2020
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
21-
interface IAToken is IERC20 {}
21+
interface IAToken is IERC20 {
22+
function UNDERLYING_ASSET_ADDRESS() external view returns (address);
23+
}
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
/*
2+
Copyright 2021 Set Labs Inc.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
16+
SPDX-License-Identifier: Apache License, Version 2.0
17+
*/
18+
19+
pragma solidity 0.6.10;
20+
pragma experimental "ABIEncoderV2";
21+
22+
import { IAToken } from "../../../interfaces/external/aave-v2/IAToken.sol";
23+
import { ILendingPool } from "../../../interfaces/external/aave-v2/ILendingPool.sol";
24+
25+
/**
26+
* @title AaveV2WrapV2Adapter
27+
* @author Set Protocol
28+
*
29+
* Wrap adapter for Aave V2 that returns data for wraps/unwraps of tokens
30+
*/
31+
contract AaveV2WrapV2Adapter {
32+
33+
/* ============ Modifiers ============ */
34+
35+
/**
36+
* Throws if the underlying/wrapped token pair is not valid
37+
*/
38+
modifier _onlyValidTokenPair(address _underlyingToken, address _wrappedToken) {
39+
require(validTokenPair(_underlyingToken, _wrappedToken), "Must be a valid token pair");
40+
_;
41+
}
42+
43+
/* ========== State Variables ========= */
44+
45+
// Address of the Aave LendingPool contract
46+
// Note: this address may change in the event of an upgrade
47+
ILendingPool public lendingPool;
48+
49+
/* ============ Constructor ============ */
50+
51+
constructor(ILendingPool _lendingPool) public {
52+
lendingPool = _lendingPool;
53+
}
54+
55+
/* ============ External Getter Functions ============ */
56+
57+
/**
58+
* Generates the calldata to wrap an underlying asset into a wrappedToken.
59+
*
60+
* @param _underlyingToken Address of the component to be wrapped
61+
* @param _wrappedToken Address of the desired wrapped token
62+
* @param _underlyingUnits Total quantity of underlying units to wrap
63+
* @param _to Address to send the wrapped tokens to
64+
*
65+
* @return address Target contract address
66+
* @return uint256 Total quantity of underlying units (if underlying is ETH)
67+
* @return bytes Wrap calldata
68+
*/
69+
function getWrapCallData(
70+
address _underlyingToken,
71+
address _wrappedToken,
72+
uint256 _underlyingUnits,
73+
address _to,
74+
bytes memory /* _wrapData */
75+
)
76+
external
77+
view
78+
_onlyValidTokenPair(_underlyingToken, _wrappedToken)
79+
returns (address, uint256, bytes memory)
80+
{
81+
bytes memory callData = abi.encodeWithSignature(
82+
"deposit(address,uint256,address,uint16)",
83+
_underlyingToken,
84+
_underlyingUnits,
85+
_to,
86+
0
87+
);
88+
89+
return (address(lendingPool), 0, callData);
90+
}
91+
92+
/**
93+
* Generates the calldata to unwrap a wrapped asset into its underlying.
94+
*
95+
* @param _underlyingToken Address of the underlying asset
96+
* @param _wrappedToken Address of the component to be unwrapped
97+
* @param _wrappedTokenUnits Total quantity of wrapped token units to unwrap
98+
* @param _to Address to send the unwrapped tokens to
99+
*
100+
* @return address Target contract address
101+
* @return uint256 Total quantity of wrapped token units to unwrap. This will always be 0 for unwrapping
102+
* @return bytes Unwrap calldata
103+
*/
104+
function getUnwrapCallData(
105+
address _underlyingToken,
106+
address _wrappedToken,
107+
uint256 _wrappedTokenUnits,
108+
address _to,
109+
bytes memory /* _wrapData */
110+
)
111+
external
112+
view
113+
_onlyValidTokenPair(_underlyingToken, _wrappedToken)
114+
returns (address, uint256, bytes memory)
115+
{
116+
bytes memory callData = abi.encodeWithSignature(
117+
"withdraw(address,uint256,address)",
118+
_underlyingToken,
119+
_wrappedTokenUnits,
120+
_to
121+
);
122+
123+
return (address(lendingPool), 0, callData);
124+
}
125+
126+
/**
127+
* Returns the address to approve source tokens for wrapping.
128+
*
129+
* @return address Address of the contract to approve tokens to
130+
*/
131+
function getSpenderAddress(address /* _underlyingToken */, address /* _wrappedToken */) external view returns(address) {
132+
return address(lendingPool);
133+
}
134+
135+
/* ============ Internal Functions ============ */
136+
137+
/**
138+
* Validates the underlying and wrapped token pair
139+
*
140+
* @param _underlyingToken Address of the underlying asset
141+
* @param _wrappedToken Address of the wrapped asset
142+
*
143+
* @return bool Whether or not the wrapped token accepts the underlying token as collateral
144+
*/
145+
function validTokenPair(address _underlyingToken, address _wrappedToken) internal view returns(bool) {
146+
return IAToken(_wrappedToken).UNDERLYING_ASSET_ADDRESS() == _underlyingToken;
147+
}
148+
}
Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
import "module-alias/register";
2+
import { BigNumber } from "@ethersproject/bignumber";
3+
4+
import { Address } from "@utils/types";
5+
import { Account } from "@utils/test/types";
6+
import { ADDRESS_ZERO, MAX_UINT_256, ZERO_BYTES } from "@utils/constants";
7+
import { AaveV2WrapV2Adapter, SetToken, StandardTokenMock, WrapModuleV2 } from "@utils/contracts";
8+
import DeployHelper from "@utils/deploys";
9+
import {
10+
ether,
11+
preciseMul,
12+
} from "@utils/index";
13+
import {
14+
getAccounts,
15+
getWaffleExpect,
16+
getSystemFixture,
17+
addSnapshotBeforeRestoreAfterEach,
18+
getAaveV2Fixture,
19+
} from "@utils/test/index";
20+
import { AaveV2Fixture, SystemFixture } from "@utils/fixtures";
21+
import {
22+
AaveV2AToken
23+
} from "@utils/contracts/aaveV2";
24+
25+
const expect = getWaffleExpect();
26+
27+
describe("AaveV2WrapModule", () => {
28+
29+
let owner: Account;
30+
let deployer: DeployHelper;
31+
32+
let setV2Setup: SystemFixture;
33+
let aaveV2Setup: AaveV2Fixture;
34+
35+
let aaveV2WrapAdapter: AaveV2WrapV2Adapter;
36+
let wrapModule: WrapModuleV2;
37+
38+
let underlyingToken: StandardTokenMock;
39+
let wrappedToken: AaveV2AToken;
40+
41+
const aaveV2WrapAdapterIntegrationName: string = "AAVE_V2_WRAPPER";
42+
43+
before(async () => {
44+
[ owner ] = await getAccounts();
45+
46+
// System setup
47+
deployer = new DeployHelper(owner.wallet);
48+
setV2Setup = getSystemFixture(owner.address);
49+
await setV2Setup.initialize();
50+
51+
// Aave setup
52+
aaveV2Setup = getAaveV2Fixture(owner.address);
53+
await aaveV2Setup.initialize(setV2Setup.weth.address, setV2Setup.dai.address);
54+
55+
underlyingToken = setV2Setup.dai;
56+
wrappedToken = aaveV2Setup.daiReserveTokens.aToken;
57+
58+
// WrapModule setup
59+
wrapModule = await deployer.modules.deployWrapModuleV2(setV2Setup.controller.address, setV2Setup.weth.address);
60+
await setV2Setup.controller.addModule(wrapModule.address);
61+
62+
// AaveV2WrapAdapter setup
63+
aaveV2WrapAdapter = await deployer.adapters.deployAaveV2WrapV2Adapter(aaveV2Setup.lendingPool.address);
64+
await setV2Setup.integrationRegistry.addIntegration(wrapModule.address, aaveV2WrapAdapterIntegrationName, aaveV2WrapAdapter.address);
65+
});
66+
67+
addSnapshotBeforeRestoreAfterEach();
68+
69+
context("when a SetToken has been deployed and issued", async () => {
70+
let setToken: SetToken;
71+
let setTokensIssued: BigNumber;
72+
73+
before(async () => {
74+
setToken = await setV2Setup.createSetToken(
75+
[setV2Setup.dai.address],
76+
[ether(1)],
77+
[setV2Setup.issuanceModule.address, wrapModule.address]
78+
);
79+
80+
// Initialize modules
81+
await setV2Setup.issuanceModule.initialize(setToken.address, ADDRESS_ZERO);
82+
await wrapModule.initialize(setToken.address);
83+
84+
// Issue some Sets
85+
setTokensIssued = ether(10);
86+
const underlyingRequired = setTokensIssued;
87+
await setV2Setup.dai.approve(setV2Setup.issuanceModule.address, underlyingRequired);
88+
await setV2Setup.issuanceModule.issue(setToken.address, setTokensIssued, owner.address);
89+
});
90+
91+
describe("#wrap", async () => {
92+
let subjectSetToken: Address;
93+
let subjectUnderlyingToken: Address;
94+
let subjectWrappedToken: Address;
95+
let subjectUnderlyingUnits: BigNumber;
96+
let subjectIntegrationName: string;
97+
let subjectWrapData: string;
98+
let subjectCaller: Account;
99+
100+
beforeEach(async () => {
101+
subjectSetToken = setToken.address;
102+
subjectUnderlyingToken = underlyingToken.address;
103+
subjectWrappedToken = wrappedToken.address;
104+
subjectUnderlyingUnits = ether(1);
105+
subjectIntegrationName = aaveV2WrapAdapterIntegrationName;
106+
subjectWrapData = ZERO_BYTES;
107+
subjectCaller = owner;
108+
});
109+
110+
async function subject(): Promise<any> {
111+
return wrapModule.connect(subjectCaller.wallet).wrap(
112+
subjectSetToken,
113+
subjectUnderlyingToken,
114+
subjectWrappedToken,
115+
subjectUnderlyingUnits,
116+
subjectIntegrationName,
117+
subjectWrapData
118+
);
119+
}
120+
121+
it("should reduce the underlying quantity and mint the wrapped asset to the SetToken", async () => {
122+
const previousUnderlyingBalance = await underlyingToken.balanceOf(setToken.address);
123+
const previousWrappedBalance = await wrappedToken.balanceOf(setToken.address);
124+
125+
await subject();
126+
127+
const underlyingBalance = await underlyingToken.balanceOf(setToken.address);
128+
const wrappedBalance = await wrappedToken.balanceOf(setToken.address);
129+
130+
const expectedUnderlyingBalance = previousUnderlyingBalance.sub(setTokensIssued);
131+
expect(underlyingBalance).to.eq(expectedUnderlyingBalance);
132+
133+
const expectedWrappedBalance = previousWrappedBalance.add(setTokensIssued);
134+
expect(wrappedBalance).to.eq(expectedWrappedBalance);
135+
});
136+
});
137+
138+
describe("#unwrap", () => {
139+
let subjectSetToken: Address;
140+
let subjectUnderlyingToken: Address;
141+
let subjectWrappedToken: Address;
142+
let subjectWrappedTokenUnits: BigNumber;
143+
let subjectIntegrationName: string;
144+
let subjectUnwrapData: string;
145+
let subjectCaller: Account;
146+
147+
let wrappedQuantity: BigNumber;
148+
149+
beforeEach(async () => {
150+
subjectSetToken = setToken.address;
151+
subjectUnderlyingToken = underlyingToken.address;
152+
subjectWrappedToken = wrappedToken.address;
153+
subjectWrappedTokenUnits = ether(0.5);
154+
subjectIntegrationName = aaveV2WrapAdapterIntegrationName;
155+
subjectUnwrapData = ZERO_BYTES;
156+
subjectCaller = owner;
157+
158+
wrappedQuantity = ether(1);
159+
160+
await wrapModule.wrap(
161+
subjectSetToken,
162+
subjectUnderlyingToken,
163+
subjectWrappedToken,
164+
wrappedQuantity,
165+
subjectIntegrationName,
166+
ZERO_BYTES
167+
);
168+
169+
await underlyingToken.approve(aaveV2Setup.lendingPool.address, MAX_UINT_256);
170+
await aaveV2Setup.lendingPool.deposit(underlyingToken.address, ether(100000), owner.address, 0);
171+
});
172+
173+
async function subject(): Promise<any> {
174+
return wrapModule.connect(subjectCaller.wallet).unwrap(
175+
subjectSetToken,
176+
subjectUnderlyingToken,
177+
subjectWrappedToken,
178+
subjectWrappedTokenUnits,
179+
subjectIntegrationName,
180+
subjectUnwrapData,
181+
{
182+
gasLimit: 5000000,
183+
}
184+
);
185+
}
186+
187+
it("should burn the wrapped asset to the SetToken and increase the underlying quantity", async () => {
188+
const previousUnderlyingBalance = await underlyingToken.balanceOf(setToken.address);
189+
const previousWrappedBalance = await wrappedToken.balanceOf(setToken.address);
190+
191+
await subject();
192+
193+
const underlyingBalance = await underlyingToken.balanceOf(setToken.address);
194+
const wrappedBalance = await wrappedToken.balanceOf(setToken.address);
195+
196+
const delta = preciseMul(setTokensIssued, wrappedQuantity.sub(subjectWrappedTokenUnits));
197+
198+
const expectedUnderlyingBalance = previousUnderlyingBalance.add(delta);
199+
expect(underlyingBalance).to.eq(expectedUnderlyingBalance);
200+
201+
const expectedWrappedBalance = previousWrappedBalance.sub(delta);
202+
expect(wrappedBalance).to.eq(expectedWrappedBalance);
203+
});
204+
});
205+
});
206+
});

0 commit comments

Comments
 (0)