代碼:PawningShop
contract PawningShop {
using SafeMath for uint256;
using SafeMath for uint8;
enum PawnStatus {
CREATED,
CANCELLED,
DEAL,
LIQUIDATED,
REPAID
}
struct Pawn {
// who borrows money
address creator;
address contractAddress;
uint256 tokenId;
PawnStatus status;
}
struct Bid {
address creator; // who create the bid
uint256 loanAmount;
// the amount of wei the lender lend to the borrower
uint256 interest; // the maximum interest that the borrower have to pay in wei
uint256 loanStartTime; // timestamp
uint256 loanDuration; // duration in days
bool isInterestProRated; // if the borrower pays sonner, then pays less interest
}
address public owner;
address[] public _whiteListNFT;
uint256 public _feeRate;
uint256 public _totalNumberOfPawn = 0;
uint256 public _totalNumberOfBid = 0;
// mapping nft address -> token id -> pawn
mapping(uint256 => Pawn) public _pawns;
// mapping bid id => bid
mapping(uint256 => Bid) public _bids;
// mapping bid id to pawn id
mapping(uint256 => uint256) public _bidToPawn;
mapping(uint256 => uint256) public _pawnToBid;
constructor() {
owner = msg.sender;
}
event PawnCreated(address borrower, uint256 indexed pawnId);
event PawnCancelled(address borrower, uint256 indexed pawnId);
event PawnDeal(address borrower, address lender, uint256 indexed pawnId, uint256 bidId);
event PawnRepaid(address borrower, address lender, uint256 indexed pawnId, uint256 bidId);
event PawnLiquidated(address borrower, address lender, uint256 indexed pawnId, uint256 bidId);
event BidCreated(address borrower, address lender, uint256 pawnId, uint256 indexed bidId);
event BidAccepted(address borrower, address lender, uint256 pawnId, uint256 indexed bidId);
event BidCancelled(address borrower, address lender, uint256 pawnId, uint256 indexed bidId);
event WhiteListAdded(address smartContract);
event WhiteListRemoved(address smartContract);
function getPawnById(uint256 id) public view returns(Pawn memory) {
return _pawns[id];
}
function createPawn(address tokenAddress, uint256 tokenId) public {
_totalNumberOfPawn += 1;
bool isInWhiteList = false;
for (uint256 i = 0; i < _whiteListNFT.length; i++) {
if (tokenAddress == _whiteListNFT[i]) {
isInWhiteList = true;
}
}
address sender = msg.sender;
require(
isInWhiteList == true,
"PawningShop: smart contract is not in white list"
);
bool isApproved = IERC721(tokenAddress).getApproved(tokenId) ==
address(this);
bool isApprovedForAll = IERC721(tokenAddress).isApprovedForAll(
sender,
address(this)
);
require(
isApproved || isApprovedForAll,
"PawningShop: haven't got permission to transfer"
);
IERC721(tokenAddress).transferFrom(msg.sender, address(this), tokenId);
_pawns[_totalNumberOfPawn].creator = sender;
_pawns[_totalNumberOfPawn].contractAddress = tokenAddress;
_pawns[_totalNumberOfPawn].tokenId = tokenId;
_pawns[_totalNumberOfPawn].status = PawnStatus.CREATED;
emit PawnCreated(sender, _totalNumberOfPawn);
}
function cancelPawn(uint256 pawnId) public {
Pawn storage pawn = _pawns[pawnId];
address creator = pawn.creator;
require(
pawn.status == PawnStatus.CREATED,
"PawningShop: Only can cancel when it has status of CREATED"
);
require(
msg.sender == creator,
"PawningShop: Only owner of the pawn can cancel it"
);
require(
_pawnToBid[pawnId] == 0,
"PawningShop: Only can cancel when no bid is accepted"
);
pawn.status = PawnStatus.CANCELLED;
IERC721(pawn.contractAddress).transferFrom(address(this), creator, pawn.tokenId);
emit PawnCancelled(owner, pawnId);
}
function createBid(
uint256 rate,
uint256 duration,
bool isInterestProRated,
uint256 pawnId
) public payable {
_totalNumberOfBid += 1;
address lender = msg.sender;
uint256 amount = msg.value;
Pawn storage pawn = _pawns[pawnId];
address borrower = pawn.creator;
require(pawnId > 0, "PawningShop: pawn id is not valid");
require(
borrower != address(0) || pawn.contractAddress != address(0),
"Pawningshop: pawn is not existed"
);
require(
borrower != lender,
"PawningShop: creator of the pawn cannot make a bid"
);
require(
pawn.status == PawnStatus.CREATED,
"PawningShop: cannot bid this pawn"
);
require(
amount > 0,
"PawningShop: amount of money must be bigger than 0"
);
_bids[_totalNumberOfBid].creator = lender;
_bids[_totalNumberOfBid].loanAmount = amount;
_bids[_totalNumberOfBid].interest = rate;
_bids[_totalNumberOfBid].loanDuration = duration;
_bids[_totalNumberOfBid].isInterestProRated = isInterestProRated;
// _bids[_totalNumberOfBid].loanStartTime = loanStartTime;
// loanStartTime will be set when the borrower accepts bid
_bidToPawn[_totalNumberOfBid] = pawnId;
emit BidCreated(borrower, lender, pawnId, _totalNumberOfBid);
}
function cancelBid(uint256 bidId) public {
Bid memory currBid = _bids[bidId];
address payable lender = payable(currBid.creator);
address sender = msg.sender;
require(
sender == lender,
"PawningShop: only creator can cancel the bid"
);
uint256 pawnId = _bidToPawn[bidId];
address borrower = _pawns[pawnId].creator;
require(
_pawnToBid[pawnId] != bidId,
"PawningShop: your bid is accepted, cannot cancel"
);
lender.transfer(currBid.loanAmount);
delete _bids[bidId];
delete _bidToPawn[bidId];
emit BidCancelled(borrower, lender, pawnId, bidId);
}
function acceptBid(uint256 bidId) public {
Bid storage currBid = _bids[bidId];
address lender = currBid.creator;
uint256 pawnId = _bidToPawn[bidId];
require(
pawnId > 0,
"PawningShop: The pawn is not existed"
);
Pawn storage pawn = _pawns[pawnId];
address payable borrower = payable(pawn.creator);
require(
borrower == msg.sender,
"PawningShop: only creator of pawn can accept bid"
);
borrower.transfer(currBid.loanAmount);
pawn.status = PawnStatus.DEAL;
_pawnToBid[pawnId] = bidId;
currBid.loanStartTime = block.timestamp;
emit BidAccepted(borrower, lender, pawnId, bidId);
}
function repaid(uint256 pawnId) public payable {
Pawn storage currPawn = _pawns[pawnId];
address borrower = currPawn.creator;
require(
borrower == msg.sender,
"PawningShop: Only creator of pawn can repay"
);
uint256 bidId = _pawnToBid[pawnId];
require(
bidId != 0,
"PawningShop: This pawn doen't have any accepted bid"
);
Bid storage currBid = _bids[bidId];
uint256 repayDeadline = _calculateRepayDeadline(currBid.loanStartTime, currBid.loanDuration);
require(
block.timestamp <= repayDeadline,
"PawningShop: to late to repaid"
);
uint256 value = msg.value;
uint256 repaidAmount = _calculateRepaidAmount(
currBid.loanAmount,
currBid.interest,
currBid.loanStartTime,
currBid.loanDuration,
currBid.isInterestProRated
);
require(
value == repaidAmount,
"PawningShop: pay exactly repaid amount"
);
// transfer token back to borrower
IERC721(currPawn.contractAddress).transferFrom(address(this), currPawn.creator, currPawn.tokenId);
// transfer money to lender
address payable lender = payable(currBid.creator);
lender.transfer(value);
delete _pawnToBid[pawnId];
delete _bidToPawn[bidId];
emit PawnRepaid(borrower, lender, pawnId, bidId);
}
function _calculateRepayDeadline(uint256 loanStartTime, uint256 loanDuration) public pure returns(uint256) {
uint256 loanDurationInSeconds = loanDuration.mul(1 days);
uint256 repayDeadline = loanStartTime.add(loanDurationInSeconds);
return repayDeadline;
}
function _calculateRepaidAmount(
uint256 original, uint256 interest, uint256 loanStartTime, uint256 duration, bool isInterestProRated
) public view returns (uint256) {
uint256 interestDue = interest;
if (isInterestProRated) {
uint256 interestPerDay = interest.div(duration);
uint256 secondPassed = block.timestamp.sub(loanStartTime);
uint256 dayPassed = ceilDiv(secondPassed, 1 days);
interestDue = dayPassed.mul(interestPerDay);
}
return original.add(interestDue);
}
function getRepaidAmount(uint256 pawnId) public view returns(uint256) {
require(pawnId > 0, "PawningShop: pawn id is not existed");
uint256 bidId = _pawnToBid[pawnId];
require(bidId > 0, "PawningShop: pawn doesn't have a accepted bid or the pawn is done");
Bid storage bid = _bids[bidId];
return _calculateRepaidAmount(bid.loanAmount, bid.interest, bid.loanStartTime, bid.loanDuration, bid.isInterestProRated);
}
// get this from openzeppelin
function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) {
// (a + b - 1) / b can overflow on addition, so we distribute.
return a / b + (a % b == 0 ? 0 : 1);
}
function liquidate(uint256 bidId) public {
Bid storage currBid = _bids[bidId];
address lender = currBid.creator;
require(lender == msg.sender, "PawningShop: only creator of bid can liquidate token");
require(block.timestamp > currBid.loanStartTime + currBid.loanAmount, "PawningShop: Not valid time to liquidate");
uint256 pawnId = _bidToPawn[bidId];
require(_pawnToBid[pawnId] == bidId, "PawningShop: this bid is not accepted by borrower");
Pawn storage currPawn = _pawns[pawnId];
address pawner = currPawn.creator;
IERC721(currPawn.contractAddress).transferFrom(address(this), lender, currPawn.tokenId);
emit PawnLiquidated(pawner, lender, pawnId, bidId);
}
function addToWhiteList(address smartContract) public {
require(msg.sender == owner, "PawningShop: Only owner can add address to white list");
require(smartContract != address(0), "PawningShop: smart contract address must be different with 0");
uint256 i = 0;
for (i = 0; i < _whiteListNFT.length; i++) {
if (_whiteListNFT[i] == smartContract) {
return;
}
}
_whiteListNFT.push(smartContract);
emit WhiteListAdded(smartContract);
}
function removeFromWhiteList(address smartContract) public {
require(msg.sender == owner, "PawningShop: Only owner can remove address from white list");
uint256 i;
for (i = 0; i < _whiteListNFT.length; i++) {
if (_whiteListNFT[i] == smartContract) {
break;
}
}
delete _whiteListNFT[i];
emit WhiteListRemoved(smartContract);
}
function getWhiteList() public view returns(address[] memory) {
return _whiteListNFT;
}
}
1、概述
PawningShop是一種基于NFT的抵押借貸的實作,PawningShop中有兩種角色:一種是抵押NFT的人,他需要通過抵押NFT借入一筆錢,我們叫他borrower,另一種是借出錢的人,我們叫他lender,
PawningShop的大致流程如下:
1、borrower創建一個NFT的抵押訂單
2、lender針對抵押訂單創建出價單,出價資訊包括借出多少錢,借出的時間,以及借出的利息,
3、borrower選擇一個合適出價單進行抵押借貸
4、borrower在規定時間內還本金和利息,就可以識訓抵押的NFT
5、borrower如果沒有在規定時間內歸還本金和利息,lender就可以進行清算,這時候NFT就會被轉移給lender,
PawningShop合約其實是有bug的,詳情見下文,但是整體設計思路值得借鑒,
2、borrower創建抵押訂單
function createPawn(address tokenAddress, uint256 tokenId) public {
_totalNumberOfPawn += 1;
bool isInWhiteList = false;
for (uint256 i = 0; i < _whiteListNFT.length; i++) {
if (tokenAddress == _whiteListNFT[i]) {
isInWhiteList = true;
}
}
address sender = msg.sender;
require(
isInWhiteList == true,
"PawningShop: smart contract is not in white list"
);
bool isApproved = IERC721(tokenAddress).getApproved(tokenId) ==
address(this);
bool isApprovedForAll = IERC721(tokenAddress).isApprovedForAll(
sender,
address(this)
);
require(
isApproved || isApprovedForAll,
"PawningShop: haven't got permission to transfer"
);
IERC721(tokenAddress).transferFrom(msg.sender, address(this), tokenId);
_pawns[_totalNumberOfPawn].creator = sender;
_pawns[_totalNumberOfPawn].contractAddress = tokenAddress;
_pawns[_totalNumberOfPawn].tokenId = tokenId;
_pawns[_totalNumberOfPawn].status = PawnStatus.CREATED;
emit PawnCreated(sender, _totalNumberOfPawn);
}
首先生成訂單號,然后校驗是否是白名單,然后需要給PawningShop這個合約授權,然后將NFT轉給這個合約保管,最后生成一條抵押記錄,
3、borrower取消抵押訂單
function cancelPawn(uint256 pawnId) public {
Pawn storage pawn = _pawns[pawnId];
address creator = pawn.creator;
require(
pawn.status == PawnStatus.CREATED,
"PawningShop: Only can cancel when it has status of CREATED"
);
require(
msg.sender == creator,
"PawningShop: Only owner of the pawn can cancel it"
);
require(
_pawnToBid[pawnId] == 0,
"PawningShop: Only can cancel when no bid is accepted"
);
pawn.status = PawnStatus.CANCELLED;
IERC721(pawn.contractAddress).transferFrom(address(this), creator, pawn.tokenId);
emit PawnCancelled(owner, pawnId);
}
首先根據訂單號拿到抵押訂單,然后校驗訂單狀態,必須是創建狀態,然后校驗抵押人身份和當前操作人是否一致以及抵押訂單是否存在,最后把訂單改成取消狀態,并且把NFT還給抵押人,
4、lender創建出借訂單
function createBid(
uint256 rate,
uint256 duration,
bool isInterestProRated,
uint256 pawnId
) public payable {
_totalNumberOfBid += 1;
address lender = msg.sender;
uint256 amount = msg.value;
Pawn storage pawn = _pawns[pawnId];
address borrower = pawn.creator;
require(pawnId > 0, "PawningShop: pawn id is not valid");
require(
borrower != address(0) || pawn.contractAddress != address(0),
"Pawningshop: pawn is not existed"
);
require(
borrower != lender,
"PawningShop: creator of the pawn cannot make a bid"
);
require(
pawn.status == PawnStatus.CREATED,
"PawningShop: cannot bid this pawn"
);
require(
amount > 0,
"PawningShop: amount of money must be bigger than 0"
);
_bids[_totalNumberOfBid].creator = lender;
_bids[_totalNumberOfBid].loanAmount = amount;
_bids[_totalNumberOfBid].interest = rate;
_bids[_totalNumberOfBid].loanDuration = duration;
_bids[_totalNumberOfBid].isInterestProRated = isInterestProRated;
// _bids[_totalNumberOfBid].loanStartTime = loanStartTime;
// loanStartTime will be set when the borrower accepts bid
_bidToPawn[_totalNumberOfBid] = pawnId;
emit BidCreated(borrower, lender, pawnId, _totalNumberOfBid);
}
首先生成出借單訂單號,然后校驗抵押訂單的合法性,然后需要保證抵押人和出借人不能是同一個人,最后生成一個出借訂單,這里使用msg.value作為出借金額,因此出借金額暫時由當前合約保管,
5、lender取消出借訂單
function cancelBid(uint256 bidId) public {
Bid memory currBid = _bids[bidId];
address payable lender = payable(currBid.creator);
address sender = msg.sender;
require(
sender == lender,
"PawningShop: only creator can cancel the bid"
);
uint256 pawnId = _bidToPawn[bidId];
address borrower = _pawns[pawnId].creator;
require(
_pawnToBid[pawnId] != bidId,
"PawningShop: your bid is accepted, cannot cancel"
);
lender.transfer(currBid.loanAmount);
delete _bids[bidId];
delete _bidToPawn[bidId];
emit BidCancelled(borrower, lender, pawnId, bidId);
}
取消出借單會校驗操作人權限,然后校驗出借單是否被執行,如果沒有被執行,會把出借金額轉還給出借人,
6、borrower選擇出借單
function acceptBid(uint256 bidId) public {
Bid storage currBid = _bids[bidId];
address lender = currBid.creator;
uint256 pawnId = _bidToPawn[bidId];
require(
pawnId > 0,
"PawningShop: The pawn is not existed"
);
Pawn storage pawn = _pawns[pawnId];
address payable borrower = payable(pawn.creator);
require(
borrower == msg.sender,
"PawningShop: only creator of pawn can accept bid"
);
borrower.transfer(currBid.loanAmount);
pawn.status = PawnStatus.DEAL;
_pawnToBid[pawnId] = bidId;
currBid.loanStartTime = block.timestamp;
emit BidAccepted(borrower, lender, pawnId, bidId);
}
先通過出借單找到抵押單,然后找到抵押單對應的borrower,然后需要校驗當前操作人就是borrower,最后把出借的錢轉給borrower,更新抵押單的狀態為完成,更新抵押單到出借單的關聯關系,
這里似乎少了對抵押單狀態的校驗,這樣borrower就可以多次執行acceptBid方法,如果當前合約有大量的ETH余額,就可以被全部套出來,
7、borrower正常還貸
function repaid(uint256 pawnId) public payable {
Pawn storage currPawn = _pawns[pawnId];
address borrower = currPawn.creator;
require(
borrower == msg.sender,
"PawningShop: Only creator of pawn can repay"
);
uint256 bidId = _pawnToBid[pawnId];
require(
bidId != 0,
"PawningShop: This pawn doen't have any accepted bid"
);
Bid storage currBid = _bids[bidId];
uint256 repayDeadline = _calculateRepayDeadline(currBid.loanStartTime, currBid.loanDuration);
require(
block.timestamp <= repayDeadline,
"PawningShop: to late to repaid"
);
uint256 value = msg.value;
uint256 repaidAmount = _calculateRepaidAmount(
currBid.loanAmount,
currBid.interest,
currBid.loanStartTime,
currBid.loanDuration,
currBid.isInterestProRated
);
require(
value == repaidAmount,
"PawningShop: pay exactly repaid amount"
);
// transfer token back to borrower
IERC721(currPawn.contractAddress).transferFrom(address(this), currPawn.creator, currPawn.tokenId);
// transfer money to lender
address payable lender = payable(currBid.creator);
lender.transfer(value);
delete _pawnToBid[pawnId];
delete _bidToPawn[bidId];
emit PawnRepaid(borrower, lender, pawnId, bidId);
}
首先校驗borrower和操作人是否一致,然后拿到出借單,計算出借最晚歸還時間,而且當前時間必須小于這個最晚歸還時間,然后計算應付金額,然后把NFT轉還給borrower,把應付金額轉給lender,最后洗掉抵押單和出借單之間的關系,
這里多次呼叫repaid是不會有問題的,因為最后刪掉了關聯關系:delete _bidToPawn[bidId];
7、borrower還款超時,lender發起清算
function liquidate(uint256 bidId) public {
Bid storage currBid = _bids[bidId];
address lender = currBid.creator;
require(lender == msg.sender, "PawningShop: only creator of bid can liquidate token");
require(block.timestamp > currBid.loanStartTime + currBid.loanAmount, "PawningShop: Not valid time to liquidate");
uint256 pawnId = _bidToPawn[bidId];
require(_pawnToBid[pawnId] == bidId, "PawningShop: this bid is not accepted by borrower");
Pawn storage currPawn = _pawns[pawnId];
address pawner = currPawn.creator;
IERC721(currPawn.contractAddress).transferFrom(address(this), lender, currPawn.tokenId);
emit PawnLiquidated(pawner, lender, pawnId, bidId);
}
首先校驗操作人權限,然后校驗是否真的超時,然后根據出借單找到抵押單,將抵押物轉移給lender,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qukuanlian/357179.html
標籤:區塊鏈
