关于 defi-protocol-templates
defi-protocol-templates 面向学习常见 DeFi 合约模式的开发者,涵盖质押、AMM、治理、借贷和闪电贷。内嵌示例仅供教学且并不完整,引用的配套文件未随附;内容强调部署前测试和独立审查。
主要功能
- 01质押与 AMM 模式示例
- 02治理代币和闪电贷示例
- 03测试与安全审查指导
- 04说明缺失的配套文件及合约限制
适用场景
- 01学习常见 DeFi 合约设计
- 02为质押或流动性池概念制作原型
- 03实现前检查安全注意事项
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ sickn33/ agentic-awesome-skills --skill defi-protocol-templates安装后在 Claude Code 中输入/defi-protocol-templates确认已加载。
详细介绍
支持材料可用性
本目录副本包含以下内联指南,但不包含下列支持文件路径。这些是外部前置条件,并非随附文件:
<support-root>/references/staking.md<support-root>/references/liquidity-pools.md<support-root>/references/governance-tokens.md<support-root>/references/lending-protocols.md<support-root>/references/flash-loans.md<support-root>/assets/staking-contract.sol<support-root>/assets/amm-contract.sol<support-root>/assets/governance-token.sol<support-root>/assets/lending-protocol.sol
使用依赖这些路径的步骤前,请索取原始支持材料或现有安装;本目录未提供经过验证的下载来源。请根据该安装解析每个路径,检查其内容,并确认适用的许可证及执行前置条件。在所需文件存在之前,不要运行命令,也不要声称已遵循相关流程。不要伪造缺失脚本,也不要将同名的无关文件视为等效文件。此处完整记录的独立步骤可在用户授权范围内继续执行;请报告任何无法执行的依赖步骤。
DeFi 协议模板
涵盖常见 DeFi 协议的教学示例,包括质押、AMM、治理、借贷和闪电贷。
以下情况请勿使用此技能
- 任务与 DeFi 协议模板无关
- 需要使用此范围之外的其他领域或工具
说明
- 明确目标、限制和所需输入。
- 应用相关最佳实践并验证结果。
- 提供可执行的步骤和验证方法。
以下情况请使用此技能
- 构建带奖励分发功能的质押平台
- 实现 AMM(自动做市商)协议
- 创建治理代币系统
- 开发借贷协议
- 集成闪电贷功能
- 推出收益耕作平台
这些内联合约是不完整的教学示例,并非经过审计或注资的协议。它们使用 OpenZeppelin 4.x 的导入和继承 API;请固定使用经过审查且兼容的版本,不要盲目安装 5.x。切勿使用用户资金部署。质押合约的固定奖励率没有上限,也没有资金或周期管理;AMM 缺少生产级的储备金、预言机和 LP 防护措施;Governor 没有法定人数、时间锁或操作载荷;闪电贷需要针对特定接收方的可信回调。在设计部署方案前,请先编译、对记账、重入和舍入进行属性测试及模糊测试,并取得独立审查。代币单位和小数位数各不相同,不支持收取转账手续费的代币或弹性供应代币。
质押合约
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
contract StakingRewards is ReentrancyGuard, Ownable {
using SafeERC20 for IERC20;
IERC20 public stakingToken;
IERC20 public rewardsToken;
uint256 public rewardRate = 100; // Rewards per second
uint256 public lastUpdateTime;
uint256 public rewardPerTokenStored;
mapping(address => uint256) public userRewardPerTokenPaid;
mapping(address => uint256) public rewards;
mapping(address => uint256) public balances;
uint256 private _totalSupply;
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 _rewardsToken) {
stakingToken = IERC20(_stakingToken);
rewardsToken = IERC20(_rewardsToken);
}
modifier updateReward(address account) {
rewardPerTokenStored = rewardPerToken();
lastUpdateTime = block.timestamp;
if (account != address(0)) {
rewards[account] = earned(account);
userRewardPerTokenPaid[account] = rewardPerTokenStored;
}
_;
}
function rewardPerToken() public view returns (uint256) {
if (_totalSupply == 0) {
return rewardPerTokenStored;
}
return rewardPerTokenStored +
((block.timestamp - lastUpdateTime) * rewardRate * 1e18) / _totalSupply;
}
function earned(address account) public view returns (uint256) {
return (balances[account] *
(rewardPerToken() - userRewardPerTokenPaid[account])) / 1e18 +
rewards[account];
}
function stake(uint256 amount) external nonReentrant updateReward(msg.sender) {
require(amount > 0, "Cannot stake 0");
_totalSupply += amount;
balances[msg.sender] += amount;
stakingToken.safeTransferFrom(msg.sender, address(this), amount);
emit Staked(msg.sender, amount);
}
function withdraw(uint256 amount) public nonReentrant updateReward(msg.sender) {
require(amount > 0, "Cannot withdraw 0");
_totalSupply -= amount;
balances[msg.sender] -= amount;
stakingToken.safeTransfer(msg.sender, amount);
emit Withdrawn(msg.sender, amount);
}
function getReward() public nonReentrant updateReward(msg.sender) {
uint256 reward = rewards[msg.sender];
if (reward > 0) {
rewards[msg.sender] = 0;
rewardsToken.safeTransfer(msg.sender, reward);
emit RewardPaid(msg.sender, reward);
}
}
function exit() external {
withdraw(balances[msg.sender]);
getReward();
}
}
AMM(自动做市商)
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
contract SimpleAMM is ReentrancyGuard {
using SafeERC20 for IERC20;
IERC20 public token0;
IERC20 public token1;
uint256 public reserve0;
uint256 public reserve1;
uint256 public totalSupply;
mapping(address => uint256) public balanceOf;
event Mint(address indexed to, uint256 amount);
event Burn(address indexed from, uint256 amount);
event Swap(address indexed trader, uint256 amount0In, uint256 amount1In, uint256 amount0Out, uint256 amount1Out);
constructor(address _token0, address _token1) {
token0 = IERC20(_token0);
token1 = IERC20(_token1);
}
function addLiquidity(uint256 amount0, uint256 amount1) external nonReentrant returns (uint256 shares) {
token0.safeTransferFrom(msg.sender, address(this), amount0);
token1.safeTransferFrom(msg.sender, address(this), amount1);
if (totalSupply == 0) {
shares = sqrt(amount0 * amount1);
} else {
shares = min(
(amount0 * totalSupply) / reserve0,
(amount1 * totalSupply) / reserve1
);
}
require(shares > 0, "Shares = 0");
_mint(msg.sender, shares);
_update(
token0.balanceOf(address(this)),
token1.balanceOf(address(this))
);
emit Mint(msg.sender, shares);
}
function removeLiquidity(uint256 shares) external nonReentrant returns (uint256 amount0, uint256 amount1) {
uint256 bal0 = token0.balanceOf(address(this));
uint256 bal1 = token1.balanceOf(address(this));
amount0 = (shares * bal0) / totalSupply;
amount1 = (shares * bal1) / totalSupply;
require(amount0 > 0 && amount1 > 0, "Amount0 or amount1 = 0");
_burn(msg.sender, shares);
_update(bal0 - amount0, bal1 - amount1);
token0.safeTransfer(msg.sender, amount0);
token1.safeTransfer(msg.sender, amount1);
emit Burn(msg.sender, shares);
}
function swap(address tokenIn, uint256 amountIn, uint256 minAmountOut, uint256 deadline) external nonReentrant returns (uint256 amountOut) {
require(block.timestamp <= deadline && amountIn > 0, "Expired or zero input");
require(tokenIn == address(token0) || tokenIn == address(token1), "Invalid token");
bool isToken0 = tokenIn == address(token0);
(IERC20 tokenIn_, IERC20 tokenOut, uint256 resIn, uint256 resOut) = isToken0
? (token0, token1, reserve0, reserve1)
: (token1, token0, reserve1, reserve0);
tokenIn_.safeTransferFrom(msg.sender, address(this), amountIn);
// 0.3% fee
uint256 amountInWithFee = (amountIn * 997) / 1000;
amountOut = (resOut * amountInWithFee) / (resIn + amountInWithFee);
require(amountOut >= minAmountOut && amountOut > 0, "Slippage or zero output");
tokenOut.safeTransfer(msg.sender, amountOut);
_update(
token0.balanceOf(address(this)),
token1.balanceOf(address(this))
);
emit Swap(msg.sender, isToken0 ? amountIn : 0, isToken0 ? 0 : amountIn, isToken0 ? 0 : amountOut, isToken0 ? amountOut : 0);
}
function _mint(address to, uint256 amount) private {
balanceOf[to] += amount;
totalSupply += amount;
}
function _burn(address from, uint256 amount) private {
balanceOf[from] -= amount;
totalSupply -= amount;
}
function _update(uint256 res0, uint256 res1) private {
reserve0 = res0;
reserve1 = res1;
}
function sqrt(uint256 y) private pure returns (uint256 z) {
if (y > 3) {
z = y;
uint256 x = y / 2 + 1;
while (x < z) {
z = x;
x = (y / x + x) / 2;
}
} else if (y != 0) {
z = 1;
}
}
function min(uint256 x, uint256 y) private pure returns (uint256) {
return x <= y ? x : y;
}
}
治理代币
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Votes.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
contract GovernanceToken is ERC20Votes, Ownable {
constructor() ERC20("Governance Token", "GOV") ERC20Permit("Governance Token") {
_mint(msg.sender, 1000000 * 10**decimals());
}
function _afterTokenTransfer(
address from,
address to,
uint256 amount
) internal override(ERC20Votes) {
super._afterTokenTransfer(from, to, amount);
}
function _mint(address to, uint256 amount) internal override(ERC20Votes) {
super._mint(to, amount);
}
function _burn(address account, uint256 amount) internal override(ERC20Votes) {
super._burn(account, amount);
}
}
contract Governor is Ownable {
GovernanceToken public governanceToken;
struct Proposal {
uint256 id;
address proposer;
string description;
uint256 forVotes;
uint256 againstVotes;
uint256 startBlock;
uint256 endBlock;
bool executed;
mapping(address => bool) hasVoted;
}
uint256 public proposalCount;
mapping(uint256 => Proposal) public proposals;
uint256 public votingPeriod = 17280; // ~3 days in blocks
uint256 public proposalThreshold = 100000 * 10**18;
event ProposalCreated(uint256 indexed proposalId, address proposer, string description);
event VoteCast(address indexed voter, uint256 indexed proposalId, bool support, uint256 weight);
event ProposalExecuted(uint256 indexed proposalId);
constructor(address _governanceToken) {
governanceToken = GovernanceToken(_governanceToken);
}
function propose(string memory description) external returns (uint256) {
require(
governanceToken.getPastVotes(msg.sender, block.number - 1) >= proposalThreshold,
"Proposer votes below threshold"
);
proposalCount++;
Proposal storage newProposal = proposals[proposalCount];
newProposal.id = proposalCount;
newProposal.proposer = msg.sender;
newProposal.description = description;
newProposal.startBlock = block.number;
newProposal.endBlock = block.number + votingPeriod;
emit ProposalCreated(proposalCount, msg.sender, description);
return proposalCount;
}
function vote(uint256 proposalId, bool support) external {
Proposal storage proposal = proposals[proposalId];
require(proposalId > 0 && proposalId <= proposalCount, "Unknown proposal");
require(block.number > proposal.startBlock, "Snapshot not yet historical");
require(block.number <= proposal.endBlock, "Voting ended");
require(!proposal.hasVoted[msg.sender], "Already voted");
uint256 weight = governanceToken.getPastVotes(msg.sender, proposal.startBlock);
require(weight > 0, "No voting power");
proposal.hasVoted[msg.sender] = true;
if (support) {
proposal.forVotes += weight;
} else {
proposal.againstVotes += weight;
}
emit VoteCast(msg.sender, proposalId, support, weight);
}
function execute(uint256 proposalId) external {
Proposal storage proposal = proposals[proposalId];
require(block.number > proposal.endBlock, "Voting not ended");
require(!proposal.executed, "Already executed");
require(proposal.forVotes > proposal.againstVotes, "Proposal failed");
revert("Teaching stub: no action execution/quorum/timelock implemented");
}
}
闪电贷
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
interface IFlashLoanReceiver {
function executeOperation(
address asset,
uint256 amount,
uint256 fee,
bytes calldata params
) external returns (bool);
}
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
contract FlashLoanProvider is ReentrancyGuard {
using SafeERC20 for IERC20;
IERC20 public token;
uint256 public feePercentage = 9; // 0.09% fee
event FlashLoan(address indexed borrower, uint256 amount, uint256 fee);
constructor(address _token) {
token = IERC20(_token);
}
function flashLoan(
address receiver,
uint256 amount,
bytes calldata params
) external nonReentrant {
uint256 balanceBefore = token.balanceOf(address(this));
require(balanceBefore >= amount, "Insufficient liquidity");
uint256 fee = (amount * feePercentage) / 10000;
// Send tokens to receiver
token.safeTransfer(receiver, amount);
// Execute callback
require(
IFlashLoanReceiver(receiver).executeOperation(
address(token),
amount,
fee,
params
),
"Flash loan failed"
);
// Verify repayment
uint256 balanceAfter = token.balanceOf(address(this));
require(balanceAfter >= balanceBefore + fee, "Flash loan not repaid");
emit FlashLoan(receiver, amount, fee);
}
}
// Example flash loan receiver
contract FlashLoanReceiver is IFlashLoanReceiver {
using SafeERC20 for IERC20;
address public immutable provider;
constructor(address trustedProvider) { provider = trustedProvider; }
function executeOperation(
address asset,
uint256 amount,
uint256 fee,
bytes calldata params
) external override returns (bool) {
require(msg.sender == provider, "Untrusted lender");
// Decode params and execute arbitrage, liquidation, etc.
// ...
// This lender checks its final balance; approval alone does not repay.
IERC20(asset).safeTransfer(provider, amount + fee);
return true;
}
}
资源
- references/staking.md:质押机制和奖励分发
- references/liquidity-pools.md:AMM 数学原理和定价
- references/governance-tokens.md:治理和投票系统
- references/lending-protocols.md:借贷实现
- references/flash-loans.md:闪电贷安全和用例
- assets/staking-contract.sol:生产级质押模板
- assets/amm-contract.sol:完整的 AMM 实现
- assets/governance-token.sol:治理系统
- assets/lending-protocol.sol:借贷平台模板
最佳实践
- 使用成熟库:OpenZeppelin、Solmate
- 充分测试:单元测试、集成测试、模糊测试
- 发布前审计:专业安全审计
- 从简单开始:先实现 MVP,再逐步添加功能
- 监控:跟踪合约健康状况和用户活动
- 可升级性:考虑使用代理模式进行升级
- 紧急控制:针对严重问题设置暂停机制
常见 DeFi 模式
- 时间加权平均价格(TWAP):抵御预言机攻击
- 流动性挖矿:激励用户提供流动性
- 归属期:锁定代币并逐步释放
- 多重签名:关键操作需要多个签名
- 时间锁:延迟执行治理决策
限制
- 仅当任务明确符合上述范围时,才使用此技能。
- 不要将输出视为环境特定验证、测试或专家审核的替代品。
- 如果缺少必需的输入、权限、安全边界或成功标准,请暂停并请求澄清。
评论
支持 Markdown