
简介这是一份国信证券出品的区块链行业研究报告共29页系统梳理区块链从1.0到3.0的进化路径适合区块链从业者、金融科技研究者和对去中心化金融感兴趣的读者。报告以比特币诞生与底层技术为起点剖析工作量证明、减半周期和挖矿能耗继而过渡到智能合约的应用案例最后重点展开DeFi生态涵盖借贷、去中心化交易所、合成资产衍生品、去中心化保险及机枪池等核心赛道结合数据与图表演绎DeFi如何开启去中心化金融时代。全资源仅1个PDF文件大小约2.28MB内容紧凑、图文并茂便于直接阅读或离线收藏。目前已有170人学习下载适合作为快速理解区块链技术演进与DeFi生态的入门研报或行业参考资料。1. 为什么DeFi把区块链从“账本”变成了“金融市场”多数人谈论区块链时首先想到的是币价但真正让行业产生质变的是DeFi。比特币用UTXO账本解决了双花以太坊把账本进化为可编程状态机DeFi则在这层状态机上搭建了不需要柜台就能运行的互换、借贷与清算协议。这份29页的行业演进史核心不是罗列币种而是一个转折金融规则从人工审核变为链上代码自动执行。抵押率超限自动清算报价异常交易失败资金流向可以被任何节点验证。对IT从业者来说理解DeFi不应从K线开始而应从区块链数据、合约状态、预言机喂价三层协同入手。下文将按“历史脉络 → 核心原语 → 本地实现 → 链上验证”展开并给出一套可以在本地跑通的DeFi最小实验环境。无论你是在评估协议、接入监控还是准备开发自己的链上产品都需要先掌握这些底层参数和行为模型。2. 从比特币UTXO到DeFi合约区块链数据演进的三个分水岭2.1 比特币区块链数据第一次具有不可篡改的资产表达比特币阶段解决的问题是把“余额表”换成“UTXO集合”。UTXO是Unspent Transaction Output的缩写。每个UTXO包含面额和锁定脚本交易花费UTXO后产生新的UTXO一条数据只能被花费一次因此在账本结构上杜绝了双花这是区块链数据跟传统数据库日志最大的差异。传统数据库用事务、锁来保证一致性比特币用“只花费一次”的链式结构保证资产唯一。但UTXO模型适合转账不适合复杂金融合约。比特币脚本是非图灵完备的没有循环无法方便地保存多个用户的仓位状态。虽然多重签名和哈希锁能解决托管与原子交换但要做“抵押、动态清算、按价格概率结算”这类业务脚本的复杂度会迅速失控。这也是后来以太坊受到关注的原因行业需要的不是更强的账本而是能承载金融状态的状态机。2.2 以太坊账户模型与智能合约让金融逻辑上链以太坊把状态从UTXO集合改为账户模型。外部账户由私钥控制合约账户由代码控制每个账户都有自己的持久化存储。交易触发合约代码后合约可以修改状态、持有资产甚至调用其他合约。更重要的是交易的原子性让多步操作要么全部生效要么全部回滚不会出现“转账成功但债务没更新”的中间态。以太坊同时引入了Gas把计算、存储、数据传输都量化。Gas一方面防止链上程序死循环另一方面也让合约优化成为必修课。对做DeFi的工程师而言Gas不仅是成本问题更是安全边界问题一个公共函数如果gas估算过高攻击者可能让其运行到Gas耗尽而回滚。整体来看以太坊最大的贡献是将“区块链数据”从记录历史的链条变成了可以被业务逻辑操作的状态网络。2.3 DeFi分水岭可组合、可审计、可清算同时成为现实有了可编程资产DeFi才出现。真正让DeFi成为独立阶段的不是某个协议而是三个能力首次同时成立可组合一个协议的LP代币可以成为另一个协议的抵押品资金路径可以被另一个合约调用可审计合约开源、区块数据公开任何地址的资产进出都可被推导可清算抵押品、价格阈值、清算执行都预先写成代码市场波动时由清算人触发不需要等待人工审批。这三个能力让金融产品从“文档柜台”变成“合约事件日志”。作为工程师你调试DeFi问题时不是在阅读内部报表而是在解析事件日志和状态变量。下表是三个阶段的技术特征对比阶段区块链数据模型执行引擎金融表达能力典型风险比特币UTXO非图灵完备脚本只能转账、托管、时间锁表达能力不足复杂业务上链难以太坊账户合约存储EVM智能合约可定义代币和资产逻辑重入、整数溢出、Gas预估失误DeFi合约组合事件多合约调用链路可构建DEX、借贷、稳定币参数配置、预言机、流动性操纵理解这张表就能理解为什么DeFi爆发不是某个“生态红利”而是数据模型和执行引擎的升级结果。比特币阶段是账本以太坊阶段是状态机DeFi阶段把状态机接成了金融网络。3. AMM、借贷、稳定币DeFi三大原语的关键参数与坑3.1 恒定乘积AMMx*yk是定价规则也是滑点来源去中心化交易所里最常遇到的公式是x * y k。它假设池中有两种代币数量分别为x和y用户向池内加入Δx代币后可以取走Δy交易前后需要满足(x Δx) * (y - Δy) k。这个公式没有订单簿价格由池内库存比例决定库存少的一边价格贵库存多的一边价格便宜。先用一个小脚本模拟池子变化。假设池中初始有100个A和200个B手续费率为0.3%def amm_quote(reserve_in, reserve_out, amount_in, fee0.003): # 先扣除手续费再按恒定乘积计算输出 amount_in_with_fee amount_in * (1 - fee) amount_out reserve_out * amount_in_with_fee / (reserve_in amount_in_with_fee) return amount_out x, y 100, 200 for dx in [1, 10, 50]: dy amm_quote(x, y, dx) print(f输入 {dx:3} A - 输出 {dy:6.2f} B, 每A产出 {dy/dx:.4f} B)运行结果可以看出两个现象输入越多单位A产出的B越少输出数量不是线性增长。输入1 A时每A能拿到约1.97 B输入10 A时降到约1.94 B输入50 A时只剩约1.33 B这就是大额交易的滑点。手续费参数在公式中的作用是“先扣掉再定价”因此0.3%费率实际上会降低同等输入下的输出。实战里常见的错误是只用报价接口算一个输出不检查滑点限制。Uniswap等DEX会在swap函数中要求用户传入minOut交易输出低于这个值就回滚。部署时如果fee设得太低很容易被套利用来搬运做市利润如果设得太高池子又缺乏竞争力。这个环节可执行的参数有三个池子储备余额、手续费率、滑点容忍度。做策略时要同时观察这三项而不是只看价格。3.2 借贷协议LTV与清算阈值决定协议存活借贷协议大多使用超额抵押用户存入抵押品后可以借出资金池里的另一种资产。系统会记录用户的抵押率抵押率 债务价值 / 抵押品价值。当抵押率超过协议设定的清算阈值任何人都可以调用清算函数帮用户归还部分债务并获得折价或手续费奖励。这里最重要的三个参数是LTV首次借款时允许的最大负债比例。如果把ETH作为抵押品LTV通常是50%到75%清算阈值Liquidation Threshold触发清算的负债比例一般比LTV高5到15个百分点清算折扣Liquidation Bonus清算人获得的额外代币收入上限设置过高普通借款人容易无辜被清算。举个例子某协议LTV为70%、清算阈值为77.5%。用户存入100 ETH此时最多可借出70 ETH等值资产当价格下跌导致债务价值达到抵押品价值的77.5%时清算机制启动。如果价格继续下跌甚至超过80%借款池会面临坏账风险所以系统还会加入借款上限和暂停借贷机制。清算触发后清算者得到的收益来自借款人额外的抵押品。这个机制的难点在于抵押品价格必须实时准确。如果预言机滞后清算人就会等价格跌得更深时才清算亏损的其实是普通用户。因此借贷协议的参数设计必须与预言机心跳频率匹配而不是随便抄一个阈值。3.3 稳定币与预言机读取价格时最容易踩的两个坑稳定币是DeFi的计价层。市场上常见的稳定币分三类法币储备型、加密资产超额抵押型、算法型。绝大多数去中心化协议使用超额抵押型因此其稳定币发行依赖链上价格。这个价格通常来自预言机聚合器而不是简单地读取某一个去中心化交易所的价格。预言机的输入输出可以看成多个参数每轮取价的中位数、心跳间隔、价格偏离阈值。如果报价偏离超过阈值哪怕心跳未到也要立即更新如果心跳超时但价格未变也应提交一次最新价格。读取预言机价格时最典型的问题是直接调用latestRoundData()后不检查返回的时间戳(uint80 roundID, int256 price, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound) priceFeed.latestRoundData(); require(updatedAt 0, stale price); require(answeredInRound roundID, incomplete round);这段代码没有完整防御但它展示了两个核心检查点确认时间戳没有过期确认当前轮次没有被中断。实际生产中你还需要设定一个“价格最大年龄”参数通常是心跳间隔的1.5到2倍超过就直接拒绝交易。稳定币交易还会有一个风险用“池子里的瞬时价格”代替“预言机价格”做清算。AMM池的瞬时价格可以被大额交易临时推高或压低单靠池子储备判断容易在几秒内制造错误清算。常见规避方式是使用多个来源的时间加权价格或者对池子价格做中位数截断。表格DeFi协议常调参数速查场景参数建议初始值需要关注的故障AMMswap fee0.3%滑点套利、池子失衡AMMminOut参考报价的99%输出低于预期时回滚借贷LTV50%借款上限不足、资金利用率低借贷liquidation threshold75%~85%触发连环清算预言机heartbeat1小时旧价被当成新价预言机deviation0.5%大波动时更新不及时这张表的价值在于任何一个参数单独看都合理但组合起来却可能出现意外。例如LTV和清算阈值间距只有5%ETH价格波动几分钟内就触发清算。做DeFi开发时参数设置前应该用历史回放验算而不是凭直觉填。4. 从0搭建一个区块链平台实验环境Hardhat最小AMM前面讲的是原语这一章把原语变成能跑起来的本地程序。Hardhat自带的节点本质上就是一个运行在本机的区块链平台它具备JSON-RPC接口会自动生成测试账户并持续产块。没有真实资产完全够用做合约实验。4.1 初始化一个本地区块节点先建立一个目录并安装依赖mkdir defi-lab cd defi-lab npm init -y npm install --save-dev hardhat nomicfoundation/hardhat-toolbox然后创建hardhat.config.jsrequire(nomicfoundation/hardhat-toolbox); module.exports { solidity: 0.8.17, networks: { localhost: { url: http://127.0.0.1:8545, chainId: 31337, }, }, };保存文件后在终端运行npx hardhat node。此时本地区块链平台开始工作终端会打印20个测试账户地址和私钥。默认情况下每提交一笔交易都会自动挖出一个新区块不需要等待时间。chainId: 31337是Hardhat节点的默认链ID后续代码签名时会用到。如果你把合约部署到其他链就必须把这里的url换成对应RPC地址并把chainId改成网络对应的值例如以太坊主网是1。4.2 写一个最小AMM合约并理解定价参数创建contracts/SimpleAMM.sol// SPDX-License-Identifier: MIT pragma solidity ^0.8.17; contract SimpleAMM { uint256 public reserveA; uint256 public reserveB; event Swap( address indexed trader, uint256 amountIn, uint256 amountOut, uint256 reserveAOut, uint256 reserveBOut ); constructor(uint256 _reserveA, uint256 _reserveB) { require(_reserveA 0 _reserveB 0, reserve0); reserveA _reserveA; reserveB _reserveB; } function swapAtoB(uint256 amountIn, uint256 minOut) external returns (uint256 amountOut) { require(amountIn 0, amountIn0); uint256 reserveIn reserveA; uint256 reserveOut reserveB; // Uniswap V2 风格的恒定乘积计算997/1000 表示 0.3% 手续费 uint256 amountInWithFee amountIn * 997; uint256 numerator reserveOut * amountInWithFee; uint256 denominator reserveIn * 1000 amountInWithFee; amountOut numerator / denominator; require(amountOut minOut, slippage too high); reserveA reserveIn amountIn; reserveB reserveOut - amountOut; emit Swap(msg.sender, amountIn, amountOut, reserveA, reserveB); } }这个合约没有真正的ERC-20转账它只维护两个储备量reserveA和reserveB。核心是997和1000两个数每次转账输入被扣掉0.3%作为手续费剩余部分参与恒定乘积计算。minOut参数用来保护交易者一旦产出不足整个交易回滚不会因为链上价格变化而吃亏。从工程角度这个简化模型有两个不足没有实现代币划转因此不能作为真实DEX使用没有对应的流动性增减函数也就不存在无常损失的完整核算。但这些缺点恰好是优点读代码时能清楚地看到定价、滑点、事件三个关键路径适合理解DeFi的核心逻辑。4.3 部署与swap验证观察池子储备和事件输出创建scripts/deploy.jsconst hre require(hardhat); async function main() { const SimpleAMM await hre.ethers.getContractFactory(SimpleAMM); const amm await SimpleAMM.deploy( 100n * 10n ** 18n, // reserveA 200n * 10n ** 18n // reserveB ); await amm.waitForDeployment(); console.log(SimpleAMM deployed to:, await amm.getAddress()); console.log(reserveA:, (await amm.reserveA()).toString()); console.log(reserveB:, (await amm.reserveB()).toString()); } main().catch(console.error);部署脚本里用10n ** 18n统一成整数精度这是代币合约的惯例1个代币在链上显示为10^18个最小单位。初始池设置为100和200保持1:2的价格比例。接着再创建一个scripts/swap.jsconst hre require(hardhat); const AMM_ADDRESS 0x...; // 用部署时打印的地址替换 async function main() { const amm await hre.ethers.getContractAt(SimpleAMM, AMM_ADDRESS); const beforeA await amm.reserveA(); const beforeB await amm.reserveB(); const tx await amm.swapAtoB(1n * 10n ** 18n, 0); const receipt await tx.wait(); console.log(gasUsed:, receipt.gasUsed.toString()); console.log(reserveA:, beforeA.toString(), -, (await amm.reserveA()).toString()); console.log(reserveB:, beforeB.toString(), -, (await amm.reserveB()).toString()); for (const log of receipt.logs) { const parsed amm.interface.parseLog(log); if (parsed parsed.name Swap) { console.log(event Swap:, { amountIn: parsed.args.amountIn.toString(), amountOut: parsed.args.amountOut.toString(), reserveAOut: parsed.args.reserveAOut.toString(), reserveBOut: parsed.args.reserveBOut.toString(), }); } } } main().catch(console.error);执行顺序是先启动npx hardhat node另开终端执行npx hardhat run scripts/deploy.js --network localhost拿到地址填入swap.js再执行npx hardhat run scripts/swap.js --network localhost。注意本地节点重启后之前部署的合约地址会失效需要重新部署。运行swap脚本后你会看到reserveA从100变为101reserveB从200约变为198.19。注意两者乘积仍然不低于20000这就是恒定乘积公式在起作用。如果设置minOut为19swap将回滚因为实际输出小于19。回滚不会改变池子状态但会消耗gas所以在实际生产中minOut要设在一个既安全又能成功的区间。表格本地实验脚本的对应关系脚本/命令作用关键输出npx hardhat node启动本地区块节点20个测试地址JSON-RPC接口npx hardhat run scripts/deploy.js --network localhost部署合约合约地址和初始储备npx hardhat run scripts/swap.js --network localhost执行swap并读取事件gas消耗、储备变化、Swap事件npx hardhat console --network localhost交互式调试手动调用合约函数这个实验环境已经是一个“最小区块链平台”的雏形有区块、有交易、有合约部署和事件日志。后续想接入其他链只需调整network配置和部署地址合约逻辑不需要大改。5. 用事件日志反推执行路径DeFi排错与验证的3个实用技巧实际DeFi应用往往不是单个合约而是多个合约互相调用。此时直接读合约状态很难定位问题事件日志是更可靠的路径。以下技巧可以用于联调、审计和后端监控。5.1 解析事件日志还原一次交易的真实路径读取交易收据中的logs并按照ABI解析是开发阶段最常见的做法。针对上一章部署的SimpleAMM可以在Node脚本中解析const { ethers } require(hardhat); const provider new ethers.JsonRpcProvider(http://127.0.0.1:8545); const AMM_ADDRESS 0x...; const abi [ event Swap(address indexed trader, uint256 amountIn, uint256 amountOut, uint256 reserveAOut, uint256 reserveBOut) ]; const iface new ethers.Interface(abi); async function readSwapLogs() { const logs await provider.getLogs({ address: AMM_ADDRESS, fromBlock: 0, toBlock: latest, }); for (const log of logs) { const parsed iface.parseLog(log); if (parsed) { console.log(block${log.blockNumber} in${parsed.args.amountIn.toString()} out${parsed.args.amountOut.toString()}); } } } readSwapLogs();代码中的fromBlock是扫描起始块高。不要直接填0特别是节点已经运行很久时。更合理的做法是从合约部署块号开始避免无谓的扫描。事件日志的topics字段包含indexed参数非indexed参数则放在data里解析日志时要注意两者区分。5.2 用eth_call预演交易不烧gas验证滑点和清算很多问题其实可以通过直接调用RPC接口eth_call验证而不用真发交易。eth_call会在节点的当前状态下模拟执行一个交易并返回结果但不改变状态、不消耗gas。用ethers调用const data iface.encodeFunctionData(swapAtoB, [10n ** 18n, 0]); const result await provider.call({ to: AMM_ADDRESS, data, }); const [predicted] iface.decodeFunctionResult(swapAtoB, result); console.log(predicted amountOut:, predicted.toString());这个模式最适合做成监控工具对每个待验证的参数先用eth_call计算出预演结果再把预演结果和真实交易结果对比。如果两者相差超过预设阈值说明交易链路中确实发生了状态变化或滑点变动再进一步跟踪日志。5.3 分块回放历史区块避免RPC超时当需要统计池子累计交易量、清算频率等指标时直接getLogs一个超大区块范围很容易让节点超时。常见做法是把扫描范围切小例如每1000个区块拉一次并把游标存下来支持续跑curl -s http://127.0.0.1:8545 \ -X POST \ -H Content-Type: application/json \ -d {jsonrpc:2.0,method:eth_getLogs,params:[{fromBlock:0x0,toBlock:0x3e8,address:0x...}],id:1}这里0x0和0x3e8分别对应起始块0和终止块1000。线下统计时可以把每一小段的结果写入本地数据库再用SQL或脚本合并成指标。回放脚本需要做成幂等同一块范围重复计算不产生重复数据游标记录则保证失败后可以从断点续跑。事件日志是DeFi里最接近“审计线索”的数据源参数变更和资金流向都能从中重现。本文还有配套的精品资源点击获取