复习 - Week 6 总结 (DeFi 合约模式 + Solana 基础)
### 一、Week 6 学习路径回顾
日期: 2026-05-08 方向: Solidity / Solana 阶段: 第二阶段:框架实战 标签: #Review #Week6 #DeFi #Solana #Vault #Staking #FlashLoan #PDA #Anchor
今日目标
| 类型 | 内容 |
|---|---|
| 学习 | 系统回顾 Week 6 所有核心知识点,建立知识连接 |
| 实操 | 完成自测题,用架构图梳理 DeFi 合约模式之间的关系 |
| 产出 | Week 6 知识图谱 + 自测 Quiz + 交互关系图 |
核心概念
一、Week 6 学习路径回顾
Week 6 (Day 32-37) 学习路径:
Day 32: Vault (ERC4626) ─── Solidity DeFi 模式
Day 33: Solana 架构总览 ─── 跳转到 Solana
Day 34: StakingRewards ─── 回到 Solidity DeFi
Day 35: Anchor Hello World ─── Solana 框架实战
Day 36: FlashLoan ─── Solidity DeFi 高阶
Day 37: Counter + PDA ─── Solana 核心概念
交替学习节奏:
Solidity (DeFi模式) ↔ Solana (架构+框架)
→ 好处: 交叉对比加深理解
→ 挑战: 需要在两种心智模型之间切换
二、DeFi 合约三大模式对比
本周学习了 DeFi 中最重要的三种合约模式。它们不是孤立的——大多数 DeFi 协议是这三种模式的组合。
模式总览表
| 维度 | Vault (ERC4626) | StakingRewards | FlashLoan (EIP3156) |
|---|---|---|---|
| 核心思想 | 存入资产 → 获得 share → share 增值 | 质押代币 → 随时间获得奖励 | 单笔交易内借→用→还 |
| 标准 | ERC-4626 | Synthetix 模式(事实标准) | EIP-3156 |
| 参考实现 | Yearn/Compound/Aave | Synthetix/Convex/Sushi | Aave/dYdX/Uniswap |
| 用户操作 | deposit → withdraw | stake → claim → withdraw | flashLoan → callback → repay |
| 收益来源 | 池子中资产增值(策略收益) | 外部注入的奖励代币 | 手续费(借款者支付) |
| 数学核心 | shares = assets * totalShares / totalAssets | rewardPerToken 累加器 | balanceAfter >= balanceBefore + fee |
| 代币机制 | 铸造/销毁 share token | 无新代币(奖励是外部注入的) | 无代币操作 |
| 时间因素 | 无(share 按比例兑换) | 有(奖励随时间线性累积) | 无(一笔交易内完成) |
| 风险 | 策略亏损 → share 贬值 | 奖励耗尽 → 收益归零 | 还不上 → 整笔 revert |
数学公式对比
============= Vault =============
存入时: shares = depositAmount × totalShares / totalAssets
取出时: assets = sharesAmount × totalAssets / totalShares
关键: totalAssets 增加(策略赚钱)→ 每个 share 可兑换更多 assets
数值例子:
初始: Alice deposit 100 USDC, 获得 100 shares (1:1)
收益: 池子从 100 → 120 USDC (策略赚了 20)
现在: Alice 的 100 shares = 100 × 120/100 = 120 USDC
Bob deposit 60 USDC: shares = 60 × 100/120 = 50 shares
池子: 180 USDC, 150 shares
============= StakingRewards =============
全局累加器: rewardPerToken += (rewardRate × Δt) / totalStaked
用户奖励: earned = staked × (rewardPerToken - userRewardPerTokenPaid)
关键: 每次用户操作时才更新该用户的奖励快照
数值例子:
T0: Alice stake 80, Bob stake 20 (total=100)
T0→T100: rewardRate = 1/sec → 100 tokens
rewardPerToken = 0 + (1 × 100) / 100 = 1.0
Alice earned = 80 × 1.0 = 80 tokens (80%)
Bob earned = 20 × 1.0 = 20 tokens (20%)
============= FlashLoan =============
检查: balanceAfter >= balanceBefore + fee
fee = amount × feeRate (通常 0.05%-0.09%)
关键: 原子性保证 — 不满足条件 → 整笔交易 revert
数值例子:
借出 1,000,000 USDC, feeRate = 0.09%
fee = 1,000,000 × 0.0009 = 900 USDC
必须归还: 1,000,900 USDC
套利利润 > 900 USDC + gas → 才值得做
三种模式的组合关系
真实 DeFi 协议中的组合:
Yearn Finance:
┌─────────────────────────────────────┐
│ Vault (ERC4626) │ ← 用户存入
│ ┌─────────────────────────┐ │
│ │ Strategy │ │
│ │ ┌──────────────────┐ │ │
│ │ │ StakingRewards │ │ │ ← 策略: 质押赚取奖励
│ │ └──────────────────┘ │ │
│ │ ┌──────────────────┐ │ │
│ │ │ FlashLoan │ │ │ ← 策略: 用闪电贷杠杆
│ │ └──────────────────┘ │ │
│ └─────────────────────────┘ │
└─────────────────────────────────────┘
Aave:
┌──────────────────────┐
│ Vault (aToken) │ ← 存款(类 ERC4626,aToken 自动增值)
│ FlashLoan Pool │ ← 闪电贷(池中资金可被闪电贷使用)
│ StakingRewards │ ← stkAAVE 安全模块质押奖励
└──────────────────────┘
Compound:
┌──────────────────────┐
│ Vault (cToken) │ ← 存款 → 铸造 cToken(和 ERC4626 类似的 share 模型)
│ StakingRewards │ ← COMP 代币挖矿奖励(流动性激励)
└──────────────────────┘
三、Solana 基础概念回顾
本周还学习了 Solana 的架构基础和 Anchor 框架。
Solana 核心概念图
Solana 核心概念关系:
Transaction
┌──────────────┐
│ Instruction 1│ ← (programId, accounts[], data)
│ Instruction 2│
│ Instruction 3│
└──────┬───────┘
│
┌───────────┼───────────┐
▼ ▼ ▼
Program 1 Program 2 System Program
(代码) (代码) (创建账户)
│ │
▼ ▼
Data Account Data Account
(状态) (状态)
│
▼
PDA
(程序拥有的
确定性地址)
Solana vs Ethereum 核心差异总结
| 维度 | Ethereum | Solana | PM 视角影响 |
|---|---|---|---|
| 账户模型 | 合约 = 代码+状态 | Program(代码) + Account(状态) 分离 | Solana 状态可以并行读写 → 更高 TPS |
| Gas 模型 | 动态 Gas Price + Gas Limit | 固定基础费 + Priority Fee (compute units) | Solana gas 更便宜且更可预测 |
| 执行模型 | 串行(每次一笔交易) | 并行(Sealevel,不冲突的交易并行执行) | Solana 适合高频场景(DEX/游戏) |
| 合约语言 | Solidity (专用) | Rust (通用) | Solana 开发门槛更高 |
| 状态存储 | 合约 storage slots | 独立的 Account(需付 rent) | Solana 需要预先规划 space |
| Token 标准 | ERC20/721(每个代币一个合约) | SPL Token(单一 program,不同 mint) | Solana 上发币不需要写合约 |
| 访问控制 | msg.sender / onlyOwner | PDA seeds + Signer 验证 | 概念相似但实现完全不同 |
Anchor 框架三大宏回顾
// ===== 1. #[program]: 定义程序指令 =====
#[program]
pub mod my_program {
use super::*;
// 每个 pub fn 就是一个可调用的指令
pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
// 业务逻辑
Ok(())
}
}
// ===== 2. #[derive(Accounts)]: 定义指令需要的账户 =====
#[derive(Accounts)]
pub struct Initialize<'info> {
#[account(init, payer = user, space = 8 + 32)]
pub data: Account<'info, MyData>,
#[account(mut)]
pub user: Signer<'info>,
pub system_program: Program<'info, System>,
}
// Anchor 自动: 验证签名、验证 owner、反序列化数据
// ===== 3. #[account]: 定义数据结构 =====
#[account]
pub struct MyData {
pub authority: Pubkey, // 32 bytes
pub value: u64, // 8 bytes
}
// Anchor 自动: Borsh 序列化/反序列化 + 8 byte discriminator
PDA 核心知识点
PDA (Program Derived Address) 总结:
推导: address = SHA256(seeds, bump, program_id)
必须不在 ed25519 曲线上
Bump:
- 从 255 递减查找
- 第一个有效值 = canonical bump
- 始终使用 canonical bump
Seeds 设计:
- 全局单例: ["config"]
- 用户关联: ["user_data", user_pubkey]
- 多维: ["position", pool, user]
Anchor 约束:
init: 创建新 PDA 账户(seeds + bump + payer + space)
mut: 修改已有 PDA(seeds + bump = account.bump)
has_one: 字段值匹配验证
关键特性:
✓ 确定性: 同 seeds → 同地址
✓ 无私钥: 不可伪造
✓ 程序可签: invoke_signed()
四、DeFi 合约交互关系图
一个完整的 DeFi 生态中,各合约之间的调用关系:
用户视角的 DeFi 交互:
┌─────────┐
│ User │
└────┬────┘
│
┌───────────────┼───────────────┐
▼ ▼ ▼
┌─────────┐ ┌──────────┐ ┌──────────┐
│ DEX │ │ Lending │ │ Yield │
│ (Swap) │ │ (Borrow) │ │ (Farm) │
└────┬────┘ └────┬─────┘ └────┬─────┘
│ │ │
┌────┴────┐ ┌────┴─────┐ ┌────┴─────┐
│ AMM │ │ Vault │ │ Vault │ ← ERC4626
│ Pool │ │ (aToken) │ │ (yToken) │
└────┬────┘ └────┬─────┘ └────┬─────┘
│ │ │
│ ┌────┴──────┐ ┌────┴──────────┐
│ │FlashLoan │ │ StakingRewards │
│ │ Pool │ │ (策略收益) │
│ └───────────┘ └────────────────┘
│
┌────┴──────────┐
│ Price Oracle │ ← Chainlink
└───────────────┘
合约间调用链:
用户 → Vault.deposit()
Vault → Strategy.invest()
Strategy → DEX.swap() (重新平衡)
Strategy → Lending.supply() (赚取利息)
Strategy → FlashLoan.flashLoan() (杠杆操作)
Lending → Oracle.getPrice() (计算抵押率)
五、Solidity 合约安全模式回顾
本周三个合约中反复出现的安全模式:
1. Checks-Effects-Interactions (CEI)
✓ Vault.withdraw(): 检查 shares → 销毁 shares → 转出 assets
✓ FlashLoan: 记录余额 → 转出资金 → 回调 → 检查余额
✓ StakingRewards: 更新奖励 → 修改余额 → 转出代币
2. ReentrancyGuard
✓ Vault: deposit/withdraw 都需要
✓ FlashLoan: flashLoan 函数需要
✓ Staking: stake/withdraw/getReward 需要
3. SafeMath / checked_*
✓ Solidity 0.8+ 默认溢出检查
✓ 但除法仍需注意: 先乘后除避免精度丢失
✓ Vault 的 share 计算: mulDiv 或 OpenZeppelin Math
4. Access Control
✓ Vault: owner 管理策略
✓ Staking: rewardDistributor 设置奖励率
✓ FlashLoan: 任何人可借(但必须还)
5. 精度处理
✓ Vault: 首次存款攻击防护(虚拟偏移量或最小存款)
✓ Staking: rewardPerToken 使用 1e18 精度
✓ FlashLoan: fee 计算避免除法截断
代码实战
Week 6 合约模式速查代码
将三大模式的核心函数放在一起,方便对比学习:
// =================== Vault (ERC4626) 核心 ===================
function deposit(uint256 assets) external returns (uint256 shares) {
// 计算能获得多少 shares
shares = totalSupply() == 0
? assets // 首次存款 1:1
: assets * totalSupply() / totalAssets(); // 按比例
// 转入资产
asset.transferFrom(msg.sender, address(this), assets);
// 铸造 shares 给用户
_mint(msg.sender, shares);
}
function withdraw(uint256 shares) external returns (uint256 assets) {
// 计算能取出多少 assets
assets = shares * totalAssets() / totalSupply();
// 销毁用户的 shares
_burn(msg.sender, shares);
// 转出资产
asset.transfer(msg.sender, assets);
}
// =================== StakingRewards 核心 ===================
// 全局累加器: 每个已质押 token 累计获得的奖励
function rewardPerToken() public view returns (uint256) {
if (totalStaked == 0) return rewardPerTokenStored;
return rewardPerTokenStored +
(rewardRate * (block.timestamp - lastUpdateTime) * 1e18) / totalStaked;
}
// 用户已赚取的奖励
function earned(address account) public view returns (uint256) {
return (staked[account] * (rewardPerToken() - userRewardPerTokenPaid[account])) / 1e18
+ rewards[account];
}
// 修饰符: 每次操作前更新奖励
modifier updateReward(address account) {
rewardPerTokenStored = rewardPerToken();
lastUpdateTime = block.timestamp;
rewards[account] = earned(account);
userRewardPerTokenPaid[account] = rewardPerTokenStored;
_;
}
function stake(uint256 amount) external updateReward(msg.sender) {
totalStaked += amount;
staked[msg.sender] += amount;
stakingToken.transferFrom(msg.sender, address(this), amount);
}
// =================== FlashLoan (EIP3156) 核心 ===================
function flashLoan(
IERC3156FlashBorrower receiver,
address token,
uint256 amount,
bytes calldata data
) external returns (bool) {
uint256 balanceBefore = IERC20(token).balanceOf(address(this));
uint256 fee = amount * feeRate / 10000;
// 1. 转出资金给借款者
IERC20(token).transfer(address(receiver), amount);
// 2. 调用借款者的回调(借款者在这里使用资金并归还)
require(
receiver.onFlashLoan(msg.sender, token, amount, fee, data)
== keccak256("ERC3156FlashBorrower.onFlashLoan"),
"Invalid callback return"
);
// 3. 验证资金已归还 + 手续费
uint256 balanceAfter = IERC20(token).balanceOf(address(this));
require(balanceAfter >= balanceBefore + fee, "Repay failed");
return true;
}
Solana/Anchor 模式速查代码
// =================== Anchor 程序结构 ===================
use anchor_lang::prelude::*;
declare_id!("...");
#[program]
pub mod my_program {
use super::*;
pub fn initialize(ctx: Context<Initialize>, param: u64) -> Result<()> {
let account = &mut ctx.accounts.my_account;
account.authority = ctx.accounts.user.key();
account.value = param;
account.bump = ctx.bumps.my_account;
Ok(())
}
pub fn update(ctx: Context<Update>, new_value: u64) -> Result<()> {
ctx.accounts.my_account.value = new_value;
Ok(())
}
}
// =================== 账户约束模板 ===================
// 创建 PDA 账户
#[derive(Accounts)]
pub struct Initialize<'info> {
#[account(
init, // 创建
payer = user, // 付 rent
space = 8 + 32 + 8 + 1, // discriminator + fields
seeds = [b"my_account", user.key().as_ref()], // PDA seeds
bump // canonical bump
)]
pub my_account: Account<'info, MyAccount>,
#[account(mut)]
pub user: Signer<'info>,
pub system_program: Program<'info, System>,
}
// 修改 PDA 账户
#[derive(Accounts)]
pub struct Update<'info> {
#[account(
mut,
seeds = [b"my_account", user.key().as_ref()],
bump = my_account.bump,
has_one = authority @ MyError::Unauthorized // 权限检查
)]
pub my_account: Account<'info, MyAccount>,
pub authority: Signer<'info>, // = my_account.authority
}
// 数据结构
#[account]
pub struct MyAccount {
pub authority: Pubkey, // 32
pub value: u64, // 8
pub bump: u8, // 1
}
// 错误码
#[error_code]
pub enum MyError {
#[msg("Unauthorized")]
Unauthorized,
}
自测 Quiz
Part 1: DeFi 合约模式 (10 题)
Q1: Vault 的 share 计算 如果一个 Vault 中有 1000 USDC 和 800 shares,Alice 存入 250 USDC,她获得多少 shares?
<details> <summary>答案</summary>shares = depositAmount × totalShares / totalAssets
= 250 × 800 / 1000
= 200 shares
验证: 存入后 Vault 有 1250 USDC, 1000 shares
Alice 的 200 shares = 200/1000 × 1250 = 250 USDC ✓
</details>
Q2: StakingRewards 累加器 Alice 质押了 100 tokens,Bob 质押了 400 tokens。rewardRate = 10 tokens/sec。过了 50 秒后,Alice 能获得多少奖励?
<details> <summary>答案</summary>totalStaked = 100 + 400 = 500
rewardPerToken = (10 × 50) / 500 = 1.0
Alice earned = 100 × 1.0 = 100 tokens
Bob earned = 400 × 1.0 = 400 tokens
总奖励 = 500 = 10 × 50 ✓ (符合)
Alice 占比 = 100/500 = 20% → 500 × 20% = 100 ✓
</details>
Q3: FlashLoan 盈亏计算 用 Aave 闪电贷借了 500,000 USDC (fee rate 0.09%),在 DEX A 买入 ETH,在 DEX B 卖出,获得 501,000 USDC。Gas 费 50 USDC。这笔套利赚了多少?
<details> <summary>答案</summary>闪电贷手续费 = 500,000 × 0.0009 = 450 USDC
总成本 = 450 (手续费) + 50 (gas) = 500 USDC
总收入 = 501,000 - 500,000 = 1,000 USDC
净利润 = 1,000 - 500 = 500 USDC
如果 DEX B 只卖了 500,400 USDC:
利润 = 400 - 500 = -100 → 亏损
但闪电贷会 revert!因为 500,400 < 500,450 (本金+手续费)
→ 实际不会亏损,交易直接失败(只损失 gas)
</details>
Q4: 为什么 ERC4626 Vault 有首次存款攻击风险?
<details> <summary>答案</summary>攻击过程:
1. Vault 为空,Alice 准备存入 10,000 USDC
2. 攻击者抢先存入 1 wei USDC,获得 1 wei shares
3. 攻击者直接往 Vault 转 10,000 USDC (不通过 deposit)
4. 现在: totalAssets = 10,000 + 1 wei, totalShares = 1 wei
5. Alice 存入 10,000 USDC:
shares = 10,000 × 1 / 10,000 ≈ 1 (向下取整可能为 0!)
6. Alice 可能获得 0 shares → 损失全部资金
防护:
a) 虚拟偏移量: 内部乘以 1e6 计算
b) 初始注入: 部署时预存 dead shares
c) 最小存款限制
</details>
Q5: StakingRewards 中,为什么要用 modifier updateReward 而不是在 view 函数中实时计算?
<details> <summary>答案</summary>view 函数 earned() 确实实时计算,但只是"查看"。
modifier updateReward 的作用:
1. 将计算结果持久化到 storage(rewardPerTokenStored, rewards[user])
2. 这是因为 solidity 无法在 view 函数中写入 storage
3. 如果不持久化,每次质押/取出时都要从 T=0 重新累加 → gas 随时间线性增长
本质: 这是一个 "lazy evaluation" 模式
→ 只在用户交互时更新(O(1) 写操作)
→ 不需要后台定时任务遍历所有用户
</details>
Q6: FlashLoan 中的 onFlashLoan 回调为什么要返回特定的 keccak256 哈希?
<details> <summary>答案</summary>return keccak256("ERC3156FlashBorrower.onFlashLoan");
目的: 防止"意外接收"攻击
场景: 如果没有返回值检查
1. 攻击者部署一个没有 onFlashLoan 函数的合约
2. Solidity 的 fallback 函数被调用,不 revert
3. 资金被转到了一个不会归还的合约
有了返回值检查:
→ 借款者必须显式实现 onFlashLoan 并返回正确的值
→ 确保借款者"知道自己在做什么"
→ 类似于 ERC721 的 onERC721Received 检查
</details>
Part 2: Solana 基础 (8 题)
Q7: Solana 的 Account 模型中,以下哪个说法是正确的? A) Program 和 Data Account 存储在一起 B) Program 是可变的,可以修改自身代码 C) Data Account 的 owner 决定了谁能修改它的数据 D) 每个 Account 可以有多个 owner
<details> <summary>答案</summary>C) Data Account 的 owner 决定了谁能修改它的数据
A ✗: 代码与数据分离,这是 Solana 的核心设计
B ✗: Program 部署后默认不可变(除非用 upgradeable loader)
C ✓: 只有 owner program 能修改 Account 的 data 字段
D ✗: 每个 Account 有且只有一个 owner
</details>
Q8: PDA 的 bump seed 是什么?为什么从 255 开始找?
<details> <summary>答案</summary>Bump 是一个 0-255 的整数,附加到 seeds 中用于生成 PDA。
目的: 确保生成的地址不在 ed25519 曲线上
从 255 开始的原因:
1. 保证确定性: 所有客户端都从 255 开始 → 找到的第一个有效 bump 一致
2. 这个第一个有效 bump = "canonical bump"
3. 如果从 0 开始找,也能工作,但社区约定从 255 开始
4. 高 bump 值不在曲线上的概率约 50%,通常 1-2 次就找到
实际: 大多数情况下 bump = 255 或 254
</details>
Q9: 在 Anchor 中,以下代码的 space 应该是多少?
#[account]
pub struct UserProfile {
pub name: String, // 最大 32 字符
pub level: u16,
pub is_active: bool,
pub wallet: Pubkey,
}
<details>
<summary>答案</summary>
space = 8 // Anchor discriminator
+ (4 + 32) // String: 4 bytes length prefix + 32 bytes content
+ 2 // u16
+ 1 // bool
+ 32 // Pubkey
= 79 bytes
注意:
- String 的 4 bytes 是长度前缀(Borsh 序列化格式)
- 如果 name 可能超过 32 字符,需要增加分配
- 宁可多分配一点,不够会导致序列化失败
</details>
Q10: 为什么 Solana 上 Counter 用 PDA 而不是让用户自己创建一个普通 Account?
<details> <summary>答案</summary>1. 确定性寻址:
PDA = f(seeds, programId) → 客户端可以直接计算地址
普通 Account = 随机 keypair → 需要额外存储映射关系
2. 安全性:
PDA 没有私钥 → 只有程序能通过 CPI 签名
普通 Account 有私钥 → 谁持有私钥谁就能直接修改
3. 一致性:
seeds = ["counter", user] → 每个用户恰好一个 counter
普通 Account → 用户可以创建多个(如何判断哪个是"官方的"?)
4. 可组合性:
其他程序可以通过 seeds 计算出 PDA → 直接读取数据
普通 Account → 必须通过链下索引查找
</details>
Part 3: 跨链对比 (4 题)
Q11: 把 Solidity 的 Vault 迁移到 Solana/Anchor,最大的架构差异是什么?
<details> <summary>答案</summary>Solidity Vault:
- 一个合约 = 代码 + 所有状态
- mapping(address => uint256) shares; ← 所有用户的 shares 在一个 slot 树中
- totalAssets() 直接读自身余额
Solana Vault:
- Program = 只有代码
- 每个用户的 deposit → 一个独立的 PDA Account
- Vault 全局状态 → 另一个 PDA
- Token 余额 → PDA 拥有的 Token Account (又一个独立账户)
最大差异: 状态的拆分方式
Solidity: 一个合约里 → 串行读写
Solana: 多个 Account → 可以并行读写不同用户的账户
影响:
- Solana 需要预先声明所有要读写的 Account(显式)
- 但获得了并行执行的性能优势
- 编程模型更复杂,但扩展性更好
</details>
Q12: Solana 上能实现 FlashLoan 吗?和 Ethereum 有什么区别?
<details> <summary>答案</summary>可以!但实现方式不同。
Ethereum FlashLoan:
→ 利用 EVM 的 "内部调用" 机制
→ 合约 A 调用合约 B → B 使用资金 → 回到 A 检查余额
→ 在一个 message call 栈中完成
Solana FlashLoan:
→ 利用 Transaction 的 "多 Instruction" 特性
→ Instruction 1: borrow(amount) — 从池子转出
→ Instruction 2-N: 用户自定义操作(swap, liquidate, etc.)
→ Instruction N+1: repay(amount + fee) — 归还
→ 如果最后余额不够 → 整个 Transaction revert
区别:
Ethereum: 回调模式(borrower 实现 onFlashLoan)
Solana: 多指令模式(在 Transaction 中编排多个 Instruction)
Solana 模式更灵活(不需要部署特殊的 borrower 合约)
</details>
Q13: 为什么说 Solana 的 PDA 是"可组合性"的关键?
<details> <summary>答案</summary>可组合性 = 不同协议之间能互相调用和读取数据
PDA 使可组合性成为可能:
1. 任何程序只要知道 seeds → 就能计算出 PDA 地址 → 读取数据
2. 不需要链下索引、不需要预言机、不需要额外的注册表
例子: DEX 程序想读取 Lending 协议中用户的抵押率
Ethereum: 调用 Lending 合约的 view 函数
Solana: 计算 PDA 地址 → 直接从 Account 数据反序列化
差异:
Ethereum 的可组合性: 通过合约调用(需要知道合约地址和 ABI)
Solana 的可组合性: 通过 PDA 推导(只需知道 seeds 约定和数据结构)
两者都很强,但 Solana 的方式更"去中心化"(不依赖合约代码的可调用性)
</details>
Q14: 如果你是 PM,在选择 Ethereum 还是 Solana 时,从架构角度有哪些考量?
<details> <summary>答案</summary>选 Ethereum (及 L2) 的场景:
- DeFi 蓝筹协议(TVL 大,安全优先)
- 需要和大量现有协议组合(Uniswap/Aave/Compound)
- 用户群体习惯 MetaMask + EVM 链
- 合约逻辑复杂但交易频率不高
选 Solana 的场景:
- 高频交易/订单簿 DEX(需要高 TPS 低延迟)
- 游戏/NFT(需要低 gas 和快速确认)
- 消费级应用(用户对 gas 敏感)
- 需要并行处理大量用户操作
PM 决策框架:
1. 目标用户在哪条链?→ 去用户在的地方
2. 需要与哪些协议组合?→ 生态决定平台
3. TPS 和延迟要求?→ 高频 → Solana
4. 开发者资源?→ Solidity 人才多于 Rust/Anchor
5. 安全优先级?→ EVM 工具链更成熟(审计/形式验证)
</details>
关键要点总结
Week 6 的三个核心收获
1. DeFi 合约不是孤立的
→ Vault + Staking + FlashLoan 是 DeFi 的三块基石
→ 理解它们的组合方式 = 理解 DeFi 协议的架构
2. Solana 和 Ethereum 是两种完全不同的编程范式
→ Ethereum: 合约中心,状态内聚,串行执行
→ Solana: 账户中心,状态分散,并行执行
→ 选择取决于产品需求,不是"哪个更好"
3. PDA 是 Solana 的灵魂
→ 类似于 Ethereum 的 mapping,但更强大(可签名、跨程序寻址)
→ 掌握 PDA = 掌握 Solana 编程的核心
下周预告 (Week 7)
Day 39: SimpleAMM — 恒定乘积公式实现 ← DeFi 核心中的核心
Day 40: SPL Token + ATA + Token 铸造 ← Solana Token 体系
Day 41: MultiSig Wallet ← 安全模式
Day 42: Governance + Timelock ← DAO 治理
Day 43: Oracle Integration ← 价格预言机
Day 44: Week 7 复习 ← 总结
常见误区
误区: "学了 Solidity 就不需要学 Solana,反之亦然"
✗ 错误: 只精通一个就够了
✓ 正确: 两种模型的对比学习能加深对底层原理的理解
为什么要两个都学:
1. 面试: 很多公司会问"你了解 Solana 吗?和 EVM 有什么区别?"
2. 产品设计: 多链战略需要理解每条链的 trade-off
3. 心智模型: 对比才能真正理解"为什么这样设计"
- 只看 Ethereum → 觉得"合约存状态"是理所当然的
- 看了 Solana → 才理解"代码和数据分离"的另一种哲学
误区: "FlashLoan 只能用来攻击"
✗ 错误: FlashLoan 是黑客工具
✓ 正确: FlashLoan 是资本效率的极致工具
正当用途:
- 套利: 维持市场价格一致性(市场有益)
- 自清算: 避免被协议惩罚清算
- 抵押品切换: 一步完成仓位调整
- Yield 优化: 闪电贷杠杆提升收益
攻击只是滥用,不是本质用途
就像菜刀不是因为有人拿来伤人就变成了武器
面试关联
Q: 如果让你从零设计一个 DeFi 借贷协议,你会用到本周学的哪些模式?
完整借贷协议的架构:
1. Vault (ERC4626) — 存款池
→ 用户存入 USDC → 获得 aUSDC (share token)
→ aUSDC 的价值随利息累积自动增长
2. StakingRewards — 流动性激励
→ 质押 aUSDC → 获得协议治理代币奖励
→ 吸引更多存款 → TVL 增长
3. FlashLoan — 资金利用率
→ 池中闲置资金可被闪电贷使用
→ 手续费收入分给存款者
→ 提高 APY → 吸引更多存款
4. Oracle — 价格数据
→ 计算抵押率和清算线
→ 不是本周内容,但必不可少
5. Liquidation — 清算机制
→ 结合 FlashLoan 实现无资本清算
→ 保护协议偿付能力
参考资源
| 资源 | 说明 |
|---|---|
| ERC-4626 标准 | Vault 标准规范 |
| Synthetix StakingRewards | Staking 参考实现 |
| EIP-3156 FlashLoan | FlashLoan 标准 |
| Solana Cookbook | Solana 开发参考 |
| Anchor Book | Anchor 框架文档 |
| Solana vs Ethereum | 官方开发者资源 |