• 【超完整懶人包】認識比特幣!原理與應用全面解析|動區新手村
  • Account
  • Account
  • BlockTempo Beginner – 動區新手村
  • Change Password
  • Forgot Password?
  • Home 3
  • Login
  • Login
  • Logout
  • Members
  • Password Reset
  • Register
  • Register
  • Reset Password
  • User
  • 不只加密貨幣,談談那些你不知道的區塊鏈應用|動區新手村
  • 動區動趨 BlockTempo – 最有影響力的區塊鏈新聞媒體 (比特幣, 加密貨幣)
  • 所有文章
  • 最完整的「區塊鏈入門懶人包」|動區新手村
  • 服務條款 (Terms of Use)
  • 關於 BlockTempo
  • 隱私政策政策頁面 / Privacy Policy
動區動趨-最具影響力的區塊鏈新聞媒體
  • 所有文章
  • 搶先看
  • 🔥動區專題
  • 🔥Tempo 30 Award
  • 加密貨幣市場
    • 市場分析
    • 交易所
    • 投資分析
    • 創投
    • RootData
  • 區塊鏈商業應用
    • 金融市場
    • 銀行
    • 錢包
    • 支付
    • defi
    • 區塊鏈平台
    • 挖礦
    • 供應鏈
    • 遊戲
    • dApps
  • 技術
    • 比特幣
    • 以太坊
    • 分散式帳本技術
    • 其他幣別
    • 數據報告
      • 私人機構報告
      • 評級報告
  • 法規
    • 央行
    • 管制
    • 犯罪
    • 稅務
  • 區塊鏈新手教學
  • 人物專訪
    • 獨立觀點
  • 懶人包
    • 比特幣概念入門
    • 從零開始認識區塊鏈
    • 區塊鏈應用
  • 登入
No Result
View All Result
  • 所有文章
  • 搶先看
  • 🔥動區專題
  • 🔥Tempo 30 Award
  • 加密貨幣市場
    • 市場分析
    • 交易所
    • 投資分析
    • 創投
    • RootData
  • 區塊鏈商業應用
    • 金融市場
    • 銀行
    • 錢包
    • 支付
    • defi
    • 區塊鏈平台
    • 挖礦
    • 供應鏈
    • 遊戲
    • dApps
  • 技術
    • 比特幣
    • 以太坊
    • 分散式帳本技術
    • 其他幣別
    • 數據報告
      • 私人機構報告
      • 評級報告
  • 法規
    • 央行
    • 管制
    • 犯罪
    • 稅務
  • 區塊鏈新手教學
  • 人物專訪
    • 獨立觀點
  • 懶人包
    • 比特幣概念入門
    • 從零開始認識區塊鏈
    • 區塊鏈應用
  • 登入
No Result
View All Result
動區動趨-最具影響力的區塊鏈新聞媒體
No Result
View All Result
Home 區塊鏈商業應用

Solidity閃電貸實現方式,與Move、Rust有何不同?

Beosin區塊鏈安全審計 by Beosin區塊鏈安全審計
2023-10-30
in 區塊鏈商業應用, 技術
A A
Solidity閃電貸實現方式,與Move、Rust有何不同?
95
SHARES
分享至Facebook分享至Twitter

對比三種語言的閃電貸流程,由於語言的特性,在實現方式上有所不同。
(前情提要:Beosin:Move VM先前毀滅級漏洞,可讓Sui、Aptos「崩潰、甚至硬分叉」 )
(背景補充:Metamask開發公司ConsenSys:給 Solidity 開發者的 16 個安全建議 )

本文目錄

  • Solidity 相關閃電貸:
  • Move 相關閃電貸:
  • Rust 相關閃電貸:

 

閃電貸是一種無抵押借款的服務,由於其擁有無需抵押便能借出資金的特性,使得資金利用率大大提高。在常見的以太坊閃電貸中,是通過以太坊交易機制來保證可以進行無抵押借出資金,以太坊中一個交易可以包含很多步驟,如:借款、兌換、使用、還款等,所有的步驟相輔相成,若其中某一個或多個步驟出現錯誤,都將導致本次的整個交易被回滾。

隨著區塊鏈生態發展,出現了大量公鏈以及合約程式語言,例如:除了 Solidity 之外最常見的 Move 和 Rust,這些合約程式語言有本質上的區別,框架與程式設計理念也有所不同, 本篇文章我們來對比一下 Solidity 閃電貸實現方式與 Move 以及 Rust 閃電貸實現方式有何不同,同時可以初步瞭解一下各種語言的程式設計理念。

延伸閱讀:解析閃電貸》今年DeFi被盜最高破1.2億鎂,去中心化金融成「駭客」斂財神器?

Solidity 相關閃電貸:

Solidity 的閃電貸是基於 Solidity 支援動態呼叫這一特性來設計的,何為動態呼叫,也就是 solidity 支援在呼叫一個函式的過程中,動態傳入需要呼叫的地址,如下例程式碼。每次呼叫都可以傳入不同的地址,根據這個特點,便出現了 solidity 閃電貸的實現邏輯。

function callfun(address addr) public {

addr.call();

}

如下程式碼,將閃電貸抽象成了 3 個核心功能,

  1. 首先直接將資金發送給呼叫者;
  2. 再呼叫呼叫者合約,從而讓呼叫者使用這些資金;
  3. 呼叫者使用結束,檢查是否歸還資金以及手續費,如果檢查失敗則回滾交易。(此處也可以直接使用 transferfrom 函式將呼叫則資金轉移回來)

function flashloan(uint amount, address to) {

transfer (to, amount); // 傳送資金給呼叫者

to.call ();// 呼叫呼叫者的合約函式

check ();// 檢查是否歸還資金

}

如下圖,為 Solidity 語言中閃電貸的實現流程:

下列程式碼為真實專案 Uniswap 閃電貸邏輯。程式碼示例:

function swap(uint amount0Out, uint amount1Out, address to, bytes calldata data) external lock {

require(amount0Out > 0 || amount1Out > 0, ‘UniswapV2: INSUFFICIENT_OUTPUT_AMOUNT’);

(uint112 _reserve0, uint112 _reserve1,) = getReserves();

require(amount0Out < _reserve0 && amount1Out < _reserve1, ‘UniswapV2: INSUFFICIENT_LIQUIDITY’); uint balance0; uint balance1; { address _token0 = token0; address _token1 = token1; require(to != _token0 && to != _token1, ‘UniswapV2: INVALID_TO’); /** 將資金轉給使用者 **/ if (amount0Out > 0) _safeTransfer(_token0, to, amount0Out);

if (amount1Out > 0) _safeTransfer(_token1, to, amount1Out);

/** 呼叫使用者指定的目標函式 **/

if (data.length > 0) IUniswapV2Callee(to).uniswapV2Call(msg.sender, amount0Out, amount1Out, data);

balance0 = IERC20(_token0).balanceOf(address(this));

balance1 = IERC20(_token1).balanceOf(address(this));

}

uint amount0In = balance0 > _reserve0 – amount0Out ? balance0 – (_reserve0 – amount0Out) : 0;

uint amount1In = balance1 > _reserve1 – amount1Out ? balance1 – (_reserve1 – amount1Out) : 0;

require(amount0In > 0 || amount1In > 0, ‘UniswapV2: INSUFFICIENT_INPUT_AMOUNT’);

{

uint balance0Adjusted = balance0.mul(1000).sub(amount0In.mul(3));

uint balance1Adjusted = balance1.mul(1000).sub(amount1In.mul(3));

/** 檢查使用者是否歸還資金以及手續費 **/

require(balance0Adjusted.mul(balance1Adjusted)>=uint(_reserve0).mul(_reserve1).mul(1000**2), ‘UniswapV2: K’);

}

_update(balance0, balance1, _reserve0, _reserve1);

emit Swap(msg.sender, amount0In, amount1In, amount0Out, amount1Out, to);

}

Move 相關閃電貸:

Move 閃電貸和 solidity 設計思想不同,move 中沒有動態呼叫這一個特性,在所有函式呼叫過程之前,都必須確定呼叫流程,明確呼叫合約地址是什麼,所以無法像 solidity 裡面那樣動態傳入地址再進行呼叫。

那麼 move 能實現閃電貸功能嗎?當然可以,move 的特性使得人們設計出與 solidity 實現方式不同的閃電貸。

在 Move 中,將資料和執行程式碼分離,造就了 Move VM 獨特的資源 – 模組模型。在這種模型中,不允許資源在交易結束時未被銷燬或者儲存在全域性儲存中,因此 Move 中的資源存在一種特殊的結構體 —— 燙手山芋(Hot Potato),它是一個沒有任何能力修飾符的結構體,因此它只能在其模組中被打包和解包。Move 能力詳情。

因此在 move 語言中的閃電貸實現,巧妙地利用了這種模式,將閃貸和還款操作抽象為兩個函式進行處理,中間產生借貸資源記錄借貸情況,該資源並沒任何能力,只能夠在還款函式中通過解包的方式將借貸資源給消耗掉,因此借貸操作必須和還款操作繫結在同一個操作中,否則閃電貸交易就會失敗。

如下圖,為 move 語言中閃電貸的實現流程。

如下程式碼,loan 與 repay 兩個函式相結合便可以實現閃電貸。需要使用閃電貸服務的使用者,先呼叫 loan 函式申請借款。函式會首先判斷是否有足夠的資金提供借款,隨後將資金髮送給呼叫者,計算好費用後,建立一個沒有任何能力的資源「receipt」並返回給呼叫者。呼叫者在自己的合約中使用借貸的資金,最後需要將「receipt」返還到 repay 函式,並且附帶歸還的資金。在 repay 函式中,首先將「receipt」資源解構,以確保交易成功執行,隨後判斷使用者歸還資金是否與之前計算好的資金數量相同,最後完成整個交易。

程式碼示例:

struct Receipt {

flash_lender_id: ID,

repay_amount: u64

}

public fun loan(self: &mut FlashLender, amount: u64, ctx: &mut TxContext):

(Coin, Receipt) {

let to_lend = &mut self.to_lend;

assert!(balance::value(to_lend) >= amount, ELoanTooLarge);

let loan = coin::take(to_lend, amount, ctx);

let repay_amount = amount + self.fee;

let receipt = Receipt { flash_lender_id: object::id(self), repay_amount };

(loan, receipt)

}

public fun repay(self: &mut FlashLender, payment: Coin, receipt: Receipt) {

let Receipt { flash_lender_id, repay_amount } = receipt;

assert!(object::id(self) == flash_lender_id, ERepayToWrongLender);

assert!(coin::value(&payment) == repay_amount, EInvalidRepaymentAmount);

coin::put(&mut self.to_lend, payment)

}

Rust 相關閃電貸:

Rust 由於其提供記憶體安全、併發安全和零成本抽象等特性。也被用在了區塊鏈智慧合約語言開發中,接下來我們以 Solana 智慧合約(Program)為例講解使用 Rust 開發實現的閃電貸。

Solana VM 亦將資料和執行程式碼進行了分離,使得一份執行程式碼可以處理多份資料副本,但與 Move 不同的是,陣列帳戶是通過程式派生的方式完成的,並且沒有類似於 Move 特性的限制。因此 Solana Rust 不能夠使用 Move 的方式實現閃電貸,並且 Solana Rust 動態呼叫指令(等同於理解為合約的函式)遞迴深度限制為 4,使用 Solidity 動態呼叫的方式同樣不可取。但在 Solana 中每個指令(instruction)呼叫在交易中是原子型別的,因此在一筆交易中可以在一個指令中檢查是否存在另一個指令。而 Solana 中的閃電貸依賴此了特性,Solana 閃電貸在閃貸的指令中將檢查閃電貸交易中是否存在還款的指令,並檢查還款的數量是否正確。

如下圖,為 Rust 語言中閃電貸的實現流程:

程式碼示例:

pub fn borrow(ctx: Context, amount: u64) -> ProgramResult {

msg!(“adobe borrow”);

if ctx.accounts.pool.borrowing {

return Err(AdobeError::Borrowing.into());

}

let ixns = ctx.accounts.instructions.to_account_info();

// make sure this isnt a cpi call

let current_index = solana::sysvar::instructions::load_current_index_checked(&ixns)? as usize;

let current_ixn = solana::sysvar::instructions::load_instruction_at_checked(current_index, &ixns)?;

if current_ixn.program_id != *ctx.program_id {

return Err(AdobeError::CpiBorrow.into());

}

let mut i = current_index + 1;

loop {

// 遍歷交易序列中的指令,

if let Ok(ixn) = solana::sysvar::instructions::load_instruction_at_checked(i, &ixns) {

// 查詢是否同時呼叫了該程式的中還款指令(repay)

if ixn.program_id == *ctx.program_id

// 檢查 invoke data 中 函式簽名

&& u64::from_be_bytes(ixn.data[..8].try_into().unwrap()) == REPAY_OPCODE

&& ixn.accounts[2].pubkey == ctx.accounts.pool.key() {

// 檢查 函式 invoke data 中 amount 數量是否正確

if u64::from_le_bytes(ixn.data[8..16].try_into().unwrap()) == amount {

break;

} else {

return Err(AdobeError::IncorrectRepay.into());

}

} else {

i += 1;

}

}else {

return Err(AdobeError::NoRepay.into());

}

}

let state_seed: &[&[&[u8]]] = &[&[

&State::discriminator()[..],

&[ctx.accounts.state.bump],

]];

let transfer_ctx = CpiContext::new_with_signer(

ctx.accounts.token_program.to_account_info(),

Transfer {

from: ctx.accounts.pool_token.to_account_info(),

to: ctx.accounts.user_token.to_account_info(),

authority: ctx.accounts.state.to_account_info(),

},

state_seed,

);

//cpi 轉帳

token::transfer(transfer_ctx, amount)?;

ctx.accounts.pool.borrowing = true;

Ok(())

}

// REPAY

// receives tokens

pub fn repay(ctx: Context, amount: u64) -> ProgramResult {

msg!(“adobe repay”);

let ixns = ctx.accounts.instructions.to_account_info();

// make sure this isnt a cpi call

let current_index = solana::sysvar::instructions::load_current_index_checked(&ixns)? as usize;

let current_ixn = solana::sysvar::instructions::load_instruction_at_checked(current_index, &ixns)?;

if current_ixn.program_id != *ctx.program_id {

return Err(AdobeError::CpiRepay.into());

}

let state_seed: &[&[&[u8]]] = &[&[

&State::discriminator()[..],

&[ctx.accounts.state.bump],

]];

let transfer_ctx = CpiContext::new_with_signer(

ctx.accounts.token_program.to_account_info(),

Transfer {

from: ctx.accounts.user_token.to_account_info(),

to: ctx.accounts.pool_token.to_account_info(),

authority: ctx.accounts.user.to_account_info(),

},

state_seed,

);

// 還款

token::transfer(transfer_ctx, amount)?;

// 更新帳本狀態

ctx.accounts.pool.borrowing = false;

Ok(())

}

對比三種語言的閃電貸流程,均為借款 -> 使用 -> 還款三步,只是由於語言的特性,在實現方式上有所不同。

Solidity 支援動態呼叫,所以可以在單個函式中完成整個交易;

Move 不支援動態呼叫,由於資源的特性,需要使用兩個函式進行借款和還款邏輯;

Rust(Solana)能支援動態呼叫,但是僅支援 4 層 CPI 呼叫,使用 CPI 實現閃電貸將產生侷限性,但是 Solana 每個指令都是原子型別,並且支援指令自省,因此使用指令自省的方式實現閃電貸是較好的方式。

📍相關報導📍

 SUI 遭爆3月秘密修復「十億美元安全漏洞」,可讓駭客閃電貸攻擊..

閃電網路爆重大安全漏洞!比特幣核心開發者退出開發:從底層改才有救

Visa搶聘區塊鏈工程師!須懂L1、L2,精熟Solidity與Rust語言

Tags: MoveRustSolidity程式語言閃電貸


關於我們

動區動趨

為您帶來最即時最全面
區塊鏈世界脈動剖析
之動感新聞站

訂閱我們的最新消息

動區精選-為您整理一週間的國際動態

戰略夥伴

Foresight Ventures Foresight News

主題分類

  • 關於 BlockTempo

動區動趨 BlockTempo © All Rights Reserved.

No Result
View All Result
  • 所有文章
  • 搶先看
  • 市場脈動
  • 商業應用
  • 區塊鏈新手教學
  • 區塊鏈技術
  • 數據洞察
  • 政府法規
  • RootData
  • 登入

動區動趨 BlockTempo © All Rights Reserved.