112學年 第2學期 金融科技安全 期末專題報告 === [TOC] - 班級: 資工碩一 學號 : R1243011 姓名:賴偉晟 - :link: [HackMD_link(建議可用網站來觀看此次報告)](https://hackmd.io/@lailai1208/SksYW5WrR) ## 區塊鍊智能合約應用程式開發 (共1題,100分,滿分100分) 請以課程中所教授智能合約範例為基礎,構思一Ethereum 智能合約應用,並透過Remix IDE + Metamask部署於Test Network。 請於2024/6/20 23:59前透過數位學習園區繳交報告及程式碼 ### A. 描述本專題的題目、動機、目的與架構。 - **題目 :** 租賃管理智能合約(以常見租房為例) - **動機 :** 在租賃市場中,常見的問題包括租金支付遲延、押金爭議及合約條款的透明度不足。 - **目的 :** 通過智能合約,我們可以自動化租金支付,確保押金的公正處理,並提供一個不可篡改的合約條款記錄,從而提高交易的透明度與信任。 - **架構 :** 1. 智能合約初始化 : 房東建立基本的租賃條款,包括租金金額、支付時間、租期等。 2. 租金支付 : 租戶按月支付租金到智能合約,如果逾期未支付,合約自動產生提醒並可能加收遲延費用。 3. 押金處理 : 租戶入住時支付押金到智能合約,租約結束時,根據物業狀態自動退回押金或部分扣除。 4. 合約終止 : 租約到期或雙方同意提前終止,合約自動執行相關條款。 </br> | Complete Workflow Diagram | |:--------------------------------------------------:| | ![image](https://hackmd.io/_uploads/Sk6uScbSA.png) | ### B. 解說本專題程式的開發環境、關鍵程式碼。 - 開發環境: Remix IDE,使用Solidity語言開發智能合約。 - 初始變數設置: ```solidity=4 address payable private owner; // 出租方地址 address private tenant; // 租戶地址 uint256 private deposit; // 押金金额(單位:Wei) uint256 private rent; // 每月租金(單位:Wei) uint256 private dueDate; // 每月租金支付截止日 bool private leaseActive; // 租约是否有效 mapping(uint256 => bool) public rentPaid; // 每個月的租金支付狀態 uint256 private contractStart; // 合约開始時間 ``` - 關鍵程式碼: - 合約建立時,確定雙方同意的條款並寫入區塊鍊。 ```solidity=18 constructor(address _tenant, uint256 _deposit, uint256 _rent, uint256 _dueDate) public { owner = msg.sender; // 將部署者設為出租方 tenant = _tenant; // 設定租户 deposit = _deposit; // 設定押金金额(以 Wei 為單位) rent = _rent; // 設定每月租金(以 Wei 為单位) dueDate = _dueDate; // 設定租金支付截止日 leaseActive = false; // 初始化時租约未開通 contractStart = block.timestamp; // 初始化合约開始 } ``` - 租金支付函數,自動檢查支付日期並記錄交易。 ```solidity=38 function payRent() public payable { require(msg.sender == tenant, "Only tenant can pay rent."); require(leaseActive, "Lease is not active."); require(msg.value == rent, "Incorrect rent amount."); uint256 currentMonth = getCurrentMonth(); require(!rentPaid[currentMonth], "Rent already paid for this month."); require(isWithinDueDate(), "Rent payment is overdue."); rentPaid[currentMonth] = true; emit RentPaid(tenant, msg.value); } ``` - 押金管理,包括存入與退還邏輯。 1. 押金存入(由租客負責) ```solidity=29 function payDeposit() public payable { require(msg.sender == tenant, "Only tenant can pay the deposit."); require(msg.value == deposit, "Incorrect deposit amount."); require(!leaseActive, "Lease is already active."); leaseActive = true; emit DepositReceived(tenant, msg.value); } ``` 2. 押金退還(任一方[租客 or 房東]終止合約後,由房東負責) ```solidity=59 function refundDeposit() public { require(msg.sender == owner, "Only owner can refund the deposit."); require(!leaseActive, "Lease is still active."); address payable tenantPayable = address(uint160(tenant)); tenantPayable.transfer(deposit); emit DepositRefunded(tenant); } ``` - 完整程式碼 : ```solidity= pragma solidity >=0.4.22 <0.6.0; contract RentalContract { address payable private owner; // 出租方地址 address private tenant; // 租户地址 uint256 private deposit; // 押金金额(單位:Wei) uint256 private rent; // 每月租金(單位:Wei) uint256 private dueDate; // 每月租金支付截止日 bool private leaseActive; // 租约是否有效 mapping(uint256 => bool) public rentPaid; // 每個月的租金支付狀態 uint256 private contractStart; // 合约開始時間 event DepositReceived(address tenant, uint256 amount); event RentPaid(address tenant, uint256 amount); event LeaseTerminated(address requestor); event DepositRefunded(address tenant); constructor(address _tenant, uint256 _deposit, uint256 _rent, uint256 _dueDate) public { owner = msg.sender; // 將部署者設為出租方 tenant = _tenant; // 設定定租户 deposit = _deposit; // 設定押金金额(以 Wei 為單位) rent = _rent; // 設定每月租金(以 Wei 為單位) dueDate = _dueDate; // 設定租金支付截止日 leaseActive = false; // 初始化時租约未開通 contractStart = block.timestamp; // 初始化合约開始時間 } function payDeposit() public payable { require(msg.sender == tenant, "Only tenant can pay the deposit."); require(msg.value == deposit, "Incorrect deposit amount."); require(!leaseActive, "Lease is already active."); leaseActive = true; emit DepositReceived(tenant, msg.value); } function payRent() public payable { require(msg.sender == tenant, "Only tenant can pay rent."); require(leaseActive, "Lease is not active."); require(msg.value == rent, "Incorrect rent amount."); uint256 currentMonth = getCurrentMonth(); require(!rentPaid[currentMonth], "Rent already paid for this month."); require(isWithinDueDate(), "Rent payment is overdue."); rentPaid[currentMonth] = true; emit RentPaid(tenant, msg.value); } function withdrawRent() public { require(msg.sender == owner, "Only owner can withdraw rent."); require(leaseActive, "Lease is not active."); uint256 currentMonth = getCurrentMonth(); require(rentPaid[currentMonth], "Rent not paid for this month."); owner.transfer(rent); } function refundDeposit() public { require(msg.sender == owner, "Only owner can refund the deposit."); require(!leaseActive, "Lease is still active."); address payable tenantPayable = address(uint160(tenant)); tenantPayable.transfer(deposit); emit DepositRefunded(tenant); } function terminateLease() public { require(msg.sender == owner || msg.sender == tenant, "Only owner or tenant can terminate the lease."); require(leaseActive, "Lease is already terminated."); leaseActive = false; emit LeaseTerminated(msg.sender); } function getCurrentMonth() private view returns (uint256) { return (block.timestamp - contractStart) / 30 days; } function isWithinDueDate() private view returns (bool) { uint256 currentDay = (block.timestamp / 1 days) % 30; return currentDay <= dueDate; } // 新增的 getter 函数,常看合約條款狀態 function getDeposit() public view returns (uint256) { return deposit; } function getRent() public view returns (uint256) { return rent; } function getDueDate() public view returns (uint256) { return dueDate; } function getLeaseActive() public view returns (bool) { return leaseActive; } function getCurrentTimestamp() public view returns (uint256) { return block.timestamp; } } ``` ### C. 呈現本專題程式的執行結果。 - **Step 1: 部署合約** 使用出租方(房東)的以太坊帳戶部署合約。部署時需要指定: - 租戶的以太坊地址(_tenant)。 - 押金金額(_deposit),單位為Wei,本次設定金額為4。 - 每月租金(_rent),單位為Wei,本次設定金額為2。 - 租金支付截止日(_dueDate),本次設定日期為30。 </br> | 結果 | |:--------------------------------------------------:| | ![image](https://hackmd.io/_uploads/rJlbk2bHR.png)| - **Step 2: 租戶支付押金** - 租戶支付押金:切換到租戶的帳戶。 - 在Value 輸入金額 4 ,並調用 payDeposit 函數,發送等於押金金額(4)的以太幣。 - 支付押金完成後再調用 getLeaseActive() 函數,確認租約正式開通(注意此時Balance金額有變化)。 </br> | 結果 | |:--------------------------------------------------:| | ![image](https://hackmd.io/_uploads/SyWhYiZH0.png) | - **Step 3: 租戶支付租金** - 在租金支付截止日之前,租戶在Value 輸入金額 2 , 應調用 payRent()函數,以支付正確的租金金額。 - 支付租金完成後再調用 event RentPaid(0)函數,確認租金已經支付。(RentPaid=true, arg: 第一個月=0; 注意此時Balance金額有變化)。 </br> | 結果 | |:--------------------------------------------------:| | ![image](https://hackmd.io/_uploads/HkCtynZr0.png)| - **Step 4: 房東提取租金** - 房東可以在確認租金已支付後,通過調用 withdrawRent 函數來提取租金(注意此時Balance金額有變化)。 </br> | 結果 | |:--------------------------------------------------:| |![image](https://hackmd.io/_uploads/Sk4p13-SA.png)| - **Step 5: 租約終止** - 租約可以由租戶或房東通過調用 terminateLease 函數來終止(本次由房東中止)。 - 這將改變合約的狀態,使得租約不再有效(getLeaseActive=false)。 </br> | 結果 | |:--------------------------------------------------:| |![image](https://hackmd.io/_uploads/B1P7ehWBC.png)| - **Step 6: 房東退還押金** - 一旦租約終止,房東需要調用 refundDeposit 函數退還押金給租戶(注意此時Balance金額有變化)。 </br> | 結果 | |:--------------------------------------------------:| |![image](https://hackmd.io/_uploads/BJVYe2ZBA.png)|