跳到主要内容

Lending Protocol

源码

BendDao 借贷协议包括以下合约

  • LendPool: 借贷协议的主合约,包括资金的存取、借款、还款、拍卖、赎回、清算
  • LendPoolLoan: NFT 借贷管理器。当使用 NFT 作为抵押物时,会生成唯一的贷款凭证,并维持 NFT 和贷款之间的关系
  • LendPoolAddressesProvider: 借贷协议的链上地址管理器
  • BToken: 带息存款的一种 ERC20 代币,并添加了一些特定的方法
  • DebtToken: ERC20 代币化的一种债务 token, 且不可转让
  • boundNFTs: 本票(Promissory note), 是 Bend 协议中使用的代币化抵押品。且不可转让
  • LendPoolConfigurator: 为 LendPool 合约提供配置功能
  • InterestRate: 保存计算和更新特定储备利率所需的信息
  • NFTOracle: 提供 Bend 协议所需的储备和 NFT 资产价格数据

LendPool

借贷协议的主合约,与 BendDAO 的大多数交互都通过该合约进行。合约变量定义在LendPoolStorage.sol

contract LendPoolStorage {
using ReserveLogic for DataTypes.ReserveData;
using NftLogic for DataTypes.NftData;

ILendPoolAddressesProvider internal _addressesProvider;

// 储备金配置
mapping(address => DataTypes.ReserveData) internal _reserves;
// 可借贷nft的配置
mapping(address => DataTypes.NftData) internal _nfts;
// 储备金对应资产的合约地址
mapping(uint256 => address) internal _reservesList;
// 储备金的数量
uint256 internal _reservesCount;

// 可借贷 nft 列表
mapping(uint256 => address) internal _nftsList;
// 可借贷 nft数量
uint256 internal _nftsCount;

bool internal _paused;

uint256 internal _maxNumberOfReserves;
uint256 internal _maxNumberOfNfts;

// !!! Never add new variable at here, because this contract is inherited by LendPool !!!
// !!! Add new variable at LendPool directly !!!
}

存款 deposit

向 BendDAO 存入一定数量的资金,并获得对应的的 BToken, 例如存入 ETH, 可获得一定的 bendWETH

function deposit(
address asset,
uint256 amount,
address onBehalfOf,
uint16 referralCode
) external override nonReentrant whenNotPaused {
SupplyLogic.executeDeposit(
_reserves,
DataTypes.ExecuteDepositParams({
initiator: _msgSender(),
asset: asset,
amount: amount,
onBehalfOf: onBehalfOf,
referralCode: referralCode
})
);
}

具体逻辑在 SupplyLogic.executeDeposit 方法中

function executeDeposit(
mapping(address => DataTypes.ReserveData) storage reservesData,
DataTypes.ExecuteDepositParams memory params
) external {}

方法内部流程如下:

  • 验证 onBehalfOf 不为空地址,onBehalfOf 为接收 BToken 的地址
  • 验证对于存入的资金是否有对应的 BToken
  • 验证存入的资金参数
  • 更新储备金状态
  • 更新储备金利率
  • 转移资产到 BToken 合约中
  • 铸造 BToken 发送给 onBehalfOf

提现 withdraw

取出存入的资金

function withdraw(
address asset,
uint256 amount,
address to
) external override nonReentrant whenNotPaused returns (uint256) {
return
SupplyLogic.executeWithdraw(
_reserves,
DataTypes.ExecuteWithdrawParams({initiator: _msgSender(), asset: asset, amount: amount, to: to})
);
}

具体逻辑在 SupplyLogic.executeWithdraw 方法中

function executeWithdraw(
mapping(address => DataTypes.ReserveData) storage reservesData,
DataTypes.ExecuteWithdrawParams memory params
) external returns (uint256) {}

方法内部流程如下:

  • 验证接收地址不为空地址
  • 验证对于存入的资金是否有对应的 BToken
  • 获取调用者的 BToken 余额
  • 验证提现参数
  • 更新储备金状态
  • 更新储备金利率
  • 销毁调用者的 BToken
  • 返回提现数量

借款 borrow

抵押资产,进行借贷。

function borrow(
address asset,
uint256 amount,
address nftAsset,
uint256 nftTokenId,
address onBehalfOf,
uint16 referralCode
) external override nonReentrant whenNotPaused {
BorrowLogic.executeBorrow(
_addressesProvider,
_reserves,
_nfts,
DataTypes.ExecuteBorrowParams({
initiator: _msgSender(),
asset: asset,
amount: amount,
nftAsset: nftAsset,
nftTokenId: nftTokenId,
onBehalfOf: onBehalfOf,
referralCode: referralCode
})
);
}

具体逻辑在 BorrowLogic.executeBorrow 方法中

function executeBorrow(
ILendPoolAddressesProvider addressesProvider,
mapping(address => DataTypes.ReserveData) storage reservesData,
mapping(address => DataTypes.NftData) storage nftsData,
DataTypes.ExecuteBorrowParams memory params
) external {
_borrow(addressesProvider, reservesData, nftsData, params);
}

function _borrow(
ILendPoolAddressesProvider addressesProvider,
mapping(address => DataTypes.ReserveData) storage reservesData,
mapping(address => DataTypes.NftData) storage nftsData,
DataTypes.ExecuteBorrowParams memory params
) internal {}

还款 repay

借贷后可以选择主动还款。

function repay(
address nftAsset,
uint256 nftTokenId,
uint256 amount
) external override nonReentrant whenNotPaused returns (uint256, bool) {
return
BorrowLogic.executeRepay(
_addressesProvider,
_reserves,
_nfts,
DataTypes.ExecuteRepayParams({
initiator: _msgSender(),
nftAsset: nftAsset,
nftTokenId: nftTokenId,
amount: amount
})
);
}

具体逻辑在 BorrowLogic.executeRepay 方法中

function executeRepay(
ILendPoolAddressesProvider addressesProvider,
mapping(address => DataTypes.ReserveData) storage reservesData,
mapping(address => DataTypes.NftData) storage nftsData,
DataTypes.ExecuteRepayParams memory params
) external returns (uint256, bool) {
return _repay(addressesProvider, reservesData, nftsData, params);
}

function _repay(
ILendPoolAddressesProvider addressesProvider,
mapping(address => DataTypes.ReserveData) storage reservesData,
mapping(address => DataTypes.NftData) storage nftsData,
DataTypes.ExecuteRepayParams memory params
) internal returns (uint256, bool) {}

拍卖 auction

当抵押的资产被清算时会执行拍卖流程

function auction(
address nftAsset,
uint256 nftTokenId,
uint256 bidPrice,
address onBehalfOf
) external override nonReentrant whenNotPaused {
LiquidateLogic.executeAuction(
_addressesProvider,
_reserves,
_nfts,
_buildLendPoolVars(),
DataTypes.ExecuteAuctionParams({
initiator: _msgSender(),
nftAsset: nftAsset,
nftTokenId: nftTokenId,
bidPrice: bidPrice,
onBehalfOf: onBehalfOf
})
);
}

赎回 redeem

借款人抵押的资产被清算并处于拍卖状态时,在赎回期内选择赎回资产

function redeem(
address nftAsset,
uint256 nftTokenId,
uint256 amount,
uint256 bidFine
) external override nonReentrant whenNotPaused returns (uint256) {
return
LiquidateLogic.executeRedeem(
_addressesProvider,
_reserves,
_nfts,
_buildLendPoolVars(),
DataTypes.ExecuteRedeemParams({
initiator: _msgSender(),
nftAsset: nftAsset,
nftTokenId: nftTokenId,
amount: amount,
bidFine: bidFine
})
);
}

清算 liquidate

购买被清算用户的抵押资产。

function liquidate(
address nftAsset,
uint256 nftTokenId,
uint256 amount
) external override nonReentrant whenNotPaused returns (uint256) {
return
LiquidateLogic.executeLiquidate(
_addressesProvider,
_reserves,
_nfts,
_buildLendPoolVars(),
DataTypes.ExecuteLiquidateParams({
initiator: _msgSender(),
nftAsset: nftAsset,
nftTokenId: nftTokenId,
amount: amount
})
);
}

LendPoolLoan

NFT 借贷管理器,在借贷中使用 NFT 作为抵押物时,会生成唯一的借贷 ID

全局变量

// 借贷ID的索引器, 每次产生借贷时,值自动+1
CountersUpgradeable.Counter private _loanIdTracker;
// 存储借贷信息,key 是借贷 ID
mapping(uint256 => DataTypes.LoanData) private _loans;

// nftAsset + nftTokenId => loanId
// nft 合约地址映射 tokenId 对应的 借贷ID
mapping(address => mapping(uint256 => uint256)) private _nftToLoanIds;
// nft 借贷的数量
mapping(address => uint256) private _nftTotalCollateral;
// 用户地址 => nft 合约地址 => 抵押的nft数量
mapping(address => mapping(address => uint256)) private _userNftCollateral;

创建借贷

function createLoan(
address initiator,
address onBehalfOf,
address nftAsset,
uint256 nftTokenId,
address bNftAddress,
address reserveAsset,
uint256 amount,
uint256 borrowIndex
) external override onlyLendPool returns (uint256) {}

方法内部流程如下:

  • 生成借贷 ID
  • 根据参数保存数据到 _nftToLoanIds
  • 将抵押的 nft 转移到当前合约
  • 铸造 BNFT 发送给 onBehalfOf
  • 更新变量 _loans、_nftTotalCollateral、_userNftCollateral

更新借贷

根据传入的参数更新借贷数据

function updateLoan(
address initiator,
uint256 loanId,
uint256 amountAdded,
uint256 amountTaken,
uint256 borrowIndex
) external override onlyLendPool {}

偿还借贷

function repayLoan(
address initiator,
uint256 loanId,
address bNftAddress,
uint256 amount,
uint256 borrowIndex
) external override onlyLendPool {}

方法内部流程如下:

  • 检查借贷状态是 active
  • 销毁借贷 ID: 更新 _nftToLoanIds 中的借贷 ID 为 0,_userNftCollateral 和 _nftTotalCollateral 中对应的数量减去 1
  • 销毁 BNFT
  • 转移质押的 NFT 给借款人

拍卖借贷

function auctionLoan(
address initiator,
uint256 loanId,
address onBehalfOf,
uint256 bidPrice,
uint256 borrowAmount,
uint256 borrowIndex
) external override onlyLendPool {}

方法内部流程如下:

  • 根据借贷 ID 获取借贷信息
  • 根据借贷的 bidStartTimestamp 是否为 0
    • 为 0 表示此前未有过拍卖,则设置借贷状态为可拍卖状态,bidStartTimestamp 为当前区块时间
    • 不为 0 表示此前有过拍卖, 则要求借贷状态为拍卖状态
  • 更新借贷其他信息

赎回借贷

function redeemLoan(
address initiator,
uint256 loanId,
uint256 amountTaken,
uint256 borrowIndex
) external override onlyLendPool {}

方法内部流程如下:

  • 根据借贷 ID 获取借贷信息
  • 检查借贷的状态必须是拍卖状态
  • 检查 amountTaken 值
  • 重置借贷状态为 active

清算借贷

function liquidateLoan(
address initiator,
uint256 loanId,
address bNftAddress,
uint256 borrowAmount,
uint256 borrowIndex
) external override onlyLendPool {}

方法内部流程如下:

  • 只有拍卖状态的借贷才可以清算
  • 销毁借贷 ID: 更新 _nftToLoanIds 中的借贷 ID 为 0,_userNftCollateral 和 _nftTotalCollateral 中对应的数量减去 1
  • 销毁 BNFT
  • 将质押的 NFT 转移给用户