去中心化樂透solidity應用

本文最後更新於:2022年1月30日 下午

去中心化樂透

前言:

這是寒假完成的第3個side project(其實還有其他的不過比較小所以忽略不計)

本來想說可以繼續把其他目標一一完成

結果看到…有…寒假…作業…_(┐ ◟;゚д゚)ノ

這操作我也是認了,不知道還能不能再生一個專案出來

包含敲代碼和寫筆記,耗時約15小時(粗略估算的,實際只會更多)

同時也發現,先理解邏輯比實際語法怎麼實現更重要一點

是個蠻好的學習方向,決定多試試幾次!

目的:

由管理者admin開啟樂透 startLottery,可以開始入場 enter ,配合 getEntranceFee檢查是否符合參加條件,再由管理者決定何時關閉樂透endLottery

規則:

  • 玩家可以使用ETH進場

  • 管理者可以決定何時結束遊戲

    • 若要完全去中心化,可以使用 Chainlink Keepers
  • 樂透會隨機選出贏家

1. 創建合約結構 Lottery.sol

Main Functions

a.入場函數enter()

  • 為了能夠使用「收款功能」 要使用payable (can receive ether into the contract.)
  • 蒐集玩家名單 players
1
2
3
4
5
6
7
8
9
10
11
12
13
14
address payable[] public players;
// 資料型別-資料特性(可接受付款的陣列)-可見度-變數名

function enter() public payable {
// 入場要求 1.遊戲開放 2.足夠入場費
require(lottery_state == LOTTERY_STATE.OPEN);
require(msg.value >= getEntranceFee(), "Not Enough ETH!");
//成功入場後,加入玩家清單
players.push(msg.sender);
}

function getEntranceFee() public view returns (uint256) {
...
}

b.constructor

  • 有些值想要在合約部屬時就先設定好: constructor

    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
    26
    27
    28
    29
    30
    contract Lottery {
    // state variable
    address payable[] public players;
    uint256 public usdEntryFee;
    AggregatorV3Interface internal ethUsdPriceFeed;//函數和狀態變量只能是內部訪問(即從**當前合約內部**或**從它派生的合約訪問**)
    enum LOTTERY_STATE {
    OPEN, // 0
    CLOSED, // 1
    CALCULATING_WINNER // 2
    }
    LOTTERY_STATE public lottery_state;
    uint256 public fee;
    bytes32 public keyhash;

    //把我們所需要的參數"parameterize"
    onstructor(
    address _priceFeedAddress, //代表「不同鏈上的Aggregator(ETH/USD)的address」
    address _vrfCoordinator,
    address _link, //chainlink token
    uint256 _fee,
    bytes32 _keyhash
    ) public VRFConsumerBase(_vrfCoordinator, _link) {
    usdEntryFee = 50 * (10**18); //內部預設單位=wei
    ethUsdPriceFeed = AggregatorV3Interface(_priceFeedAddress);//1個eth轉換多少usd
    lottery_state = LOTTERY_STATE.CLOSED;//
    fee = _fee;
    keyhash = _keyhash;
    }
    ....
    }
  • 相關合約/接口

    1. AggregatorV3Interface Using Data Feeds | Chainlink Documentation

      1. 目標: 獲取外部資訊

      2. import "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol";

      3. 到config file設定dependencies和remapping

      4. 宣告state variable AggregatorV3Interface internal ethUsdPriceFeed;

      5. constructor把不同鏈上的Aggregator address參數化

        1
        2
        3
        4
        onstructor(
        //代表「不同鏈上的Aggregator(ETH/USD)的address」
        address _priceFeedAddress,
        }
      6. ethUsdPriceFeed = AggregatorV3Interface(_priceFeedAddress)

c.入場費函數 getEntranceFee()

目的: 入場費用是多少?

1
2
3
4
5
6
7
function getEntranceFee() public view returns (uint256) {
(, int256 price, , , ) = ethUsdPriceFeed.latestRoundData();//把PriceFeed的資料都抓下來之後,只取用price的部分
uint256 adjustedPrice = uint256(price) * 10**10; //total 18decimals, price originally has 8 decimals
uint256 costToEnter = (usdEntryFee * 10**18) / adjustedPrice;
// 1 usd = 10^18/ ethToUsd-price
return costToEnter;//單位是wei
}
  • image-20220129120859993

  • 為了得到usd→eth的結果:

    • usdEntryFee = 50 * (10**18); //內部預設單位=wei
    • 得到匯率 price ,調整單位到wei後是 adjustedPrice
    • 因為內部計算單位是wei。數字計算要保持10^18
      • costToEnter 我們的入場費從美元轉換為eth(wei)是多少?
      • uint256 costToEnter = (usdEntryFee * 10**18) / adjustedPrice;

quick and dirty test: 測試 test_lottery.py

  • 場景: 數學運算多的函數

  • 怎麼做?

    1. mainnet-fork
    2. development with mocks
    3. testnet
  • 注意該合約的constructor是否有參數

    • 這邊我們使用 mainnet-fork,所以直接從主網上抓取 ETH/USD 的地址放在config
1
2
3
Lottery.deploy( #部屬合約
config["networks"][network.show_active()]["eth_usd_price_feed"],
{"from": account},)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from brownie import Lottery, accounts, config, network, exceptions
from web3 import Web3

def test_get_entrance_fee():
account = accounts[0] #獲取本地帳戶
lottery = Lottery.deploy( #部屬合約
config["networks"][network.show_active()]["eth_usd_price_feed"],
{"from": account},
)
assert lottery.getEntranceFee() > Web3.toWei(0.022, "ether")
assert lottery.getEntranceFee() < Web3.toWei(0.026, "ether")


def main():
test_get_entrance_fee()

如何新增一條新的鍊? 新增 mainnet-fork
  • delete networks
    • brownie networks delete mainnet-fork
  • 利用Alchemy的project id建立自己的 mainnet-fork

brownie networks add development mainnet-fork cmd=ganache-cli host=http://127.0.0.1 fork=https://eth-mainnet.alchemyapi.io/v2/ThSv1D7C37bH7HJMWpfDhjZm3A4I3LMw accounts=10 mnemonic=brownie port=8545

  • 測試
    • brownie test --network mainnet-fork

enum

  • 目的: 在已知的不同狀態之間切換
1
2
3
4
5
6
7
8
contract Lottery is VRFConsumerBase, Ownable {
// state variable
enum LOTTERY_STATE {
OPEN, // 0
CLOSED, // 1
CALCULATING_WINNER // 2
}
}

d.開獎函數 startLottery()

目的:

  • 讓管理者開始樂透抽獎,onlyOwner modifier確保只能由管理者來使用這個函數
1
2
3
4
5
6
7
function startLottery() public onlyOwner {
require(
lottery_state == LOTTERY_STATE.CLOSED,
"cant start a new lottery yet!"
);
lottery_state = LOTTERY_STATE.OPEN;
}

onlyOwner modifier

  • 可以藉由import import "@openzeppelin/contracts/access/Ownable.sol";來直接使用
    • 記得加入dependencies和remapping
  • 在contract那邊繼承 Ownable.sol contract Lottery is Ownable { ... }

image-20220129200236561

e. 結束樂透函數 endLottery()

你的隨機真的隨機嗎?

你想要在「deterministic的系統中(即區塊鍊)」獲取「隨機性」是不可能的!

所以必須透過外部的預言機oracle來輔助獲取外部資料取的隨機值

  • 「使用全域變數再進行hash」,基本上是「偽隨機Pseudorandomness」
    • 例如 block.difficulty,看起來是隨機的,但其實可以被礦工控制
  • 必須使用oracle來輔助,Chainlink! Get a Random Number | Chainlink Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function endLottery() public {
//方法1:容易被駭
// uint256(
// keccack256(
// abi.encodePacked(
// nonce, // nonce is preditable (aka, transaction number)
// msg.sender, // msg.sender is predictable
// block.difficulty, // can actually be manipulated by the miners!
// block.timestamp // timestamp is predictable
// )
// )
// ) % players.length;
//方法2:調用oracle
lottery_state = LOTTERY_STATE.CALCULATING_WINNER;
bytes32 requestId = requestRandomness(keyhash, fee);//requestRandomness是VRFConsumerBase.sol的內部函數,所以可以直接調用
}
VRFConsumerBase
  • import "@chainlink/contracts/src/v0.6/VRFConsumerBase.sol";
  • contract Lottery is VRFConsumerBase, Ownable {...}
  • 影片示範

To consume randomness, your contract should inherit from VRFConsumerBase and define two required functions:

  • requestRandomness, which makes the initial request for randomness.
  • fulfillRandomness, which is the function that receives and does something with verified randomness.
  • 注意事項:
  • 格式:
    • LINK Token - LINK token address on the corresponding network (Ethereum, Polygon, BSC, etc),用來給提供服務的node支付token
    • VRF Coordinator - address of the Chainlink VRF Coordinator,是位在鍊上的合約,負責檢查隨機值是否真的隨機
    • Key Hash - public key against which randomness is generated,唯一地識別了我們要使用的chainlink node,
    • Fee - fee required to fulfill a VRF request,我們要支付多少LINK Token費用給提供我們服務的node
當你繼承來的合約(母合約)它裡面自己有constructor,怎麼辦?
  • 可以直接使用在「子constructor裡面」!
1
2
3
4
5
6
constructor() //子合約的constructor
VRFConsumerBase( //母合約的constructor
0xdD3782915140c8f3b190B5D67eAc6dc5760C46E9, // VRF Coordinator
0xa36085F69e2889c224210F603D836748e7dC0088 // LINK Token
)
{
  • 實戰:
1
2
3
4
5
6
7
8
9
10
11
12
13
constructor(
address _priceFeedAddress,
address _vrfCoordinator, //鏈上的合約,確保隨機值真的是隨機的
address _link, //address of chainlink token
uint256 _fee,
bytes32 _keyhash
) public VRFConsumerBase(_vrfCoordinator, _link) {
usdEntryFee = 50 * (10**18);
ethUsdPriceFeed = AggregatorV3Interface(_priceFeedAddress);
lottery_state = LOTTERY_STATE.CLOSED;
fee = _fee;
keyhash = _keyhash;
}
  • 綜合
    • 主要是注意「母合約」參數的宣告 以及 「子合約」參數的宣告
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
26
27
28
29
30
contract Lottery is VRFConsumerBase, Ownable {
// state variable
address payable[] public players;
address payable public recentWinner;
uint256 public randomness;
uint256 public usdEntryFee;
AggregatorV3Interface internal ethUsdPriceFeed;
enum LOTTERY_STATE {
OPEN, // 0
CLOSED, // 1
CALCULATING_WINNER // 2
}
LOTTERY_STATE public lottery_state;
uint256 public fee;
bytes32 public keyhash;

// you can put your parent's contructor into your own
constructor(
address _priceFeedAddress,
address _vrfCoordinator, //鏈上的合約,確保隨機值真的是隨機的
address _link, //address of chainlink token
uint256 _fee,
bytes32 _keyhash
) public VRFConsumerBase(_vrfCoordinator, _link) {
usdEntryFee = 50 * (10**18);
ethUsdPriceFeed = AggregatorV3Interface(_priceFeedAddress);
lottery_state = LOTTERY_STATE.CLOSED;
fee = _fee;
keyhash = _keyhash;
}
Oracle Gas && Tx Gas
  • Oracle Gas 是支付token給 提供服務的oracle
  • Tx Gas 是支付token給礦工

取得隨機值的機制 = Request and Receive

  1. 總共會有兩筆非同步交易
    1. 從合約對chainlink oracle發出request( 給我隨機數! ) requestRandomness(keyhash, fee)
    2. chainlink oracle (呼叫函數 VRFCoordinator ,再呼叫fulfillRandomness),並回傳隨機數
  2. 需要先確保你的合約帳戶有足夠的錢支付Oracle Gas
fulfillRandomness()
  • 目的: chainlink node 回傳data到這個合約,
override
  • 覆蓋原本 fulfillRandomness()的宣告
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//second callback tx = chainlink node return the data to this contract into another function (fulfilledRandomness)
function fulfillRandomness(bytes32 _requestId, uint256 _randomness)
internal
override
{
require(
lottery_state == LOTTERY_STATE.CALCULATING_WINNER,
"You arent there yet"
);
require(_randomness > 0, "random-not-found");
uint256 indexOfWinner = _randomness % players.length;
recentWinner = players[indexOfWinner];
recentWinner.transfer(address(this).balance); //pay the winner all the money gathered by enter() in this address
//recentWinner should be a 「payable」variable!

//reset
players = new address payable[](0); // size 0
lottery_state = LOTTERY_STATE.CLOSED;
randomness = _randomness;
}

2. 開始部屬 deploy_lottery.py

  • 記得要建立 __init.py__
  • key_hash和fee的設定
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, get_contract
from brownie import Lottery, network, config
import time

def deploy_lottery():
account = get_account() #部屬合約的第一步:拿到帳號
lottery = Lottery.deploy(
get_contract("eth_usd_price_feed").address,
get_contract("vrf_coordinator").address,
get_contract("link_token").address,
config["networks"][network.show_active()]["fee"],
config["networks"][network.show_active()]["keyhash"],
{"from": account},
publish_source=config["networks"][network.show_active()].get("verify", False),
)
print("Deployed lottery!")
return lottery


def main():
deploy_lottery()

2-1 helpful_scripts.py

  • 可以藉由 brownie accounts list 查看目前總帳戶狀況
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
from brownie import ( accounts,network,config,)

FORKED_LOCAL_ENVIRONMENTS = ["mainnet-fork", "mainnet-fork-dev"]
LOCAL_BLOCKCHAIN_ENVIRONMENTS = ["development", "ganache-local"]

def get_account(index=None, id=None):
# 3 ways to get account
# accounts[0]→ brownie's ganache account
# accounts.add("env")→ env variable
# accounts.load("id")
if index:
return accounts[index]
if id:
return accounts.load(id)
if (
network.show_active() in LOCAL_BLOCKCHAIN_ENVIRONMENTS
or network.show_active() in FORKED_LOCAL_ENVIRONMENTS
):
return accounts[0] #local
return accounts.add(config["wallets"]["from_key"])#default

2.2 get_contracts() + mapping

  • 目的: 把「檢查在哪一條鏈上」和「部屬mock」合在一起
    • 這個函數會從brownie config抓取「合約地址」(如果已經有定義),
    • 否則他會部屬「mock version」的合約並回傳該合約
  • mapping
    • 是一個動態大小的陣列,key和value可以自己設定
    • 每個key都是一個外部合約,所以需要到其他地方設定 影片示範
      1. contracts/test增加.sol
      2. brownie config的network
      3. 在會使用到的合約上方from brownie import xxx.sol
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
26
27
28
29
30
31
32
33
34
35
contract_to_mock = {#mapping,是一個動態大小的陣列,key和value可以自己設定
"eth_usd_price_feed": MockV3Aggregator,#local
"vrf_coordinator": VRFCoordinatorMock,#live
"link_token": LinkToken,
}
def get_contract(contract_name):
"""
You'll see examples like the 'link_token'.
This script will then either:
- Get a address from the config
- Or deploy a mock to use for a network that doesn't have it
Args:(要部屬的合約地址)
contract_name (string): This is the name that is referred to in the
brownie config and 'contract_to_mock' variable.
Returns:(回傳brownie.network.contract.PorjectContract,也就是該合約最新發布的版本,可能是mock或real )
brownie.network.contract.ProjectContract: The most recently deployed Contract of the type specificed by the dictionary. This could be either
a mock or the 'real' contract on a live network.
"""
contract_type = contract_to_mock[contract_name] #回傳地址

if network.show_active() in LOCAL_BLOCKCHAIN_ENVIRONMENTS:#in development(檢查鍊)
#如果過去還沒部屬過mock contract,就來部屬一次,整個運行也只需要一個合約!
if len(contract_type) <= 0: # MockV3Aggregator.length
deploy_mocks()
contract = contract_type[-1] # latest one(get the mock contract)
# MockV3Aggregator[-1](獲得合約)
else:# in test net(檢查鍊)/real network
contract_address = config["networks"][network.show_active()][contract_name]#獲得實際存在的地址
#這邊為了獲得mock合約,會需要address和ABI(獲得合約)
contract = Contract.from_abi(
contract_type._name, contract_address, contract_type.abi
)#會回傳mock合約(擁有和原本合約一樣的函數功能所以可以直接使用含數)
# MockV3Aggregator.abi
return contract

2.3 deploy_mocks()

  • 目的: development或ganache-local的環境中需要
1
2
3
4
5
6
7
8
def deploy_mocks(decimals=DECIMALS, initial_value=INITIAL_VALUE):
account = get_account()

MockV3Aggregator.deploy(decimals, initial_value, {"from": account})

link_token = LinkToken.deploy({"from": account})
VRFCoordinatorMock.deploy(link_token.address, {"from": account})
print("Deployed!")

3. 實際 開始遊戲/進入遊戲/結束遊戲

startLottery()

1
2
3
4
5
6
7
def start_lottery():
account = get_account()
lottery = Lottery[-1] # most recent deployment of lottery
starting_tx = lottery.startLottery({"from": account})
starting_tx.wait(1) # wait for last tx to go through

print("The lottery is started!")

enter_lottery()

1
2
3
4
5
6
7
def enter_lottery():
account = get_account()
lottery = Lottery[-1]
value = lottery.getEntranceFee() + 100000000
tx = lottery.enter({"from": account, "value": value})
tx.wait(1)
print("You entered the lottery!")

end_lottery()

  • 在實際關閉遊戲之前,會需要一些LINK TOKEN存在此遊戲合約中
    • 因為我們的end_lottery() 會用到 requestRandomness (chainlink node/ fuifilledRandomness())
1
2
3
4
5
6
7
8
9
10
11
12
13
def end_lottery():
account = get_account()
lottery = Lottery[-1]

# 用fund_with_link()來fund the contract + end the lottery
tx = fund_with_link(lottery.address)
tx.wait(1)

#結束遊戲
ending_transaction = lottery.endLottery({"from": account})
ending_transaction.wait(1)#等待chainlink node
time.sleep(180)
print(f"{lottery.recentWinner()} is the new winner!")
1
2
3
4
5
6
7
8
9
10
11
12
def fund_with_link(
contract_address, account=None, link_token=None, amount=100000000000000000
): # 0.1 LINK
account = account if account else get_account()
link_token = link_token if link_token else get_contract("link_token")
tx = link_token.transfer(contract_address, amount, {"from": account})
#way2: 使用interface:
# link_ token_contract = interface.LinkTokenInterface(link_token.address)
# tx = link_token_contract.transfer(contract_address, amount, {"from": account})
tx.wait(1)
print("Fund contract!")
return tx

法2: interface with transfer(來和合約進行交互) LinkTokenInterface.sol

  • interface也會在幕後compile down to abi
  • 所以我們在使用過程直接取用就好
1
2
link_ token_contract = interface.LinkTokenInterface(link_token.address)
tx = link_token_contract.transfer(contract_address, amount, {"from": account})

法1:直接compile down to ABI

1
2
3
contract = Contract.from_abi(
contract_type._name, contract_address, contract_type.abi
)

4. 測試!

影片示範

pytest例子

pytest.raise

python - How to properly assert that an exception gets raised in pytest? - Stack Overflow

(6条消息) pytest 测试框架学习(11):pytest.raises_mokwing-CSDN博客_pytest.raises

  • Use pytest.raises as a context manager, which will capture the exception of the given type
  • If the code block does not raise the expected exception (ZeroDivisionError in the example above), or no exception at all, the check will fail instead.
    • 使用 raises 捕獲匹配到的異常,可以繼續讓代碼正常運行。

  • 只會想在local-dev環境下使用
1
2
if network.show_active() not in LOCAL_BLOCKCHAIN_ENVIRONMENTS:
pytest.skip()
  • 測試寫法
1
brownie test -k test_get_entrance_fee --network rinkeby

assert

使用 assert (openhome.cc)

斷言(Assertion),指的是程式進行到某個時間點,斷定其必然是某種狀態,具體而言,也就是斷定該時間點上,某變數必然是某值,或某物件必具擁有何種特性值。

  • 斷言可以在條件不滿足程序運行的情況下直接返回錯誤,而不必等待程序運行後出現崩潰的情況,
1
2
3
def deposit(self, amount):
assert amount > 0, '必須是大於 0 的正數'
self.balance += amount

event

  • 合約無法存取events
  • 比「儲存變數」來的gas efficient
  • print of blockchain
  • 目的: 為了寫測試 test_can_pick_winner_correctly()
    • emit RequestedRandomness(requestId);
1
2
3
4
5
6
event RequestedRandomness(bytes32 requestId);
function endLottery() public {
lottery_state = LOTTERY_STATE.CALCULATING_WINNER;
bytes32 requestId = requestRandomness(keyhash, fee);
emit RequestedRandomness(requestId);
}
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
26
27
28
29
def test_can_pick_winner_correctly():
# Arrange
if network.show_active() not in LOCAL_BLOCKCHAIN_ENVIRONMENTS:
pytest.skip()
lottery = deploy_lottery()
account = get_account()
#得到幾個測試用帳號
lottery.startLottery({"from": account})
lottery.enter({"from": account, "value": lottery.getEntranceFee()})
lottery.enter({"from": get_account(index=1), "value": lottery.getEntranceFee()})
lottery.enter({"from": get_account(index=2), "value": lottery.getEntranceFee()})

fund_with_link(lottery)
starting_balance_of_account = account.balance()
balance_of_lottery = lottery.balance()
transaction = lottery.endLottery({"from": account})

request_id = transaction.events["RequestedRandomness"]["requestId"]
STATIC_RNG = 777

#假裝是chainlink node在呼叫函數!
get_contract("vrf_coordinator").callBackWithRandomness(
request_id, STATIC_RNG, lottery.address, {"from": account}
)
# 777 % 3 = 0
assert lottery.recentWinner() == account
assert lottery.balance() == 0
assert account.balance() == starting_balance_of_account + balance_of_lottery

integration test

https://youtu.be/M576WGiDBdQ?t=29501

補充

1. 接口和抽象合约

(6条消息) solidity学习笔记(9)—— 接口和抽象合约_lj900911的专栏-CSDN博客_solidity 接口

合約如何讀取「其他合約的數據或調用其他合約的方法」?

有兩種實現方式:抽象合約 和 接口

一、抽象合約

抽象函數是沒有函數體的的函數。如下:

1
2
3
4
pragma solidity ^0.4.0;
contract Feline {
function utterance() returns (bytes32);
}
  • 這樣的合約不能通過編譯,即使合約內也包含一些正常的函數。
  • 但它們可以做為基合約base contract被繼承
  • 如果一個合約從一個抽象合約裡繼承,但卻沒實現所有函數,那麼它也是一個抽象合約。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
pragma solidity ^0.4.0;

contract Feline {
function utterance()
returns (bytes32);
function getContractName() returns (string){
return "Feline";
}
}

contract Cat is Feline {
function utterance() returns (bytes32) {
return "miaow";
}
}

如何通過抽象合約實現接口功能?

如果contract B要使用contract A的方法或數據,本質上:

  1. 先定義一個抽象合約,讓contract A繼承於這個抽象合約;
  2. 把contract A中已經實現了的方法放入抽象合約中,==solidity會自動把這個抽象合約視作接口==;
  3. contract B通過contract A的地址來創建連接到contract A的接口實例
  4. 調用contract A中的方法或讀取數據;
    image-20220129123322210

二、接口

接口與抽象合約類似,與之不同的是,接口內沒有任何函數是已實現的,同時還有如下限制:

  • 不能繼承其它合約,或接口。
  • 不能定義構造器
  • 不能定義變量
  • 不能定義結構體
  • 不能定義枚舉類
  • 接口基本上限制為「合約ABI定義可以表示的內容」
    • ABI和接口定義之間的轉換應該是可能的,不會有任何信息丟失。

注意:

  1. 在兩個.sol文件中都聲明接口,或者兩個合約寫到一個.sol文件裡,那就只要聲明一次;
  2. 在一個合約中實現METHOD_A,該合同必須繼承自接口interfaceContract;
  3. 在另一個合約中創建一個interfaceContract實例,該實例接受實現接口的合約的地址;
  4. 通過這個實例調用目標合約的方法,獲取目標合約的數據;

image-20220129123528076

主要資料來源:

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