LoanWolf Smart Contracts – LoanWolf智能合约区块链毕设代写

区块链毕设代写本文提供国外最新区块链项目源码下载,包括solidity,eth,fabric等blockchain区块链,LoanWolf Smart Contracts – LoanWolf智能合约区块链毕设代写 是一篇很好的国外资料

LoanWolf Smart Contracts

(for the Chainlink2021 Hackathon)

These are the V2 contracts for loanwolf decentralized non-colateralized lending. Unlike V1, the ERC20 payment contract standard here exists as an overridable template for issuing loans with payment in ERC20 tokens. There are 3 important contracts here. Bonds.sol, ERC20PaymentStandard.sol and ERC20CollateralStandard.sol. The depreciated SimpleEthPayment.sol is there as well. There is also a mock dai contract meant for testing as an erc20 token. There is no functionality, it only exists for testing. Truffle tets are in the tests folder. Migrations are not complete so don’t just copy those over. Bellow are the descriptions of the functions for the relevant contracts.

NOTE: Contracts are now using Solc 0.6.6. That is because this commit includes Chainlink and the Chainlink client is not yet working with newer Solidity Compier versions. This has caused the need for some changes. Which will be expressed here.

Bonds.sol (v2)

Introduction

Bonds is an ERC-1155 token. And it inherits the code from OpenZeppelin Bonds are minted by borrowers. These bonds are ERC-1155s that can then be staked (to accrue interest).

Functions

Constructor:
No params for contructor. However, the metadata URL required by ERC-1155 is hardcoded in the constructor. This should be changed.

newLoan(address _paymentContractAddress, uint256 _id) external
newLoan will mint new bond ERC-1155s to the borrower as defined in a paymentContract who’s address is passed as a parameter.

  • the function calls the issueBonds() function of the payment contract with the ID and collects the returned address.
  • that address must match the function caller (should be the borrower of the loan)
  • the id is also saved in a mapping called idToContract to connec the id to the payment contract address.

unstake(uint256 _index) external returns(bool)
unstake sends staked ERC-1155s back to their owner. The _index parameter is the pointer to the place in the linked list where this staking is done. The function also mints new ERC-1155s (of the same ID) as interest. When tokens are first staked the timestamp is noted. The difference between the unstake time and the stake time is then taken and divided by the “acruall period” from the payment contract. This is then multiplied by the ammount staked divided by the inverse interest rate. The result is a non-compounding simple interest. Walking through an example the staking process might look like this:

  1. Bob stakes 100 ERC-1155s which have an acruall period of 1 month and an inverse interest rate of 50 (2% monthly interest)
  2. 3 months pass
  3. Bob unstakes his ERC-1155s. And is given 106 back. As 3 Months * (100/50) = 6. That 6 is minted back along with his origionall 100.
    NOTE: tokens are MINTED back not transfered back. There was a strange issue with a contract not being approved to spend ERC-1155s that were in it’s possesion. Would like to fix this. But it’s a non breaking issue for now.

A final note is the return value of unstakeAll(). This returns true if interest generation is successful. In a particular case it will not be. And that is when a paymentContract is marked as complete. Meaning a borrower has completed the “total payment due” as defined in the payment contract. If a contract is complete a staker will be returned a false flag and be only sent back his principal investment, no interest.

getAccruances(address _who, uint256 _index) public view returns(uint256)
This function returns the number of passed accruance periods for a staker _who. Using the example of Bob above, if bob called this function just before unstaking he would have been returned 3 as 3 months passed and 1 month was the accruance period. -NOTE: _who must be currently staking to call the function

stake(uint256 _id, uint256 _amm) external This function sends your ERC1155s to the Bonds contract to stake. MUST approve bonds to spend your bonds before calling. It will add the staking to the linked list detailed bellow.

Staking (Linked List)

The staking mapping is a nested mapping containing a circular doubly linked list data structure that holds an address’s staking info. Traversal of the linked list is to be done with the public mapping offchain. To traverse start at the HEAD (will always be 0) for a given user’s address. Then look up the NEXT value for that user. Continue until you loop back to HEAD (0). Values are stored under the IOU struct named value. The IOU struct has the following values within it:

  • uint256 ID; nft ID
  • uint256 ammount; quantity staking
  • uint256 timeStaked; timestamp when staking started

The unstake() function makes use of the index in this linked list for deletions in constant time with low gas fees. Yaaaaaayyyy! When traversing the linked list and displaying to a user make sure to keep track of the “index” of each entry in the list. As if a user decides to unstake you will need to pass that index into the unstake() funciton.

NOTE: Solc 0.6.x does not support returning user defined datatypes. So the staking mapping is not public and does not have a getter function. A new function was added:
getStakingAt(address _who, uint256 _index) external view returns(uint, uint, uint256, uint256, uint256)
This function returns all the info above as well as the last/next node pointers (the two uints)

ERC20PaymentStandard.sol

Introduction

The payment contract of a loan can be custom made and it’s encouraged to be. But they all should be based off the ERC20PaymentStandard. That’s not to say payment contracts must be ERC20 payment contracts but they must have functions like this so bonds can call them and interact with features of the contract. All the functions in this contract are virual so they can be overridden by any child contract. An example of a child contract will be bellow with the ERC20CollateralPayment contract.

NOTE: The payment standard uses chainlink. If you publish a custom payment contract and do not override the use of chainlink, you will need to fund the contract with LINK

Loans/Lookups

The contract holds 2 mappings. loanIDs and loanLookup. loanIDs maps each address to an array of loan IDs. The loanLookup returns a loan struct in response to a loanID. This is what a loan looks like:

  • bool issued;
  • address ERC20Address;
  • address borrower;
  • bytes32 chainlinkRequestId;
  • uint256 paymentPeriod;
  • uint256 paymentDueDate;
  • uint256 minPayment;
  • uint256 interestRateInverse;
  • uint256 accrualPeriod;
  • uint256 principal;
  • uint256 totalPaymentsValue;
  • uint256 awaitingCollection;
  • uint256 paymentComplete;

Functions (also this is the interface used by bonds.sol)

Constructor(address _bonds) The bonds contract address should go here

getNumberOfLoans(address) external view returns(uint256) Simple enough. This returns the length of the loanIds array for address

issueBonds(uint256) external returns(uint256,address); This function is called when new bond NFTs are minted. It should verify that the ID is not already issued and return the borrower address for confirmation that borrower is calling the function.

addInterest(uint256, uint256) external returns(bool); This updates the totalPaymentsValue variable with new interest. Is called when a lender unstakes their loans in Bonds.sol

getInterest(uint256) external view returns(uint256); This returns the interestRateInverse for a loan. For Bonds.sol to use so it doesn’t have to parse a struct

isDelinquent(uint256) external view returns(bool); Returns true if the loan is delinquent. Can be overridden to return based off any kind of terms. But for now that term is “if minPayment is not made by paymentDueDate”

configureNew(address, address, uint256, uint256, uint256, uint256, uint256)external returns(uint256); This function configures a new loan for a given borrower. ANYONE can configure a loan for anyone else. But only the borrower can mint the ERC-1155s and thus begin the loan. A lender can call this function with a borrowers name to give a sort of “loan request” the borrower can choose to accept by minting the ERC-1155s for it. The parameters are listed bellow:

  • _erc20 is the ERC20 contract address that will be used for payments
  • _borrower is the borrower this loan is being configured for
  • _minPayment is the minimum payment that must be made before the payment period ends
  • _paymentPeriod payment must be made by this time or delinquent function will return true
  • _principal the origional loan value before interest
  • _inverseInterestRate the interest rate expressed as inverse. 2% = 1/5 = inverse of 5
  • _accrualPeriod the time it takes for interest to accrue in seconds
    This function also uses chainlink to retreive the

returns the id of the contract that was just created chainlinkRequestId for the loan. More on Chainlink bellow

payment(uint256, uint256) external; This is the function that borrower calls to make their payments. IMPORTANT: You must approve the transfer with the designated ERC20 contract first.

isComplete(uint256) external view returns(bool); Returns true if a loanID reflects a complete and paid off loan

getId(address, uint256) external view; This function creates an ID for a loan. This is the hash of the address of a borrower, address of this address, and the index in the loanIDs array

withdrawl(uint256, uint256) external This function is used to exchange ERC1155s as a lender for the ERC20s made as payments. 1 for 1. MUST approve in the Bonds contract before calling

Chainlink

Ahh yes. Chainlink. That’s what this is all about. Chainlink oracles are called at the end of the configureNew function to the bellow API address for the purpose of getting a merkle root on chain.
http://40.121.211.35:5000/api/getloansdetails/:LOAN_ID
This is the LoanWolf API. Independant payment contract creators are welcome to replace with a different API. But it must have the functionality outlined bellow. But first, note, LOAN_ID is, as you may imagine, the loan ID calculated by the getId function. This means that upon loan application the backend must publish the merkleRoot to the coorseponding loanID before calling configureNew. This means the frontend (or server) must call getId and calculate the ID as well.

Merkle roots

The tree root is the backbone of LoanWolf. The merkle tree allows information about a loan to be provably verifyable to anyone, without revealing non-pertinant personal information, and it can do this all very efficiently. However, in order for this to work, a trusted public merkle root must be available. Luckily that’s what smart contracts are all about. But we have to get the merkle root to the contract. So a backend must construct a merkle tree from the user’s information and publish the root on a public API for the contract to access by Loan Id.
This Loan ID must be expressed as a NUMBER. Even though it is a hash. You may be tempted to display it as a hex string but the Chainlink node will not read this string as hex. It has no job spec for base system. So it will read it as ASCII. And as I’m sure you can guess 0xabc…. is very different when read in hex than ascii. So the node will read it as a base 10 interger and then the smart contract will convert that to a Solidity bytes32 type. Since JS and many other languages do not support large 256 bit numbers the node have to read in number strings. So here’s some handy code to turn your hex string in the common hash format to a string interger using the BN.js library:
let hashNum = new BN(MERKE_ROOT_AS_HEX_STRING, 16).toString();

Now the smart contract does NOT store the merkle root in the loan object since it takes time for the oracle to submit the value. Instead it holds a kind of pointer to the value as the Chainlink Request Id. This request id can be used to look up the merkle root in the public merkleRoots mapping. As the merkle root will be published there in bytes32 format once submitted by the node.

ERC20CollateralStandard.sol

This contract is an example of a custom implementation of the ERC20PaymentStandard. This child contract inherits everything from it’s parent but adds the ability to post ERC20 collateral to a loan. It adds a collateralLookup mapping to store the ERC20 contract address and the ammount as collateral. It also overrides the ERC-1155 recieve function to handle collateral withdrawl if a loan is marked delinquent. If the loan is completed the borrower can withdrawl. NOTE: onERC1155BatchReceived is not implemented. Only single ERC1155 transfers handle collaterall colleciton. The new functions added are as follows:

addCollateral(address _ERC20Contract, uint256 _ammount, uint256 _loanId) external Only borrower can call this and add collateral at any point but can only add one kind of collateral. The ERC20 contract here does not have to be the same as the one in payment and can instead be anything. But if you choose to add more collateral it must be of the same ERC20 as the first one was.

returnCollateral(uint256 _loanId) external Function returns the collateral to the borrower if the loan is completed and borrower is calling.

Truffle Tests

Truffle tests can be run by running

truffle test

Testing is done with the Chai JS library and tests can be added under

test/loanwolf.test.js

Feel free to add tests


LoanWolf Smart Contracts

(对于Chainlink2021 Hackathon)

这些是loanwolf分散非合作贷款的V2合同。与V1不同,这里的ERC20支付合同标准作为一个可覆盖的模板存在,用于发行以ERC20代币支付的贷款。这里有三份重要的合同。债券.sol,ERC20型付款标准.sol和ERC20对照标准溶液. 贬值的简单支付.sol也有。还有一个模拟dai合同,用于作为erc20令牌进行测试。没有任何功能,它只用于测试。块菌Tet在tests文件夹中。迁移还没有完成,所以不要把它们复制过来。以下是相关合同的功能说明。

注意:合同现在使用Solc 0.6.6。这是因为此提交包括Chainlink,而Chainlink客户机尚未使用较新的Solidity Compier版本。这就需要一些改变。这将在这里表达。

Bonds.sol (v2)

Introduction

债券是一种ERC-1155代币。它继承了OpenZeppelin的代码,OpenZeppelin债券由借款人发行。这些债券是ERC-1155,然后可以下注(以累积利息)。

Functions

构造函数:
没有构造函数的参数。但是,ERC-1155所需的元数据URL在构造函数中是硬编码的。这应该改变。

newLoan(addressu paymentContractAddress,uint256u id)external
newLoan将按照paymentContract中的定义,向借款人发行新债券ERC-1155s,借款人的地址作为参数传递。

  • the function calls the issueBonds() function of the payment contract with the ID and collects the returned address.
  • that address must match the function caller (should be the borrower of the loan)
  • the id is also saved in a mapping called idToContract to connec the id to the payment contract address.

unstake(uint256u index)外部收益(bool)
unstake将押注的ERC-1155s发回其所有者。索引参数是指向链表中完成此锁定的位置的指针。该功能还铸造新的ERC-1155s(相同的ID)作为兴趣。当令牌第一次被标记时,时间戳被记录下来。然后取未结算时间和持仓时间之间的差额,除以付款合同中的“结算期”。然后乘以所押金额除以逆利率。其结果是非复利单利。遍历一个示例,锁紧过程可能如下所示:

  1. Bob stakes 100 ERC-1155s which have an acruall period of 1 month and an inverse interest rate of 50 (2% monthly interest)
  2. 3 months pass
  3. Bob unstakes his ERC-1155s. And is given 106 back. As 3 Months * (100/50) = 6. That 6 is minted back along with his origionall 100.
    NOTE: tokens are MINTED back not transfered back. There was a strange issue with a contract not being approved to spend ERC-1155s that were in it’s possesion. Would like to fix this. But it’s a non breaking issue for now.

最后一个注释是unstakeAll()的返回值。如果利息生成成功,则返回true。在特定的情况下,它不会是。这就是付款合同被标记为完成的时候。指借款人已完成付款合同中定义的“到期总付款”。如果一个合同是完整的一个赌注将返回一个虚假的旗帜,并只送回他的主要投资,没有利息。

getAccurances(addressu who,uint256u index)public view returns(uint256)
此函数返回stakeru who传递的累计期数。以上面的Bob为例,如果Bob在unstake之前调用这个函数,那么当3个月过去时,他将被返回3,1个月是应计期。-注意:当前必须锁定才能调用函数

stake(uint256u id,uint256u amm)external此函数将您的ERC1155发送到债券合约以锁定。必须批准债券才能在打电话之前花掉你的债券。它将把立桩添加到下面详述的链表中。

Staking (Linked List)

锁紧映射是一种嵌套映射,包含一个循环双链表数据结构,该结构保存地址的锁紧信息。链表的遍历是通过公共映射链外完成的。从给定用户地址的开头开始遍历(始终为0)。然后查找该用户的下一个值。继续,直到循环回到头部(0)。值存储在名为value的IOU结构下。IOU结构中包含以下值:

  • uint256 ID; nft ID
  • uint256 ammount; quantity staking
  • uint256 timeStaked; timestamp when staking started

unstake()函数使用此链表中的索引,以较低的煤气费在固定时间内进行删除。呀呀呀呀呀!当遍历链表并向用户显示时,请确保跟踪列表中每个条目的“索引”。如果用户决定取消捕获,则需要将该索引传递到unstake()函数中。

注意:solc0.6.x不支持返回用户定义的数据类型。所以锁紧映射不是公共的,也没有getter函数。增加了一个新的函数:
getlockeat(addressu who,uint256u index)external view returns(uint,uint,uint256,uint256,uint256)
此函数返回上面的所有信息以及上一个/下一个节点指针(两个uint)

ERC20PaymentStandard.sol

Introduction

贷款的付款合同可以定制,鼓励使用。但它们都应该以ERC20的支付标准为基础。这并不是说支付合同必须是ERC20支付合同,但他们必须有这样的功能,以便债券可以调用它们,并与合同的功能进行交互。此合约中的所有函数都是虚拟的,因此它们可以被任何子合约覆盖。儿童合同的一个例子是ERC20抵押付款合同。

注意:支付标准使用链接。如果您发布自定义付款合同,并且不覆盖chainlink的使用,则需要使用LINK为该合同提供资金该合同包含2个映射。loanIDs和loanLookup。loanIDs将每个地址映射到一个贷款id数组。loanLookup返回一个loanID对应的LoanStruct。贷款是这样的:

Loans/Lookups

构造器(地址u bonds)债券合同地址应该放在这里

  • bool issued;
  • address ERC20Address;
  • address borrower;
  • bytes32 chainlinkRequestId;
  • uint256 paymentPeriod;
  • uint256 paymentDueDate;
  • uint256 minPayment;
  • uint256 interestRateInverse;
  • uint256 accrualPeriod;
  • uint256 principal;
  • uint256 totalPaymentsValue;
  • uint256 awaitingCollection;
  • uint256 paymentComplete;

Functions (also this is the interface used by bonds.sol)

获取NumberOfLoans(地址)外部视图返回(uint256)足够简单。此函数返回地址的loanIds数组长度,发行债券(uint256)外部返回(uint256,address);在生成新债券nft时调用此函数。它应该验证ID是否已经被发布,并返回借贷者地址以确认借贷者正在调用函数。

addInterest(uint256,uint256)external returns(bool);这会用新的兴趣更新totalPaymentsValue变量。当一个贷方在一个月内取消他们的贷款时被称为债券.sol

getInterest(uint256)外部视图返回(uint256);它返回贷款的interestateinverse。为了债券.sol要使用,它不必解析结构isdelinkent(uint256)外部视图返回(bool);如果贷款拖欠,则返回true。可以重写为基于任何类型的术语返回。但目前这个术语是“if minPayment is not made by paymentDueDate”

configureNew(address,address,uint256,uint256,uint256,uint256)external returns(uint256);这个函数为给定的借款人配置新的贷款。任何人都可以为其他人配置贷款。但只有借款人可以造出ERC-1155,从而开始贷款。贷款人可以用借款人的名字调用这个函数,给出一种借款人可以选择接受的“贷款请求”,方法是为它铸造ERC-1155s。参数如下所示:

返回刚刚为贷款创建的合同的id chainlinkRequestId。更多关于Chainlink的信息,请参见下文“外部支付”(uint256,uint256);这是借款人调用的用于支付的函数。重要提示:您必须首先使用指定的ERC20合同批准转让。

isComplete(uint256)external view返回(bool);如果loanID反映了一个完整且已付清的贷款,则返回true

getId(address,uint256)external view;此函数为贷款创建一个ID。这是借款人地址、该地址的地址和loanIDs数组中索引的哈希值

  • _erc20 is the ERC20 contract address that will be used for payments
  • _borrower is the borrower this loan is being configured for
  • _minPayment is the minimum payment that must be made before the payment period ends
  • _paymentPeriod payment must be made by this time or delinquent function will return true
  • _principal the origional loan value before interest
  • _inverseInterestRate the interest rate expressed as inverse. 2% = 1/5 = inverse of 5
  • _accrualPeriod the time it takes for interest to accrue in seconds
    This function also uses chainlink to retreive the

取款(uint256,uint256)外部此函数用于将作为贷方的ERC1155s交换为作为付款的ERC20s。一对一。必须在债券合同中批准后才能打电话给

Ahh是的。链环。这就是为什么。Chainlink oracle在configureNew函数的末尾被调用到下面的API地址,目的是获取链上的merkle根。
http://40.121.211.35:5000/api/getloansdetails/:LOANu ID
这是LoanWolf api。独立的支付合同创建者欢迎用不同的API替换。但它必须具有下面概述的功能。但是首先,请注意,LOANu ID是您可以想象的,由getId函数计算的LOAN ID。这意味着在贷款申请时,后端必须在调用configureNew之前将merkleRoot发布到coorseponding loanID。这意味着前端(或服务器)必须调用getId并计算ID。

树根是壤土的主干。merkle树允许任何人都可以验证贷款的信息,而不必透露非相关的个人信息,而且它可以非常有效地做到这一点。但是,为了使其工作,必须有一个受信任的公共merkle根。幸运的是,这就是智能合约的意义所在。但我们必须把梅克尔根加入合同。因此,后端必须根据用户的信息构造一个merkle树,并在公共API上发布根目录,以便通过贷款Id访问合同。
此贷款Id必须表示为一个数字。即使这是一个杂凑。您可能会试图将其显示为十六进制字符串,但Chainlink节点不会将此字符串读取为十六进制。它没有基本系统的工作规范。所以它会把它读成ASCII码。我相信你能猜到。。。。以十六进制读取时与ascii非常不同。因此,节点将其读取为基数为10的整数,然后智能合约将其转换为一个稳定的bytes32类型。由于JS和许多其他语言不支持大的256位数字,节点必须读入数字字符串。下面是一些方便的代码,可以使用BN.js公司库:
让hashNum=new BN(MERKE_ROOT_AS_HEX_STRING,16).toString();

现在智能合约不会将merkle ROOT存储在loan对象中,因为oracle提交值需要时间。相反,它持有一种指向值的指针作为Chainlink请求Id。此请求Id可用于在公共merkleRoots映射中查找merkle根。因为一旦节点提交,merkle根将以bytes32格式发布在那里。

此合同是ERC自定义实现的一个示例

Chainlink

Ahh yes. Chainlink. That’s what this is all about. Chainlink oracles are called at the end of the configureNew function to the bellow API address for the purpose of getting a merkle root on chain.
http://40.121.211.35:5000/api/getloansdetails/:LOAN_ID
This is the LoanWolf API. Independant payment contract creators are welcome to replace with a different API. But it must have the functionality outlined bellow. But first, note, LOAN_ID is, as you may imagine, the loan ID calculated by the getId function. This means that upon loan application the backend must publish the merkleRoot to the coorseponding loanID before calling configureNew. This means the frontend (or server) must call getId and calculate the ID as well.

Merkle roots

The tree root is the backbone of LoanWolf. The merkle tree allows information about a loan to be provably verifyable to anyone, without revealing non-pertinant personal information, and it can do this all very efficiently. However, in order for this to work, a trusted public merkle root must be available. Luckily that’s what smart contracts are all about. But we have to get the merkle root to the contract. So a backend must construct a merkle tree from the user’s information and publish the root on a public API for the contract to access by Loan Id.
This Loan ID must be expressed as a NUMBER. Even though it is a hash. You may be tempted to display it as a hex string but the Chainlink node will not read this string as hex. It has no job spec for base system. So it will read it as ASCII. And as I’m sure you can guess 0xabc…. is very different when read in hex than ascii. So the node will read it as a base 10 interger and then the smart contract will convert that to a Solidity bytes32 type. Since JS and many other languages do not support large 256 bit numbers the node have to read in number strings. So here’s some handy code to turn your hex string in the common hash format to a string interger using the BN.js library:
let hashNum = new BN(MERKE_ROOT_AS_HEX_STRING, 16).toString();

Now the smart contract does NOT store the merkle root in the loan object since it takes time for the oracle to submit the value. Instead it holds a kind of pointer to the value as the Chainlink Request Id. This request id can be used to look up the merkle root in the public merkleRoots mapping. As the merkle root will be published there in bytes32 format once submitted by the node.

ERC20CollateralStandard.sol

This contract is an example of a custom implementation of the ERC20PaymentStandard. This child contract inherits everything from it’s parent but adds the ability to post ERC20 collateral to a loan. It adds a collateralLookup mapping to store the ERC20 contract address and the ammount as collateral. It also overrides the ERC-1155 recieve function to handle collateral withdrawl if a loan is marked delinquent. If the loan is completed the borrower can withdrawl. NOTE: onERC1155BatchReceived is not implemented. Only single ERC1155 transfers handle collaterall colleciton. The new functions added are as follows:

addCollateral(address _ERC20Contract, uint256 _ammount, uint256 _loanId) external Only borrower can call this and add collateral at any point but can only add one kind of collateral. The ERC20 contract here does not have to be the same as the one in payment and can instead be anything. But if you choose to add more collateral it must be of the same ERC20 as the first one was.

returnCollateral(uint256 _loanId) external Function returns the collateral to the borrower if the loan is completed and borrower is calling.

Truffle Tests

Truffle tests can be run by running

truffle test

Testing is done with the Chai JS library and tests can be added under

test/loanwolf.test.js

Feel free to add tests

部分转自网络,侵权联系删除区块链源码网

www.interchains.cc

https://www.interchains.cc/21576.html

区块链毕设网(www.interchains.cc)全网最靠谱的原创区块链毕设代做网站 部分资料来自网络,侵权联系删除! 最全最大的区块链源码站 ! QQ3039046426
区块链知识分享网, 以太坊dapp资源网, 区块链教程, fabric教程下载, 区块链书籍下载, 区块链资料下载, 区块链视频教程下载, 区块链基础教程, 区块链入门教程, 区块链资源 » LoanWolf Smart Contracts – LoanWolf智能合约区块链毕设代写

提供最优质的资源集合

立即查看 了解详情