defi-protocol-templates ยท diff
git:20260307.47a5dbc to git:20260522.be57c0b
2 added, 188 removed. Audit A to A.
---
name: defi-protocol-templates
description: Implement DeFi protocols with production-ready templates for staking, AMMs, governance, and lending systems. Use when building decentralized finance applications or smart contract protocols.
---
# DeFi Protocol Templates
Production-ready templates for common DeFi protocols including staking, AMMs, governance, lending, and flash loans.
## When to Use This Skill
- Building staking platforms with reward distribution
- Implementing AMM (Automated Market Maker) protocols
- Creating governance token systems
- Developing lending/borrowing protocols
- Integrating flash loan functionality
- Launching yield farming platforms
## Staking Contract
```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
contract StakingRewards is ReentrancyGuard, Ownable {
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.transferFrom(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.transfer(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.transfer(msg.sender, reward);
emit RewardPaid(msg.sender, reward);
}
}
function exit() external {
withdraw(balances[msg.sender]);
getReward();
}
}
```
## AMM (Automated Market Maker)
```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
contract SimpleAMM {
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 returns (uint256 shares) {
token0.transferFrom(msg.sender, address(this), amount0);
token1.transferFrom(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 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.transfer(msg.sender, amount0);
token1.transfer(msg.sender, amount1);
emit Burn(msg.sender, shares);
}
function swap(address tokenIn, uint256 amountIn) external returns (uint256 amountOut) {
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_.transferFrom(msg.sender, address(this), amountIn);
// 0.3% fee
uint256 amountInWithFee = (amountIn * 997) / 1000;
amountOut = (resOut * amountInWithFee) / (resIn + amountInWithFee);
tokenOut.transfer(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;
}
}
```
- ## Governance Token
-
- ```solidity
- // 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(block.number >= proposal.startBlock, "Voting not started");
- 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");
-
- proposal.executed = true;
-
- // Execute proposal logic here
-
- emit ProposalExecuted(proposalId);
- }
- }
- ```
-
- ## Flash Loan
-
- ```solidity
- // SPDX-License-Identifier: MIT
- pragma solidity ^0.8.0;
-
- import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
-
- interface IFlashLoanReceiver {
- function executeOperation(
- address asset,
- uint256 amount,
- uint256 fee,
- bytes calldata params
- ) external returns (bool);
- }
-
- contract FlashLoanProvider {
- 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 {
- uint256 balanceBefore = token.balanceOf(address(this));
- require(balanceBefore >= amount, "Insufficient liquidity");
-
- uint256 fee = (amount * feePercentage) / 10000;
-
- // Send tokens to receiver
- token.transfer(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 {
- function executeOperation(
- address asset,
- uint256 amount,
- uint256 fee,
- bytes calldata params
- ) external override returns (bool) {
- // Decode params and execute arbitrage, liquidation, etc.
- // ...
+ ## Additional patterns and templates
- // Approve repayment
- IERC20(asset).approve(msg.sender, amount + fee);
+ More detailed templates and worked examples live in `references/details.md`. Read that file for the full pattern library.
- return true;
- }
- }
- ```