用 Python 開發 DeFi 去中心化借貸應用

本文最後更新於:2022年1月29日 凌晨

利用Solidity和Python在Aave測試鏈上進行交易的Defi應用

最近投入大部分的寒假時間來學習,第一次把專案用比較詳細的文字敘述記錄下來,收穫頗多!

也剛好是因為已經有好心人整理要點,自己在思考過程中順暢很多(也省掉碼字的力氣)!

一直在反思要怎麼更好的吸收學習的東西,目前是這樣子的想法:

「先寫筆記,再回頭code」。兩件事情混在一起,對大腦認知負擔會太大

總之拉回正題,今天的專案是「利用Solidity和Python在Aave測試鏈上進行交易的Defi應用」

總目標:

  1. 抵押
    1. 將ETH換成 WETH
    2. 把一些ETH存入aave
    3. 使用存入的ETH(抵押品Collateral)來借入一些資產assets
    4. 做空Short Selling(把借來的資產賣出)
    5. 歸還Repay所有債務
  2. 拿出貸款
  3. 償還貸款

工具:

  1. brownie
  2. Aave
  3. Python

0. 前置工作

  1. brownie init

  2. 這個專案我們不會自己創建合約,因為都是要使用外部鏈上已經發布的˙合約

    1. 把ETH存進WETH 合約會讓你拿到WETH Token
    2. 所以我們需要先呼叫WETH 合約的deposit()
      1. 如何呼叫合約呢? 透過合約地址和合約abi
      2. 怎麼取得合約地址和合約abi? abi = interface(address)
        1. 透過「本地自定或config」(地址)以及「 WETH 的 Interface (IWeth.sol)」(abi)!
        2. 先取得address,再利用address取得abi
        3. IWeth.sol 裡面包含了 ERC20 合約的所有方法
  3. 先使用 Koven上的weth_token address(==WETH 在Koven上的合約地址)

  4. 測試

    1. 線上+integration test : koven

    2. 本地+ unit test : mainnet-fork

      1. 因為不使用oracle,所以也不需要 部屬mocking contract,不過還是可以用
      2. 直接把mainnet上的資料都抓下來
        1. 要先把 mainnet-fork 加入「development」的鏈上(用 brownie list true 查看)
          1. 先移除在原本的 「ETHEREUM」鏈上的
          2. 再加入「development」的鏈上

      預設的測試網路其實是「development + mocking」

      不過如果你不會用到oracle,你可以直接使用mainnet-fork

1
2
3
4
5
networks:
kovan: //這是kovan鏈上 WETH合約的地址
weth_token: "0xd0a1e359811322d97991e03f863a0c30c2cf029c"
mainnet-fork: // 這是主鏈上,WETH合約的地址
weth_token: "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2"

超級常用的get_account()

1
2
3
4
5
6
7
8
9
10
11
12
13
from brownie import accounts, network, config
LOCAL_BLOCKCHAIN_ENVIRONMENTS = ["development", "ganache", "hardhat", "local-ganache", "mainnet-fork",]

def get_account(index=None, id=None):
if index:
return accounts[index]#本地帳號
if network.show_active() in LOCAL_BLOCKCHAIN_ENVIRONMENTS:
print(accounts[0].balance())
return accounts[0] #本地帳號
if id:
return accounts.load(id)
return accounts.add(config["wallets"]["from_key"])#鏈上帳號

accounts.load

目標: 為了從script或console存取local account,首先必須要「解鎖」

鑰匙本人: accounts.load ,解完鎖就可以在Accounts container.中使用

1
2
3
4
5
6
7
8
//回傳一個帳號總列表
>>> accounts.load()
['my_account']

//回傳一個本地帳號
>>> accounts.load('my_account')
Enter the password for this account:
<LocalAccount object '0xa9c2DD830DfFE8934fEb0A93BAbcb6e823e1FF05'>

accounts / local account

  • 唯一差別: local account 的私鑰是直接被放置的

Resetting the RPC client will delete all LocalAccount objects from the Account container.

1
2
3
4
>>> accounts.add()
<LocalAccount object '0x716E8419F2926d6AcE07442675F476ace972C580'>
>>> accounts[-1]
<LocalAccount object '0x716E8419F2926d6AcE07442675F476ace972C580'>

dotenv= .env

1
2
3
export PRIVATE_KEY = 0x<自己的私鑰>
export WEB3_INFURA_PROJECT_ID =530c9d1b97ad4e9c8c4939eefcab5c6f
export ETHERSCAN_TOKEN=FIC7FIYDT16AXNBN3SY1CFQ82PS9R22DZ2

1. 將ETH換成 WETH(By AAVE)

我們使用 ./scripts/get_weth.py 腳本中的 get_weth 函數完成了此操作。

測試網上的第一筆交易!

  1. 為了與 Aave 協議交互,把我們的 ETH 換成 ERC20 版本的 ETH,稱為 WETH(也稱為wrapped ETH)。它使我們更容易與 AAVE 協議交互。
    1. ERC20 是以太坊上的通用代幣標準。
  2. 您應該會看到您的 MetaMask 餘額減少。這是因為我們正在將 ETH 換成 WETH。
  3. 為了查看我們的新餘額,進入 MetaMask,然後點擊添加令牌。然後,在自定義令牌下輸入地址 0xd0a1e359811322d97991e03f863a0c30c2cf029c。
    1. 這是 Kovan 測試網上 Wrapped Ether 代幣的合約地址。
    2. 你會注意到現在總共不到 2 個 ETH (1 + 0.99 != 2)。這是因為每次在區塊鏈上進行交易時,都需要支付一點 gas。
      1. gas 是一種向礦工和驗證者支付少量交易費用的方式。有兩種 gas:
        1. TransactionGas2、
        2. OracleGas
        3. 在這個專案中,我們只需要關注 transaction gas。
  4. 就是你在測試網上的第一筆交易!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
from scripts.helpful_scripts import get_account
from brownie import interface, config, network

def main():
get_weth()

def get_weth():
"""
Mints WETH by depositing ETH
"""
account = get_account()

#利用weth合約的address,取得weth合約的abi
weth = interface.IWeth(config["networks"][network.show_active()]["weth_token"])

#調用合約函式
#brownie 預設的eth貨幣單位是wei,1ETH = 10^18 WEI
tx = weth.deposit({"from": account, "value": 0.1 * 10 ** 18})

tx.wait(1)
print("Received 0.1 WETH")
return tx

我們本身有一個儲存ETH的帳戶,要和WETH的合約進行交互

  1. 我們需要獲取 WETH 合約對象,以便與該合約對象進行交互。
    • 我們想將 ETH 存入合約,所以它會為我們鑄造相同數量的 WETH。
      • 我們可以隨時使用此合約將 WETH 轉換回 ETH。(使用 withdraw() )
    • 所有 ERC20 代幣(如 LINK、WETH、AAVE 等)本身都是鏈上合約。
  2. 要與合約交互,我們需要兩個東西。
    • 合約ABI/接口
    • 合約地址
  3. 我們可以使用該interfaces 接口,因為它可以編譯為 ABI
    1. 編譯後的項目將合約放入 build 文件夾中。
    2. 如果我們查看 build 下的 interfaces 文件夾,我們可以看到一個名為 WethInterface.json 的文件。在該文件夾中,有一個名為 abi 的密鑰。
      • ABI 代表 APP 二進制接口,是程序了解如何與合約交互的標準方式,簡單來說,就是每個合約的「使用手冊」
    3. 我們可以通過創建一個這樣的 weth 變數將地址和 ABI 添加到一個對像中以供我們交互
1
2
weth = interface.WethInterface(       
config["networks"][network.show_active()]["weth_token"])
  1. 我們再一次從配置文件中獲取了 weth_token 地址。你會注意到不同的代幣有不同的地址,這取決於你正在處理的鏈
    1. 如果我們要 print(type(weth)),我們會得到:
1
<class'brownie.network.contract.Contract'>
  1. Contract 對象就像一個類,它代錶鍊上的合約。然後我們可以在鏈上調用該合約的函數
    • 因此,我們調用 weth合約上的存款函數 deposit,並回傳該次transactiontx :
1
2
tx = weth.deposit({"from": account, "value": 1000000000000000000})
#我打算使用account帳戶裡面的錢,存value進去weth合約裡面

補充原理

1. 我要如何在以太坊上進行交易或調用?

  1. 以太坊上交易(transact)==修改區塊鍊的狀態,調用(call)只是查看狀態

  2. 如果您想修改區塊鏈的狀態,您必須始終 from一個帳戶

  3. 應用:

    1. 我們正在修改區塊鏈的狀態,因為我們將修改我們的 ETH 和 WETH 餘額。
  4. 每當我們進行函數調用並修改區塊鏈的狀態時,我們就會進行交易。

    1
    2
    Transaction sent: 
    0x888bb9d6657b1de2e5eec465bf9641b401647a61a2bd428b51d8a95d5a3e329a

    然後,您可以將此交易哈希複製到區塊瀏覽器中以查看該交易的詳細信息。

2.何謂interface接口?

  1. 接口就是一組「可以面向外部的共同的調用方法」
  2. 對於外部程序來說,如果繼承了這個接口,那麼這個合約一定包含接口中的方法和實現
1
2
3
4
5
pragma solidity ^0.4.19;
interface Cash {
function receive(address recipient, uint amount) external;
function getRemain(address cashAccount) external;
}

3.函式的modifier宣告的順序

  1. Visibility
  2. Mutability
  3. Virtual
  4. Override
  5. Custom modifiers

4.Constructor 怎麼用?

  1. 用來初始化該合約的state variable
  2. 是選擇性使用的
    1. 有用
      1. 每個合約只能有一個
      2. 當該合約被建立,Constructor 就會執行
    2. 沒用
      1. 一個default constructor 會自動出現在合約
  3. 執行完畢,final code會被部屬到鏈上
    1. YES: public function + code reachable through public functions
    2. NO: Constructor code + any internal method used only by constructor
  4. 可見度: public internal
    1. 如果是 internal ,代表這是「抽象合約」
  5. 如果繼承來的合約(父合約)的 Constructor 有參數,那「子合約」也要輸入參數
    1. 有兩種方法可以使用
    2. 注意: (父合約)本人不能用這兩種方法來初始化
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
pragma solidity ^0.5.0;

contract Base {
uint data;
constructor(uint _data) public {
data = _data;
}
}
// 第一種直接的方法
contract Derived is Base (5) {
constructor() public {}
}
// 第二種間接的方法
contract Derived is Base {
constructor(uint _info) Base(_info * _info) public {}
}
  1. 若「子合約」沒有輸入參數,會被視為「抽象合約」
  2. 如何建立可以在腳本中使用的帳戶? 如何用PYTHON獲得交易所需的帳戶?
    • 位於 brownie-config.yaml。使用我們的 PRIVATE_KEY 環境變量。
1
2
3
wallets: 
from_key: ${PRIVATE_KEY}
from_mnemonic: ${MNEMONIC}
  • 現在不用擔心 MNEMONIC。我们將該帳戶添加到我們的Brownie accounts列表中:
1
accounts.add(config["wallets"]["from_key"])
  1. 如何透過「對方合約的地址和ABI」和對方合約進行交互?
  2. 如何使用函式來「調用Call」並傳送交易

Brownie的功能

  1. accounts
  2. network
  3. config
  4. interface

2. 把一些ETH存入aave

運行 aave_borrow.py 腳本,它做了 4 件事:

  • 將抵押品(Collateral,此處是ETH)存入 Aave 貸款池
  • 獲得了我們的抵押品和另一種資產之間的匯率
  • 使用該抵押品借入不同的資產(貸款)
  • 還清了貸款

我们看一下main函數的開頭:

1
2
3
4
5
6
7
8
9
def main():
account = get_account()

#erc20合約的地址
erc20_address = config["networks"][network.show_active()]["weth_token"]
if network.show_active() in ["mainnet-fork"]:
get_weth() #如果在我們本地運行,調用 get_weth()讓我們的錢包中有 WETH,也得到deposit交易的地址

lending_pool = get_lending_pool()

1. 從配置文件中獲取我們的地址

  1. 這在 get_account 函數中定義
    1. 如果我們在測試網上,它會再次從我們的配置中提取
    2. 如果您在本地鏈上進行測試,我們只使用它生成的第一個帳戶。

2. 我們也從配置文件中獲取 WETH 令牌的地址

  • 如果我們在本地鏈上,我們調用之前調用的相同 get_weth 函數。
    • 這是為了確保如果我們使用本地鏈,我們的錢包中有 WETH。

3. 我們得到了lending_pool的地址

  • lending_pool是管理借貸功能的鏈上智能合約。
    • 您可以在 Aave 文檔中查看功能,或者您可以直接在鏈上查看合約。

lending_pool

我們在這個合約中看到了許多有用的功能:

  • 借貸
  • 抵押
  • 獲取用戶帳戶數據
  • 償還

請記住,鏈上智能合約與傳統軟件工程中的對像object或類class相同,每個都有與之相關的功能。

如果您要print(type(lending_pool)),我們會得到這種類型

1
<class 'brownie.network.contract.Contract'>

這意味著借貸池是一個合約,就像 WETH 一樣!

get_lending_pool() 函數

  • 我們的 get_lending_pool() 函數實際上做了一些額外的步驟來獲取貸款池合約lending_pool :
1
2
3
4
5
6
7
8
9
def get_lending_pool():
# address
lending_pool_addresses_provider = interface.ILendingPoolAddressesProvider(
config["networks"][network.show_active()]["lending_pool_addresses_provider"])
lending_pool_address = lending_pool_addresses_provider.getLendingPool()

# abi = interface(address)
lending_pool = interface.ILendingPool(lending_pool_address)
return lending_pool
  • 我們需要兩個東西才能與智能合約進行交互:ABI/接口+ 地址
  1. 我們有位於interfaces文件夾中的借貸池的接口。
    • 補充: 你會注意到一個流行的接口約定是讓它們以字母“I”開頭。我們在這個例子中有一個mix,表示它不必以“I”開頭。
  • 為了讓我們獲得借貸池的地址,我們必須透過 LendingPoolAddressesProvider 的地址
    • 這是一個鏈上合約,其中包含 Aave 借貸池的所有地址
      • 有時,出借池地址發生變化,但出借池提供者地址永遠不會改變,因此我們始終可以使用該合約的地址來獲取出借池地址。
      • 你會看到我們在lenting_pool_addresses_provider 變量上調用了 getLendingPool 函數

存入抵押品

讓我們回到主函數。在我們得到了貸款池合約的地址後,我們要先存入抵押品,才能獲得貸款

  • 存入抵押品將使我們獲得:

    1. 收益: 該協議向將抵押品存入平台的用戶支付報酬。

    2. 取出貸款: 我們只有在有足夠的抵押品的情況下才能貸款/借入。

      • 超額抵押貸款
        • Aave 有一個名為 Liquidationcall 的函數,如果您沒有足夠的抵押品,任何人都可以調用該函數並獲得獎勵。抵押品的價值必須保持大於貸款的價值,
    3. 流動性挖礦 = 用戶存入抵押品增加流動性,該合約會提供獎勵

      • 也稱為收益耕作,讓用戶能夠以治理govermance或其他類型代幣的形式獲得獎勵以向協議提供流動性
      • 但是,為了讓我們存入一些代幣,我們必須批准智能合約才能將代幣從我們的錢包中取出。
      • 我們將把我們的 WETH 作為抵押品。

approve_erc20

讓我們看看我們的approve_erc20函數。

我的account允許lending_pool_address這個合約(借貸池地址)從erc20_address這個地址領取amount這麼多的ERC20 代幣

1
2
3
4
5
6
7
def approve_erc20(amount, lending_pool_address, erc20_address, account):
print("Approving ERC20...")
erc20 = interface.IERC20(erc20_address)
tx_hash = erc20.approve(lending_pool_address, amount, {"from": account})
tx_hash.wait(1)
print("Approved!")
return True
  • amount:允許協議使用多少 ERC20,以 wei 為單位。
    • wei 是區塊鏈世界中最小的計量單位。 1 ETH == 1000000000000000000wei (10^18)
  • lending_pool_address:借貸池地址。
  • erc20_address:ERC20 代幣的地址。(哪一個幣種?)
  • account:我們想要交易的賬戶。

原理:

  • 在使用 ERC20 代幣時,如果我們希望從我們的錢包中“調用”另一個合約,我們必須首先批准該合約

  • 我們調用 ERC20 合約(我們的 WETH)上的批准函數(approve_erc20),然後我們“等待”tx_hash.wait(1)該交易繼續進行。

  • 現在我們已經批准了合同,我們可以將我們的抵押品存入我們的主函數中的 lending_pool 合同中(來自 get_lending_pool() )

lending_pool 借貸池合約,完成deposit!

我讓account.address存amount這麼多的erc20_address該種類代幣到借貸池

1
2
3
print("Depositing...")
lending_pool.deposit(erc20_address, amount, account.address, 0, {"from": account})
print("Deposited!")

我們可以在這裡看到,我們在合約上調用了deposit函數,我們給了它:

  • erc20_address:要存入的代幣(在我們的例子中是 WETH)
  • amount:要存入的 wei 金額。
  • account.address:我們想為誰貸款,在這種情況下是我們自己。
  • 0:這是推薦代碼(已折舊,始終使用 0)

我們可以從 Aave 文檔中了解更多關於智能合約中的存款功能。

Etherscan 上 Kovan 測試網的例子

如果你正確地完成了這部分,你會得到一個交易哈希,它會告訴你發生了什麼。

讓我們看一個關於 Etherscan 上 Kovan 測試網的例子。

image-20220129000032424

如果我們查看 Tokens Transferred 部分,我們可以看到 1 筆存款交易發生了 3 筆交易

按順序,這些是:

  • 一些 aToken 被鑄造出來供我們存入 (aWETH)
  • 我們發送了 1 個 WETH 到 lending_pool 合約(這是我們的存款)
  • 我們收到了一些 aToken

所以中間代幣轉移是有道理的——我們正在向借貸池發送一些 WETH。但是其他token是什麼?

屬於支付利息Interest Bearing 的代幣aToken

  • aToken 是一種計息代幣,在存款時鑄造並在贖回時銷毀

    • 所以當你取回你的存款時,你燒掉你的 aToken 以獲得等量的基礎資產
    • aTokens 最酷的部分是它們的價值每時每刻都在上漲!
    • 餘額增加是因為人們使用 Aave 協議並藉用您的資產,因此您因此獲得報酬!
  • 您可以查看某人的餘額並每隔幾秒鐘刷新一次,以實時查看餘額的增加情況。

  • 每當我們想要贖回或提取我們的 WETH 時,無論我們擁有多少 aWETH,我們都會提取,而這些 aWETH 將被燒毀。

  • 與 WETH token相同,您現在可以在您的 MetaMask 錢包中看到 aWETH。只需進入您的 MetaMask,並執行我們上面所做的添加的token部分,Kovan WETH 地址為 0x87b1f4cf9bd63f7bbd3ee1ad04e8f52540349347

3. 貸款–使用存入的ETH(抵押品Collateral)來借入一些資產assets

現在我們已經存入了一些抵押品,我們可以貸款了。現在我們為什麼要貸款?

  • 無摩擦賣空
  • 無需平倉即可獲得流動性

健康系數

您可以借入資產以保持對標的抵押品的敞口,同時還可以用另一種資產進行交易或支付。只要確保將您的健康係數保持在 1 以上。

  • 您的健康因素是您貸款的抵押品數量。
  • 假設我存入了 1 ETH,借了 0.5 ETH。如果清算閾值為 1,那麼我的健康係數將為 2。

image-20220129000045954

清算門檻( liquidation threshold )是貸款被定義為抵押不足的百分比

  • 例如,清算門檻為 80% 意味著如果價值超過抵押品的 80%,則貸款抵押不足,可以清算

  • 但是假設我有 2 ETH 借入和 1 ETH 作為抵押品,1 作為清算門檻。我的健康係數是 0.5,我會被要求清算。這是清算人代表借款人償還部分或全部未償還借入金額的情況,同時獲得折扣金額的抵押品作為回報(也稱為清算“獎金”)。

  • 清算人可以決定他們是否想要獲得等值的金額 抵押aToken,或者直接標的資產,

  • 當清算成功完成後,增加倉位的健康係數,使健康係數在1以上。通過這種方式,來保持貸款健康

getUserAccountData 函數

目的: 了解帳戶情況( 抵押品/借貸(債務)/可借貸數量/清算門檻/貸款價值比 (Loan-to-value ratio)/健康系數)

以我們的抵押品借款,我們首先要衡量我們作為抵押品的抵押品數據。我們可以調用貸款池上的 getUserAccountData 函數來找出這一點。

  • 我們將它封裝到一個名為 get_borrowable_data 的函數中
  • 我們只關注 available_borrow_eth 和 total_debt_eth,這樣我們就會知道:
    • 我們可以借多少錢
    • 我們目前有多少債務

Web3.fromWei( )

web3.utils.fromWei(number [, unit]) wei 轉為指定單位

  1. number:接受格式字串、數字、BN,單位是 wei
  2. uint:接受格式字串 (非必填),預設是 ether
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
def get_borrowable_data(lending_pool, account):
(
total_collateral_eth,#抵押品
total_debt_eth,#借貸(債務)
available_borrow_eth,#可借貸數量
current_liquidation_threshold,#清算門檻
ltv,#貸款價值比 (Loan-to-value ratio)
health_factor,#健康系數
) = lending_pool.getUserAccountData(account.address)#getUserAccountData

available_borrow_eth = Web3.fromWei(available_borrow_eth, "ether")#從wei轉為ether
total_collateral_eth = Web3.fromWei(total_collateral_eth, "ether")#從wei轉為ether
total_debt_eth = Web3.fromWei(total_debt_eth, "ether")#從wei轉為ether

print(f"You have {total_collateral_eth} worth of ETH deposited.")
print(f"You have {total_debt_eth} worth of ETH borrowed.")
print(f"You can borrow {available_borrow_eth} worth of ETH.")
return (float(available_borrow_eth), float(total_debt_eth))

為什麼我還沒有進行交易?

其實有兩種類型的智能合約功能,您無需進行交易即可調用。

  • 視圖函數 view
  • 純函數

這些函數不會修改區塊鏈的狀態,因此我們可以調用它們,因為我們只是在讀取智能合約的狀態。

請記住,如果您修改區塊鏈的狀態,它只會花費 gas。

獲取可借資產價值

目的: 借不同種類的代幣,需要得到「資產VS目標代幣的匯率」

  • 現在,當我們借款時,我們會被告知我們擁有的以 ETH 表示的資產數量。然而,也許我們想借用一個不同的代幣,比如 DAI。如果我們要以 ETH 為抵押借入 DAI,我們首先要得到 ETH -> DAI 的兌換率。

Chainlink 是 DeFi 和智能合約生態系統中獲取數據和執行外部計算的標準,也是 Aave 用來獲取費率的標準!他們有一個價格 Oracle 部分,描述如何通過他們的智能合約 API 獲取 Chainlink 數據。如果你學會了一般地使用 Chainlink 數據,你可以跨協議使用它,即使他們沒有使用 Chainlink,或者沒有價格預言機機制!

Chainlink 通過去中心化的預言機網絡獲取數據,其中多個節點將跨多個 API 和服務的數據聚合到一個去中心化的來源,該來源經過驗證並記錄在鏈上供我們使用。我們可以在 data.chain.link 頁面或文檔上看到當前可用數據提要的列表。如您所知,數據是大多數量化系統、算法交易者和整個 DeFi 生態系統的支柱,因此您要始終確保自己是從去中心化系統中提取的,這樣您就可以獲得最準確、最可靠和最安全的數據。

此外,如果您不想要某些數據,您可以從任何 API 調用您的智能合約,獲得可證明的隨機數以及許多其他不同的服務。這些使得智能合約數據安全、可靠和去中心化。

但是讓我們回到 Python!

法1: get_asset_price

目的: 它從 dai_eth_price_feed 中讀取數據,如下所示:

1
2
3
4
5
6
7
8
9
def get_asset_price():
# For mainnet we can just do:
# return Contract(f"{pair}.data.eth").latestAnswer() / 1e8
dai_eth_price_feed = interface.AggregatorV3Interface(
config["networks"][network.show_active()]["dai_eth_price_feed"]
)
latest_price = Web3.fromWei(dai_eth_price_feed.latestRoundData()[1], "ether")
print(f"The DAI/ETH price is {latest_price}")
return float(latest_price)
  • 我們從Interface接口中提取的 Chainlink 合約 AggregatorV3Interface 已存儲在我們的配置文件中。

    • 自行複製AggregatorV3Interface.sol檔案並設置在config中
  • 在該合約中,我們調用 latestRoundData 函數

    • 你會注意到我們也不需要為這個做一個交易,這也是一個視圖函數!

法2: 使用ENS 來獲取價格對的地址

  • 請注意,ENS 僅適用於主網網絡,
    • 因此您在運行 brownie 時必須使用 –network mainnet 或 –network mainnet-fork。

你會看到通過brownie獲得 Chainlink 數據價格的另一種方式是:

1
2
from brownie import Contract
print(Contract(f"{pair}.data.eth").latestAnswer() / 1e8)

這是使用 ENS 來獲取價格對的地址。

  • ENS 是一種獲取智能合約地址並使其可讀的方法。例如,您可以執行以下操作:
1
print(Contract(f"eth-usd.data.eth").latestAnswer() / 1e8)

以美元為單位獲取以太坊的最新價格。

回到貸款。

每項資產都有不同的貸款借入比率,它告訴您的清算門檻,以便您借入資產。

一旦我們得出了可藉貸款量,我們就可以借了!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 得到整體帳戶資料
borrowable_eth, total_debt = get_borrowable_data(lending_pool, account)
print(" let's borrow!")
# DAI im terms of ETH (DAI以ETH計價 )
dai_eth_price = get_asset_price(
config["networks"][network.show_active()]["dai_eth_price_feed"]
)

#我們的DAI可以換多少ETH
amount_dai_to_borrow = (1 / dai_eth_price) * (borrowable_eth * 0.95)
# borrowable_eth - > borrowable_dai *95%
print(f"we are going to borrow {amount_dai_to_borrow} DAI ")
# 開借! NOW we will borrow
dai_address = config["networks"][network.show_active()]["dai_token"]
borrow_tx = lending_pool.borrow(
dai_address,
Web3.toWei(amount_dai_to_borrow, "ether"),
1,
0,
account.address,
{"from": account},
)
borrow_tx.wait(1)
print("WE Borrowed some dai!")
get_borrowable_data(lending_pool, account)#帳戶詳情

做空Short Selling(把借來的資產賣出)

將價值轉換為借用

  • 現在我們有了抵押品和我們想要借入的資產之間的轉換率
    • 我們可以設置一個變量來確定我們想要借入多少。
1
2
amount_dai_to_borrow = (1 / erc20_eth_price) * (borrowable_eth * 0.95)
print(f"we are going to borrow {amount_dai_to_borrow} DAI ")
  • 我們得到 ==ETH/DAI 價格的倒數==,並將其乘以我們==可以借入的金額(以 ETH 為單位)==。
    • 為了安全起見,我們還將其乘以 0.95。然後,我們調用借用函數 lending_pool.borrow
1
2
3
4
5
6
7
8
9
10
11
12
13
# NOW we will borrow
dai_address = config["networks"][network.show_active()]["dai_token"]
borrow_tx = lending_pool.borrow(
dai_address,#資產的地址(在我們的例子中是 DAI 代幣)
Web3.toWei(amount_dai_to_borrow, "ether"),#我們要借的金額,單位為 wei
1,#nterestRateMode,(0, 1, 2: 1 是穩定的,2 是可變的……現在不用擔心 0)
0,#推薦碼(忽略)
account.address,#onBehalfOf 值,即誰將承擔債務
{"from": account},
)
borrow_tx.wait(1)
print("WE Borrowed some dai!")
get_borrowable_data(lending_pool, account)#帳戶詳情

借用函數需要幾個參數:

  • 資產的地址(在我們的例子中是 DAI 代幣)
  • 我們要借的金額,單位為 wei
  • interestRateMode,(0, 1, 2: 1 是穩定的,2 是可變的……現在不用擔心 0)
  • 推薦碼(忽略)
  • onBehalfOf 值,即誰將承擔債務

讓我們看一下 Etherscan 上這筆交易的樣本,以了解剛剛發生的事情。

image-20220129000101990

按順序,代幣轉移的方向是:

  • Aave 協議正在鑄造一些 aDAI
  • 我們的錢包正在鑄造一些 Aave Stable Debt Bearing DAI
  • 我們的錢包獲得了一些 DAI

重要說明:Aave 有時會更改其測試網token。通過檢查已部署的合約地址,確保您擁有正確的 testnet DAI 地址。並檢查官方 json 列表。

債務代幣

這一次,我們得到了一種不同類型的計息代幣,一種債務代幣。

  • 這是我們必須償還的金額,並且與 aToken 一樣,它的價值也會增長
  • 如果你欠更多的債務(用這個債務代幣表示),有人可以清算你並拿走你所有的抵押品!所以要警惕你有多少債務。

查看狀況:

  • 通過添加新的代幣地址,我們可以再次在我們的 MetaNask 中看到借入的資產
  • 我們還可以通過添加債務token地址來查看我們的債務
  • 現在最簡單的賣空方法之一就是出售我們的資產。如果資產價格下跌,我們將不得不償還比借入的更少的錢。

然而,我越來越擔心我會被清算。讓我們償還我們的債務。

4. 歸還Repay所有債務

償還我們的債務

我們想弄清楚我們欠了多少,然後償還!我們可以用這些函數做到這一點:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
borrowable_eth, total_debt_eth = get_borrowable_data(lending_pool, account)
amount_erc20_to_repay = (1 / erc20_eth_price) * (total_debt_eth * 0.95)
# repay
repay_all(Web3.toWei(amount_erc20_to_repay, "ether"), lending_pool, account)
print("You just deposited, borrowed, and repayed with Aave, Brownie and Chainlink")

def repay_all(amount, lending_pool, account):
approve_erc20(
amount,
lending_pool,
config["networks"][network.show_active()]["aave_dai_token"],
account,
)
repay_tx = lending_pool.repay(
config["networks"][network.show_active()]["aave_dai_token"],
amount,
1,
account.address,
{"from": account},
)
repay_tx.wait(1)
print("Repay!")

總結

我們已經完成了與 Aave 合作的所有步驟。

  • 抵押
  • 拿出貸款
  • 償還貸款

小結:

筆記很花時間(約4~5小時),不過省下更多之後回頭重看影片的麻煩

寫完也把很多模糊的概念理清楚了,繼續精進!

資料來源:

Solidity, Blockchain, and Smart Contract Course – Beginner to Expert Python Tutorial - YouTube

用 Python 开发 DeFi 去中心化借贷应用 (qq.com)

用 Python 开发 DeFi 去中心化借贷应用(中) (qq.com)

用 Python 开发 DeFi 去中心化借贷应用(下)-技术圈 (proginn.com)