├── image (7) (1).png ├── Bend NFT Oracle 0517.png ├── BendDAO Protocols 0812.jpg ├── 68747470733a2f2f6c68332e676f6f676c6575736572636f6e74656e742e636f6d2f706e5a487175355361704c5f374a6651774f446c2d562d57544a4d4275754347675556364f694839534869654658583255596e7a39645075535879484e554735786f5f534949393847676f6a416f4b48666661484b50.png ├── LICENSE └── README.md /image (7) (1).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ByteWizardJ/BendDAO-analysis/HEAD/image (7) (1).png -------------------------------------------------------------------------------- /Bend NFT Oracle 0517.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ByteWizardJ/BendDAO-analysis/HEAD/Bend NFT Oracle 0517.png -------------------------------------------------------------------------------- /BendDAO Protocols 0812.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ByteWizardJ/BendDAO-analysis/HEAD/BendDAO Protocols 0812.jpg -------------------------------------------------------------------------------- /68747470733a2f2f6c68332e676f6f676c6575736572636f6e74656e742e636f6d2f706e5a487175355361704c5f374a6651774f446c2d562d57544a4d4275754347675556364f694839534869654658583255596e7a39645075535879484e554735786f5f534949393847676f6a416f4b48666661484b50.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ByteWizardJ/BendDAO-analysis/HEAD/68747470733a2f2f6c68332e676f6f676c6575736572636f6e74656e742e636f6d2f706e5a487175355361704c5f374a6651774f446c2d562d57544a4d4275754347675556364f694839534869654658583255596e7a39645075535879484e554735786f5f534949393847676f6a416f4b48666661484b50.png -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Jukes 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # BendDAO-analysis 2 | 3 | BendDAO 是一个蓝筹 NFT 流动性协议,支持 NFT 借贷,抵押,抵押品挂单和 NFT 首付购买。 4 | 5 | 1. 官网:https://www.benddao.xyz/ 6 | 2. 文档:https://docs.benddao.xyz/ 7 | 3. 中文文档(一部分内容已经不再适用于最新版本):https://github.com/cgq0123/bend-gitbook-portal/blob/chinese-v2/SUMMARY.md 8 | 4. 开发者文档:https://docs.benddao.xyz/developers/ 9 | 5. 测试网站地址:https://goerli.benddao.xyz/ 10 | 11 | 这里对他的业务和合约进行了解析。 12 | 13 | - [BendDAO-analysis](#benddao-analysis) 14 | - [业务](#业务) 15 | - [Liquidity Listing](#liquidity-listing) 16 | - [boundNFT 的用途](#boundnft-的用途) 17 | - [预言机](#预言机) 18 | - [合约解析](#合约解析) 19 | - [Exchange Protocol](#exchange-protocol) 20 | - [Order](#order) 21 | - [成单方法](#成单方法) 22 | - [matchAskWithTakerBid](#matchaskwithtakerbid) 23 | - [matchAskWithTakerBidUsingETHAndWETH](#matchaskwithtakerbidusingethandweth) 24 | - [matchBidWithTakerAsk](#matchbidwithtakerask) 25 | - [事件](#事件) 26 | - [Execution Strategy(执行策略)](#execution-strategy执行策略) 27 | - [AuthorizationManager](#authorizationmanager) 28 | - [CurrencyManager](#currencymanager) 29 | - [Lending Protocol](#lending-protocol) 30 | - [Main Contracts 和 Supporting Contracts](#main-contracts-和-supporting-contracts) 31 | - [LendPool](#lendpool) 32 | - [重要属性](#重要属性) 33 | - [方法](#方法) 34 | - [LendPoolLoan](#lendpoolloan) 35 | - [属性](#属性) 36 | - [方法](#方法-1) 37 | - [BToken](#btoken) 38 | - [DebtToken](#debttoken) 39 | - [BoundNFT](#boundnft) 40 | - [Down Payment](#down-payment) 41 | 42 | ## 业务 43 | ### Liquidity Listing 44 | 45 | ![Liquidity Listing](68747470733a2f2f6c68332e676f6f676c6575736572636f6e74656e742e636f6d2f706e5a487175355361704c5f374a6651774f446c2d562d57544a4d4275754347675556364f694839534869654658583255596e7a39645075535879484e554735786f5f534949393847676f6a416f4b48666661484b50.png) 46 | 47 | #### 卖方视角 48 | 49 | 通过抵押品挂单,NFT 持有人/卖家可以选择接受即时 NFT 支持的贷款,并在挂单时即时获得最高达 40% 的地板价。用户可以随时在 BendDAO 上挂单抵押品。 50 | 51 | 1. 卖家创建 Liquidity Listing 52 | 2. 挂单的同时获取最多 40% 该系列 NFT 地板价 的 eth 和 一个作为抵押品的 boundNFT 53 | 3. 卖家可以随时卖出抵押品 boundNFT 54 | 4. 买方将在交易后偿还包括利息在内的贷款。扣除债务与利息后的余额将在交易后转给借款人(卖方) 55 | 5. 卖方将获得的金额 = 总价 - 含息债务 56 | 57 | #### 买方视角 58 | 59 | 买家可以根据实际价格,支付最低为 60% 的首付来购买蓝筹 NFT,同时启动 AAVE 的闪电贷款来支付剩余部分。如果 NFT 的总价远远高于系列地板价,首付的比例会增加。闪电贷的借款金额将通过 BendDAO 上的即时 NFT 支持的贷款来偿还。 60 | 61 | 具体来说,BendDAO NFT 首付的流程如下。 62 | 63 | 1. 买家至少支付 60% 的首付款; 64 | 2. 启动闪电贷,从第三方平台(如 AAVE)借出剩余的资金; 65 | 3. 新购买的 NFT 将被用作 BendDAO 的抵押品以获得即时的流动性; 66 | 4. 该闪电贷将用从 BendDAO 借出的 ETH 来偿还。 67 | 5. 买家将在支付首付款时自动成为借款人。而借款人也可以在 BendDAO 市场上挂出他们抵押的 NFT 进行销售。 68 | 69 | ### boundNFT 的用途 70 | 71 | 1. 作为债务的 NFT:在借贷时被铸造,在偿还时被烧毁; 72 | 2. 通过不可转移和不可授权的方式保护 NFT 所有者免受黑客攻击; 73 | 3. 与用作 Twitter Blue PFP 同样的元数据; 74 | 4. 通过它获得任何空投、可领取的和可铸造的资产; 75 | 76 | ![](image%20(7)%20(1).png) 77 | 78 | ### 预言机 79 | 80 | Bend 协议使用来自 OpenSea 和 LooksRare 的 NFT 地板价作为 NFT 抵押品的价格推送数据。Bend 协议只支持蓝筹 NFT 资产的地板价,用于链上价格推送。蓝筹 NFT 的地板价不容易被操纵。此外,Bend 协议计算地板价的 TWAP(时间加权均价),以过滤来自 OpenSea 和 LooksRare 交易市场的价格波动。 81 | 82 | Bend 的预言机设计和运行机制: 83 | 84 | 1. 链下节点从 OpenSea 和 Looksrare 交易市场获得 NFT 的原始地板价; 85 | 2. 过滤原始地板价数据,如 Opensea 和 Lookrare 之间的地板价差异太大; 86 | 3. 根据 Opensea 和 Looksrare 的交易量计算出地板价; 87 | 4. 比较链上价格和最新地板价之间的差异,以决定是否需要将地板价上传到链上; 88 | 5. 调用合约接口,将地板价上传至链上合约,并计算链上时间加权平均价格(TWAP)以对地板价进行加权,确保价格合理; 89 | 6. twap 的算法在链上合约上是开源的,每个人都可以验证数据的有效性; 90 | 91 | ![Oracle](Bend%20NFT%20Oracle%200517.png) 92 | 93 | ## 合约解析 94 | 95 | https://docs.benddao.xyz/developers/ 96 | 97 | ![](BendDAO%20Protocols%200812.jpg) 98 | 99 | BendDAO 的协议包含两部分,分别是 Lending Protocol 包含了借贷相关的业务逻辑,Exchange Protocol 包含了交易相关的业务逻辑。 100 | 101 | ### Exchange Protocol 102 | 103 | https://github.com/BendDAO/bend-exchange-protocol 104 | 105 | Bend 交易所的核心合约是 BendExchange [0x7e832eC8ad6F66E6C9ECE63acD94516Dd7fC537A](https://etherscan.io/address/0x7e832eC8ad6F66E6C9ECE63acD94516Dd7fC537A),它也是交易协议的主要入口点。大多数交互将通过 BendExchange 进行。 106 | 107 | #### Order 108 | 109 | Bend 交易所建立在混合系统(链下/链上)之上,该系统包含链下签名(称为 Maker Orders)和链上订单(称为 Taker Orders)。 110 | 111 | * MakerBid——一种存在于链下的被动订单,用户希望使用特定的 ERC-20 代币获得 NFT。 112 | * MakerAsk——一种存在于链下的被动订单,用户希望为特定的 ERC-20 代币出售 NFT。 113 | * TakerBid — 在链上执行的与 MakerAsk 匹配的订单,例如,接受者接受 maker 的报价并为指定的 ERC-20 代币购买 NFT。 114 | * TakerAsk — 在链上执行的与 MakerBid 匹配的订单,例如,接受者接受 maker 的报价并出售指定的 ERC-20 代币的 NFT。 115 | 116 | ##### Maker order 117 | 118 | ```solidity 119 | struct MakerOrder { 120 | bool isOrderAsk; // true --> ask / false --> bid 121 | address maker; // maker of the maker order 122 | address collection; // collection address 123 | uint256 price; // The price of the order. The price is expressed in BigNumber based on the number of decimals of the “currency”. For Dutch auction, it is the minimum price at the end of the auction. 124 | uint256 tokenId; // id of the token,For collection orders, this field is not used in the execution and set it at 0. 125 | uint256 amount; // amount of tokens to sell/purchase (must be 1 for ERC721, 1+ for ERC1155) 126 | address strategy; // strategy for trade execution (e.g. StandardSaleForFixedPrice) 订单的执行策略 127 | address currency; 128 | uint256 nonce; // order nonce (must be unique unless new maker order is meant to override existing one e.g., lower ask price) 129 | uint256 startTime; // startTime in timestamp 130 | uint256 endTime; // endTime in timestamp 131 | uint256 minPercentageToAsk; // slippage protection, The minimum percentage required to be transferred to the ask or the trade is rejected (e.g., 8500 = 85% of the trade price). 132 | bytes params; // additional parameters, Can contain additional parameters that are not used for standard orders (e.g., maximum price for a Dutch auction, recipient address for a private sale, or a Merkle root... for future advanced order types!). If it doesn't, it is displayed "0x" 133 | address interceptor; // This field is only avaiable for ask order and used to execute specific logic before transferring the collection, For BNFT, it is used to repay the debt and redeeming the original NFT. 134 | bytes interceptorExtra; // Additional parameters when executing interceptor 135 | uint8 v; // v: parameter (27 or 28) 136 | bytes32 r; // r: parameter 137 | bytes32 s; // s: parameter 138 | } 139 | ``` 140 | 141 | ##### Taker order 142 | 143 | ```solidity 144 | struct TakerOrder { 145 | bool isOrderAsk; // true --> ask / false --> bid 146 | address taker; // msg.sender 147 | uint256 price; // final price for the purchase 148 | uint256 tokenId; // id of the token 149 | uint256 minPercentageToAsk; // // slippage protection 150 | bytes params; // other params (e.g., tokenId) 151 | address interceptor; 152 | bytes interceptorExtra; 153 | } 154 | ``` 155 | 156 | ##### isOrderAsk 157 | 158 | Ask 表示用户正在出售 NFT,而 Bid 表示用户正在购买 NFT(使用 WETH 等可替代货币)。 159 | 160 | ##### strategy 161 | 162 | 执行交易的执行策略地址。只有被 ExecutionManager 合约列入白名单的策略才能在 Bend 交易所中有效。如果执行策略未列入白名单,则交易不会发生。 163 | 164 | ##### currency 165 | 166 | 执行交易的货币地址。只有被 CurrencyManager 合约列入白名单的货币才能在 Bend 交易所中有效。如果货币未列入白名单,则无法进行交易。 167 | 168 | ##### minPercentageToAsk​ 169 | 170 | minPercentageToAsk 保护询问用户免受版税费用的潜在意外变化。当交易在链上执行时,如果平台总价格的特定百分比没有询问用户,交易将无法进行。 171 | 172 | 例如,如果 Alice 用 minPercentageToAsk = 8500 签署了她的收藏订单。如果协议费用为 2%,而版税更新为 16%,则该交易将无法在链上执行,因为 18% > 15 %。 173 | 174 | ##### params 175 | 176 | 此字段用于特定于不太频繁的复杂订单的附加参数(例如,私人销售、荷兰式拍卖和更复杂的订单类型)。附加参数集以字节表示,其中参数是连接在一起的。 177 | 178 | ##### interceptor(拦截器) 179 | 180 | 该字段仅对询单(ask order)可用,用于在转收之前执行特定的逻辑。比如对于BNFT,用于偿还债务和赎回原始NFT。 181 | 182 | #### 成单方法 183 | 184 | ##### matchAskWithTakerBid 185 | 186 | 撮合成交一个卖单。 187 | 188 | 订单成交后悔发出 `TakerBid` 的事件。 189 | 190 | ```solidity 191 | function matchAskWithTakerBid( 192 | OrderTypes.TakerOrder calldata takerBid, 193 | OrderTypes.MakerOrder calldata makerAsk 194 | ) external payable override nonReentrant 195 | ``` 196 | 197 | ##### matchAskWithTakerBidUsingETHAndWETH 198 | 199 | 撮合成交一个以 eth 支付的卖单。 200 | 201 | 订单成交后悔发出 `TakerBid` 的事件。 202 | 203 | ```solidity 204 | function matchAskWithTakerBidUsingETHAndWETH( 205 | OrderTypes.TakerOrder calldata takerBid, 206 | OrderTypes.MakerOrder calldata makerAsk 207 | ) 208 | ``` 209 | 210 | ##### matchBidWithTakerAsk 211 | 212 | 撮合成交一个买单。 213 | 214 | 订单成交后悔发出 `TakerAsk` 的事件。 215 | 216 | ```solidity 217 | function matchBidWithTakerAsk(OrderTypes.TakerOrder calldata takerAsk, OrderTypes.MakerOrder calldata makerBid) 218 | external 219 | override 220 | nonReentrant 221 | ``` 222 | 223 | #### 事件 224 | 225 | 卖单成交后发出的事件。 226 | 227 | ```solidity 228 | event TakerAsk( 229 | bytes32 makerOrderHash, // bid hash of the maker order 230 | uint256 orderNonce, // user order nonce 231 | address indexed taker, // sender address for the taker ask order 232 | address indexed maker, // maker address of the initial bid order 233 | address indexed strategy, // strategy that defines the execution 234 | address currency, // currency address 235 | address collection, // collection address 236 | uint256 tokenId, // tokenId transferred 237 | uint256 amount, // amount of tokens transferred 238 | uint256 price // final transacted price 239 | ); 240 | ``` 241 | 242 | 买单成交后发出的事件。 243 | 244 | ```solidity 245 | event TakerBid( 246 | bytes32 makerOrderHash, // ask hash of the maker order 247 | uint256 orderNonce, // user order nonce 248 | address indexed taker, // sender address for the taker bid order 249 | address indexed maker, // maker address of the initial ask order 250 | address indexed strategy, // strategy that defines the execution 251 | address currency, // currency address 252 | address collection, // collection address 253 | uint256 tokenId, // tokenId transferred 254 | uint256 amount, // amount of tokens transferred 255 | uint256 price // final transacted price 256 | ); 257 | ``` 258 | 259 | #### Execution Strategy(执行策略) 260 | 261 | 每个订单中都有 strategy 这个属性,来描述订单具体的执行策略对应的合约的地址。 262 | 263 | 在成单方法中会调用指定的 strategy 的 `canExecuteTakerAsk()` 或者 `canExecuteTakerBid()` 方法来检查是否符合指定的策略。 264 | 265 | BendExchange 合约有个属性 executionManager 就是用来管理所有执行策略的。 266 | 267 | ##### 1. StrategyStandardSaleForFixedPrice 268 | 269 | 以固定价格执行订单的策略,可以通过买盘或卖盘来执行。 270 | 271 | canExecuteTaker 方法会校验价格,token id 以及订单的时间。 272 | 273 | ##### 2. StrategyPrivateSale 274 | 275 | 与上面的策略类似,不同的是 StrategyPrivateSale 策略的订单额外通过订单的 params 指定 接收者的地址。 276 | 277 | ##### 3. StrategyDutchAuction 278 | 279 | 拍卖的时候执行的策略。会根据时间计算出当前价价格 currentAuctionPrice。 280 | 281 | 需要注意的是该策略只有 `canExecuteTakerAsk()` 方法永远返回 false 。 282 | 283 | ##### 4. StrategyAnyItemInASetForFixedPrice 284 | 285 | 以固定价格发送订单的策略,可由一组 tokenIds 中的任何 tokenId 匹配。 286 | 287 | 与 Seaport 中基于标准的订单一样,都是通过 Merkle Tree 来实现具体的逻辑的。 288 | 289 | ##### 5. StrategyAnyItemFromCollectionForFixedPrice 290 | 291 | 以固定价格发送订单的策略,该订单可由集合的任何 tokenId 匹配。 292 | 293 | 同样类似于 Seaport 中基于标准的订单中不指定具体的 tokenId。 294 | 295 | #### AuthorizationManager 296 | 297 | 用于注册授权代理,每个用户都有自己独立的代理。类似于 Wyvern Protocol。 298 | 299 | 用户将自己的资产授权给生成的代理合约。然后具体执行的时候由代理合约进行资产转移。 300 | 301 | #### CurrencyManager 302 | 303 | 它允许添加/删除用于在 Bend 交易所进行交易的货币。 304 | 305 | ### Lending Protocol 306 | 307 | https://github.com/BendDAO/bend-lending-protocol 308 | 309 | #### Main Contracts 和 Supporting Contracts 310 | 311 | Lending Protocol 中的合约分为两类,Main Contracts 和 Supporting Contracts。 312 | 313 | Main Contracts 包含: 314 | 315 | 1. `LendPool` : Bend 借贷协议的主要入口。与 Bend 的大多数借贷相关交互将通过 LendPool 进行 316 | 2. `LendPoolLoan` : NFT 借贷管理者,在借贷中使用 NFT 作为抵押品时生成唯一的借贷,并维护 NFT 与借贷之间的关系。 317 | 3. `LendPoolAddressesProvider` : 协议的用于特定市场主要地址。应通过适当的调用从该合约中检索最新的合约地址。 318 | 4. `LendPoolAddressesProviderRegistry` : 包含针对不同市场的活动 LendPoolAddressesProvider 地址列表。 319 | 5. `bTokens` : 一种特殊的 ERC20 代币。用于协议中的计息、代币化存款。 320 | 6. `debtTokens` : 一种特殊的 ERC20 代币。协议中使用的代币化债务。转移方法被禁用 321 | 7. `boundNFTs`: 一种特殊的 ERC721 代币本票,协议中使用的代币化抵押品。大多数标准的 ERC721 方法被禁用,因为 boundNFT 是不可转让的。 322 | 323 | Supporting Contracts 一般不应直接与之交互,但通过合同调用在整个 Bend 协议中使用。 324 | 325 | Supporting Contracts 包含: 326 | 327 | 1. `LendPoolConfigurator` : 为 LendPool 提供配置功能。 328 | 2. `InterestRate` : 保存了计算和更新特定储备的利率所需的信息。每份合同都使用每种货币的相应参数存储优化的基础曲线。这意味着有一个数学函数决定了每个资产池的利率,利率根据借入资金的数量和资产池的总流动性(即利用率)而变化。 329 | 3. `NFTOracle`: 提供整个协议所需的储备和 NFTs 资产价格数据。 330 | 331 | #### LendPool 332 | 333 | LendPool 是 Bend 借贷协议的主要入口,它暴露了所有面向用户的行动。与 Bend 的大多数借贷相关交互将通过 LendPool 进行。 334 | 335 | > LendPool 的入金、借款、取款和还款方法只适用于 ERC20 和 ERC721,如果你想使用原生 ETH 入金、取款、借款或还款,请使用 WETHGateway 代替,如果你想使用 CryptoPunks 作为抵押品借款或还款,请使用 PunkGateway。 336 | 337 | ##### 重要属性 338 | 339 | 所有 LendPool 的属性都在 LendPoolStorage 合约中定义。 340 | 341 | ###### 1. _reserves(储备金) 342 | 343 | 存储储备金配置的字典。key 是储备金资产对应的合约地址。value 是具体的配置信息。 344 | 345 | ```solidity 346 | mapping(address => DataTypes.ReserveData) internal _reserves; 347 | ``` 348 | 349 | ```solidity 350 | struct ReserveData { 351 | //stores the reserve configuration 352 | // 储备金的一些配置 353 | ReserveConfigurationMap configuration; 354 | //the liquidity index. Expressed in ray 355 | // 流动性指数,用向量表示 356 | uint128 liquidityIndex; 357 | //variable borrow index. Expressed in ray 358 | // 浮动借贷指数,用向量表示 359 | uint128 variableBorrowIndex; 360 | //the current supply rate. Expressed in ray 361 | // 当前的供应率,用向量表示 362 | uint128 currentLiquidityRate; 363 | //the current variable borrow rate. Expressed in ray 364 | // 当前的可变借款利率,用向量表示 365 | uint128 currentVariableBorrowRate; 366 | uint40 lastUpdateTimestamp; 367 | //tokens addresses 368 | // bToken 代币的地址 369 | address bTokenAddress; 370 | // 债务代币的地址 371 | address debtTokenAddress; 372 | //address of the interest rate strategy 373 | // 利息策略 374 | address interestRateAddress; 375 | //the id of the reserve. Represents the position in the list of the active reserves 376 | // 储备的ID。代表在活动的储备列表中的位置 377 | uint8 id; 378 | } 379 | ``` 380 | 381 | ```solidity 382 | // 不同的位置表示不同的配置项 383 | struct ReserveConfigurationMap { 384 | //bit 0-15: LTV 385 | //bit 16-31: Liq. threshold 386 | //bit 32-47: Liq. bonus 387 | //bit 48-55: Decimals 388 | //bit 56: Reserve is active 389 | //bit 57: reserve is frozen 390 | //bit 58: borrowing is enabled 391 | //bit 59: stable rate borrowing enabled 392 | //bit 60-63: reserved 393 | //bit 64-79: reserve factor 394 | uint256 data; 395 | } 396 | ``` 397 | 398 | ###### 2. _reservesList 399 | 400 | 存储的是储备金对应资产的合约地址。key 是储备金对应的 id 401 | 402 | ```solidity 403 | mapping(uint256 => address) internal _reservesList; 404 | ``` 405 | 406 | ###### 3. _reservesCount 407 | 408 | 存储的是所有储备金的类型数量 409 | 410 | ```solidity 411 | uint256 internal _reservesCount; 412 | ``` 413 | 414 | ###### 4. _nfts 415 | 416 | 存储的是可以进行借贷的 NFT 的信息。 417 | 418 | key 是 NFT 的合约地址,value 是对应 NFT 的配置信息。 419 | 420 | ```solidity 421 | mapping(address => DataTypes.NftData) internal _nfts; 422 | ``` 423 | 424 | ```solidity 425 | struct NftData { 426 | //stores the nft configuration 427 | // 配置信息 428 | NftConfigurationMap configuration; 429 | //address of the bNFT contract 430 | // bNFT 的合约地址 431 | address bNftAddress; 432 | //the id of the nft. Represents the position in the list of the active nfts 433 | uint8 id; 434 | uint256 maxSupply; 435 | uint256 maxTokenId; 436 | } 437 | ``` 438 | 439 | ```solidity 440 | struct NftConfigurationMap { 441 | //bit 0-15: LTV 442 | //bit 16-31: Liq. threshold 443 | //bit 32-47: Liq. bonus 444 | //bit 56: NFT is active 445 | //bit 57: NFT is frozen 446 | uint256 data; 447 | } 448 | ``` 449 | 450 | ###### 5. _nftsList 451 | 452 | ```solidity 453 | mapping(uint256 => address) internal _nftsList; 454 | ``` 455 | 456 | ###### 6. _nftsCount 457 | 458 | ```solidity 459 | uint256 internal _nftsCount; 460 | ``` 461 | 462 | ##### 方法 463 | 464 | ###### 1. deposit(存款) 465 | 466 | 向储备金存入一定数量的相关资产,获得相关的 bTokens 作为债权证明。例如,用户存入 100 USDC,得到 100 bUSDC。 467 | 468 | ```solidity 469 | function deposit( 470 | address asset, // 存入资产的合约地址 471 | uint256 amount, // 数量 472 | address onBehalfOf, // 收到 btoken 的地址 473 | uint16 referralCode // 用于注册发起该操作的集成商的代码,以获得潜在的奖励。如果该操作是由用户直接执行的,没有任何中间人,则为0 474 | ) 475 | ``` 476 | 477 | 这个方法的具体逻辑在 `SupplyLogic` 的 `executeDeposit()` 方法中。 478 | 479 | ```solidity 480 | function executeDeposit( 481 | mapping(address => DataTypes.ReserveData) storage reservesData, 482 | DataTypes.ExecuteDepositParams memory params 483 | ) external { 484 | // onBehalfOf 是接收 bTokens 的地址,如果用户想在自己的钱包上接收 bTokens,则与 msg.sender相同,如果 bTokens 的受益人是其他的钱包,则是不同的地址。 485 | require(params.onBehalfOf != address(0), Errors.VL_INVALID_ONBEHALFOF_ADDRESS); 486 | 487 | DataTypes.ReserveData storage reserve = reservesData[params.asset]; 488 | address bToken = reserve.bTokenAddress; 489 | require(bToken != address(0), Errors.VL_INVALID_RESERVE_ADDRESS); 490 | 491 | ValidationLogic.validateDeposit(reserve, params.amount); 492 | 493 | // 更新 reserve (储备金)的状态 494 | reserve.updateState(); 495 | // 更新 reserve (储备金)的利率 496 | reserve.updateInterestRates(params.asset, bToken, params.amount, 0); 497 | 498 | // 将资产转移到 bToken 的合约中 499 | IERC20Upgradeable(params.asset).safeTransferFrom(params.initiator, bToken, params.amount); 500 | 501 | // 铸造 Btoken 给用户 502 | IBToken(bToken).mint(params.onBehalfOf, params.amount, reserve.liquidityIndex); 503 | 504 | // 发出事件 505 | emit Deposit(params.initiator, params.asset, params.amount, params.onBehalfOf, params.referralCode); 506 | } 507 | ``` 508 | 509 | 存入的资产会转移到 bToken 对应的合约中。同时指定数量的 bToken 将会被铸造。 510 | 511 | 需要注意的是 `reserve.updateState()`, 这个方法用来更新流动资金累积指数和可变借贷指数。 512 | 513 | 每个涉及到借贷的方法都会首先调用这个方法来更新储备金的各种指数。 514 | 515 | ```solidity 516 | // ReserveLogic.sol => updateState() 517 | 518 | function updateState(DataTypes.ReserveData storage reserve) internal { 519 | // 缩放的可变债务 520 | uint256 scaledVariableDebt = IDebtToken(reserve.debtTokenAddress).scaledTotalSupply(); 521 | // 之前的变量借贷指数 522 | uint256 previousVariableBorrowIndex = reserve.variableBorrowIndex; 523 | // 之前的流动性指数 524 | uint256 previousLiquidityIndex = reserve.liquidityIndex; 525 | // 上次更新时间 526 | uint40 lastUpdatedTimestamp = reserve.lastUpdateTimestamp; 527 | 528 | // 更新储备指数和更新的时间戳 529 | (uint256 newLiquidityIndex, uint256 newVariableBorrowIndex) = _updateIndexes( 530 | reserve, 531 | scaledVariableDebt, 532 | previousLiquidityIndex, 533 | previousVariableBorrowIndex, 534 | lastUpdatedTimestamp 535 | ); 536 | 537 | // 以特定资产的储备系数为函数,将部分偿还的利息存入储备金库。 538 | _mintToTreasury( 539 | reserve, 540 | scaledVariableDebt, 541 | previousVariableBorrowIndex, 542 | newLiquidityIndex, 543 | newVariableBorrowIndex, 544 | lastUpdatedTimestamp 545 | ); 546 | } 547 | ``` 548 | 549 | ```solidity 550 | // ReserveLogic.sol => _updateIndexes() 551 | 552 | function _updateIndexes( 553 | DataTypes.ReserveData storage reserve, 554 | uint256 scaledVariableDebt, 555 | uint256 liquidityIndex, 556 | uint256 variableBorrowIndex, 557 | uint40 timestamp 558 | ) internal returns (uint256, uint256) { 559 | // 当前的供应率 560 | uint256 currentLiquidityRate = reserve.currentLiquidityRate; 561 | 562 | // 新的清算指数设置为之前的流动性指数 563 | uint256 newLiquidityIndex = liquidityIndex; 564 | // 新的借贷指数设置为之前的变量借贷指数 565 | uint256 newVariableBorrowIndex = variableBorrowIndex; 566 | 567 | //only cumulating if there is any income being produced 568 | // 只有在当前的流动性率大于 0 的情况下才会进行累积 569 | if (currentLiquidityRate > 0) { 570 | // 计算距离上次计算后累积的流动性资金利息 571 | uint256 cumulatedLiquidityInterest = MathUtils.calculateLinearInterest(currentLiquidityRate, timestamp); 572 | // 计算新的流动性指数 573 | newLiquidityIndex = cumulatedLiquidityInterest.rayMul(liquidityIndex); 574 | // 防止溢出 575 | require(newLiquidityIndex <= type(uint128).max, Errors.RL_LIQUIDITY_INDEX_OVERFLOW); 576 | 577 | reserve.liquidityIndex = uint128(newLiquidityIndex); 578 | 579 | //as the liquidity rate might come only from stable rate loans, we need to ensure 580 | //that there is actual variable debt before accumulating 581 | // 由于存款的利息完全来自于贷款的利息,因此要确保债务必须大于 0 582 | if (scaledVariableDebt != 0) { 583 | // 计算距离上次计算后累积的流动借贷的利息 584 | uint256 cumulatedVariableBorrowInterest = MathUtils.calculateCompoundedInterest( 585 | reserve.currentVariableBorrowRate, 586 | timestamp 587 | ); 588 | // 计算新的借贷指数 589 | newVariableBorrowIndex = cumulatedVariableBorrowInterest.rayMul(variableBorrowIndex); 590 | require(newVariableBorrowIndex <= type(uint128).max, Errors.RL_VARIABLE_BORROW_INDEX_OVERFLOW); 591 | reserve.variableBorrowIndex = uint128(newVariableBorrowIndex); 592 | } 593 | } 594 | 595 | //solium-disable-next-line 596 | // 修改更新时间 597 | reserve.lastUpdateTimestamp = uint40(block.timestamp); 598 | return (newLiquidityIndex, newVariableBorrowIndex); 599 | } 600 | ``` 601 | 602 | ```solidity 603 | // ReserveLogic.sol => _mintToTreasury() 604 | 605 | function _mintToTreasury( 606 | DataTypes.ReserveData storage reserve, 607 | uint256 scaledVariableDebt, 608 | uint256 previousVariableBorrowIndex, 609 | uint256 newLiquidityIndex, 610 | uint256 newVariableBorrowIndex, 611 | uint40 timestamp 612 | ) internal { 613 | timestamp; 614 | MintToTreasuryLocalVars memory vars; 615 | 616 | vars.reserveFactor = reserve.configuration.getReserveFactor(); 617 | 618 | // 如果储备指数等于0直接返回 619 | if (vars.reserveFactor == 0) { 620 | return; 621 | } 622 | 623 | //calculate the last principal variable debt 624 | // 计算上一次的债务 625 | vars.previousVariableDebt = scaledVariableDebt.rayMul(previousVariableBorrowIndex); 626 | 627 | //calculate the new total supply after accumulation of the index 628 | // 计算当前的债务 629 | vars.currentVariableDebt = scaledVariableDebt.rayMul(newVariableBorrowIndex); 630 | 631 | //debt accrued is the sum of the current debt minus the sum of the debt at the last update 632 | // 计算新增的债务 633 | vars.totalDebtAccrued = vars.currentVariableDebt - (vars.previousVariableDebt); 634 | 635 | // 根据准备金因子计算需要增发的 BToken 数量 636 | vars.amountToMint = vars.totalDebtAccrued.percentMul(vars.reserveFactor); 637 | 638 | // amountToMint 大于 0 的时候进行铸造 BToken 到储备金库,来计算利息。 639 | if (vars.amountToMint != 0) { 640 | IBToken(reserve.bTokenAddress).mintToTreasury(vars.amountToMint, newLiquidityIndex); 641 | } 642 | } 643 | ``` 644 | 645 | ###### 2. withdraw(提取) 646 | 647 | 从储备中提取一定数量的质押资产,销毁所拥有的等值 bToken。 648 | 649 | ```solidity 650 | function withdraw( 651 | address asset, // 取出的资产合约地址 652 | uint256 amount, // 数量 653 | address to 654 | ) 655 | ``` 656 | 657 | 这个方法的具体逻辑在 `SupplyLogic` 的 `executeWithdraw()` 方法中。 658 | 659 | ```solidity 660 | function executeWithdraw( 661 | mapping(address => DataTypes.ReserveData) storage reservesData, 662 | DataTypes.ExecuteWithdrawParams memory params 663 | ) external returns (uint256) { 664 | require(params.to != address(0), Errors.VL_INVALID_TARGET_ADDRESS); 665 | 666 | DataTypes.ReserveData storage reserve = reservesData[params.asset]; 667 | address bToken = reserve.bTokenAddress; 668 | require(bToken != address(0), Errors.VL_INVALID_RESERVE_ADDRESS); 669 | 670 | // 查看用户的 bToken 余额 671 | uint256 userBalance = IBToken(bToken).balanceOf(params.initiator); 672 | 673 | uint256 amountToWithdraw = params.amount; 674 | 675 | if (params.amount == type(uint256).max) { 676 | amountToWithdraw = userBalance; 677 | } 678 | 679 | ValidationLogic.validateWithdraw(reserve, amountToWithdraw, userBalance); 680 | 681 | // 更新储备金状态 682 | reserve.updateState(); 683 | 684 | reserve.updateInterestRates(params.asset, bToken, 0, amountToWithdraw); 685 | 686 | // 销毁 BToken,并将指定数量的资产转移给用户 687 | IBToken(bToken).burn(params.initiator, params.to, amountToWithdraw, reserve.liquidityIndex); 688 | 689 | emit Withdraw(params.initiator, params.asset, amountToWithdraw, params.to); 690 | 691 | return amountToWithdraw; 692 | } 693 | ``` 694 | 695 | 指定数量的 bToken 将会被销毁,然后将对应数量的资产转移给指定的地址。 696 | 697 | ###### 3. borrow(借) 698 | 699 | 允许抵押资产的用户借入特定数量的储备基础资产。例如。用户借入 100 USDC,在钱包中收到 100 USDC 并锁定合约中的抵押资产。 700 | 701 | ```solidity 702 | function borrow( 703 | address asset, // 要借的资产合约地址 704 | uint256 amount, 705 | address nftAsset, // 用作抵押品的 NFT 的地址 706 | uint256 nftTokenId, // 用作抵押品的 NFT 的代币 ID 707 | address onBehalfOf, // 将收到贷款的用户的地址。如果他想用自己的抵押品借款,应该是借款人自己调用函数的地址 708 | uint16 referralCode 709 | ) 710 | ``` 711 | 712 | 这个方法的具体逻辑在 `BorrowLogic` 中的 `_borrow()`。 713 | 714 | ```solidity 715 | function _borrow( 716 | ILendPoolAddressesProvider addressesProvider, 717 | mapping(address => DataTypes.ReserveData) storage reservesData, 718 | mapping(address => DataTypes.NftData) storage nftsData, 719 | DataTypes.ExecuteBorrowParams memory params 720 | ) internal { 721 | // onBehalfOf 是收到贷款的地址,不能为空。如果是借款人想用自己的抵押品借款,那 onBehalfOf 应该是借款人本身的地址 722 | require(params.onBehalfOf != address(0), Errors.VL_INVALID_ONBEHALFOF_ADDRESS); 723 | 724 | ExecuteBorrowLocalVars memory vars; 725 | // initiator 是调用者的地址 726 | vars.initiator = params.initiator; 727 | 728 | // 获取要借贷的货币对应储备金的数据 729 | DataTypes.ReserveData storage reserveData = reservesData[params.asset]; 730 | // 获取要进行抵押的 NFT 相关数据 731 | DataTypes.NftData storage nftData = nftsData[params.nftAsset]; 732 | 733 | // update state MUST BEFORE get borrow amount which is depent on latest borrow index 734 | // 更新 reserve (储备金)的状态 735 | reserveData.updateState(); 736 | 737 | // Convert asset amount to ETH 738 | vars.reserveOracle = addressesProvider.getReserveOracle(); 739 | vars.nftOracle = addressesProvider.getNFTOracle(); 740 | vars.loanAddress = addressesProvider.getLendPoolLoan(); 741 | 742 | // 根据抵押品 NFT 的信息来获取借贷是否已经存在 743 | vars.loanId = ILendPoolLoan(vars.loanAddress).getCollateralLoanId(params.nftAsset, params.nftTokenId); 744 | 745 | vars.totalSupply = IERC721EnumerableUpgradeable(params.nftAsset).totalSupply(); 746 | require(vars.totalSupply <= nftData.maxSupply, Errors.LP_NFT_SUPPLY_NUM_EXCEED_MAX_LIMIT); 747 | require(params.nftTokenId <= nftData.maxTokenId, Errors.LP_NFT_TOKEN_ID_EXCEED_MAX_LIMIT); 748 | 749 | // 校验借款参数 750 | ValidationLogic.validateBorrow( 751 | params.onBehalfOf, 752 | params.asset, 753 | params.amount, 754 | reserveData, 755 | params.nftAsset, 756 | nftData, 757 | vars.loanAddress, 758 | vars.loanId, 759 | vars.reserveOracle, 760 | vars.nftOracle 761 | ); 762 | 763 | // 如果借贷不存在则创建借贷,铸造 bNFT 给用户 764 | if (vars.loanId == 0) { 765 | IERC721Upgradeable(params.nftAsset).safeTransferFrom(vars.initiator, address(this), params.nftTokenId); 766 | 767 | vars.loanId = ILendPoolLoan(vars.loanAddress).createLoan( 768 | vars.initiator, 769 | params.onBehalfOf, 770 | params.nftAsset, 771 | params.nftTokenId, 772 | nftData.bNftAddress, 773 | params.asset, 774 | params.amount, 775 | reserveData.variableBorrowIndex 776 | ); 777 | } else { // 如果借贷存在则更新借贷信息 778 | ILendPoolLoan(vars.loanAddress).updateLoan( 779 | vars.initiator, 780 | vars.loanId, 781 | params.amount, 782 | 0, 783 | reserveData.variableBorrowIndex 784 | ); 785 | } 786 | 787 | // 铸造指定数量的 debtToken(债务代币)给借款人或者贷款接收者 788 | IDebtToken(reserveData.debtTokenAddress).mint( 789 | vars.initiator, 790 | params.onBehalfOf, 791 | params.amount, 792 | reserveData.variableBorrowIndex 793 | ); 794 | 795 | // update interest rate according latest borrow amount (utilizaton) 796 | // 更新 reserve (储备金)的利率 797 | reserveData.updateInterestRates(params.asset, reserveData.bTokenAddress, 0, params.amount); 798 | 799 | // 将 BToken 中的锚定的资产转移给借款人 800 | IBToken(reserveData.bTokenAddress).transferUnderlyingTo(vars.initiator, params.amount); 801 | 802 | // 发出 `Borrow` 事件 803 | emit Borrow( 804 | vars.initiator, 805 | params.asset, 806 | params.amount, 807 | params.nftAsset, 808 | params.nftTokenId, 809 | params.onBehalfOf, 810 | reserveData.currentVariableBorrowRate, 811 | vars.loanId, 812 | params.referralCode 813 | ); 814 | } 815 | ``` 816 | 817 | ###### 4. repay(还款) 818 | 819 | 偿还特定储备的借入金额,销毁所拥有的等值贷款。例如。用户偿还 100 USDC,销毁贷款并获得抵押资产。 820 | 821 | ```solidity 822 | function repay( 823 | address nftAsset, // 用作抵押品的 NFT 的合约地址 824 | uint256 nftTokenId, 825 | uint256 amount // 还款的数量 826 | ) external override nonReentrant whenNotPaused returns (uint256, bool) 827 | ``` 828 | 829 | 这个方法的具体逻辑在 `BorrowLogic` 中的 `_repay()`。 830 | 831 | ```solidity 832 | function _repay( 833 | ILendPoolAddressesProvider addressesProvider, 834 | mapping(address => DataTypes.ReserveData) storage reservesData, 835 | mapping(address => DataTypes.NftData) storage nftsData, 836 | DataTypes.ExecuteRepayParams memory params 837 | ) internal returns (uint256, bool) { 838 | RepayLocalVars memory vars; 839 | // initiator 是调用者的地址 840 | vars.initiator = params.initiator; 841 | 842 | vars.poolLoan = addressesProvider.getLendPoolLoan(); 843 | 844 | // 获取借贷的id,借贷必须已经存在 845 | vars.loanId = ILendPoolLoan(vars.poolLoan).getCollateralLoanId(params.nftAsset, params.nftTokenId); 846 | require(vars.loanId != 0, Errors.LP_NFT_IS_NOT_USED_AS_COLLATERAL); 847 | 848 | // 获取借贷的具体信息 849 | DataTypes.LoanData memory loanData = ILendPoolLoan(vars.poolLoan).getLoan(vars.loanId); 850 | 851 | // 获取借贷的货币对应储备金的数据 852 | DataTypes.ReserveData storage reserveData = reservesData[loanData.reserveAsset]; 853 | // 获取进行抵押的 NFT 相关数据 854 | DataTypes.NftData storage nftData = nftsData[loanData.nftAsset]; 855 | 856 | // update state MUST BEFORE get borrow amount which is depent on latest borrow index 857 | // 更新储备金的状态 858 | reserveData.updateState(); 859 | 860 | // 获取借款数量 861 | (, vars.borrowAmount) = ILendPoolLoan(vars.poolLoan).getLoanReserveBorrowAmount(vars.loanId); 862 | 863 | ValidationLogic.validateRepay(reserveData, nftData, loanData, params.amount, vars.borrowAmount); 864 | 865 | vars.repayAmount = vars.borrowAmount; 866 | vars.isUpdate = false; 867 | if (params.amount < vars.repayAmount) { // 部分偿还 868 | vars.isUpdate = true; 869 | vars.repayAmount = params.amount; 870 | } 871 | 872 | if (vars.isUpdate) { // 只偿还部分借贷时候会去更新借贷信息 873 | ILendPoolLoan(vars.poolLoan).updateLoan( 874 | vars.initiator, 875 | vars.loanId, 876 | 0, 877 | vars.repayAmount, 878 | reserveData.variableBorrowIndex 879 | ); 880 | } else { // 全部偿还则调用偿还借贷的方法 881 | ILendPoolLoan(vars.poolLoan).repayLoan( 882 | vars.initiator, 883 | vars.loanId, 884 | nftData.bNftAddress, 885 | vars.repayAmount, 886 | reserveData.variableBorrowIndex 887 | ); 888 | } 889 | 890 | // 销毁 DebtToken 891 | IDebtToken(reserveData.debtTokenAddress).burn(loanData.borrower, vars.repayAmount, reserveData.variableBorrowIndex); 892 | 893 | // update interest rate according latest borrow amount (utilizaton) 894 | // 更新 reserve (储备金)的利率 895 | reserveData.updateInterestRates(loanData.reserveAsset, reserveData.bTokenAddress, vars.repayAmount, 0); 896 | 897 | // transfer repay amount to bToken 898 | // 将还的资产转移给 bToken 899 | IERC20Upgradeable(loanData.reserveAsset).safeTransferFrom( 900 | vars.initiator, 901 | reserveData.bTokenAddress, 902 | vars.repayAmount 903 | ); 904 | 905 | // transfer erc721 to borrower 906 | // 全部还清贷款的时候将 NFT 归还给用户 907 | if (!vars.isUpdate) { 908 | IERC721Upgradeable(loanData.nftAsset).safeTransferFrom(address(this), loanData.borrower, params.nftTokenId); 909 | } 910 | 911 | // 发出 `Repay` 事件 912 | emit Repay( 913 | vars.initiator, 914 | loanData.reserveAsset, 915 | vars.repayAmount, 916 | loanData.nftAsset, 917 | loanData.nftTokenId, 918 | loanData.borrower, 919 | vars.loanId 920 | ); 921 | 922 | return (vars.repayAmount, !vars.isUpdate); 923 | } 924 | ``` 925 | 926 | ###### 5. auction(拍卖) 927 | 928 | 拍卖非健康的借贷。调用者(清算人)想要购买被清算用户的抵押资产。 929 | 930 | Bend 的拍卖机制是英式拍卖,出价最高的人将成为获胜者。 931 | 932 | ```solidity 933 | function auction( 934 | address nftAsset, 935 | uint256 nftTokenId, 936 | uint256 bidPrice, 937 | address onBehalfOf // 获得 NFT 的用户的地址,如果用户想在自己的钱包上接收,则与 msg.sender 相同,否则是不同的地址 938 | ) 939 | ``` 940 | 941 | 具体逻辑的实现在 `LiquidateLogic` 的 `executeAuction()` 942 | 943 | ```solidity 944 | function executeAuction( 945 | ILendPoolAddressesProvider addressesProvider, 946 | mapping(address => DataTypes.ReserveData) storage reservesData, 947 | mapping(address => DataTypes.NftData) storage nftsData, 948 | DataTypes.ExecuteLendPoolStates memory poolStates, 949 | DataTypes.ExecuteAuctionParams memory params 950 | ) external { 951 | // onBehalfOf 是获得 NFT 的用户的地址 952 | require(params.onBehalfOf != address(0), Errors.VL_INVALID_ONBEHALFOF_ADDRESS); 953 | 954 | AuctionLocalVars memory vars; 955 | // initiator 是调用者地址 956 | vars.initiator = params.initiator; 957 | 958 | vars.loanAddress = addressesProvider.getLendPoolLoan(); 959 | vars.reserveOracle = addressesProvider.getReserveOracle(); 960 | vars.nftOracle = addressesProvider.getNFTOracle(); 961 | 962 | // 获得 loanId,并且 loanId 不能为空 963 | vars.loanId = ILendPoolLoan(vars.loanAddress).getCollateralLoanId(params.nftAsset, params.nftTokenId); 964 | require(vars.loanId != 0, Errors.LP_NFT_IS_NOT_USED_AS_COLLATERAL); 965 | 966 | DataTypes.LoanData memory loanData = ILendPoolLoan(vars.loanAddress).getLoan(vars.loanId); 967 | 968 | DataTypes.ReserveData storage reserveData = reservesData[loanData.reserveAsset]; 969 | DataTypes.NftData storage nftData = nftsData[loanData.nftAsset]; 970 | 971 | // 校验拍卖信息 972 | ValidationLogic.validateAuction(reserveData, nftData, loanData, params.bidPrice); 973 | 974 | // update state MUST BEFORE get borrow amount which is depent on latest borrow index 975 | // 更新 reserve (储备金)的状态 976 | reserveData.updateState(); 977 | 978 | // 计算价格信息 979 | (vars.borrowAmount, vars.thresholdPrice, vars.liquidatePrice) = GenericLogic.calculateLoanLiquidatePrice( 980 | vars.loanId, 981 | loanData.reserveAsset, 982 | reserveData, 983 | loanData.nftAsset, 984 | nftData, 985 | vars.loanAddress, 986 | vars.reserveOracle, 987 | vars.nftOracle 988 | ); 989 | 990 | // first time bid need to burn debt tokens and transfer reserve to bTokens 991 | // 上面这个注释好像有问题,因为并没有销毁 debt token 992 | if (loanData.state == DataTypes.LoanState.Active) { 993 | // loan's accumulated debt must exceed threshold (heath factor below 1.0) 994 | // 健康因子必须小于 1 的才能进行拍卖 995 | require(vars.borrowAmount > vars.thresholdPrice, Errors.LP_BORROW_NOT_EXCEED_LIQUIDATION_THRESHOLD); 996 | 997 | // bid price must greater than borrow debt 998 | // 出价必须大于借款债务 999 | require(params.bidPrice >= vars.borrowAmount, Errors.LPL_BID_PRICE_LESS_THAN_BORROW); 1000 | 1001 | // bid price must greater than liquidate price 1002 | // 出价必须大于清算价格 1003 | require(params.bidPrice >= vars.liquidatePrice, Errors.LPL_BID_PRICE_LESS_THAN_LIQUIDATION_PRICE); 1004 | } else { 1005 | // bid price must greater than borrow debt 1006 | require(params.bidPrice >= vars.borrowAmount, Errors.LPL_BID_PRICE_LESS_THAN_BORROW); 1007 | 1008 | if ((poolStates.pauseDurationTime > 0) && (loanData.bidStartTimestamp <= poolStates.pauseStartTime)) { 1009 | vars.extraAuctionDuration = poolStates.pauseDurationTime; 1010 | } 1011 | vars.auctionEndTimestamp = 1012 | loanData.bidStartTimestamp + 1013 | vars.extraAuctionDuration + 1014 | (nftData.configuration.getAuctionDuration() * 1 hours); 1015 | require(block.timestamp <= vars.auctionEndTimestamp, Errors.LPL_BID_AUCTION_DURATION_HAS_END); 1016 | 1017 | // bid price must greater than highest bid + delta 1018 | // 拍卖价格必须大于最大的出价的区间 1019 | vars.minBidDelta = vars.borrowAmount.percentMul(PercentageMath.ONE_PERCENT); 1020 | require(params.bidPrice >= (loanData.bidPrice + vars.minBidDelta), Errors.LPL_BID_PRICE_LESS_THAN_HIGHEST_PRICE); 1021 | } 1022 | 1023 | // 执行拍卖 1024 | ILendPoolLoan(vars.loanAddress).auctionLoan( 1025 | vars.initiator, 1026 | vars.loanId, 1027 | params.onBehalfOf, 1028 | params.bidPrice, 1029 | vars.borrowAmount, 1030 | reserveData.variableBorrowIndex 1031 | ); 1032 | 1033 | // lock highest bidder bid price amount to lend pool 1034 | // 将出价最高者的资产转移到当前地址 1035 | IERC20Upgradeable(loanData.reserveAsset).safeTransferFrom(vars.initiator, address(this), params.bidPrice); 1036 | 1037 | // transfer (return back) last bid price amount to previous bidder from lend pool 1038 | // 归还之前出价者的资产 1039 | if (loanData.bidderAddress != address(0)) { 1040 | IERC20Upgradeable(loanData.reserveAsset).safeTransfer(loanData.bidderAddress, loanData.bidPrice); 1041 | } 1042 | 1043 | // update interest rate according latest borrow amount (utilizaton) 1044 | reserveData.updateInterestRates(loanData.reserveAsset, reserveData.bTokenAddress, 0, 0); 1045 | 1046 | // 发出 Auction 事件 1047 | emit Auction( 1048 | vars.initiator, 1049 | loanData.reserveAsset, 1050 | params.bidPrice, 1051 | params.nftAsset, 1052 | params.nftTokenId, 1053 | params.onBehalfOf, 1054 | loanData.borrower, 1055 | vars.loanId 1056 | ); 1057 | } 1058 | ``` 1059 | 1060 | ###### 6. redeem(赎回) 1061 | 1062 | 用于赎回处于拍卖状态的非健康 NFT 贷款的功能。调用者必须是借款人。借款人可以在赎回时间到期前赎回自己的东西。 1063 | 1064 | ```solidity 1065 | function redeem( 1066 | address nftAsset, 1067 | uint256 nftTokenId, 1068 | uint256 amount, 1069 | uint256 bidFine // 罚款的金额 1070 | ) external override nonReentrant whenNotPaused returns (uint256) 1071 | ``` 1072 | 1073 | 具体逻辑的实现在 `LiquidateLogic` 的 `executeRedeem()` 1074 | 1075 | ```solidity 1076 | function executeRedeem( 1077 | ILendPoolAddressesProvider addressesProvider, 1078 | mapping(address => DataTypes.ReserveData) storage reservesData, 1079 | mapping(address => DataTypes.NftData) storage nftsData, 1080 | DataTypes.ExecuteLendPoolStates memory poolStates, 1081 | DataTypes.ExecuteRedeemParams memory params 1082 | ) external returns (uint256) { 1083 | RedeemLocalVars memory vars; 1084 | // initiator 是调用者 1085 | vars.initiator = params.initiator; 1086 | 1087 | vars.poolLoan = addressesProvider.getLendPoolLoan(); 1088 | vars.reserveOracle = addressesProvider.getReserveOracle(); 1089 | vars.nftOracle = addressesProvider.getNFTOracle(); 1090 | 1091 | vars.loanId = ILendPoolLoan(vars.poolLoan).getCollateralLoanId(params.nftAsset, params.nftTokenId); 1092 | require(vars.loanId != 0, Errors.LP_NFT_IS_NOT_USED_AS_COLLATERAL); 1093 | 1094 | DataTypes.LoanData memory loanData = ILendPoolLoan(vars.poolLoan).getLoan(vars.loanId); 1095 | 1096 | DataTypes.ReserveData storage reserveData = reservesData[loanData.reserveAsset]; 1097 | DataTypes.NftData storage nftData = nftsData[loanData.nftAsset]; 1098 | 1099 | // 校验赎回的参数 1100 | ValidationLogic.validateRedeem(reserveData, nftData, loanData, params.amount); 1101 | 1102 | if ((poolStates.pauseDurationTime > 0) && (loanData.bidStartTimestamp <= poolStates.pauseStartTime)) { 1103 | vars.extraRedeemDuration = poolStates.pauseDurationTime; 1104 | } 1105 | vars.redeemEndTimestamp = (loanData.bidStartTimestamp + 1106 | vars.extraRedeemDuration + 1107 | nftData.configuration.getRedeemDuration() * 1108 | 1 hours); 1109 | 1110 | // 必须在可赎回时间内赎回 1111 | require(block.timestamp <= vars.redeemEndTimestamp, Errors.LPL_BID_REDEEM_DURATION_HAS_END); 1112 | 1113 | // update state MUST BEFORE get borrow amount which is depent on latest borrow index 1114 | // 更新状态 1115 | reserveData.updateState(); 1116 | 1117 | (vars.borrowAmount, , ) = GenericLogic.calculateLoanLiquidatePrice( 1118 | vars.loanId, 1119 | loanData.reserveAsset, 1120 | reserveData, 1121 | loanData.nftAsset, 1122 | nftData, 1123 | vars.poolLoan, 1124 | vars.reserveOracle, 1125 | vars.nftOracle 1126 | ); 1127 | 1128 | // check bid fine in min & max range 1129 | // 检查罚息在正确的范围内 1130 | (, vars.bidFine) = GenericLogic.calculateLoanBidFine( 1131 | loanData.reserveAsset, 1132 | reserveData, 1133 | loanData.nftAsset, 1134 | nftData, 1135 | loanData, 1136 | vars.poolLoan, 1137 | vars.reserveOracle 1138 | ); 1139 | 1140 | // check bid fine is enough 1141 | // 检查罚息主够 1142 | require(vars.bidFine <= params.bidFine, Errors.LPL_INVALID_BID_FINE); 1143 | 1144 | // check the minimum debt repay amount, use redeem threshold in config 1145 | // 计算最小偿还价格 1146 | vars.repayAmount = params.amount; 1147 | vars.minRepayAmount = vars.borrowAmount.percentMul(nftData.configuration.getRedeemThreshold()); 1148 | require(vars.repayAmount >= vars.minRepayAmount, Errors.LP_AMOUNT_LESS_THAN_REDEEM_THRESHOLD); 1149 | 1150 | // check the maxinmum debt repay amount, 90%? 1151 | // 检查最大偿还价格 1152 | vars.maxRepayAmount = vars.borrowAmount.percentMul(PercentageMath.PERCENTAGE_FACTOR - PercentageMath.TEN_PERCENT); 1153 | require(vars.repayAmount <= vars.maxRepayAmount, Errors.LP_AMOUNT_GREATER_THAN_MAX_REPAY); 1154 | 1155 | // 调用开始偿还贷款 1156 | ILendPoolLoan(vars.poolLoan).redeemLoan( 1157 | vars.initiator, 1158 | vars.loanId, 1159 | vars.repayAmount, 1160 | reserveData.variableBorrowIndex 1161 | ); 1162 | 1163 | // 销毁 DebtToken 1164 | IDebtToken(reserveData.debtTokenAddress).burn(loanData.borrower, vars.repayAmount, reserveData.variableBorrowIndex); 1165 | 1166 | // update interest rate according latest borrow amount (utilizaton) 1167 | // 更新 reserve (储备金)的利率 1168 | reserveData.updateInterestRates(loanData.reserveAsset, reserveData.bTokenAddress, vars.repayAmount, 0); 1169 | 1170 | // transfer repay amount from borrower to bToken 1171 | // 将资产转移给 bToken 1172 | IERC20Upgradeable(loanData.reserveAsset).safeTransferFrom( 1173 | vars.initiator, 1174 | reserveData.bTokenAddress, 1175 | vars.repayAmount 1176 | ); 1177 | 1178 | if (loanData.bidderAddress != address(0)) { 1179 | // transfer (return back) last bid price amount from lend pool to bidder 1180 | // 返还之前出价者的资金 1181 | IERC20Upgradeable(loanData.reserveAsset).safeTransfer(loanData.bidderAddress, loanData.bidPrice); 1182 | 1183 | // transfer bid penalty fine amount from borrower to the first bidder 1184 | // 转移给之前出价者的奖金 1185 | IERC20Upgradeable(loanData.reserveAsset).safeTransferFrom( 1186 | vars.initiator, 1187 | loanData.firstBidderAddress, 1188 | vars.bidFine 1189 | ); 1190 | } 1191 | 1192 | // 发出事件 1193 | emit Redeem( 1194 | vars.initiator, 1195 | loanData.reserveAsset, 1196 | vars.repayAmount, 1197 | vars.bidFine, 1198 | loanData.nftAsset, 1199 | loanData.nftTokenId, 1200 | loanData.borrower, 1201 | vars.loanId 1202 | ); 1203 | 1204 | return (vars.repayAmount + vars.bidFine); 1205 | } 1206 | ``` 1207 | 1208 | ###### 7. liquidate(清算) 1209 | 1210 | 清算处于拍卖状态的非健康 NFT 贷款的功能。调用者(清算人)购买被清算用户的抵押资产,并收到抵押资产。 1211 | 1212 | ```solidity 1213 | function liquidate( 1214 | address nftAsset, 1215 | uint256 nftTokenId, 1216 | uint256 amount 1217 | ) external override nonReentrant whenNotPaused returns (uint256) 1218 | ``` 1219 | 1220 | 具体逻辑的实现在 `LiquidateLogic` 的 `executeLiquidate()` 1221 | 1222 | ```solidity 1223 | function executeLiquidate( 1224 | ILendPoolAddressesProvider addressesProvider, 1225 | mapping(address => DataTypes.ReserveData) storage reservesData, 1226 | mapping(address => DataTypes.NftData) storage nftsData, 1227 | DataTypes.ExecuteLendPoolStates memory poolStates, // 当前流动池的状态,主要是流动池暂停的开始时间和暂停时间 1228 | DataTypes.ExecuteLiquidateParams memory params 1229 | ) external returns (uint256) { 1230 | LiquidateLocalVars memory vars; 1231 | vars.initiator = params.initiator; 1232 | 1233 | vars.poolLoan = addressesProvider.getLendPoolLoan(); 1234 | vars.reserveOracle = addressesProvider.getReserveOracle(); 1235 | vars.nftOracle = addressesProvider.getNFTOracle(); 1236 | 1237 | vars.loanId = ILendPoolLoan(vars.poolLoan).getCollateralLoanId(params.nftAsset, params.nftTokenId); 1238 | require(vars.loanId != 0, Errors.LP_NFT_IS_NOT_USED_AS_COLLATERAL); 1239 | 1240 | DataTypes.LoanData memory loanData = ILendPoolLoan(vars.poolLoan).getLoan(vars.loanId); 1241 | 1242 | DataTypes.ReserveData storage reserveData = reservesData[loanData.reserveAsset]; 1243 | DataTypes.NftData storage nftData = nftsData[loanData.nftAsset]; 1244 | 1245 | ValidationLogic.validateLiquidate(reserveData, nftData, loanData); 1246 | 1247 | // 如果当前流动池是暂停时长大于0且清算的NFT拍卖开始时间小于暂停开始时间 1248 | if ((poolStates.pauseDurationTime > 0) && (loanData.bidStartTimestamp <= poolStates.pauseStartTime)) { 1249 | // 拍卖的额外时长设置为暂停时长 1250 | // 就是说如果拍卖开始之后当前流动池暂停了一段时间,则拍卖时长要加上这个暂停时长 1251 | vars.extraAuctionDuration = poolStates.pauseDurationTime; 1252 | } 1253 | 1254 | // 拍卖结束时间等于拍卖开始时间加上额外拍卖时长加上nft配置拍卖的时间 1255 | vars.auctionEndTimestamp = 1256 | loanData.bidStartTimestamp + 1257 | vars.extraAuctionDuration + 1258 | (nftData.configuration.getAuctionDuration() * 1 hours); 1259 | 1260 | // 当前时间要大于拍卖结束时间 1261 | require(block.timestamp > vars.auctionEndTimestamp, Errors.LPL_BID_AUCTION_DURATION_NOT_END); 1262 | 1263 | // update state MUST BEFORE get borrow amount which is depent on latest borrow index 1264 | // 更新状态 1265 | reserveData.updateState(); 1266 | 1267 | (vars.borrowAmount, , ) = GenericLogic.calculateLoanLiquidatePrice( 1268 | vars.loanId, 1269 | loanData.reserveAsset, 1270 | reserveData, 1271 | loanData.nftAsset, 1272 | nftData, 1273 | vars.poolLoan, 1274 | vars.reserveOracle, 1275 | vars.nftOracle 1276 | ); 1277 | 1278 | // Last bid price can not cover borrow amount 1279 | // 拍卖价格小于借出的价格的时候需要清算人加上这个额外的金额 1280 | if (loanData.bidPrice < vars.borrowAmount) { 1281 | vars.extraDebtAmount = vars.borrowAmount - loanData.bidPrice; 1282 | require(params.amount >= vars.extraDebtAmount, Errors.LP_AMOUNT_LESS_THAN_EXTRA_DEBT); 1283 | } 1284 | 1285 | // 拍卖金额大于借款金额,算出剩余的金额 1286 | if (loanData.bidPrice > vars.borrowAmount) { 1287 | vars.remainAmount = loanData.bidPrice - vars.borrowAmount; 1288 | } 1289 | 1290 | // 清算借贷 1291 | ILendPoolLoan(vars.poolLoan).liquidateLoan( 1292 | loanData.bidderAddress, 1293 | vars.loanId, 1294 | nftData.bNftAddress, 1295 | vars.borrowAmount, 1296 | reserveData.variableBorrowIndex 1297 | ); 1298 | 1299 | // 销毁 debtToken 1300 | IDebtToken(reserveData.debtTokenAddress).burn( 1301 | loanData.borrower, 1302 | vars.borrowAmount, 1303 | reserveData.variableBorrowIndex 1304 | ); 1305 | 1306 | // update interest rate according latest borrow amount (utilizaton) 1307 | reserveData.updateInterestRates(loanData.reserveAsset, reserveData.bTokenAddress, vars.borrowAmount, 0); 1308 | 1309 | // transfer extra borrow amount from liquidator to lend pool 1310 | // 将额外的债务金额转到流动池中 1311 | if (vars.extraDebtAmount > 0) { 1312 | IERC20Upgradeable(loanData.reserveAsset).safeTransferFrom(vars.initiator, address(this), vars.extraDebtAmount); 1313 | } 1314 | 1315 | // transfer borrow amount from lend pool to bToken, repay debt 1316 | // 归还借款金额到 bToken 的合约中 1317 | IERC20Upgradeable(loanData.reserveAsset).safeTransfer(reserveData.bTokenAddress, vars.borrowAmount); 1318 | 1319 | // transfer remain amount to borrower 1320 | // 额外拍卖多出来的转给借贷者 1321 | if (vars.remainAmount > 0) { 1322 | IERC20Upgradeable(loanData.reserveAsset).safeTransfer(loanData.borrower, vars.remainAmount); 1323 | } 1324 | 1325 | // transfer erc721 to bidder 1326 | // 将 nft 转移给拍卖出价者 1327 | IERC721Upgradeable(loanData.nftAsset).safeTransferFrom(address(this), loanData.bidderAddress, params.nftTokenId); 1328 | 1329 | emit Liquidate( 1330 | vars.initiator, 1331 | loanData.reserveAsset, 1332 | vars.borrowAmount, 1333 | vars.remainAmount, 1334 | loanData.nftAsset, 1335 | loanData.nftTokenId, 1336 | loanData.borrower, 1337 | vars.loanId 1338 | ); 1339 | 1340 | return (vars.extraDebtAmount); 1341 | } 1342 | ``` 1343 | 1344 | #### LendPoolLoan 1345 | 1346 | 协议的 NFT 借贷管理者,在借贷中使用 NFT 作为抵押品时生成唯一的借贷,并维护 NFT 与借贷之间的关系。 1347 | 1348 | ##### 属性 1349 | 1350 | ###### 1. _loanIdTracker 1351 | 1352 | 当前生成借贷的 id 索引。 1353 | 1354 | ```solidity 1355 | CountersUpgradeable.Counter private _loanIdTracker; 1356 | ``` 1357 | 1358 | ###### 2. _loans 1359 | 1360 | 存储的是所有借贷的具体信息。key 是借贷的 id,value 是借贷的具体信息。 1361 | 1362 | ```solidity 1363 | mapping(uint256 => DataTypes.LoanData) private _loans; 1364 | ``` 1365 | 1366 | ```solidity 1367 | struct LoanData { 1368 | //the id of the nft loan 1369 | uint256 loanId; 1370 | //the current state of the loan 1371 | LoanState state; 1372 | //address of borrower 1373 | // 借款人 1374 | address borrower; 1375 | //address of nft asset token 1376 | address nftAsset; 1377 | //the id of nft token 1378 | uint256 nftTokenId; 1379 | //address of reserve asset token 1380 | // 储备金的合约地址,也就是要借出资产的合约地址 1381 | address reserveAsset; 1382 | //scaled borrow amount. Expressed in ray 1383 | // 按比例的借款金额 1384 | uint256 scaledAmount; 1385 | //start time of first bid time 1386 | // 清算时间 1387 | uint256 bidStartTimestamp; 1388 | //bidder address of higest bid 1389 | address bidderAddress; 1390 | //price of higest bid 1391 | uint256 bidPrice; 1392 | //borrow amount of loan 1393 | uint256 bidBorrowAmount; 1394 | //bidder address of first bid 1395 | address firstBidderAddress; 1396 | } 1397 | ``` 1398 | 1399 | ###### 3. _nftToLoanIds 1400 | 1401 | 以 nft 的合约地址和token id 存储对应的借贷 loanId,存储某个 nft 的具体某个 id 对应的 loanId。 1402 | 1403 | ```solidity 1404 | mapping(address => mapping(uint256 => uint256)) private _nftToLoanIds; 1405 | ``` 1406 | 1407 | ```solidity 1408 | _nftToLoanIds[nftAsset][nftTokenId] = loanId; 1409 | ``` 1410 | 1411 | ###### 4. _nftTotalCollateral 1412 | 1413 | 存储某个 nft 抵押品的数量。 1414 | 1415 | ```solidity 1416 | mapping(address => uint256) private _nftTotalCollateral; 1417 | ``` 1418 | 1419 | ###### 5. _userNftCollateral 1420 | 1421 | 记录某个用户的某种 nft 下抵押品的数量。 1422 | 1423 | ```solidity 1424 | mapping(address => mapping(address => uint256)) private _userNftCollateral; 1425 | ``` 1426 | 1427 | ```solidity 1428 | _userNftCollateral[loan.borrower][loan.nftAsset] -= 1; 1429 | ``` 1430 | 1431 | ##### 方法 1432 | 1433 | ###### 1. createLoan 1434 | 1435 | ```solidity 1436 | function createLoan( 1437 | address initiator, 1438 | address onBehalfOf, 1439 | address nftAsset, 1440 | uint256 nftTokenId, 1441 | address bNftAddress, 1442 | address reserveAsset, 1443 | uint256 amount, 1444 | uint256 borrowIndex 1445 | ) external override onlyLendPool returns (uint256) 1446 | ``` 1447 | 1448 | 该方法中有以下操作 1449 | 1450 | 1. 生成 loanid 1451 | 2. 根据抵押的 nft 的合约地址和 tokenId 保存 loanid 1452 | 3. 将抵押的 nft 转移到当前 pool 合约 1453 | 4. 铸造 bNFT 给用户 1454 | 5. 保存信息 1455 | 6. 发出 `LoanCreated` 事件 1456 | 1457 | ###### 2. updateLoan 1458 | 1459 | ```solidity 1460 | function updateLoan( 1461 | address initiator, 1462 | uint256 loanId, 1463 | uint256 amountAdded, 1464 | uint256 amountTaken, 1465 | uint256 borrowIndex 1466 | ) 1467 | ``` 1468 | 1469 | 根据传入的 `amountAdded` 和 `amountTaken` 来调节对应 loanId 的借贷信息。 1470 | 1471 | 发出 `LoanUpdated` 事件。 1472 | 1473 | ###### 3. repayLoan(偿还贷款) 1474 | 1475 | ```solidity 1476 | function repayLoan( 1477 | address initiator, 1478 | uint256 loanId, 1479 | address bNftAddress, 1480 | uint256 amount, 1481 | uint256 borrowIndex 1482 | ) 1483 | ``` 1484 | 1485 | 该方法中有以下操作 1486 | 1487 | 1. 首先修改存储的 loan 状态,防止重入攻击 1488 | 2. 将 `_nftToLoanIds` 中对应的 loanId 置为 0 1489 | 3. `_userNftCollateral` 和 `_nftTotalCollateral` 中对应的数量减去 1 1490 | 4. 销毁债务 bNFT 1491 | 5. 将质押的 NFT 转移给用户 1492 | 6. 发出 `LoanRepaid` 事件 1493 | 1494 | ###### 4. auctionLoan(拍卖) 1495 | 1496 | ```solidity 1497 | function auctionLoan( 1498 | address initiator, 1499 | uint256 loanId, 1500 | address onBehalfOf, 1501 | uint256 bidPrice, 1502 | uint256 borrowAmount, 1503 | uint256 borrowIndex 1504 | ) 1505 | ``` 1506 | 1507 | 1. 获取 loan 的信息 1508 | 2. 如果 `bidStartTimestamp` 是 0,则说明还没开始拍卖,设置对应的状态和数据 1509 | 3. 如果 loan 当前的状态不是 Auction ,则报错 1510 | 4. 如果 当前传入的 `bidPrice` 小于等于 loan 的 `bidPrice`,也报错 1511 | 5. 设置 loan 的状态 1512 | 6. 发出 `LoanAuctioned` 的事件 1513 | 1514 | ###### 5. redeemLoan(赎回) 1515 | 1516 | ```solidity 1517 | function redeemLoan( 1518 | address initiator, 1519 | uint256 loanId, 1520 | uint256 amountTaken, 1521 | uint256 borrowIndex 1522 | ) 1523 | ``` 1524 | 1525 | 1. 只有 Auction 状态的才能进行赎回 1526 | 2. 传入的 `amountTaken` 必须满足要求的数量 1527 | 3. 清除 loan 的状态 1528 | 4. 发出 `LoanRedeemed` 的事件 1529 | 1530 | ###### 6. liquidateLoan(清算) 1531 | 1532 | ```solidity 1533 | function liquidateLoan( 1534 | address initiator, 1535 | uint256 loanId, 1536 | address bNftAddress, 1537 | uint256 borrowAmount, 1538 | uint256 borrowIndex 1539 | ) 1540 | ``` 1541 | 1542 | 1. 只有 Auction 状态的才能进行清算 1543 | 2. 更改 loan 的状态,防止重入攻击 1544 | 3. 将 `_nftToLoanIds` 中对应的 loanId 置为 0 1545 | 4. `_userNftCollateral` 和 `_nftTotalCollateral` 中对应的数量减去 1 1546 | 5. 销毁债务 bNFT 1547 | 6. 将质押的 NFT 转移给用户 1548 | 7. 发出 `LoanLiquidated` 事件 1549 | 1550 | #### BToken 1551 | 1552 | BToken(比如 bendETH)是在存款和取款时铸造和烧毁的计息代币。 BToken 的价值与相应存入资产的价值以 1:1 的比例挂钩,可以安全地存储、转移或交易。 1553 | 1554 | 存款的时候会铸造对应数量的 BToken,并将用户存入的资产转到 BToken 的合约中。 1555 | 1556 | 取款的时候会销毁对应数量的 BToken,并将 BToken 合约中的资产转移给用户。 1557 | 1558 | 抵押借贷的时候会将 BToken 合约中对应数量的资产转移给借贷者。而如果在两次更新储备金状态的时候有新增的贷款,需要铸造指定数量的 BToken 给国库来收取利息。 1559 | 1560 | 归还贷款的时候会将对应数量的资产转移给 BToken 合约中,这个归还资产的数量是本金加利息的总和。其中利息的数量对应着转给国库的 BToken 数量。 1561 | 1562 | 通过上面的这些操作,保证了 BToken 与锚定资产的动态平衡。 1563 | 1564 | 最后 BToken 储备收集的所有利息通过不断增加其钱包余额直接分配给 BToken 持有者。 1565 | 1566 | #### DebtToken 1567 | 1568 | 债务代币是在借款和偿还时铸造和燃烧的计息代币,代表代币持有人所欠的债务。 1569 | 1570 | #### BoundNFT 1571 | 1572 | boundNFT 是本票代币,在借款和偿还时铸造和销毁,代表代币持有者用作抵押品的 NFT,具有相同的代币 ID。 1573 | 1574 | boundNFT 的代币与对应的 NFT 抵押品的代币 1:1 挂钩。绑定 NFT 持有者拥有的所有代币都可以很容易地集成到 NFT 钱包和社交媒体中。 1575 | 1576 | #### Down Payment 1577 | 1578 | https://github.com/BendDAO/bend-downpayment 1579 | 1580 | 这是首付的入口合约。 1581 | 1582 | 买家可以根据实际价格支付最低 60% 的首付来购买蓝筹 NFT,同时从 AAVE 发起闪电贷以支付剩余部分。如果 NFT 的总价远高于收藏底价,则首付比例会增加。闪贷的借入金额将通过 BendDAO 上的即时 NFT 支持贷款偿还。 1583 | 1584 | 具体来说,工作流程如下。 1585 | 1586 | 1. 买方支付最低60%的首付; 1587 | 2. 启动闪电贷以从第三方平台借入剩余资金,例如AAVE; 1588 | 3. 新购买的 NFT 将用作 BendDAO 的抵押品,以获得即时流动性; 1589 | 4. 闪电贷将用从 BendDAO 借来的 ETH 偿还。 1590 | 1591 | 买家将自动成为首付的借款人。借款人也可以在 BendDAO 市场上出售他们抵押的 NFT。 1592 | 1593 | 目前支持的 NFT 集合是 BAYC、CryptoPunks、MAYC、Doodles、Space Doodles、CloneX 和 Azuki。首付的百分比由 BendDAO 上的 LTV 决定。 1594 | --------------------------------------------------------------------------------