DeFi 協議程式碼實作完整指南:從智能合約到前端交互

本文提供從智能合約層到前端交互的完整程式碼範例,涵蓋 ERC-20 代幣合約、借貸協議、AMM 交易所、質押協議等主要 DeFi 應用場景,使用 Solidity 和 JavaScript/TypeScript 提供可直接運行的程式碼範例。

DeFi 協議程式碼實作完整指南:從智能合約到前端交互

概述

去中心化金融(DeFi)協議的核心是智能合約,而理解這些合約的程式碼是深入掌握 DeFi 運作機制的關鍵。本文提供從智能合約層到前端交互的完整程式碼範例,涵蓋借貸協議、DEX、質押協議等主要 DeFi 應用場景。透過這些範例,開發者可以理解如何在以太坊上構建、部署和交互各類 DeFi 應用,同時學習安全編碼的最佳實踐。

本文的範例使用 Solidity 編寫智能合約,並配合 JavaScript/TypeScript 展示與合約的交互方式。所有程式碼均基於 Solidity 0.8.x 版本和 Ethers.js v6,確保與當前以太坊生態系統的兼容性。

第一章:智能合約開發環境設定

1.1 開發工具選擇

以太坊智能合約開發主要有以下工具:

Hardhat:目前最流行的開發框架,支援 Solidity 編譯、本地區塊鏈節點、合約測試和部署腳本。

Foundry:新興的開發框架,以速度快著稱,內建 Forge 測試工具和 Anvil 本地節點。

Truffle:老牌開發框架,適合初學者,但近年來逐漸被 Hardhat 取代。

本文以 Hardhat 為主要開發工具,其配置如下:

// hardhat.config.js
require("@nomicfoundation/hardhat-toolbox");
require("dotenv").config();

module.exports = {
  solidity: "0.8.24",
  networks: {
    hardhat: {
      chainId: 31337,
    },
    mainnet: {
      url: process.env.MAINNET_RPC_URL,
      accounts: [process.env.PRIVATE_KEY],
    },
    sepolia: {
      url: process.env.SEPOLIA_RPC_URL,
      accounts: [process.env.PRIVATE_KEY],
    },
  },
  etherscan: {
    apiKey: process.env.ETHERSCAN_API_KEY,
  },
};

1.2 本地測試環境

Hardhat 內建 EVM 兼容的本地測試網絡,無需連接公開網絡即可測試合約。以下啟動腳本:

// scripts/deploy.js
const { ethers } = require("hardhat");

async function main() {
  // 獲取部署帳戶
  const [deployer] = await ethers.getSigners();
  console.log("Deploying contracts with account:", deployer.address);
  
  // 獲取合約工廠
  const Token = await ethers.getContractFactory("MyToken");
  
  // 部署合約
  const token = await Token.deploy(ethers.parseEther("1000000"));
  
  console.log("Token deployed to:", await token.getAddress());
}

main()
  .then(() => process.exit(0))
  .catch((error) => {
    console.error(error);
    process.exit(1);
  });

第二章:ERC-20 代幣合約實作

2.1 基礎 ERC-20 合約

ERC-20 是 DeFi 的基礎代幣標準,所有代幣交互都基於這一標準。以下是符合 ERC-20 規範的完整合約:

// contracts/Token.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";

contract DeFiToken is ERC20, Ownable {
    // 代幣總供應量
    uint256 public constant MAX_SUPPLY = 1_000_000_000 * 10**18;
    
    // 合約部署者鑄造全部代幣
    constructor() ERC20("DeFi Token", "DFT") Ownable(msg.sender) {
        _mint(msg.sender, MAX_SUPPLY);
    }
    
    // 鑄造新代幣(僅所有者)
    function mint(address to, uint256 amount) external onlyOwner {
        require(totalSupply() + amount <= MAX_SUPPLY, "Max supply exceeded");
        _mint(to, amount);
    }
    
    // 燃燒代幣
    function burn(uint256 amount) external {
        _burn(msg.sender, amount);
    }
}

2.2 授權與轉帳機制

DeFi 協議的交互核心是 ERC-20 的授權機制。以下展示如何安全地進行授權和轉帳:

// contracts/TokenInteractor.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";

contract TokenInteractor is ReentrancyGuard {
    // 安全的代幣轉帳
    function safeTransferFrom(
        IERC20 token,
        address from,
        address to,
        uint256 amount
    ) external nonReentrant {
        require(
            token.transferFrom(from, to, amount),
            "Transfer failed"
        );
    }
    
    // 批量轉帳
    function batchTransfer(
        address[] calldata recipients,
        uint256[] calldata amounts
    ) external {
        require(recipients.length == amounts.length, "Length mismatch");
        
        for (uint256 i = 0; i < recipients.length; i++) {
            IERC20(msg.sender).transfer(recipients[i], amounts[i]);
        }
    }
}

2.3 前端交互範例

使用 Ethers.js 與 ERC-20 合約交互:

// scripts/token-interaction.js
const { ethers } = require("ethers");

// 連接錢包
const provider = new ethers.BrowserProvider(window.ethereum);
const signer = await provider.getSigner();

// ERC-20 ABI(僅包含需要的函數)
const erc20ABI = [
  "function name() view returns (string)",
  "function symbol() view returns (string)",
  "function decimals() view returns (uint8)",
  "function totalSupply() view returns (uint256)",
  "function balanceOf(address owner) view returns (uint256)",
  "function transfer(address to, uint256 amount) returns (bool)",
  "function allowance(address owner, address spender) view returns (uint256)",
  "function approve(address spender, uint256 amount) returns (bool)",
  "function transferFrom(address from, address to, uint256 amount) returns (bool)",
  "event Transfer(address indexed from, address indexed to, uint256 value)",
  "event Approval(address indexed owner, address indexed spender, uint256 value)"
];

// 創建合約實例
const tokenAddress = "0x123..."; // 代幣地址
const token = new ethers.Contract(tokenAddress, erc20ABI, signer);

// 查詢餘額
const balance = await token.balanceOf(userAddress);
console.log(`Balance: ${ethers.formatEther(balance)} tokens`);

// 授權合約使用代幣
const spender = "0xABC..."; // DeFi 合約地址
const amount = ethers.parseEther("100");
const tx = await token.approve(spender, amount);
await tx.wait();
console.log("Approval confirmed");

第三章:借貸協議實作

3.1 簡化借貸合約

以下是一個基礎的抵押借貸合約,展示了借貸協議的核心邏輯:

// contracts/SimpleLending.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";

contract SimpleLending is ReentrancyGuard, Ownable {
    // 存款利率(年化)
    uint256 public constant ANNUAL_RATE = 350; // 3.5%
    
    // 抵押率 75%
    uint256 public constant COLLATERAL_RATE = 7500;
    uint256 public constant DENOMINATOR = 10000;
    
    // 市場
    struct Market {
        IERC20 token;
        uint256 totalDeposits;
        uint256 totalBorrows;
        uint256 depositRate;
        uint256 borrowRate;
    }
    
    // 用戶頭寸
    struct Position {
        uint256 depositAmount;
        uint256 borrowAmount;
        uint256 lastUpdateTime;
    }
    
    mapping(address => Market) public markets;
    mapping(address => mapping(address => Position)) public positions;
    
    // 存款利率因子(累積)
    uint256 public depositIndex = 1e18;
    uint256 public borrowIndex = 1e18;
    uint256 public lastUpdateTime;
    
    // 存款事件
    event Deposit(address indexed user, address indexed token, uint256 amount);
    event Borrow(address indexed user, address indexed token, uint256 amount);
    event Repay(address indexed user, address indexed token, uint256 amount);
    event Withdraw(address indexed user, address indexed token, uint256 amount);
    
    // 初始化市場
    function initMarket(address _token) external onlyOwner {
        require(address(markets[_token].token) == address(0), "Market exists");
        markets[_token] = Market({
            token: IERC20(_token),
            totalDeposits: 0,
            totalBorrows: 0,
            depositRate: 0,
            borrowRate: 0
        });
    }
    
    // 存款
    function deposit(address _token, uint256 _amount) external nonReentrant {
        Market storage market = markets[_token];
        require(address(market.token) != address(0), "Market not found");
        
        // 更新存款利息
        _accrueDepositInterest(_token);
        
        // 從用戶轉入代幣
        require(
            market.token.transferFrom(msg.sender, address(this), _amount),
            "Transfer failed"
        );
        
        // 更新用戶頭寸
        Position storage pos = positions[msg.sender][_token];
        pos.depositAmount += _amount;
        pos.lastUpdateTime = block.timestamp;
        
        // 更新市場總存款
        market.totalDeposits += _amount;
        
        emit Deposit(msg.sender, _token, _amount);
    }
    
    // 借款
    function borrow(address _token, uint256 _amount) external nonReentrant {
        Market storage market = markets[_token];
        require(address(market.token) != address(0), "Market not found");
        
        // 更新借款利息
        _accrueBorrowInterest(_token);
        
        Position storage pos = positions[msg.sender][_token];
        
        // 計算最大借款額
        uint256 maxBorrow = (pos.depositAmount * COLLATERAL_RATE) / DENOMINATOR;
        require(
            pos.borrowAmount + _amount <= maxBorrow,
            "Insufficient collateral"
        );
        
        // 更新頭寸
        pos.borrowAmount += _amount;
        pos.lastUpdateTime = block.timestamp;
        
        // 更新市場總借款
        market.totalBorrows += _amount;
        
        // 轉出借出的代幣
        require(market.token.transfer(msg.sender, _amount), "Transfer failed");
        
        emit Borrow(msg.sender, _token, _amount);
    }
    
    // 還款
    function repay(address _token, uint256 _amount) external nonReentrant {
        Market storage market = markets[_token];
        require(address(market.token) != address(0), "Market not found");
        
        _accrueBorrowInterest(_token);
        
        Position storage pos = positions[msg.sender][_token];
        uint256 repayAmount = _amount > pos.borrowAmount ? pos.borrowAmount : _amount;
        
        // 從用戶轉入代幣
        require(
            market.token.transferFrom(msg.sender, address(this), repayAmount),
            "Transfer failed"
        );
        
        pos.borrowAmount -= repayAmount;
        pos.lastUpdateTime = block.timestamp;
        
        market.totalBorrows -= repayAmount;
        
        emit Repay(msg.sender, _token, repayAmount);
    }
    
    // 提款
    function withdraw(address _token, uint256 _amount) external nonReentrant {
        Market storage market = markets[_token];
        require(address(market.token) != address(0), "Market not found");
        
        _accrueDepositInterest(_token);
        
        Position storage pos = positions[msg.sender][_token];
        uint256 withdrawAmount = _amount > pos.depositAmount ? pos.depositAmount : _amount;
        
        // 檢查借款額是否超過抵押額
        uint256 maxBorrow = (pos.depositAmount * COLLATERAL_RATE) / DENOMINATOR;
        require(
            pos.borrowAmount <= ((pos.depositAmount - withdrawAmount) * COLLATERAL_RATE) / DENOMINATOR,
            "Insufficient collateral after withdraw"
        );
        
        // 更新頭寸
        pos.depositAmount -= withdrawAmount;
        pos.lastUpdateTime = block.timestamp;
        
        market.totalDeposits -= withdrawAmount;
        
        require(market.token.transfer(msg.sender, withdrawAmount), "Transfer failed");
        
        emit Withdraw(msg.sender, _token, withdrawAmount);
    }
    
    // 計算健康因子
    function getHealthFactor(address _user, address _token) external view returns (uint256) {
        Position storage pos = positions[_user][_token];
        if (pos.depositAmount == 0) return 0;
        
        uint256 maxBorrow = (pos.depositAmount * COLLATERAL_RATE) / DENOMINATOR;
        return (maxBorrow * DENOMINATOR) / (pos.borrowAmount + 1);
    }
    
    // 累積存款利息
    function _accrueDepositInterest(address _token) internal {
        uint256 timeElapsed = block.timestamp - lastUpdateTime;
        if (timeElapsed == 0) return;
        
        // 簡化的利率計算
        uint256 interest = (markets[_token].totalDeposits * ANNUAL_RATE * timeElapsed) 
            / (365 days * 10000);
        
        depositIndex += (interest * 1e18) / markets[_token].totalDeposits;
        markets[_token].totalDeposits += interest;
        lastUpdateTime = block.timestamp;
    }
    
    // 累積借款利息
    function _accrueBorrowInterest(address _token) internal {
        uint256 timeElapsed = block.timestamp - lastUpdateTime;
        if (timeElapsed == 0) return;
        
        uint256 borrowRate = ANNUAL_RATE * 2; // 借款利率為存款利率的 2 倍
        uint256 interest = (markets[_token].totalBorrows * borrowRate * timeElapsed)
            / (365 days * 10000);
        
        borrowIndex += (interest * 1e18) / (markets[_token].totalBorrows + 1);
        markets[_token].totalBorrows += interest;
        lastUpdateTime = block.timestamp;
    }
}

3.2 與 Aave 協議交互

實際的 DeFi 協議更加複雜,以下展示如何通過程式碼與 Aave V3 交互:

// scripts/aave-interaction.js
const { ethers } = require("ethers");

// Aave V3 Pool 合約 ABI
const aPoolABI = [
  "function supply(address asset, uint256 amount, address onBehalfOf, uint16 referralCode)",
  "function withdraw(address asset, uint256 amount, address to) returns (uint256)",
  "function borrow(address asset, uint256 amount, uint256 interestRateMode, uint16 referralCode, address onBehalfOf)",
  "function repay(address asset, uint256 amount, uint256 interestRateMode, address onBehalfOf) returns (uint256)",
  "function getUserAccountData(address user) view returns (uint256 totalCollateralBase, uint256 totalDebtBase, uint256 availableBorrowsBase, uint256 currentLiquidationThreshold, uint256 ltv, uint256 healthFactor)"
];

// Aave Pool 地址(以太坊主網)
const AAVE_POOL_ADDRESS = "0x87870Bca3F3fD6335C3FbdC83E7a82f43aa5B931";

class AaveInteractor {
  constructor(provider, signer, poolAddress = AAVE_POOL_ADDRESS) {
    this.provider = provider;
    this.signer = signer;
    this.pool = new ethers.Contract(poolAddress, aPoolABI, signer);
  }
  
  // 存款
  async supply(assetAddress, amount) {
    const userAddress = await this.signer.getAddress();
    
    // 先授權 Pool 使用代幣
    const token = new ethers.Contract(assetAddress, [
      "function approve(address spender, uint256 amount) returns (bool)"
    ], this.signer);
    
    const approveTx = await token.approve(AAVE_POOL_ADDRESS, amount);
    await approveTx.wait();
    
    // 存款
    const tx = await this.pool.supply(assetAddress, amount, userAddress, 0);
    await tx.wait();
    
    console.log(`Supplied ${amount} of asset ${assetAddress}`);
  }
  
  // 借款
  async borrow(assetAddress, amount, interestRateMode = 2) {
    // interestRateMode: 1 = 穩定利率, 2 = 浮動利率
    const userAddress = await this.signer.getAddress();
    
    const tx = await this.pool.borrow(assetAddress, amount, interestRateMode, 0, userAddress);
    await tx.wait();
    
    console.log(`Borrowed ${amount} of asset ${assetAddress}`);
  }
  
  // 還款
  async repay(assetAddress, amount, interestRateMode = 2) {
    const userAddress = await this.signer.getAddress();
    
    // 授權
    const token = new ethers.Contract(assetAddress, [
      "function approve(address spender, uint256 amount) returns (bool)"
    ], this.signer);
    
    const approveTx = await token.approve(AAVE_POOL_ADDRESS, amount);
    await approveTx.wait();
    
    const tx = await this.pool.repay(assetAddress, amount, interestRateMode, userAddress);
    await tx.wait();
    
    console.log(`Repaid ${amount} of asset ${assetAddress}`);
  }
  
  // 提款
  async withdraw(assetAddress, amount) {
    const userAddress = await this.signer.getAddress();
    
    const tx = await this.pool.withdraw(assetAddress, amount, userAddress);
    await tx.wait();
    
    console.log(`Withdrew ${amount} of asset ${assetAddress}`);
  }
  
  // 獲取帳戶數據
  async getAccountData() {
    const userAddress = await this.signer.getAddress();
    const data = await this.pool.getUserAccountData(userAddress);
    
    return {
      totalCollateral: data[0],
      totalDebt: data[1],
      availableBorrows: data[2],
      currentLiquidationThreshold: data[3],
      ltv: data[4],
      healthFactor: data[5]
    };
  }
}

module.exports = { AaveInteractor };

第四章:去中心化交易所實作

4.1 AMM 基礎合約

自動做市商(AMM)是 DeFi 的核心創新。以下是一個簡化的 AMM 合約:

// contracts/SimpleAMM.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";

contract SimpleAMM is ReentrancyGuard {
    // 代幣對
    IERC20 public immutable token0;
    IERC20 public immutable token1;
    
    // 儲備量
    uint256 public reserve0;
    uint256 public reserve1;
    
    // 工廠合約地址(用於驗證)
    address public factory;
    
    // 交易手續費(0.3%)
    uint256 public constant FEE = 3;
    uint256 public constant FEE_DENOMINATOR = 1000;
    
    // 事件
    event Swap(
        address indexed sender,
        uint256 amount0In,
        uint256 amount1In,
        uint256 amount0Out,
        uint256 amount1Out,
        address indexed to
    );
    
    event Mint(address indexed sender, uint256 amount0, uint256 amount1);
    event Burn(address indexed sender, uint256 amount0, uint256 amount1, address indexed to);
    
    constructor(address _token0, address _token1) {
        require(_token0 != _token1, "Same tokens");
        token0 = IERC20(_token0);
        token1 = IERC20(_token1);
        factory = msg.sender;
    }
    
    // 初始化(由工廠調用)
    function initialize(uint256 _reserve0, uint256 _reserve1) external {
        require(msg.sender == factory, "Only factory");
        require(_reserve0 != 0 && _reserve1 != 0, "Invalid reserves");
        reserve0 = _reserve0;
        reserve1 = _reserve1;
    }
    
    // 兌換
    function swap(
        uint256 amount0Out,
        uint256 amount1Out,
        address to,
        bytes calldata data
    ) external nonReentrant {
        require(amount0Out > 0 || amount1Out > 0, "Insufficient output amount");
        require(
            amount0Out < reserve0 && amount1Out < reserve1,
            "Insufficient liquidity"
        );
        
        uint256 amount0In = 0;
        uint256 amount1In = 0;
        
        // 計算輸入金額
        if (amount0Out > 0) {
            token0.transfer(to, amount0Out);
            amount0In = _getAmountIn(amount0Out, reserve1, reserve0);
        }
        
        if (amount1Out > 0) {
            token1.transfer(to, amount1Out);
            amount1In = _getAmountIn(amount1Out, reserve0, reserve1);
        }
        
        // 轉入輸入代幣
        if (amount0In > 0) {
            require(
                token0.transferFrom(msg.sender, address(this), amount0In),
                "Transfer failed"
            );
        }
        
        if (amount1In > 0) {
            require(
                token1.transferFrom(msg.sender, address(this), amount1In),
                "Transfer failed"
            );
        }
        
        // 調整儲備量
        reserve0 = token0.balanceOf(address(this));
        reserve1 = token1.balanceOf(address(this));
        
        emit Swap(
            msg.sender,
            amount0In,
            amount1In,
            amount0Out,
            amount1Out,
            to
        );
    }
    
    // 計算輸入金額(考慮手續費)
    function _getAmountIn(
        uint256 amountOut,
        uint256 reserveIn,
        uint256 reserveOut
    ) internal pure returns (uint256) {
        require(amountOut > 0, "Insufficient output amount");
        require(reserveOut > amountOut, "Insufficient liquidity");
        
        uint256 amountInWithFee = amountOut * FEE_DENOMINATOR;
        uint256 numerator = amountInWithFee * reserveIn;
        uint256 denominator = (reserveOut - amountOut) * (FEE_DENOMINATOR - FEE);
        
        return numerator / denominator + 1;
    }
    
    // 添加流動性
    function addLiquidity(
        address to,
        uint256 desiredAmount0,
        uint256 desiredAmount1
    ) external nonReentrant returns (uint256 amount0, uint256 amount1) {
        // 獲取當前餘額
        uint256 balance0 = token0.balanceOf(address(this));
        uint256 balance1 = token1.balanceOf(address(this));
        
        // 計算實際添加的數量
        if (reserve0 == 0 && reserve1 == 0) {
            amount0 = desiredAmount0;
            amount1 = desiredAmount1;
        } else {
            uint256 amount1Optimal = desiredAmount1 * reserve0 / reserve1;
            
            if (amount1Optimal <= desiredAmount0) {
                amount0 = amount1Optimal;
                amount1 = desiredAmount1;
            } else {
                uint256 amount0Optimal = desiredAmount0 * reserve1 / reserve0;
                amount0 = desiredAmount0;
                amount1 = amount0Optimal;
            }
        }
        
        // 轉入代幣
        require(
            token0.transferFrom(msg.sender, address(this), amount0),
            "Transfer0 failed"
        );
        require(
            token1.transferFrom(msg.sender, address(this), amount1),
            "Transfer1 failed"
        );
        
        // 更新儲備量
        reserve0 = token0.balanceOf(address(this));
        reserve1 = token1.balanceOf(address(this));
        
        emit Mint(msg.sender, amount0, amount1);
    }
    
    // 移除流動性
    function removeLiquidity(
        uint256 liquidity,
        address to
    ) external nonReentrant returns (uint256 amount0, uint256 amount1) {
        require(liquidity > 0, "Insufficient liquidity");
        
        uint256 totalSupply = 0; // 應由 LP 代幣合約提供
        uint256 balance0 = token0.balanceOf(address(this));
        uint256 balance1 = token1.balanceOf(address(this));
        
        amount0 = liquidity * balance0 / totalSupply;
        amount1 = liquidity * balance1 / totalSupply;
        
        require(amount0 > 0 && amount1 > 0, "Insufficient liquidity received");
        
        // 轉出代幣
        token0.transfer(to, amount0);
        token1.transfer(to, amount1);
        
        // 更新儲備量
        reserve0 = token0.balanceOf(address(this));
        reserve1 = token1.balanceOf(address(this));
        
        emit Burn(msg.sender, amount0, amount1, to);
    }
}

4.2 與 Uniswap 交互

使用 SDK 與 Uniswap V3 交互:

// scripts/uniswap-interaction.js
const { ethers } = require("ethers");
const { Pool, Trade, Route, Token, CurrencyAmount, TradeType, Percent } = require("@uniswap/v3-sdk");
const { AlphaRouter } = require("@uniswap/smart-order-router");
const JSBI = require("jsbi");

// Uniswap V3 工廠合約
const UNISWAP_V3_FACTORY_ADDRESS = "0x1F98431c8aD98523631AE4a59f267346ea31F984";

// 路由配置
const routerConfig = {
  chainId: 1,
  quoterInfiniteLoopTimeout: 30000,
};

// 獲取路由
async function getSwapRoute(tokenIn, tokenOut, amountIn, slippageTolerance = 0.5) {
  const provider = new ethers.JsonRpcProvider(process.env.MAINNET_RPC_URL);
  
  const router = new AlphaRouter({
    provider: provider,
    chainId: 1,
  });
  
  const route = await router.route(
    CurrencyAmount.fromRawAmount(tokenIn, amountIn.toString()),
    tokenOut,
    TradeType.EXACT_INPUT,
    {
      recipient: process.env.WALLET_ADDRESS,
      slippageTolerance: new Percent(Math.floor(slippageTolerance * 100), 10000),
      deadline: Math.floor(Date.now() / 1000) + 60 * 20,
    }
  );
  
  return route;
}

// 執行兌換
async function executeSwap(route, signer) {
  const tx = await signer.sendTransaction({
    data: route.methodParameters.calldata,
    to: route.methodParameters.to,
    value: route.methodParameters.value,
    from: await signer.getAddress(),
    maxFeePerGas: route.gasSettings.maxFeePerGas,
    maxPriorityFeePerGas: route.gasSettings.maxPriorityFeePerGas,
  });
  
  console.log(`Swap transaction: ${tx.hash}`);
  return tx;
}

// 獲取流動性池信息
async function getPoolInfo(factoryAddress, tokenA, tokenB, fee) {
  const factoryABI = [
    "function getPool(address tokenA, address tokenB, uint24 fee) view returns (address pool)"
  ];
  
  const factory = new ethers.Contract(factoryAddress, factoryABI, provider);
  const poolAddress = await factory.getPool(tokenA.address, tokenB.address, fee);
  
  console.log(`Pool address: ${poolAddress}`);
  return poolAddress;
}

第五章:質押協議實作

5.1 質押合約

以太坊質押合約的核心邏輯:

// contracts/SimpleStaking.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";

contract SimpleStaking is ReentrancyGuard {
    // 質押代幣
    IERC20 public immutable stakingToken;
    // 獎勵代幣
    IERC20 public immutable rewardToken;
    
    // 獎勵參數
    uint256 public constant REWARD_RATE = 350; // 3.5% 年化
    uint256 public constant DENOMINATOR = 10000;
    
    // 獎勵狀態
    uint256 public rewardPerTokenStored;
    uint256 public lastUpdateTime;
    uint256 public periodFinish;
    
    // 用戶狀態
    mapping(address => uint256) public userRewardPerTokenPaid;
    mapping(address => uint256) public rewards;
    
    // 總質押量
    uint256 private _totalSupply;
    mapping(address => uint256) private _balances;
    
    event RewardAdded(uint256 reward);
    event Staked(address indexed user, uint256 amount);
    event Withdrawn(address indexed user, uint256 amount);
    event RewardPaid(address indexed user, uint256 reward);
    
    constructor(address _stakingToken, address _rewardToken) {
        stakingToken = IERC20(_stakingToken);
        rewardToken = IERC20(_rewardToken);
    }
    
    // 質押
    function stake(uint256 _amount) external nonReentrant updateReward(msg.sender) {
        require(_amount > 0, "Cannot stake 0");
        
        _totalSupply += _amount;
        _balances[msg.sender] += _amount;
        
        require(
            stakingToken.transferFrom(msg.sender, address(this), _amount),
            "Transfer failed"
        );
        
        emit Staked(msg.sender, _amount);
    }
    
    // 提款
    function withdraw(uint256 _amount) external nonReentrant updateReward(msg.sender) {
        require(_amount > 0, "Cannot withdraw 0");
        
        _totalSupply -= _amount;
        _balances[msg.sender] -= _amount;
        
        require(stakingToken.transfer(msg.sender, _amount), "Transfer failed");
        
        emit Withdrawn(msg.sender, _amount);
    }
    
    // 領取獎勵
    function getReward() external nonReentrant updateReward(msg.sender) {
        uint256 reward = rewards[msg.sender];
        
        if (reward > 0) {
            rewards[msg.sender] = 0;
            require(rewardToken.transfer(msg.sender, reward), "Transfer failed");
            emit RewardPaid(msg.sender, reward);
        }
    }
    
    // 質押總量
    function totalSupply() external view returns (uint256) {
        return _totalSupply;
    }
    
    // 用戶餘額
    function balanceOf(address account) external view returns (uint256) {
        return _balances[account];
    }
    
    // 計算用戶待領取獎勵
    function earned(address account) external view returns (uint256) {
        return _balances[account] * (rewardPerTokenStored - userRewardPerTokenPaid[account]) 
            / 1e18 + rewards[account];
    }
    
    // 更新獎勵(修改器)
    modifier updateReward(address account) {
        rewardPerTokenStored = rewardPerToken();
        lastUpdateTime = lastTimeRewardApplicable();
        
        if (account != address(0)) {
            rewards[account] = earned(account);
            userRewardPerTokenPaid[account] = rewardPerTokenStored;
        }
        
        _;
    }
    
    // 最後一個可領取獎勵的時間
    function lastTimeRewardApplicable() public view returns (uint256) {
        return block.timestamp < periodFinish ? block.timestamp : periodFinish;
    }
    
    // 每代幣的獎勵率
    function rewardPerToken() public view returns (uint256) {
        if (_totalSupply == 0) {
            return rewardPerTokenStored;
        }
        
        uint256 timeElapsed = lastTimeRewardApplicable() - lastUpdateTime;
        uint256 reward = timeElapsed * REWARD_RATE * 1e18 / (365 days * DENOMINATOR);
        
        return rewardPerTokenStored + reward;
    }
}

5.2 LSD 質押交互

液態質押代幣(LSD)的交互範例:

// scripts/lsd-interaction.js
const { ethers } = require("ethers");

// Lido stETH 合約 ABI
const stETHABI = [
  "function submit(address _referral) payable returns (uint256)",
  "function balanceOf(address _account) view returns (uint256)",
  "function approve(address _spender, uint256 _amount) view returns (bool)",
  "function transfer(address _to, uint256 _amount) returns (bool)",
  "event Submitted(address indexed sender, address indexed referral, uint256 amount, uint256 shares)"
];

// Lido 合約地址
const LIDO_ADDRESS = "0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84";

// stETH 在 DeFi 中的應用 - 抵押借款
async function useStETHAsCollateral(signer) {
  const stETH = new ethers.Contract(LIDO_ADDRESS, stETHABI, signer);
  const userAddress = await signer.getAddress();
  
  // 1. 質押 ETH 獲得 stETH
  const stakeAmount = ethers.parseEther("10");
  const tx = await stETH.submit(userAddress, { value: stakeAmount });
  const receipt = await tx.wait();
  
  console.log(`Staked ${stakeAmount} ETH, received stETH`);
  
  // 2. 在 Aave 中使用 stETH 作為抵押品
  // 需要先批准 aave pool 使用 stETH
  const aavePool = "0x87870Bca3F3fD6335C3FbdC83E7a82f43aa5B931";
  const approveTx = await stETH.approve(aavePool, ethers.MaxUint256);
  await approveTx.wait();
  
  console.log("Approved stETH for Aave");
  
  // 3. 在 Aave 存款(這會自動作為抵押品)
  const aaveABI = ["function supply(address asset, uint256 amount, address onBehalfOf, uint16 referralCode)"];
  const aavePoolContract = new ethers.Contract(aavePool, aaveABI, signer);
  
  const stETHBalance = await stETH.balanceOf(userAddress);
  const supplyTx = await aavePoolContract.supply(LIDO_ADDRESS, stETHBalance, userAddress, 0);
  await supplyTx.wait();
  
  console.log(`Supplied ${stETHBalance} stETH to Aave as collateral`);
}

第六章:安全最佳實踐

6.1 常見漏洞與防範

智能合約開發中需要特別注意以下安全問題:

重入攻擊:在合約狀態更新之前轉移資金,攻擊者可以反覆調用提款函數。

// 不安全的實現
function withdraw() external {
    uint256 amount = balances[msg.sender];
    balances[msg.sender] = 0;  // 狀態更新在轉帳之後
    
    (bool success, ) = msg.sender.call{value: amount}("");
    require(success, "Transfer failed");
}

// 安全的實現(使用 ReentrancyGuard)
function withdraw() external nonReentrant {
    uint256 amount = balances[msg.sender];
    require(amount > 0, "No balance");
    
    balances[msg.sender] = 0;  // 狀態更新在轉帳之前
    
    (bool success, ) = msg.sender.call{value: amount}("");
    require(success, "Transfer failed");
}

整數溢位:Solidity 0.8+ 內建溢位檢查,但仍需注意。

// SafeMath 風格的檢查(Solidity 0.8+ 自動處理)
function safeAdd(uint256 a, uint256 b) internal pure returns (uint256) {
    uint256 c = a + b;
    require(c >= a, "SafeMath: addition overflow");
    return c;
}

6.2 合約升級模式

使用代理模式實現可升級合約:

// contracts/UpgradeableProxy.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";

contract UpgradeableProxy is ERC1967Proxy {
    constructor(address _implementation, bytes memory _data) 
        ERC1967Proxy(_implementation, _data) 
    {}
}

// 代理管理合約
contract ProxyAdmin {
    address public owner;
    address public implementation;
    
    constructor() {
        owner = msg.sender;
    }
    
    function upgrade(address _newImplementation) external {
        require(msg.sender == owner, "Not authorized");
        implementation = _newImplementation;
    }
    
    function getImplementation() external view returns (address) {
        return implementation;
    }
}

結論

本文提供了 DeFi 協議開發的完整程式碼範例,從基礎的 ERC-20 代幣合約到複雜的借貸和 AMM 系統。這些範例展示了 DeFi 協議的核心設計模式和交互方式,同時強調了安全編碼的重要性。

對於開發者而言,理解這些基礎元件的工作原理是構建更複雜 DeFi 應用的前提。建議在實際項目中使用經過審計的開源庫(如 OpenZeppelin),並在部署前進行全面的安全測試。

參考資源

  1. OpenZeppelin Contracts: https://docs.openzeppelin.com/contracts
  2. Uniswap V3 Documentation: https://docs.uniswap.org
  3. Aave V3 Documentation: https://docs.aave.com
  4. Solidity Documentation: https://docs.soliditylang.org
  5. Hardhat Documentation: https://hardhat.org/docs

延伸閱讀與來源

這篇文章對您有幫助嗎?

評論

發表評論

注意:由於這是靜態網站,您的評論將儲存在本地瀏覽器中,不會公開顯示。

目前尚無評論,成為第一個發表評論的人吧!