文章目录
内容简介
使用 solidity 实现的基于 ERC20 代币协议的借贷款去中心化应用平台(极简版)。实现存款、取款、贷款、还款以及利息计算的功能。
设计逻辑
- 平台提供ERC20协议代币的相关存取和利息计算工作。部署智能合约时初始化贷款和存款的年利率、代币实现地址。
- 用户可以将手中的代币存入平台,等到一定的期限再次拿出获得本金加利息。也可以向平台申请代币,在一定的期限之后自主还款即可。
ERC20TokenLoanPlatform 合约
事件
合约包含4个事件,包括 Deposit 存款、Withdrawal 取款、CreateLoan 贷款、PayLoan 还款。
javascript
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
import "erc-token-standard/ERC/IERC20.sol";
// 基于ERC20代币协议的借贷款平台
contract ERC20TokenLoanPlatform {
event Deposit(address depositor, uint amount ,uint DepositTime);
event Withdrawal(address payee, uint amount, uint WithdrawalTime);
event CreateLoan(address loanAddress, uint amount, uint interest, uint CreateLoanTime);
event PayLoan(address loanAddress, uint amount, bool total, uint PayLoanTime);
结构体
合约包含2个结构体, 包括 Client 客户信息、Loan 贷款信息。
javascript
// 客户信息
struct Client {
uint amount;
uint depositTime;
uint withdrawalTime;
}
// 贷款信息
struct Loan {
address loanAddress;
uint amount;
uint interest;
}
状态变量
本合约使用了6个状态变量,其中有 clients 客户信息映射、loans 贷款信息映射、annualInterestRate 贷款年利率、annualDepositRate 存款年利率、tokenAddress 代币实现地址、erc20Token 代币对象。
javascript
mapping(address => Client) private clients;
mapping (uint => Loan) private loans;
uint public annualInterestRate; // 贷款年利率,如5%
uint public annualDepositRate; // 存款年利率,如2%
address public tokenAddress; // ERC20代币地址
IERC20 erc20Token;
函数
本合约包含了8个基本函数,其中包括构造函数、deposit 存款、withdrawal 取款、createLoan 贷款、payLoan 还款、loanInquiry 待还款查询、depositInquiry 账户余额查询、balanceInquiry 客户信息查询 。
javascript
// 构造函数
constructor(address _tokenAddress, uint _initLoanInterest, uint _initDepositInterest) {
tokenAddress = _tokenAddress; // 初始化代币地址
erc20Token = IERC20(tokenAddress); // 声明IERC20接口合约变量
annualInterestRate = _initLoanInterest;
annualDepositRate = _initDepositInterest;
}
// 存款
function deposit(uint _amount) public {
require(_amount > 0, "Amount is less than or equal to 0."); // 存款代币数不能为0
require(erc20Token.balanceOf(msg.sender) >= _amount, "Number of tokens is insufficient."); // 检查持有代币数是否大于等于存款代币数
require(erc20Token.allowances(msg.sender, address(this)) >= _amount, "Not enough approvals."); // 检查是否有足够的授权
erc20Token.transferFrom(msg.sender, address(this), _amount);
clients[msg.sender].amount += _amount; // 增加存款
if (clients[msg.sender].depositTime == 0){
clients[msg.sender].depositTime = block.timestamp; // 当前的存款时间
}
emit Deposit(msg.sender,_amount ,block.timestamp);
}
// 取款
function withdrawal(uint _amount) public {
require(_amount > 0, "Amount is less than or equal to 0."); // 取款代币数不能为0
uint timestamp = block.timestamp;
depositInquiry(); // 计算本金 + 利息2%
require(clients[msg.sender].amount >= _amount, "Insufficient balance."); // 检查存款代币数是否大于等于取款代币数
require(erc20Token.balanceOf(address(this)) >= _amount, "Platform bankruptcy."); // 检查平台的代币数是否满足取款
erc20Token.transfer(msg.sender, _amount);
clients[msg.sender].amount -= _amount;
clients[msg.sender].withdrawalTime = timestamp;
emit Withdrawal(msg.sender, _amount, timestamp);
}
// 贷款
function createLoan(uint _amount) public returns (uint){
require(_amount > 0, "Amount is less than or equal to 0."); // 贷款不能小于0
require(erc20Token.balanceOf(address(this))*10/100 >= _amount, "Exceeding the platform loan limit."); // 超过平台总存款的10%
require(erc20Token.balanceOf(msg.sender) + clients[msg.sender].amount >= _amount*20/100, "Invalid loan."); // 账户资产数需有贷款的20%
erc20Token.transfer(msg.sender, _amount);
uint timestamp = block.timestamp; // 以当前块的时间戳为贷款id值
uint interest = _amount * annualInterestRate / 100; // 计算利率
loans[timestamp].loanAddress = msg.sender;
loans[timestamp].amount = _amount;
loans[timestamp].interest = interest; // 计算利率
emit CreateLoan(msg.sender, _amount, interest, timestamp);
return timestamp;
}
// 还款
function payLoan(uint _amount, uint _loanId) public {
require(_amount > 0, "Amount is less than or equal to 0."); // 还款需大于0
require(erc20Token.balanceOf(msg.sender) >= _amount, "Number of tokens is insufficient."); // 检查持有代币数是否大于等于存款代币数
require(erc20Token.allowances(msg.sender, address(this)) >= _amount, "Not enough approvals."); // 检查是否有足够的授权
uint total = loanInquiry(_loanId); // 计算代还款
bool fullPayment = false;
// 还一部分或一次性还
if (total <= _amount) {
erc20Token.transferFrom(msg.sender, address(this), total); // 一次性还完
delete loans[_loanId]; // 删除贷款记录
fullPayment = true;
} else {
erc20Token.transferFrom(msg.sender, address(this), _amount); // 还了一部分 _amount
loans[_loanId].amount -= _amount; // 重写贷款记录
}
emit PayLoan(msg.sender, _amount, fullPayment, block.timestamp);
}
// 待还款查询
function loanInquiry(uint _loanId) public returns (uint) {
require(loans[_loanId].amount != 0, "Invalid loan id.");
require(loans[_loanId].loanAddress == msg.sender, "Non-personal enquiry.");
// 计算需要还款的代币数: 本金 + 利息
uint timestamp = block.timestamp;
uint diffDays = ( timestamp - _loanId) / 86400; // 时间戳转换为天数
uint total = loans[_loanId].amount + loans[_loanId].interest*diffDays/365; // 总计还款数
loans[_loanId].amount = total; //重写用户贷款数
return total;
}
// 余额+利息的计算
function depositInquiry() public returns (uint){
uint principal = clients[msg.sender].amount; // 本金
uint diffDays = (block.timestamp - clients[msg.sender].depositTime) / 86400; // 时间戳转换为天数
uint interest = principal * annualDepositRate / 100 * diffDays / 365;
uint total = principal + interest;
clients[msg.sender].amount = total;
return total;
}
// 客户信息查询
function balanceInquiry() public returns (address, uint, uint, uint){
depositInquiry(); // 计算本金 + 利息2%
uint balance = clients[msg.sender].amount;
uint depositTime = clients[msg.sender].depositTime;
uint withdrawalTime = clients[msg.sender].withdrawalTime;
return (msg.sender, balance, depositTime, withdrawalTime);
}
}
Remix 运行实现
部署相关智能合约
先部署 ++ERC20 代币合约++ ,初始化代币合约信息。
部署 ++ERC20TokenLoanPlatform 合约++ ,初始化代币实现地址、贷款年利率5和存款年利率2。
首先需要给自己的账户铸造 200 个代币用于功能测试,
再给 ++ERC20TokenLoanPlatform 合约地址++ 铸造一定数量的代币和授权代币转账权限,保证平台功能的正常运行。
存款和取款
调用 deposit 函数 存款 100 个代币,
balanceInquiry 函数 查看余额变化,这里已经显示自己的余额为刚才存入的100。
调用 withdrawal 函数 取出指定数量的代币,不能超出自己的余额。平台会自动进行存款利息计算。
balanceInquiry 函数 查看余额变化,这里已经显示自己的余额还剩下50。
贷款和还款
调用 createLoan 函数 输入自己需要贷多少代币。在这里注意账户的资产需要有贷款数的20%才有资格贷款。成功后拿到此次贷款的id值 1703236890。
调用 payLoan 函数输入贷款的 id 值进行还款,可以先还一部分也可以一次性还完,平台会自动进行贷款利息计算。
调用 loanInquiry 函数 输入贷款的 id 值进行待还款的查询。
源码地址
本文只是简单介绍,具体实现看代码。gitee 开源地址。