智能合約部署實戰:從環境搭建到主網上線

涵蓋從開發環境準備、本地測試、測試網部署、正式環境部署的完整流程,深入探討 Gas 優化、安全審計、合約升級等進階主題,提供開發者從新手到專業的實戰指南。

智能合約部署實戰:從環境搭建到主網上線

概述

智能合約部署是以太坊開發的核心技能之一。本指南涵蓋從開發環境準備、本地測試、測試網部署、正式環境部署的完整流程,並深入探討 Gas 優化、安全審計、合約升級等進階主題。無論是開發者首次部署合約,還是需要系統化流程的團隊,都能從本指南獲得實用資訊。

開發環境準備

開發工具鏈選擇

主流開發框架比較

框架語言特色適用場景
HardhatTypeScript/JavaScript靈活、插件豐富、調試強大主流選擇
FoundryRust極速測試、Solidity 原生高級開發者
BrowniePython簡單易用、Python 生態Python 開發者
TruffleJavaScript歷史悠久、成熟穩定傳統項目

推薦選擇:對於大多數項目,Hardhat 是最佳選擇;對於需要高性能測試的項目,Foundry 是更好的選擇。

Hardhat 環境搭建

初始化項目

# 創建項目目錄
mkdir my-smart-contract && cd my-smart-contract

# 初始化 npm
npm init -y

# 安裝 Hardhat
npm install --save-dev hardhat

# 初始化 Hardhat 配置
npx hardhat init
# 選擇 "Create a JavaScript project" 或 "Create a TypeScript project"

項目結構

my-smart-contract/
├── contracts/
│   └── MyContract.sol
├── scripts/
│   └── deploy.js
├── test/
│   └── my-contract.js
├── hardhat.config.js
└── package.json

必要的依賴安裝

# Ethers.js(與區塊鏈交互)
npm install ethers

# OpenZeppelin 合約庫
npm install @openzeppelin/contracts

# dotenv(環境變量管理)
npm install dotenv

# chai(測試框架)
npm install --save-dev @nomicfoundation/hardhat-toolbox

智能合約編寫

基礎合約結構

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";

contract MyToken is Ownable, ReentrancyGuard {
    // 合約變量
    string public name = "My Token";
    string public symbol = "MTK";
    uint8 public decimals = 18;
    uint256 public totalSupply;

    mapping(address => uint256) public balanceOf;
    mapping(address => mapping(address => uint256)) public allowance;

    // 事件定義
    event Transfer(address indexed from, address indexed to, uint256 value);
    event Approval(address indexed owner, address indexed spender, uint256 value);

    // 構造函數
    constructor(uint256 initialSupply) {
        totalSupply = initialSupply * 10 ** decimals;
        balanceOf[msg.sender] = totalSupply;
    }

    // 轉帳函數
    function transfer(address to, uint256 amount) external returns (bool) {
        require(balanceOf[msg.sender] >= amount, "Insufficient balance");

        balanceOf[msg.sender] -= amount;
        balanceOf[to] += amount;

        emit Transfer(msg.sender, to, amount);
        return true;
    }

    // 授權函數
    function approve(address spender, uint256 amount) external returns (bool) {
        allowance[msg.sender][spender] = amount;
        emit Approval(msg.sender, spender, amount);
        return true;
    }

    // 轉帳從函數
    function transferFrom(
        address from,
        address to,
        uint256 amount
    ) external returns (bool) {
        require(balanceOf[from] >= amount, "Insufficient balance");
        require(allowance[from][msg.sender] >= amount, "Allowance exceeded");

        balanceOf[from] -= amount;
        balanceOf[to] += amount;
        allowance[from][msg.sender] -= amount;

        emit Transfer(from, to, amount);
        return true;
    }
}

合約設計最佳實踐

安全優先原則

  1. 使用經過審計的庫:優先使用 OpenZeppelin 的合約庫
  2. 防止重入攻擊:使用 ReentrancyGuard 或 Checks-Effects-Interactions 模式
  3. 權限控制:使用 Ownable 或 AccessControl
  4. 溢出保護:使用 Solidity 0.8+ 內建溢出檢查

Gas 優化技巧

// 不優化:每次讀取都會產生 Gas
function badExample(uint256[] storage arr) external {
    for (uint256 i = 0; i < arr.length; i++) {
        // do something
    }
}

// 優化:緩存長度
function goodExample(uint256[] storage arr) external {
    uint256 len = arr.length;
    for (uint256 i = 0; i < len; i++) {
        // do something
    }
}

// 使用 mapping 替代 array(如果不需要迭代)
mapping(address => uint256) public balances;

本地測試環境

Hardhat 網路配置

hardhat.config.js 配置

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

/** @type import('hardhat/config').HardhatUserConfig */
module.exports = {
  solidity: {
    version: "0.8.20",
    settings: {
      optimizer: {
        enabled: true,
        runs: 200
      }
    }
  },
  networks: {
    hardhat: {
      chainId: 31337
    },
    localhost: {
      url: "http://127.0.0.1:8545"
    }
  },
  paths: {
    sources: "./contracts",
    tests: "./test",
    cache: "./cache",
    artifacts: "./artifacts"
  }
};

啟動本地節點

# 啟動 Hardhat 節點(內置)
npx hardhat node

# 或連接到現有本地節點
npx hardhat console --network localhost

本地節點會提供 20 個預先資助的測試帳戶,格式如下:

Account #0: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 (10000 ETH)
Account #1: 0x70997970C51812dc3A010C7d01b50e0d17dc79C8 (10000 ETH)
...

編寫測試合約

const { expect } = require("chai");
const { ethers } = require("hardhat");

describe("MyToken", function () {
  let token;
  let owner;
  let addr1;
  let addr2;

  beforeEach(async function () {
    // 獲取帳戶
    [owner, addr1, addr2] = await ethers.getSigners();

    // 部署合約
    const Token = await ethers.getContractFactory("MyToken");
    token = await Token.deploy(1000000);
    await token.waitForDeployment();
  });

  describe("部署", function () {
    it("應該設置正確的名稱和符號", async function () {
      expect(await token.name()).to.equal("My Token");
      expect(await token.symbol()).to.equal("MTK");
    });

    it("應該將總供應量分配給部署者", async function () {
      const ownerBalance = await token.balanceOf(owner.address);
      expect(await token.totalSupply()).to.equal(ownerBalance);
    });
  });

  describe("轉帳交易", function () {
    it("應該轉帳代幣到其他帳戶", async function () {
      // 轉帳 50 代幣給 addr1
      await token.transfer(addr1.address, 50);

      expect(await token.balanceOf(addr1.address)).to.equal(50);
    });

    it("應該在餘額不足時失敗", async function () {
      await expect(
        token.transfer(addr1.address, 999999999)
      ).to.be.revertedWith("Insufficient balance");
    });
  });

  describe("授權機制", function () {
    it("應該更新授權額度", async function () {
      await token.approve(addr1.address, 100);
      expect(await token.allowance(owner.address, addr1.address)).to.equal(100);
    });

    it("應該在轉帳時扣除授權額度", async function () {
      await token.approve(addr1.address, 100);
      await token.connect(addr1).transferFrom(owner.address, addr2.address, 50);

      expect(await token.balanceOf(addr2.address)).to.equal(50);
      expect(await token.allowance(owner.address, addr1.address)).to.equal(50);
    });
  });
});

運行測試

# 運行所有測試
npx hardhat test

# 運行特定測試文件
npx hardhat test test/my-token.js

# 運行特定測試
npx hardhat test --grep "轉帳"

# 顯示 Gas 報告
npx hardhat test --show-stack-traces

測試網部署

獲取測試網 ETH

測試網絡絡選擇

網絡鏈 ID水龍頭
Sepolia11155111sepoliafaucet.com
Holesky17000holeskyfaucet.com
Goerli5goerlifaucet.org

獲取測試 ETH 方法

  1. 水龍頭網站:訪問對應測試網的水龍頭
  2. HydraDog:Twitter 機器人
  3. Infura/Alchemy:創建項目獲取測試網 ETH

部署到測試網

創建部署腳本

// scripts/deploy.js
const hre = require("hardhat");
const fs = require("fs");

async function main() {
  console.log("開始部署合約...");

  // 獲取部署帳戶
  const [deployer] = await hre.ethers.getSigners();
  console.log("部署帳戶:", deployer.address);

  // 獲取帳戶餘額
  const balance = await hre.ethers.provider.getBalance(deployer.address);
  console.log("帳戶餘額:", hre.ethers.formatEther(balance), "ETH");

  // 部署 MyToken 合約
  const Token = await hre.ethers.getContractFactory("MyToken");
  const token = await Token.deploy(1000000);

  await token.waitForDeployment();
  const tokenAddress = await token.getAddress();

  console.log("MyToken 部署到:", tokenAddress);

  // 驗證合約(如果需要)
  if (hre.network.config.chainId === 11155111) {
    console.log("等待區塊確認...");
    await token.deploymentTransaction().wait(6);

    try {
      await hre.run("verify:verify", {
        address: tokenAddress,
        constructorArguments: [1000000],
      });
      console.log("合約驗證成功");
    } catch (error) {
      console.log("驗證失敗:", error.message);
    }
  }

  // 保存部署信息
  const deploymentInfo = {
    network: hre.network.name,
    contract: "MyToken",
    address: tokenAddress,
    deployer: deployer.address,
    timestamp: new Date().toISOString(),
  };

  fs.writeFileSync(
    "deployment-info.json",
    JSON.stringify(deploymentInfo, null, 2)
  );

  console.log("部署信息已保存到 deployment-info.json");
}

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

執行部署

# 部署到本地網 hardhat run scripts/deploy.js --network hardhat

# 絡
npx部署到 Sepolia 測試網
npx hardhat run scripts/deploy.js --network sepolia

# 部署到 Holesky
npx hardhat run scripts/deploy.js --network holesky

環境變量配置

創建 .env 文件

# 私鑰(不要提交到 Git!)
DEPLOYER_PRIVATE_KEY=0xYourPrivateKeyHere

# Infura API Key
INFURA_API_KEY=YourInfuraProjectId

# Etherscan API Key(用於合約驗證)
ETHERSCAN_API_KEY=YourEtherscanApiKey

# 其他 RPC URL
SEPOLIA_RPC_URL=https://sepolia.infura.io/v3/YourProjectId
HOLESKY_RPC_URL=https://holesky.infura.io/v3/YourProjectId

更新 hardhat.config.js

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

const { PRIVATE_KEY, SEPOLIA_RPC_URL, ETHERSCAN_API_KEY } = process.env;

module.exports = {
  solidity: "0.8.20",
  networks: {
    sepolia: {
      url: SEPOLIA_RPC_URL,
      accounts: [PRIVATE_KEY],
      chainId: 11155111,
    },
    holesky: {
      url: process.env.HOLESKY_RPC_URL,
      accounts: [PRIVATE_KEY],
      chainId: 17000,
    }
  },
  etherscan: {
    apiKey: ETHERSCAN_API_KEY
  }
};

主網部署流程

部署前檢查清單

代碼審計

測試覆蓋

Gas 優化

合約驗證

主網部署腳本

// scripts/deploy-mainnet.js
const hre = require("hardhat");
const fs = require("fs");

// 延遲函數
const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

async function main() {
  const networkName = hre.network.name;
  const chainId = hre.network.config.chainId;

  console.log(`開始部署到 ${networkName} (Chain ID: ${chainId})...`);

  // 獲取部署帳戶
  const [deployer] = await hre.ethers.getSigners();
  const balance = await hre.ethers.provider.getBalance(deployer.address);

  console.log(`部署帳戶: ${deployer.address}`);
  console.log(`帳戶餘額: ${hre.ethers.formatEther(balance)} ETH`);

  // 檢查餘額是否足夠
  const minBalance = hre.ethers.parseEther("0.01");
  if (balance < minBalance) {
    throw new Error("帳戶餘額不足,請先充值");
  }

  // 部署主要合約
  console.log("\n部署 MyToken...");
  const Token = await hre.ethers.getContractFactory("MyToken");
  const token = await Token.deploy(1000000);
  await token.waitForDeployment();
  const tokenAddress = await token.getAddress();
  console.log(`MyToken 部署到: ${tokenAddress}`);

  // 等待幾個區塊確認
  console.log("\n等待區塊確認...");
  await token.deploymentTransaction().wait(6);

  // 驗證合約
  console.log("\n驗證合約...");
  try {
    await hre.run("verify:verify", {
      address: tokenAddress,
      constructorArguments: [1000000],
    });
    console.log("合約驗證成功");
  } catch (error) {
    console.log("驗證失敗:", error.message);
  }

  // 部署後配置
  console.log("\n執行部署後配置...");

  // 轉帳代幣
  const transferAmount = hre.ethers.parseEther("100000");
  const transferTx = await token.transfer(deployer.address, transferAmount);
  await transferTx.wait(3);
  console.log(`已轉帳 ${hre.ethers.formatEther(transferAmount)} 代幣`);

  // 保存部署記錄
  const deploymentInfo = {
    network: networkName,
    chainId: chainId,
    contracts: {
      MyToken: {
        address: tokenAddress,
        constructorArgs: [1000000],
      }
    },
    deployer: deployer.address,
    timestamp: new Date().toISOString(),
    transactionHash: token.deploymentTransaction().hash,
  };

  fs.writeFileSync(
    `deployment-${networkName}-${Date.now()}.json`,
    JSON.stringify(deploymentInfo, null, 2)
  );

  console.log(`\n部署完成!`);
  console.log(`部署記錄已保存`);
}

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

執行主網部署

# 部署到主網
npx hardhat run scripts/deploy-mainnet.js --network mainnet

部署時的注意事項

  1. 選擇低峰期部署:避開網路擁塞時段以節省 Gas
  2. 設置合理的 Gas 價格:使用 ETH Gas Tracker 估算
  3. 分階段部署:先部署核心合約,再部署周邊合約
  4. 記錄交易哈希:用於後續追踪和爭議處理

部署後驗證

Etherscan 驗證

// 自動驗證
npx hardhat verify --network mainnet 0xYourContractAddress "constructorArg1"

手動驗證

  1. 打開 Etherscan
  2. 進入合約地址
  3. 點擊 "Contract" → "Verify and Publish"
  4. 填入編譯器版本和合約代碼

Gas 優化策略

合約層面優化

存儲優化

// 不優化:使用較大的類型
uint256 public largeNumber;  // 256 位元

// 優化:使用較小的類型(如果值範圍允許)
uint64 public smallNumber;   // 64 位元

// 批量存儲
struct UserInfo {
    uint256 amount;
    uint256 rewardDebt;
    uint256 pendingReward;
}
// 使用 struct 減少 SLOAD 次數

函數優化

// 不優化
function processArray(uint256[] arr) external {
    for (uint256 i = 0; i < arr.length; i++) {
        // 每次迴圈都讀取
        require(balances[msg.sender] > 0);
        // do something
    }
}

// 優化
function processArray(uint256[] arr) external {
    uint256 balance = balances[msg.sender];  // 緩存
    for (uint256 i = 0; i < arr.length; i++) {
        require(balance > 0);
        // do something
    }
}

部署層面優化

編譯器優化

// hardhat.config.js
module.exports = {
  solidity: {
    version: "0.8.20",
    settings: {
      optimizer: {
        enabled: true,
        runs: 1000,  // 優化運行次數越高,合約越大但 Gas 越低
      }
    }
  }
};

Gas 估算

// 估算部署 Gas
const estimatedGas = await ethers.provider.estimateGas({
  from: deployer.address,
  data: token.contractDeploymentTransaction
});

console.log("預估 Gas:", estimatedGas.toString());

// 估算部署成本
const gasPrice = await ethers.provider.getGasPrice();
const cost = estimatedGas * gasPrice;
console.log("預估成本:", ethers.formatEther(cost), "ETH");

合約升級模式

可升級合約模式

代理模式(Proxy Pattern)

// 邏輯合約
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

contract LogicV1 {
    uint256 public value;

    function setValue(uint256 _value) external {
        value = _value;
    }

    function getValue() external view returns (uint256) {
        return value;
    }
}

// 代理合約
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import "@openzeppelin/contracts/proxy/Proxy.sol";
import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Upgrade.sol";

contract ProxyContract is Proxy, ERC1967Upgrade {
    function _implementation() internal view override returns (address) {
        return _getImplementation();
    }
}

使用 OpenZeppelin Upgrades 插件

// 安裝插件
npm install --save-dev @openzeppelin/hardhat-upgrades

// hardhat.config.js
require("@openzeppelin/hardhat-upgrades");

// 部署可升級合約
const { upgradeProxy } = require("@openzeppelin/hardhat-upgrades");

async function main() {
  const Box = await ethers.getContractFactory("Box");

  // 首次部署
  const box = await upgrades.deployProxy(Box, [42], { initializer: 'store' });
  await box.waitForDeployment();

  // 升級
  const BoxV2 = await ethers.getContractFactory("BoxV2");
  await upgrades.upgradeProxy(box.address, BoxV2);
}

升級安全考量

  1. 存儲衝突:確保新合約與舊合約的存儲布局兼容
  2. 初始化函數:使用 initializer modifier 替代 constructor
  3. 測試升級:在測試網充分測試升級流程
  4. 時間鎖:建議使用 Timelock 控制器控制升級

安全審計準備

審計前檢查清單

選擇審計機構

知名審計機構

機構特色價格範圍
Trail of Bits頂級安全公司,詳細報告$50K+
OpenZeppelin合約專家,標準兼容$30K+
Certora形式化驗證$40K+
SlowMist亞洲團隊,價格適中$20K+

審計後處理

  1. 修復問題:優先處理高風險問題
  2. 重新測試:確保修復有效
  3. 發布審計報告:提升用戶信任
  4. 設置賞金計劃:鼓勵社區發現問題

維護與監控

合約監javascript

// 監控合約事件

const { ethers } = require("控

```ethers");

async function monitorContract(contractAddress) {

const provider = new ethers.JsonRpcProvider("mainnet-rpc-url");

const contract = new ethers.Contract(contractAddress, abi, provider);

// 監控 Transfer 事件

contract.on("Transfer", (from, to, value, event) => {

console.log(轉帳: ${from} -> ${to}: ${value});

});

// 監控所有事件

contract.on("*", (event) => {

console.log(事件: ${event.event});

});

}


### 緊急暫停機制

// 添加緊急暫停功能

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

import "@openzeppelin/contracts/security/Pausable.sol";

contract MyContract is Ownable, Pausable {

function pause() external onlyOwner {

_pause();

}

function unpause() external onlyOwner {

_unpause();

}

function sensitiveOperation() external whenNotPaused {

// 敏感操作

}

}


## 結論

智能合約部署是區塊鏈開發的關鍵技能。本指南涵蓋了從環境準備到主網上線的完整流程,包括開發框架選擇、合約編寫、本地測試、測試網部署、主網部署、Gas 優化、合約升級和安全審計等各個環節。

**關鍵要點**:

1. **充分測試**:在測試網進行全面測試
2. **安全優先**:遵循安全最佳實踐,進行專業審計
3. **Gas 優化**:在部署前進行 Gas 優化
4. **可升級性**:考慮未來的升級需求
5. **監控運維**:部署後持續監控和維護

智能合約一旦部署就很難修改,因此部署前的充分準備至關重要。建議開發團隊建立標準化的部署流程,並進行多次演練以確保部署順利。

---

## 延伸閱讀

### 智能合約開發
- [OpenZeppelin 智慧合約庫使用完整指南](./openzeppelin-guide.md)
- [智慧合約測試方法論完整指南](./contract-testing-methodology.md)
- [智慧合約形式化驗證完整指南](./formal-verification.md)

### 以太坊開發工具
- [以太坊開發工具完整指南](./ethereum-developer-tools-guide.md)

### 安全與審計
- [DeFi 合約風險檢查清單](./defi-smart-contract-risk.md)
- [智慧合約審計流程](./smart-contract-audit-process.md)

---

## 參考資料

1. Hardhat Documentation. hardhat.org/docs
2. OpenZeppelin Contracts Documentation. docs.openzeppelin.com
3. Solidity Documentation. docs.soliditylang.org
4. Ethers.js Documentation. docs.ethers.org
5. Smart Contract Security Verification Standard. github.com/securing/SCSVS

延伸閱讀與來源

這篇文章對您有幫助嗎?

評論

發表評論

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

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