05.AAVE_v1_code
2024-04-14 14:40:18 # 12.DeFi

AAVE_v1_code

代码架构

image-20230806171007930

借贷池

存款

  • 解释:我们存入标的资产,获得aToken,aToken可以1:1兑换标的资产,换句话说aToken是我们资产的凭证。
  • 用法:如果想存入ETH,则_reserve是0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE,并且msg.value大于等于存入的ETH数量;如果是存入ERC20代币,则msg.value必须等于0
  • 参数

    • _reserve:标的资产(underlying token)的地址

    • _amount:存多少钱

    • _referralCode:推荐代码,一般填0,跟推广有关,已经过时了就懒得去了解。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
  function deposit(address _reserve, uint256 _amount, uint16 _referralCode)
external
payable
nonReentrant
onlyActiveReserve(_reserve)
onlyUnfreezedReserve(_reserve)
onlyAmountGreaterThanZero(_amount)
{
// 获得标的资产的aToken地址
AToken aToken = AToken(core.getReserveATokenAddress(_reserve));

// 看看是不是第一个存款
bool isFirstDeposit = aToken.balanceOf(msg.sender) == 0;

// 更新存款信息
core.updateStateOnDeposit(_reserve, msg.sender, _amount, isFirstDeposit);

// 给存款人挖 aToken。aToken可以1:1兑换标的资产
aToken.mintOnDeposit(msg.sender, _amount);

//transfer to the core contract
core.transferToReserve.value(msg.value)(_reserve, msg.sender, _amount);

//solium-disable-next-line
emit Deposit(_reserve, msg.sender, _amount, _referralCode, block.timestamp);

}

然后进入transferToReserve()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function transferToReserve(address _reserve, address payable _user, uint256 _amount)
external
payable
onlyLendingPool
{
// 判断是不是要存入ETH,如果是存入ETH,
// 则_reserve = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE
if (_reserve != EthAddressLib.ethAddress()) {
require(msg.value == 0, "User is sending ETH along with the ERC20 transfer.");
ERC20(_reserve).safeTransferFrom(_user, address(this), _amount);

} else {
require(msg.value >= _amount, "The amount and the value sent to deposit do not match");

if (msg.value > _amount) { // 多的钱发回给用户
//send back excess ETH
uint256 excessAmount = msg.value.sub(_amount);
//solium-disable-next-line
(bool result, ) = _user.call.value(excessAmount).gas(50000)("");
require(result, "Transfer of ETH failed");
}
}
}

取款

  • _reserve:取出的标的资产地址
  • _user:取款地址,可以是自己,也可以是别人
  • _amount:取款数额
  • _aTokenBalanceAfterRedeem:不知道有啥用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
  function redeemUnderlying(
address _reserve,
address payable _user,
uint256 _amount,
uint256 _aTokenBalanceAfterRedeem
)
external
nonReentrant
onlyOverlyingAToken(_reserve)
onlyActiveReserve(_reserve)
onlyAmountGreaterThanZero(_amount)
{
uint256 currentAvailableLiquidity = core.getReserveAvailableLiquidity(_reserve);
require(
currentAvailableLiquidity >= _amount,
"There is not enough liquidity available to redeem"
);

// 取款之后,更新AAVE池子信息
core.updateStateOnRedeem(_reserve, _user, _amount, _aTokenBalanceAfterRedeem == 0);

// 取款
core.transferToUser(_reserve, _user, _amount);

//solium-disable-next-line
emit RedeemUnderlying(_reserve, _user, _amount, block.timestamp);

}

设置抵押

用户可以将自己存入AAVE的资产作为抵押品,这是一个设置开关,为后续调用借款做准备

  • _reserve:标的资产的地址
  • _useAsCollateral:是否设置作为抵押品
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function setUserUseReserveAsCollateral(address _reserve, bool _useAsCollateral)
external
nonReentrant
onlyActiveReserve(_reserve)
onlyUnfreezedReserve(_reserve)
{
uint256 underlyingBalance = core.getUserUnderlyingAssetBalance(_reserve, msg.sender);

require(underlyingBalance > 0, "User does not have any liquidity deposited");

require(
dataProvider.balanceDecreaseAllowed(_reserve, msg.sender, underlyingBalance),
"User deposit is already being used as collateral"
);

core.setUserUseReserveAsCollateral(_reserve, msg.sender, _useAsCollateral);

if (_useAsCollateral) {
emit ReserveUsedAsCollateralEnabled(_reserve, msg.sender);
} else {
emit ReserveUsedAsCollateralDisabled(_reserve, msg.sender);
}
}

然后进入core合约进行设置可以抵押

1
2
3
4
5
6
7
function setUserUseReserveAsCollateral(address _reserve, address _user, bool _useAsCollateral)
public
onlyLendingPool
{
CoreLibrary.UserReserveData storage user = usersReserveData[_user][_reserve];
user.useAsCollateral = _useAsCollateral;
}

使用方法:

1
2
3
4
5
6
7
8
9
10
// 获取借贷池接口
LendingPoolAddressesProvider provider = LendingPoolAddressesProvider(address(0x24a42fD28C976A61Df5D00D0599C34c4f90748c8));
LendingPool lendingPool = LendingPool(provider.getLendingPool());

/// 标的资产为DAI
address daiAddress = address(0x6B175474E89094C44Da98b954EedeAC495271d0F); // mainnet DAI
bool useAsCollateral = true;

//设置开关
lendingPool.setUserUseReserveAsCollateral(daiAddress, useAsCollateral);

借款

超额抵押借款,调用这个方法之前要保证你已经存入了足够数量的抵押品

  • _reserve:要借款的标的资产地址
  • _amount:借入金额
  • _interestRateMode:利率模型,1代表稳定利率,2代表可变利率
  • _referralCode:推荐代码,一般填0,跟推广有关,已经过时了就懒得去了解。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
function borrow(
address _reserve,
uint256 _amount,
uint256 _interestRateMode,
uint16 _referralCode
)
external
nonReentrant
onlyActiveReserve(_reserve)
onlyUnfreezedReserve(_reserve)
onlyAmountGreaterThanZero(_amount)
{
// 用一个结构体来记录信息,防止栈过深,怕超过16个
BorrowLocalVars memory vars;

// 合约中的标的资产要设置为可借出,否则此标的资产无法借出
require(core.isReserveBorrowingEnabled(_reserve), "Reserve is not enabled for borrowing");
// 只允许1和2两种利率模型:稳定利率,可变利率
require(
uint256(CoreLibrary.InterestRateMode.VARIABLE) == _interestRateMode ||
uint256(CoreLibrary.InterestRateMode.STABLE) == _interestRateMode,
"Invalid interest rate mode selected"
);

// 设置利率模型
vars.rateMode = CoreLibrary.InterestRateMode(_interestRateMode);

// 查看此标的资产可借出的数量
vars.availableLiquidity = core.getReserveAvailableLiquidity(_reserve);

require(
vars.availableLiquidity >= _amount,
"There is not enough liquidity available in the reserve"
);

(
,
vars.userCollateralBalanceETH, // 总质押物品的总价值:用ETH计价
vars.userBorrowBalanceETH, // 目前已经借了多少物品:用ETH计价
vars.userTotalFeesETH, // 总手续费ETH
vars.currentLtv,
vars.currentLiquidationThreshold, // 当前流动性阈值
,
vars.healthFactorBelowThreshold // 是否达到质押的健康因子的阈值
) = dataProvider.calculateUserGlobalData(msg.sender);

// 需要用户质押过抵押品
require(vars.userCollateralBalanceETH > 0, "The collateral balance is 0");

// 如果健康因子达到了清算阈值,则可被清算,不可以再借款
require(
!vars.healthFactorBelowThreshold,
"The borrower can already be liquidated so he cannot borrow more"
);

// 计算手续费:借款数量 * 比例
vars.borrowFee = feeProvider.calculateLoanOriginationFee(msg.sender, _amount);

// 借款数目不能太小,否则手续费四舍五入变成0 revert
require(vars.borrowFee > 0, "The amount to borrow is too small");

// 根据借款的信息,计算用户需要质押多少价值:用ETH计价
vars.amountOfCollateralNeededETH = dataProvider.calculateCollateralNeededInETH(
_reserve,
_amount,
vars.borrowFee,
vars.userBorrowBalanceETH,
vars.userTotalFeesETH,
vars.currentLtv
);

// 超额抵押的检验关键:用户总质押的物品的价值必须大于等于用户将要借出的物品的价值
require(
vars.amountOfCollateralNeededETH <= vars.userCollateralBalanceETH,
"There is not enough collateral to cover a new borrow"
);

// 如果是稳定利率模式
if (vars.rateMode == CoreLibrary.InterestRateMode.STABLE) {
// AAVE检测该用户是否被允许以稳定利率模式借款
require(
core.isUserAllowedToBorrowAtStable(_reserve, msg.sender, _amount),
"User cannot borrow the selected amount with a stable rate"
);

// 计算用户在稳定利率模式下能够借贷的最大百分比
// 然后计算可以借款的数额
uint256 maxLoanPercent = parametersProvider.getMaxStableRateBorrowSizePercent();
uint256 maxLoanSizeStable = vars.availableLiquidity.mul(maxLoanPercent).div(100);

// 用户借款的数额要小于等于最大可借出的数额
require(
_amount <= maxLoanSizeStable,
"User is trying to borrow too much liquidity at a stable rate"
);
}

// 可变利率没有做操作,但是系统前面的检测已经保证了质押的金额要大于等于借出的数额,系统已经安全了

//上面的所有检查都通过,AAVE设置新的借款信息
(vars.finalUserBorrowRate, vars.borrowBalanceIncrease) = core.updateStateOnBorrow(
_reserve,
msg.sender,
_amount,
vars.borrowFee,
vars.rateMode
);

// 上面的所有检查都通过,AAVE才借款
core.transferToUser(_reserve, msg.sender, _amount);

emit Borrow(
_reserve,
msg.sender,
_amount,
_interestRateMode,
vars.finalUserBorrowRate,
vars.borrowFee,
vars.borrowBalanceIncrease,
_referralCode,
//solium-disable-next-line
block.timestamp
);
}

使用方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 下面的操作之前,需要先质押ETH或者代币到AAVE,并且设置抵押

// 获取合约接口
LendingPoolAddressesProvider provider = LendingPoolAddressesProvider(address(0x24a42fD28C976A61Df5D00D0599C34c4f90748c8));
LendingPool lendingPool = LendingPool(provider.getLendingPool());

// 我们借DAI
address daiAddress = address(0x6B175474E89094C44Da98b954EedeAC495271d0F);
uint256 amount = 1000 * 1e18;

// 可变利率模式
uint256 variableRate = 2;
uint256 referral = 0;

// 借款
lendingPool.borrow(daiAddress, amount, variableRate, referral);

还款

  • _reserve:标的资产,如果是还ETH,则填写0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE
  • _amount:还款金额。如果是自己还款,可以用-1表示还所有钱;帮别人还不能用-1,建议发送略高于借款金额的钱
  • _onBehalfOf:自己还则填写自己的地址,帮别人还则填写别人的地址
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
function repay(address _reserve, uint256 _amount, address payable _onBehalfOf)
external
payable
nonReentrant
onlyActiveReserve(_reserve)
onlyAmountGreaterThanZero(_amount)
{
// 防止栈过深
RepayLocalVars memory vars;

// 查看用户借款信息,_onBehalfOf可以是自己,也可以是别人,如果是别人则代表帮别人还款
(
vars.principalBorrowBalance,
vars.compoundedBorrowBalance,
vars.borrowBalanceIncrease
) = core.getUserBorrowBalances(_reserve, _onBehalfOf);

// 手续费
vars.originationFee = core.getUserOriginationFee(_reserve, _onBehalfOf);
// 借的是不是ETH
vars.isETH = EthAddressLib.ethAddress() == _reserve;

// 借款总金额要大于0
require(vars.compoundedBorrowBalance > 0, "The user does not have any borrow pending");

// 如果是帮别人还全部,则不能填写-1,应该写具体金额
// 如果是自己还款,可以填写-1
require(
_amount != UINT_MAX_VALUE || msg.sender == _onBehalfOf,
"To repay on behalf of an user an explicit amount to repay is needed."
);

// 手续费
vars.paybackAmount = vars.compoundedBorrowBalance.add(vars.originationFee);

// 部分还款的时候不需要还手续费
if (_amount != UINT_MAX_VALUE && _amount < vars.paybackAmount) {
vars.paybackAmount = _amount;
}

// 查看是不是还ETH:如果还ETH,则输入msg.value,如果是还token,则继续往下
require(
!vars.isETH || msg.value >= vars.paybackAmount,
"Invalid msg.value sent for the repayment"
);

// 如果偿还的金额比手续费还少,还完这点钱然后就退出
if (vars.paybackAmount <= vars.originationFee) {
// 更新还款信息
core.updateStateOnRepay(
_reserve,
_onBehalfOf,
0,
vars.paybackAmount,
vars.borrowBalanceIncrease,
false
);

// 偿还金额
core.transferToFeeCollectionAddress.value(vars.isETH ? vars.paybackAmount : 0)(
_reserve,
_onBehalfOf,
vars.paybackAmount,
addressesProvider.getTokenDistributor()
);

emit Repay(
_reserve,
_onBehalfOf,
msg.sender,
0,
vars.paybackAmount,
vars.borrowBalanceIncrease,
//solium-disable-next-line
block.timestamp
);
return;
}

// 减去手续费
vars.paybackAmountMinusFees = vars.paybackAmount.sub(vars.originationFee);

// 更新还款信息
core.updateStateOnRepay(
_reserve,
_onBehalfOf,
vars.paybackAmountMinusFees,
vars.originationFee,
vars.borrowBalanceIncrease,
vars.compoundedBorrowBalance == vars.paybackAmountMinusFees
);

//if the user didn't repay the origination fee, transfer the fee to the fee collection address
// 还手续费
if(vars.originationFee > 0) {
core.transferToFeeCollectionAddress.value(vars.isETH ? vars.originationFee : 0)(
_reserve,
msg.sender,
vars.originationFee,
addressesProvider.getTokenDistributor()
);
}

// 还款
core.transferToReserve.value(vars.isETH ? msg.value.sub(vars.originationFee) : 0)(
_reserve,
msg.sender,
vars.paybackAmountMinusFees
);

emit Repay(
_reserve,
_onBehalfOf,
msg.sender,
vars.paybackAmountMinusFees,
vars.originationFee,
vars.borrowBalanceIncrease,
//solium-disable-next-line
block.timestamp
);
}

使用方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import "openzeppelin-solidity/contracts/token/ERC20/IERC20.sol";

// 接口
LendingPoolAddressesProvider provider = LendingPoolAddressesProvider(address(0x24a42fD28C976A61Df5D00D0599C34c4f90748c8));
LendingPool lendingPool = LendingPool(provider.getLendingPool());

// 还DAI
address daiAddress = address(0x6B175474E89094C44Da98b954EedeAC495271d0F);
uint256 amount = 1000 * 1e18;

// 如果是自己还款
lendingPool.repay(daiAddress, amount, msg.sender);

// 如果是帮别人还款
address userAddress = /*users_address*/;
IERC20(daiAddress).approve(provider.getLendingPoolCore(), amount); // Approve LendingPool contract
lendingPool.repay(daiAddres, amount, userAddress);

清算

  • 清算条件:当健康因子小于1的时候,可以进行清算。可以用getUserAccountData()查看健康因子
  • 清算过程:可以清算部分资产,也可以清算全部,并且获得打了折扣的抵押品作为回报(相当于清算奖励)。清算人可以指定获取aToken或者标的资产作为清算奖励。清算完之后,健康因子会回到1以上
  • 清算比例:清算人最多清算待偿还金额的 50%,清算折扣就是按照该金额计算的
  • 清算前准备:清算人必须approve给AAVE _collateral ,否则无法清算
  • 注意事项
    • 在大多数情况下,清算人会选择尽可能多的清算(接近50%),将_purchaseAmount设置为-1表示AAVE最大允许的清算比例
    • 如果是用ETH清算,要保证msg.value=_purchaseAmount

清算接口如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
function liquidationCall(
address _collateral, // 借款人质押了啥标的资产
address _reserve, // 借款人借了啥标的资产
address _user, // 清算人地址
uint256 _purchaseAmount, // 清算人帮忙偿还的金额,偿还给AAVE
bool _receiveAToken // true:清算获得aTokens,false:获得标的资产
) external payable nonReentrant onlyActiveReserve(_reserve) onlyActiveReserve(_collateral) {
address liquidationManager = addressesProvider.getLendingPoolLiquidationManager();

//solium-disable-next-line
(bool success, bytes memory result) = liquidationManager.delegatecall(
abi.encodeWithSignature(
"liquidationCall(address,address,address,uint256,bool)",
_collateral,
_reserve,
_user,
_purchaseAmount,
_receiveAToken
)
);
require(success, "Liquidation call failed");

(uint256 returnCode, string memory returnMessage) = abi.decode(result, (uint256, string));

if (returnCode != 0) {
//error found
revert(string(abi.encodePacked("Liquidation failed: ", returnMessage)));
}
}

实际的清算会跳转LendingPoolLiquidationManager合约执行liquidationCall()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
function liquidationCall(
address _collateral,
address _reserve,
address _user,
uint256 _purchaseAmount,
bool _receiveAToken
) external payable returns (uint256, string memory) {
// 防止栈过深
LiquidationCallLocalVars memory vars;

// 获取健康因子
(, , , , , , , vars.healthFactorBelowThreshold) = dataProvider.calculateUserGlobalData(
_user
);

// 没有达到清算阈值则返回
if (!vars.healthFactorBelowThreshold) {
return (
uint256(LiquidationErrors.HEALTH_FACTOR_ABOVE_THRESHOLD),
"Health factor is not below the threshold"
);
}

// 达到了清算阈值,执行下面的逻辑

// 查看借款人质押了多少标的资产
vars.userCollateralBalance = core.getUserUnderlyingAssetBalance(_collateral, _user);

// 借款人质押的标的资产不可能是0
if (vars.userCollateralBalance == 0) {
return (
uint256(LiquidationErrors.NO_COLLATERAL_AVAILABLE),
"Invalid collateral to liquidate"
);
}

// 用户此标的资产必须设置为可用于清算
vars.isCollateralEnabled =
core.isReserveUsageAsCollateralEnabled(_collateral) &&
core.isUserUseReserveAsCollateralEnabled(_collateral, _user);

if (!vars.isCollateralEnabled) {
return (
uint256(LiquidationErrors.COLLATERAL_CANNOT_BE_LIQUIDATED),
"The collateral chosen cannot be liquidated"
);
}

// 用户必须借了这个标的资产才可用于清算
(, vars.userCompoundedBorrowBalance, vars.borrowBalanceIncrease) = core
.getUserBorrowBalances(_reserve, _user);

if (vars.userCompoundedBorrowBalance == 0) {
return (
uint256(LiquidationErrors.CURRRENCY_NOT_BORROWED),
"User did not borrow the specified currency"
);
}

//all clear - calculate the max principal amount that can be liquidated
// 计算用户最大可清算的资产数额
vars.maxPrincipalAmountToLiquidate = vars
.userCompoundedBorrowBalance
.mul(LIQUIDATION_CLOSE_FACTOR_PERCENT)
.div(100);

// 计算清算人实际清算的金额
vars.actualAmountToLiquidate = _purchaseAmount > vars.maxPrincipalAmountToLiquidate
? vars.maxPrincipalAmountToLiquidate
: _purchaseAmount;

// 计算实际可清算的金额
(uint256 maxCollateralToLiquidate, uint256 principalAmountNeeded) = calculateAvailableCollateralToLiquidate(
_collateral,
_reserve,
vars.actualAmountToLiquidate,
vars.userCollateralBalance
);

// 计算手续费
vars.originationFee = core.getUserOriginationFee(_reserve, _user);

// 清算加上手续费
if (vars.originationFee > 0) {
(
vars.liquidatedCollateralForFee,
vars.feeLiquidated
) = calculateAvailableCollateralToLiquidate(
_collateral,
_reserve,
vars.originationFee,
vars.userCollateralBalance.sub(maxCollateralToLiquidate)
);
}

//if principalAmountNeeded < vars.ActualAmountToLiquidate, there isn't enough
//of _collateral to cover the actual amount that is being liquidated, hence we liquidate
//a smaller amount
// 如果实际清算金额大于用于借款人清算的资产金额,那么说明不够钱清算,只能清算这一部分
if (principalAmountNeeded < vars.actualAmountToLiquidate) {
vars.actualAmountToLiquidate = principalAmountNeeded;
}

//if liquidator reclaims the underlying asset, we make sure there is enough available collateral in the reserve
// 清算人想要获取标的资产
if (!_receiveAToken) {
// 获取当前可用的质押金额
uint256 currentAvailableCollateral = core.getReserveAvailableLiquidity(_collateral);
// 如果借款人没有那么多钱来偿还清算,则报错
if (currentAvailableCollateral < maxCollateralToLiquidate) {
return (
uint256(LiquidationErrors.NOT_ENOUGH_LIQUIDITY),
"There isn't enough liquidity available to liquidate"
);
}
}

// 更新清算信息
core.updateStateOnLiquidation(
_reserve,
_collateral,
_user,
vars.actualAmountToLiquidate,
maxCollateralToLiquidate,
vars.feeLiquidated,
vars.liquidatedCollateralForFee,
vars.borrowBalanceIncrease,
_receiveAToken
);

// 获取aToken地址
AToken collateralAtoken = AToken(core.getReserveATokenAddress(_collateral));

//if liquidator reclaims the aToken, he receives the equivalent atoken amount
// 如果清算人想要获取aToken
if (_receiveAToken) {
collateralAtoken.transferOnLiquidation(_user, msg.sender, maxCollateralToLiquidate);
} else { // 如果清算人想要获得标的资产
collateralAtoken.burnOnLiquidation(_user, maxCollateralToLiquidate); // 销毁借款人的aToken
core.transferToUser(_collateral, msg.sender, maxCollateralToLiquidate); // 转标的资产给清算人
}

// 清算人帮助借款人还款给AAVE池子
core.transferToReserve.value(msg.value)(_reserve, msg.sender, vars.actualAmountToLiquidate);

if (vars.feeLiquidated > 0) {
//if there is enough collateral to liquidate the fee, first transfer burn an equivalent amount of
//aTokens of the user
// 如果清算完之后,借款人还有足够的金额支付手续费,则扣除用户的aToken
collateralAtoken.burnOnLiquidation(_user, vars.liquidatedCollateralForFee);

// 清算手续费
core.liquidateFee(
_collateral,
vars.liquidatedCollateralForFee,
addressesProvider.getTokenDistributor()
);

emit OriginationFeeLiquidated(
_collateral,
_reserve,
_user,
vars.feeLiquidated,
vars.liquidatedCollateralForFee,
//solium-disable-next-line
block.timestamp
);

}
emit LiquidationCall(
_collateral,
_reserve,
_user,
vars.actualAmountToLiquidate,
maxCollateralToLiquidate,
vars.borrowBalanceIncrease,
msg.sender,
_receiveAToken,
//solium-disable-next-line
block.timestamp
);

return (uint256(LiquidationErrors.NO_ERROR), "No errors");
}

使用方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import "openzeppelin-solidity/contracts/token/ERC20/IERC20.sol";

// 获取接口
LendingPoolAddressesProvider provider = LendingPoolAddressesProvider(address(0x24a42fD28C976A61Df5D00D0599C34c4f90748c8));
LendingPool lendingPool = LendingPool(provider.getLendingPool());

// 借款人的清算资产地址
address collateralAddress = /*collateral_address*/;
// 清算人用DAI来清算
address daiAddress = address(0x6B175474E89094C44Da98b954EedeAC495271d0F); // mainnet DAI
// 借款人地址
address userAddress = /*user_address_being_liquidated*/;
// 偿还金额
uint256 purchaseAmount = 100 * 1e18;
// 获取aToken
bool receiveATokens = true;

// DAI授权,这样池子才能工作
IERC20(daiAddress).approve(provider.getLendingPoolCore(), purchaseAmount);

// 清算
lendingPool.liquidationCall(
collateralAddress,
daiAddress,
userAddress,
purchaseAmount,
receiveATokens
);

闪电贷

  • _receiver:调用闪电贷的合约
  • _reserve:借贷多少金额
  • _params:回调函数的参数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
function flashLoan(address _receiver, address _reserve, uint256 _amount, bytes memory _params)
public
nonReentrant
onlyActiveReserve(_reserve)
onlyAmountGreaterThanZero(_amount)
{

// 查看AAVE池子是否有足够的钱给你闪电贷
// 不用getAvailableLiquidity()来查询,因为这个方法太消耗gas了
uint256 availableLiquidityBefore = _reserve == EthAddressLib.ethAddress()
? address(core).balance
: IERC20(_reserve).balanceOf(address(core));

require(
availableLiquidityBefore >= _amount,
"There is not enough liquidity available to borrow"
);

// 闪电贷手续费
(uint256 totalFeeBips, uint256 protocolFeeBips) = parametersProvider
.getFlashLoanFeesInBips();
uint256 amountFee = _amount.mul(totalFeeBips).div(10000);

// 借款的金额太小,四舍五入导致手续费为0,则revert,因此闪电贷的金额不能太小
uint256 protocolFee = amountFee.mul(protocolFeeBips).div(10000);
require(
amountFee > 0 && protocolFee > 0,
"The requested amount is too small for a flashLoan."
);

// 获取到调用闪电贷的合约实例
IFlashLoanReceiver receiver = IFlashLoanReceiver(_receiver);

address payable userPayable = address(uint160(_receiver));

// 转钱给调用闪电贷的合约实例
core.transferToUser(_reserve, userPayable, _amount);

// 调用闪电贷的合约实例 调用回调函数。合约需要在回调函数中偿还金额:借款金额+手续费
receiver.executeOperation(_reserve, _amount, amountFee, _params);

// 闪电贷结束之后,查看合约的资产情况
uint256 availableLiquidityAfter = _reserve == EthAddressLib.ethAddress()
? address(core).balance
: IERC20(_reserve).balanceOf(address(core));

// 闪电贷结束之后的合约资产 = 闪电贷结束之前的合约资产 + 手续费
// V1版本非常不友好,我们必须完全精确的计算,否则交易失败
// 这里严格等于并不会导致DoS,因为不是用合约的变量记录资产信息,
// 这个方法是直接获取资产信息的,因此避免了这个问题
require(
availableLiquidityAfter == availableLiquidityBefore.add(amountFee),
"The actual balance of the protocol is inconsistent"
);

// 更新闪电贷信息
core.updateStateOnFlashLoan(
_reserve,
availableLiquidityBefore,
amountFee.sub(protocolFee),
protocolFee
);

//solium-disable-next-line
emit FlashLoan(_receiver, _reserve, _amount, amountFee, protocolFee, block.timestamp);
}

使用方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 合约解接口
LendingPoolAddressesProvider provider = LendingPoolAddressesProvider(address(0x24a42fD28C976A61Df5D00D0599C34c4f90748c8));
LendingPool lendingPool = LendingPool(provider.getLendingPool());

// 一个实现了闪电贷回调函数的合约
address receiver = /*contract_address*/;
// 借DAI
address daiAddress = address(0x6B175474E89094C44Da98b954EedeAC495271d0F); // mainnet DAI
uint256 amount = 1000 * 1e18;

// 回调函数参数
bytes memory params = "";
// Else encode the params like below (bytes encoded param of type `address` and `uint`)
// bytes memory params = abi.encode(address(this), 1234);

lendingPool.flashLoan(receiver, daiAddress, amount, params);

其他

  • 借款利率模式:改变借款的模式,要做一定的检验才可以转换模式
1
function swapBorrowRateMode(address _reserve)
  • 改变稳定模式的利率:市场的流动性比稳定利率还大时,用户想更新自己借款的稳定模式利率
1
function rebalanceStableBorrowRate(address _reserve, address _user)

view方法

  • getReserveConfigurationData():查看AAVE池子的基本信息,包括清算阈值、利率、是否启用质押、是否启用可借款等
  • getReserveData():返回池子的详细信息,包括总流动性、可用流动性、总借款、流动性比例、稳定模式利率等
  • getUserAccountData():查看用户在池子中总资产的情况,包括总质押数量、总可借款数量、健康因子、清算阈值、总抵押数量等,全部用ETH计价
  • getUserReserveData():查看用户在池子中某种资产的情况,包括总质押数量、总可借款数量、健康因子、清算阈值、总抵押数量等,全部用ETH计价

AAVE代币

链下签名

链下签名授权

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
function permit(
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) external {
require(owner != address(0), "INVALID_OWNER");
//solium-disable-next-line
require(block.timestamp <= deadline, "INVALID_EXPIRATION");
uint256 currentValidNonce = _nonces[owner];
bytes32 digest = keccak256(
abi.encodePacked(
"\x19\x01",
DOMAIN_SEPARATOR,
keccak256(
abi.encode(PERMIT_TYPEHASH, owner, spender, value, currentValidNonce, deadline))
)
);

require(owner == ecrecover(digest, v, r, s), "INVALID_SIGNATURE");
_nonces[owner] = currentValidNonce.add(1);
_approve(owner, spender, value);
}

使用方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
import { signTypedData_v4 } from 'eth-sig-util'
import { fromRpcSig } from 'ethereumjs-util'

// ... other imports

import AaveTokenAbi from "./AaveTokenAbi.json"

// ... setup your web3 provider

const aaveTokenAddress = "AAVE_TOKEN_ADDRESS"
const aaveTokenContract = new web3.eth.Contract(AaveTokenAbi, aaveTokenAddress)

const privateKey = "YOUR_PRIVATE_KEY_WITHOUT_0x"
const chainId = 1
const owner = "OWNER_ADDRESS"
const spender = "SPENDER_ADDRESS"
const value = 100 // Amount the spender is permitted
const nonce = 1 // The next valid nonce, use `_nonces()`
const deadline = 1600093162

const permitParams = {
types: {
EIP712Domain: [
{ name: "name", type: "string" },
{ name: "version", type: "string" },
{ name: "chainId", type: "uint256" },
{ name: "verifyingContract", type: "address" },
],
Permit: [
{ name: "owner", type: "address" },
{ name: "spender", type: "address" },
{ name: "value", type: "uint256" },
{ name: "nonce", type: "uint256" },
{ name: "deadline", type: "uint256" },
],
},
primaryType: "Permit",
domain: {
name: "Aave Token",
version: "1",
chainId: chainId,
verifyingContract: aaveTokenAddress,
},
message: {
owner,
spender,
value,
nonce,
deadline,
},
}

const signature = signTypedData_v4(
Buffer.from(privateKey, "hex"),
{ data: permitParams }
)

const { v, r, s } = fromRpcSig(signature)

await aaveTokenContract.methods
.permit({
owner,
spender,
value,
deadline,
v,
r,
s
})
.send()
.catch((e) => {
throw Error(`Error permitting: ${e.message}`)
})

快照

配备了快照机制,每次转账,mint,销毁之前,都会拍一次快照

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
/**
* @dev Writes a snapshot for an owner of tokens
* @param owner The owner of the tokens
* @param oldValue The value before the operation that is gonna be executed after the snapshot
* @param newValue The value after the operation
*/
function _writeSnapshot(address owner, uint128 oldValue, uint128 newValue) internal {
uint128 currentBlock = uint128(block.number);

uint256 ownerCountOfSnapshots = _countsSnapshots[owner];
mapping (uint256 => Snapshot) storage snapshotsOwner = _snapshots[owner];

// Doing multiple operations in the same block
if (ownerCountOfSnapshots != 0 && snapshotsOwner[ownerCountOfSnapshots.sub(1)].blockNumber == currentBlock) {
snapshotsOwner[ownerCountOfSnapshots.sub(1)].value = newValue;
} else {
snapshotsOwner[ownerCountOfSnapshots] = Snapshot(currentBlock, newValue);
_countsSnapshots[owner] = ownerCountOfSnapshots.add(1);
}

emit SnapshotDone(owner, oldValue, newValue);
}

/**
* @dev Writes a snapshot before any operation involving transfer of value: _transfer, _mint and _burn
* - On _transfer, it writes snapshots for both "from" and "to"
* - On _mint, only for _to
* - On _burn, only for _from
* @param from the from address
* @param to the to address
* @param amount the amount to transfer
*/
function _beforeTokenTransfer(address from, address to, uint256 amount) internal override {
if (from == to) {
return;
}

if (from != address(0)) {
uint256 fromBalance = balanceOf(from);
_writeSnapshot(from, uint128(fromBalance), uint128(fromBalance.sub(amount)));
}
if (to != address(0)) {
uint256 toBalance = balanceOf(to);
_writeSnapshot(to, uint128(toBalance), uint128(toBalance.add(amount)));
}

// caching the aave governance address to avoid multiple state loads
ITransferHook aaveGovernance = _aaveGovernance;
if (aaveGovernance != ITransferHook(0)) {
aaveGovernance.onTransfer(from, to, amount);
}
}

安全模块

质押

  • onBehalfOf:获得stkToken的地址,一般是给自己
  • amount:质押AAVE Token的数量
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
function stake(address onBehalfOf, uint256 amount) external override {
require(amount != 0, 'INVALID_ZERO_AMOUNT');
uint256 balanceOfUser = balanceOf(onBehalfOf);

// 更新用户资产情况,得到用户到目前为止利润的累计数量(如果之前stake过)
uint256 accruedRewards = _updateUserAssetInternal(
onBehalfOf, // 进行stake的用户
address(this), // stkToken
balanceOfUser, // 用户拥有多少stkToken
totalSupply() // stkToken总量
);
// 如果上次到这次有累计利润,则把可以领取的利润记录下来,
// stake之后质押的金额会变多,得到的利润速率也会变化,因此需要把之前的利润先记录
if (accruedRewards != 0) {
emit RewardsAccrued(onBehalfOf, accruedRewards);
stakerRewardsToClaim[onBehalfOf] = stakerRewardsToClaim[onBehalfOf].add(accruedRewards);
}

// 更新冷却时间,其实就是说你stake之后,不能马上取款,根据一定的算法来设置你可以取款利润的时间
stakersCooldowns[onBehalfOf] = getNextCooldownTimestamp(0, amount, onBehalfOf, balanceOfUser);

_mint(onBehalfOf, amount);
IERC20(STAKED_TOKEN).safeTransferFrom(msg.sender, address(this), amount);

emit Staked(msg.sender, onBehalfOf, amount);
}

获取利润

可以看出,利润的token又是另外一种新的token

  • to:接收利润token的地址
  • amount:提取多少利润,-1表示全部提取
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function claimRewards(address to, uint256 amount) external override {
// 查看目前已累计的利润金额
uint256 newTotalRewards = _updateCurrentUnclaimedRewards(
msg.sender,
balanceOf(msg.sender),
false
);

// amount=-1表示领取完所有利润,否则领取amount数量
uint256 amountToClaim = (amount == type(uint256).max) ? newTotalRewards : amount;
stakerRewardsToClaim[msg.sender] = newTotalRewards.sub(amountToClaim, "INVALID_AMOUNT");

// 发送奖励token
REWARD_TOKEN.safeTransferFrom(REWARDS_VAULT, to, amountToClaim);

emit RewardsClaimed(msg.sender, to, amountToClaim);
}

取款

设计

  • 存款之后,需要等待COOLDOWN_SECONDS之后,才可以取款,并且要在UNSTAKE_WINDOW时间内取款,否则无法取款
  • 如果超过了取款时间,则需要调用cooldown()刷新时间,重新等待

这么设计的原因

  • COOLDOWN_SECONDS:防止用户短时间内不断重复质押提现这个操作
  • UNSTAKE_WINDOW:AAVE的业务逻辑,我们猜不到他为什么这么设计

参数

  • to:接收存款的地址
  • amount:提取多少存款,如果提取金额大于质押金额,则表示提取完所有,否则提取部分
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
function redeem(address to, uint256 amount) external override {
require(amount != 0, 'INVALID_ZERO_AMOUNT');

// 取款要达到冷却时间COOLDOWN_SECONDS之后取款,质押之后不能马上取款
// 比如 COOLDOWN_SECONDS = 2days
uint256 cooldownStartTimestamp = stakersCooldowns[msg.sender];
// 那么从开始质押到取款,至少要等2days
require(
block.timestamp > cooldownStartTimestamp.add(COOLDOWN_SECONDS),
'INSUFFICIENT_COOLDOWN'
);
// 假如UNSTAKE_WINDOW = 4 days,那么在2 days之后我可以取款,但是 4 days之后,我就无法取款了,
// 需要调用cooldown()来更新冷却,然后再等待,再在窗口期取款
require(
block.timestamp.sub(cooldownStartTimestamp.add(COOLDOWN_SECONDS)) <= UNSTAKE_WINDOW,
'UNSTAKE_WINDOW_FINISHED'
);
// 获取msg.sender的stkToken数量
uint256 balanceOfMessageSender = balanceOf(msg.sender);

// 如果想要提取的金额大于质押的金额,则取款所有;否则取款部分
uint256 amountToRedeem = (amount > balanceOfMessageSender) ? balanceOfMessageSender : amount;

// 更新用户未领取的利润
_updateCurrentUnclaimedRewards(msg.sender, balanceOfMessageSender, true);

// 烧掉用户的stkToken
_burn(msg.sender, amountToRedeem);

// 如果用户是全部取完,则冷却时间改成0
if (balanceOfMessageSender.sub(amountToRedeem) == 0) {
stakersCooldowns[msg.sender] = 0;
}

IERC20(STAKED_TOKEN).safeTransfer(to, amountToRedeem);

emit Redeem(msg.sender, to, amountToRedeem);
}

冷却

根据发送者/接收者时间戳计算冷却时间戳。

  • fromCooldownTimestamp:发送者的冷却时间戳
  • amountToReceive:要发送的 stkAAVE 代币数量
  • toAddress:接收者地址
  • toBalance:接收者余额
1
2
3
4
5
6
function getNextCooldownTimestamp(
uint256 fromCooldownTimestamp,
uint256 amountToReceive,
address toAddress,
uint256 toBalance
) public returns (uint256)

view方法

  • stakersCooldowns():mapping,获取用户的冷却时间

治理

合约

  • AaveProtoGovernance:处理大部分投票逻辑
  • AssetVotingWeightProvider:将资产列入白名单并设置投票权重
  • AavePropositionPower:控制协议治理的权限,例如注册新提案
  • GovernanceParamsProvider:存储全局协议治理参数,例如注册新提案所需的提案权阈值

执行流程

  1. 创建:提案由具有足够提案权的用户创建。在早期阶段,这是 Genesis 团队。
  2. 投票:提案进入“投票”阶段,持续时间为_votingBlocksDuration。通过AaveProtoGovernance的submitVoteByVoter()进行投票
  3. 验证:如果投票数额达到_threshold,该提案将进入“验证”阶段。验证持续时间为_validatingBlocksDuration。
    • 如果投票数没达到_threshold,则提案将保留在“投票”阶段,直至_threshold达成。
    • 在“验证”阶段,任何人都可以通过调用challengeVoters()来质疑(并取消无效)投票。如果当前投票代币余额低于投票时的投票代币余额,则投票被视为无效。
    • 这个从“验证”到“投票”的过程最多可以发生_maxMovesToVotingAllowed次,之后如果没有通过,则被认为是“过期Expired”。
  4. 结束:_validatingBlocksDuration之后,提案执行,状态更改为‘Executed’
    1. 如果投票数目超过_threshold,则调用execute()执行提案
    2. .如果尚未达到投票数_threshold,则不会执行任何提案代码。

如何投票

  • 获取提案 ID 列表
    • 使用AaveProtoGovernance合约发出的事件:ProposalCreated()
    • 链上:使用try...catch模式迭代提案 ID,从索引 0 开始
    • 通过 GraphQL:查询AAVE的subgraph以接收提案及其 ID 的列表
  • 查询提案数据
    • 链上:使用getProposalBasicData()
    • 通过 GraphQL:subgraph
  • 投票
    • 有了提案ID,调用submitVoteByVoter()进行投票,如果调用了一次之后再次调用,则下一次调用覆盖上次调用,以最新的投票为准
    • 使用cancelVoteByVoter()进行取消投票
  • 获取提案状态
    • 链上调用getProposalBasicData()
    • subgraph

投票

输入提案ID,投票数目

  • _proposalId:提案ID
  • _vote:0表示弃权,1表示同意,2表示否决
  • _asset:锁定的资产,用于投票,只有在合约中列入白名单的情况下才允许投票
1
2
3
function submitVoteByVoter(uint256 _proposalId, uint256 _vote, IERC20 _asset) external {
internalSubmitVote(_proposalId, _vote, msg.sender, _asset);
}

跳到实际逻辑:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
  function internalSubmitVote(uint256 _proposalId, uint256 _vote, address _voter, IERC20 _asset) internal {
// 获取提案
Proposal storage _proposal = proposals[_proposalId];
// 该提案要处于投票期
require(_proposal.proposalStatus == ProposalStatus.Voting, "VOTING_STATUS_REQUIRED");
// 资产的投票权重,0代表不允许投票,也就是该资产并不是白名单中的
uint256 _assetVotingWeight = govParamsProvider.getAssetVotingWeightProvider().getVotingWeight(_asset);
require(_assetVotingWeight != 0, "ASSET_NOT_LISTED");
// 只能输入0、1、2
require(_vote <= COUNT_CHOICES, "INVALID_VOTE_PARAM");
// 没有足够的资产不得投票
uint256 _voterAssetBalance = _asset.balanceOf(_voter);
require(_voterAssetBalance > 0, "INVALID_VOTER_BALANCE");

// 如果之前投票过了,则这次投票将覆盖上次
if (address(_proposal.voters[_voter].asset) != address(0)) {
internalCancelVote(_proposalId, _voter);
}

// 写入投票信息
uint256 _assetWeight = _assetVotingWeight;
uint256 _votingPower = _voterAssetBalance.mul(_assetWeight);
_proposal.totalVotes = _proposal.totalVotes.add(1);
_proposal.votes[_vote] = _votingPower.add(_proposal.votes[_vote]);
Voter storage voter = _proposal.voters[_voter];
voter.vote = _vote;
voter.weight = _assetWeight;
voter.balance = _voterAssetBalance;
voter.asset = _asset;
voter.nonce = voter.nonce.add(1);

emit VoteEmitted(_proposalId, _voter, _vote, voter.asset, _assetWeight, _voterAssetBalance);

// 投票之后,如果在投票的有效期内达到了投票阈值, 则该提案进入有效模式
tryToMoveToValidating(_proposalId);
}

判断提案是否进入有效模式

1
2
3
4
5
6
7
8
9
10
11
12
13
function tryToMoveToValidating(uint256 _proposalId) public {
Proposal storage _proposal = proposals[_proposalId];
require(_proposal.proposalStatus == ProposalStatus.Voting, "VOTING_STATUS_REQUIRED");
if (_proposal.currentStatusInitBlock.add(_proposal.votingBlocksDuration) <= block.number) {
for (uint256 i = 0; i <= COUNT_CHOICES; i++) {
if (_proposal.votes[i] > _proposal.threshold) {
// 设置该提案为有效
internalMoveToValidating(_proposalId);
return;
}
}
}
}

取消投票

这个方法用于防止重复投票攻击,任何人发现了有人尝试重复投票攻击,可以进行质疑:当投票者当前的投票资产余额小于投票时资产的余额时,投票被视为无效,因为他可能拿着这笔钱干其他事情,比如转给另外一个人,然后继续投票;或者拿这个钱干其他事情。

举个例子:Alice 的余额为 100 LEND,对提案 1 进行投票,并在“验证”阶段结束之前将 50 LEND 发送到交易所。由于她当前的余额少于投票时的余额,她的投票将无效。

  • 只有在提案有效的时候才可以质疑
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
  function challengeVoters(uint256 _proposalId, address[] calldata _voters) external {

Proposal storage _proposal = proposals[_proposalId];
// 只有在提案有效的时候才可以质疑
require(_proposal.proposalStatus == ProposalStatus.Validating, "VALIDATING_STATUS_REQUIRED");

for (uint256 i = 0; i < _voters.length; i++) {
address _voterAddress = _voters[i];
Voter memory _voter = _proposal.voters[_voterAddress];
uint256 _voterAssetBalance = _voter.asset.balanceOf(_voterAddress);
// 取消某个恶意用户的投票资格
if (_voterAssetBalance < _voter.balance) {
internalCancelVote(_proposalId, _voterAddress);
}
}

// 取消某个恶意用户的投票资格后,重新遍历投票数,如果没达到投票阈值,则重新
// 回到投票期间(前提是投票时间尚未结束)
if (_proposal.movesToVoting < _proposal.maxMovesToVotingAllowed &&
_proposal.votes[getLeadingChoice(_proposalId)] < _proposal.threshold) {
internalMoveToVoting(_proposalId);
}
}

view方法

  • getLimitBlockOfProposal():查看某个提案到达哪个取款之后是无效的

  • getLeadingChoice():查看某个提案哪个票多,赞成?弃权?反对?

  • getProposalBasicData():获取某个提案的详细信息

  • getVoterData():获取某人对某个提案的投票情况
  • getVotesData():获取给定提案 ID 的投票数据,即每个选项的累积投票数。例如,[1, 2, 3]翻译为:1人齐全,2人同意,3人反对

aToken

  • aToken是生息衍生代币,deposit的时候被铸造,提现的时候被销毁。
  • aTokens的价值与相应存入资产的价值以1:1的比例挂钩,可以安全地存储、转移或交易。
  • AAVE收取的手续费将会给到aToken的持有者,aToken会不断增值

取款

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
function redeem(uint256 _amount) external {

require(_amount > 0, "Amount to redeem needs to be > 0");

// 计算用户的余额
(,
uint256 currentBalance,
uint256 balanceIncrease,
uint256 index) = cumulateBalanceInternal(msg.sender);

uint256 amountToRedeem = _amount;

// 如果取出的金额是-1,则代表全部资产取出
if(_amount == UINT_MAX_VALUE){
amountToRedeem = currentBalance;
}

require(amountToRedeem <= currentBalance, "User cannot redeem more than the available balance");

// 查看用户是否被允许取款
require(isTransferAllowed(msg.sender, amountToRedeem), "Transfer cannot be allowed.");

// 如果用户将他的利润给其他人,我们计算利息。如果没重定向给别人,则啥也不做
// mapping (address => address) private interestRedirectionAddresses; 用来重定向利润给谁
updateRedirectedBalanceOfRedirectionAddressInternal(msg.sender, balanceIncrease, amountToRedeem);

// 烧掉aToken
_burn(msg.sender, amountToRedeem);

bool userIndexReset = false;
// 如果取出之后,用户的资产归零,则设置用户的信息
if(currentBalance.sub(amountToRedeem) == 0){
userIndexReset = resetDataOnZeroBalanceInternal(msg.sender);
}

// 拿回标的资产
pool.redeemUnderlying(
underlyingAssetAddress,
msg.sender,
amountToRedeem,
currentBalance.sub(amountToRedeem)
);

emit Redeem(msg.sender, amountToRedeem, balanceIncrease, userIndexReset ? 0 : index);
}

转账

用于将代币转移msg.sender到指定的recipient

注意,如果要转账的aToken被用作抵押品,则调用会失败

1
2
function transfer(address recipient, uint256 amount) public
function transferFrom(address from, address to, uint256 amount) public

利息重定向

将aToken生成的利息给谁,_from不可以是 _to

1
2
3
4
function redirectInterestStreamInternal(
address _from,
address _to
) internal

允许_to执行利息重定向。此方法允许第三方代表存款人设置利息流重定向。

1
function allowInterestRedirectionTo(address _to)

_from授权给msg.sender了,它可以进一步重定向利润给其他人

1
function redirectInterestStreamOf(address _from, address _to) external

view方法

  • isTransferAllowed():用于查看如果调用transfer或者取款函数会不会失败,因为这两个操作会影响健康因子

信用委托

使用OpenLaw实现,AAVE并没有部署相关的合约,可以在区块链浏览器找到完整代码

部署Vault

部署一个_asset可供借用的保管库,返回新部署的 AaveCollateralVault 的地址

1
2
3
4
5
6
7
8
9
10
11
function deployVault(address _asset) external returns (address) {
address vault = address(new AaveCollateralVault());
AaveCollateralVault(vault).setBorrow(_asset);
// Mark address as vault
_vaults[vault] = msg.sender;

// Set vault owner
_ownedVaults[msg.sender].push(vault);
emit DeployVault(vault, msg.sender, _asset);
return vault;
}

利率模型

在稳定利率或可变利率之间更改利率模型。

利率模型:1是稳定利率,2是可变利率。默认为2.

1
2
3
function setModel(uint _model) external onlyOwner {
model = _model;
}

增加限制

增加对spender的限制

  • vault:已部署的AaveCollateralVault地址。
  • spender:借款人
  • addedValue:spender能够借入的最大金额(以资产的基本单位表示,例如WBTC有6位小数,USDC有8位小数,ETH有18位小数)。
1
2
3
4
5
6
7
8
9
10
11
function increaseLimit(address vault, address spender, uint addedValue) external {
require(isVaultOwner(address(vault), msg.sender), "!owner");
if (!_borrowerContains[vault][spender]) {
_borrowerContains[vault][spender] = true;
_borrowers[vault].push(spender);
_borrowerVaults[spender].push(vault);
}
uint amount = _limits[vault][spender].add(addedValue);
_approve(vault, spender, amount);
emit IncreaseLimit(vault, msg.sender, spender, amount);
}

启动委托信贷

将 aToken 抵押品存入AaveCollateralVault以启用委托信贷。

  • vault:已部署的AaveCollateralVault地址。
  • aToken:aToken地址
  • amount:数额
1
2
3
4
5
6
7
8
9
function deposit(AaveCollateralVault vault, address aToken, uint amount) external {
require(isVault(address(vault)), "!vault");
IERC20(aToken).safeTransferFrom(msg.sender, address(vault), amount);
address underlying = AaveToken(aToken).underlyingAssetAddress();
if (vault.isReserve(underlying) == false) {
vault.activate(underlying);
}
emit Deposit(address(vault), msg.sender, aToken, amount);
}

借款

只有低于最大可借金额的spender才可以调用该方法借入金额

1
2
3
4
5
6
7
8
9
10
function borrow(AaveCollateralVault vault, address reserve, uint amount) external {
require(isVault(address(vault)), "!vault");
uint _borrow = amount;
if (vault.asset() == address(0)) {
_borrow = getReservePriceUSD(reserve).mul(amount);
}
_approve(address(vault), msg.sender, _limits[address(vault)][msg.sender].sub(_borrow, "borrow amount exceeds allowance"));
vault.borrow(reserve, amount, msg.sender);
emit Borrow(address(vault), msg.sender, reserve, amount);
}

还款

在调用之前,调用者必须approve授权给AaveCollateralVaultProxy

1
2
3
4
5
6
function repay(AaveCollateralVault vault, address reserve, uint amount) external {
require(isVault(address(vault)), "!vault");
IERC20(reserve).safeTransferFrom(msg.sender, address(vault), amount);
vault.repay(reserve, amount);
emit Repay(address(vault), msg.sender, reserve, amount);
}

取款

信用委托人可以在适当的时候提取 aToken 抵押品

1
2
3
4
5
function withdraw(AaveCollateralVault vault, address aToken, uint amount) external {
require(isVaultOwner(address(vault), msg.sender), "!owner");
vault.withdraw(aToken, amount, msg.sender);
emit Withdraw(address(vault), msg.sender, aToken, amount);
}

价格预言机

见文档

Prev
2024-04-14 14:40:18 # 12.DeFi
Next