<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/">
    <channel>
        <title>Earnest_stu</title>
        <link>https://paragraph.com/@Earnest_stus</link>
        <description>undefined</description>
        <lastBuildDate>Fri, 05 Jun 2026 17:23:24 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <language>en</language>
        <copyright>All rights reserved</copyright>
        <item>
            <title><![CDATA[MiniDexPair 合约学习笔记]]></title>
            <link>https://paragraph.com/@Earnest_stus/minidexpair-合约学习笔记</link>
            <guid>Vzbb7UPKgSelKsd7Lcii</guid>
            <pubDate>Sat, 08 Nov 2025 16:13:59 GMT</pubDate>
            <description><![CDATA[// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "@openzeppelin/contracts/security/ReentrancyGuard.sol"; contract MiniDexPair is ReentrancyGuard { address public immutable tokenA; address public immutable tokenB; uint256 public reserveA; uint256 public reserveB; uint256 public totalLPSupply; mapping(address => uint256) public lpBalances; event LiquidityAdded(address indexed provider, uint256 amountA, uint256 amountB, uint...]]></description>
            <content:encoded><![CDATA[<pre data-type="codeBlock" text=" 
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import &quot;@openzeppelin/contracts/token/ERC20/IERC20.sol&quot;;
import &quot;@openzeppelin/contracts/security/ReentrancyGuard.sol&quot;;

contract MiniDexPair is ReentrancyGuard {
    address public immutable tokenA;
    address public immutable tokenB;

    uint256 public reserveA;
    uint256 public reserveB;
    uint256 public totalLPSupply;

    mapping(address =&gt; uint256) public lpBalances;

    event LiquidityAdded(address indexed provider, uint256 amountA, uint256 amountB, uint256 lpMinted);
    event LiquidityRemoved(address indexed provider, uint256 amountA, uint256 amountB, uint256 lpBurned);
    event Swapped(address indexed user, address inputToken, uint256 inputAmount, address outputToken, uint256 outputAmount);

    constructor(address _tokenA, address _tokenB) {
        require(_tokenA != _tokenB, &quot;Identical tokens&quot;);
        require(_tokenA != address(0) &amp;&amp; _tokenB != address(0), &quot;Zero address&quot;);

        tokenA = _tokenA;
        tokenB = _tokenB;
    }

    // Utilities
    function sqrt(uint y) internal pure returns (uint z) {
        if (y &gt; 3) {
            z = y;
            uint x = y / 2 + 1;
            while (x &lt; z) {
                z = x;
                x = (y / x + x) / 2;
            }
        } else if (y != 0) {
            z = 1;
        }
    }

    function min(uint256 a, uint256 b) internal pure returns (uint256) {
        return a &lt; b ? a : b;
    }

    function _updateReserves() private {
        reserveA = IERC20(tokenA).balanceOf(address(this));
        reserveB = IERC20(tokenB).balanceOf(address(this));
    }

    function addLiquidity(uint256 amountA, uint256 amountB) external nonReentrant {
        require(amountA &gt; 0 &amp;&amp; amountB &gt; 0, &quot;Invalid amounts&quot;);

        IERC20(tokenA).transferFrom(msg.sender, address(this), amountA);
        IERC20(tokenB).transferFrom(msg.sender, address(this), amountB);

        uint256 lpToMint;
        if (totalLPSupply == 0) {
            lpToMint = sqrt(amountA * amountB);
        } else {
            lpToMint = min(
                (amountA * totalLPSupply) / reserveA,
                (amountB * totalLPSupply) / reserveB
            );
        }

        require(lpToMint &gt; 0, &quot;Zero LP minted&quot;);

        lpBalances[msg.sender] += lpToMint;
        totalLPSupply += lpToMint;

        _updateReserves();

        emit LiquidityAdded(msg.sender, amountA, amountB, lpToMint);
    }

    function removeLiquidity(uint256 lpAmount) external nonReentrant {
        require(lpAmount &gt; 0 &amp;&amp; lpAmount &lt;= lpBalances[msg.sender], &quot;Invalid LP amount&quot;);

        uint256 amountA = (lpAmount * reserveA) / totalLPSupply;
        uint256 amountB = (lpAmount * reserveB) / totalLPSupply;

        lpBalances[msg.sender] -= lpAmount;
        totalLPSupply -= lpAmount;

        IERC20(tokenA).transfer(msg.sender, amountA);
        IERC20(tokenB).transfer(msg.sender, amountB);

        _updateReserves();

        emit LiquidityRemoved(msg.sender, amountA, amountB, lpAmount);
    }

    function getAmountOut(uint256 inputAmount, address inputToken) public view returns (uint256 outputAmount) {
        require(inputToken == tokenA || inputToken == tokenB, &quot;Invalid input token&quot;);

        bool isTokenA = inputToken == tokenA;
        (uint256 inputReserve, uint256 outputReserve) = isTokenA ? (reserveA, reserveB) : (reserveB, reserveA);

        uint256 inputWithFee = inputAmount * 997;
        uint256 numerator = inputWithFee * outputReserve;
        uint256 denominator = (inputReserve * 1000) + inputWithFee;

        outputAmount = numerator / denominator;
    }

    function swap(uint256 inputAmount, address inputToken) external nonReentrant {
        require(inputAmount &gt; 0, &quot;Zero input&quot;);
        require(inputToken == tokenA || inputToken == tokenB, &quot;Invalid token&quot;);

        address outputToken = inputToken == tokenA ? tokenB : tokenA;
        uint256 outputAmount = getAmountOut(inputAmount, inputToken);

        require(outputAmount &gt; 0, &quot;Insufficient output&quot;);

        IERC20(inputToken).transferFrom(msg.sender, address(this), inputAmount);
        IERC20(outputToken).transfer(msg.sender, outputAmount);

        _updateReserves();

        emit Swapped(msg.sender, inputToken, inputAmount, outputToken, outputAmount);
    }

    // View functions
    function getReserves() external view returns (uint256, uint256) {
        return (reserveA, reserveB);
    }

    function getLPBalance(address user) external view returns (uint256) {
        return lpBalances[user];
    }

    function getTotalLPSupply() external view returns (uint256) {
        return totalLPSupply;
    }
}"><code> 
<span class="hljs-comment">// SPDX-License-Identifier: MIT</span>
<span class="hljs-meta"><span class="hljs-keyword">pragma</span> <span class="hljs-keyword">solidity</span> ^0.8.20;</span>

<span class="hljs-keyword">import</span> <span class="hljs-string">"@openzeppelin/contracts/token/ERC20/IERC20.sol"</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">"@openzeppelin/contracts/security/ReentrancyGuard.sol"</span>;

<span class="hljs-class"><span class="hljs-keyword">contract</span> <span class="hljs-title">MiniDexPair</span> <span class="hljs-keyword">is</span> <span class="hljs-title">ReentrancyGuard</span> </span>{
    <span class="hljs-keyword">address</span> <span class="hljs-keyword">public</span> <span class="hljs-keyword">immutable</span> tokenA;
    <span class="hljs-keyword">address</span> <span class="hljs-keyword">public</span> <span class="hljs-keyword">immutable</span> tokenB;

    <span class="hljs-keyword">uint256</span> <span class="hljs-keyword">public</span> reserveA;
    <span class="hljs-keyword">uint256</span> <span class="hljs-keyword">public</span> reserveB;
    <span class="hljs-keyword">uint256</span> <span class="hljs-keyword">public</span> totalLPSupply;

    <span class="hljs-keyword">mapping</span>(<span class="hljs-keyword">address</span> <span class="hljs-operator">=</span><span class="hljs-operator">&gt;</span> <span class="hljs-keyword">uint256</span>) <span class="hljs-keyword">public</span> lpBalances;

    <span class="hljs-function"><span class="hljs-keyword">event</span> <span class="hljs-title">LiquidityAdded</span>(<span class="hljs-params"><span class="hljs-keyword">address</span> <span class="hljs-keyword">indexed</span> provider, <span class="hljs-keyword">uint256</span> amountA, <span class="hljs-keyword">uint256</span> amountB, <span class="hljs-keyword">uint256</span> lpMinted</span>)</span>;
    <span class="hljs-function"><span class="hljs-keyword">event</span> <span class="hljs-title">LiquidityRemoved</span>(<span class="hljs-params"><span class="hljs-keyword">address</span> <span class="hljs-keyword">indexed</span> provider, <span class="hljs-keyword">uint256</span> amountA, <span class="hljs-keyword">uint256</span> amountB, <span class="hljs-keyword">uint256</span> lpBurned</span>)</span>;
    <span class="hljs-function"><span class="hljs-keyword">event</span> <span class="hljs-title">Swapped</span>(<span class="hljs-params"><span class="hljs-keyword">address</span> <span class="hljs-keyword">indexed</span> user, <span class="hljs-keyword">address</span> inputToken, <span class="hljs-keyword">uint256</span> inputAmount, <span class="hljs-keyword">address</span> outputToken, <span class="hljs-keyword">uint256</span> outputAmount</span>)</span>;

    <span class="hljs-function"><span class="hljs-keyword">constructor</span>(<span class="hljs-params"><span class="hljs-keyword">address</span> _tokenA, <span class="hljs-keyword">address</span> _tokenB</span>) </span>{
        <span class="hljs-built_in">require</span>(_tokenA <span class="hljs-operator">!</span><span class="hljs-operator">=</span> _tokenB, <span class="hljs-string">"Identical tokens"</span>);
        <span class="hljs-built_in">require</span>(_tokenA <span class="hljs-operator">!</span><span class="hljs-operator">=</span> <span class="hljs-keyword">address</span>(<span class="hljs-number">0</span>) <span class="hljs-operator">&amp;</span><span class="hljs-operator">&amp;</span> _tokenB <span class="hljs-operator">!</span><span class="hljs-operator">=</span> <span class="hljs-keyword">address</span>(<span class="hljs-number">0</span>), <span class="hljs-string">"Zero address"</span>);

        tokenA <span class="hljs-operator">=</span> _tokenA;
        tokenB <span class="hljs-operator">=</span> _tokenB;
    }

    <span class="hljs-comment">// Utilities</span>
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">sqrt</span>(<span class="hljs-params"><span class="hljs-keyword">uint</span> y</span>) <span class="hljs-title"><span class="hljs-keyword">internal</span></span> <span class="hljs-title"><span class="hljs-keyword">pure</span></span> <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params"><span class="hljs-keyword">uint</span> z</span>) </span>{
        <span class="hljs-keyword">if</span> (y <span class="hljs-operator">&gt;</span> <span class="hljs-number">3</span>) {
            z <span class="hljs-operator">=</span> y;
            <span class="hljs-keyword">uint</span> x <span class="hljs-operator">=</span> y <span class="hljs-operator">/</span> <span class="hljs-number">2</span> <span class="hljs-operator">+</span> <span class="hljs-number">1</span>;
            <span class="hljs-keyword">while</span> (x <span class="hljs-operator">&lt;</span> z) {
                z <span class="hljs-operator">=</span> x;
                x <span class="hljs-operator">=</span> (y <span class="hljs-operator">/</span> x <span class="hljs-operator">+</span> x) <span class="hljs-operator">/</span> <span class="hljs-number">2</span>;
            }
        } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (y <span class="hljs-operator">!</span><span class="hljs-operator">=</span> <span class="hljs-number">0</span>) {
            z <span class="hljs-operator">=</span> <span class="hljs-number">1</span>;
        }
    }

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">min</span>(<span class="hljs-params"><span class="hljs-keyword">uint256</span> a, <span class="hljs-keyword">uint256</span> b</span>) <span class="hljs-title"><span class="hljs-keyword">internal</span></span> <span class="hljs-title"><span class="hljs-keyword">pure</span></span> <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params"><span class="hljs-keyword">uint256</span></span>) </span>{
        <span class="hljs-keyword">return</span> a <span class="hljs-operator">&lt;</span> b ? a : b;
    }

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">_updateReserves</span>(<span class="hljs-params"></span>) <span class="hljs-title"><span class="hljs-keyword">private</span></span> </span>{
        reserveA <span class="hljs-operator">=</span> IERC20(tokenA).balanceOf(<span class="hljs-keyword">address</span>(<span class="hljs-built_in">this</span>));
        reserveB <span class="hljs-operator">=</span> IERC20(tokenB).balanceOf(<span class="hljs-keyword">address</span>(<span class="hljs-built_in">this</span>));
    }

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">addLiquidity</span>(<span class="hljs-params"><span class="hljs-keyword">uint256</span> amountA, <span class="hljs-keyword">uint256</span> amountB</span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span> <span class="hljs-title">nonReentrant</span> </span>{
        <span class="hljs-built_in">require</span>(amountA <span class="hljs-operator">&gt;</span> <span class="hljs-number">0</span> <span class="hljs-operator">&amp;</span><span class="hljs-operator">&amp;</span> amountB <span class="hljs-operator">&gt;</span> <span class="hljs-number">0</span>, <span class="hljs-string">"Invalid amounts"</span>);

        IERC20(tokenA).transferFrom(<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>, <span class="hljs-keyword">address</span>(<span class="hljs-built_in">this</span>), amountA);
        IERC20(tokenB).transferFrom(<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>, <span class="hljs-keyword">address</span>(<span class="hljs-built_in">this</span>), amountB);

        <span class="hljs-keyword">uint256</span> lpToMint;
        <span class="hljs-keyword">if</span> (totalLPSupply <span class="hljs-operator">=</span><span class="hljs-operator">=</span> <span class="hljs-number">0</span>) {
            lpToMint <span class="hljs-operator">=</span> sqrt(amountA <span class="hljs-operator">*</span> amountB);
        } <span class="hljs-keyword">else</span> {
            lpToMint <span class="hljs-operator">=</span> min(
                (amountA <span class="hljs-operator">*</span> totalLPSupply) <span class="hljs-operator">/</span> reserveA,
                (amountB <span class="hljs-operator">*</span> totalLPSupply) <span class="hljs-operator">/</span> reserveB
            );
        }

        <span class="hljs-built_in">require</span>(lpToMint <span class="hljs-operator">&gt;</span> <span class="hljs-number">0</span>, <span class="hljs-string">"Zero LP minted"</span>);

        lpBalances[<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>] <span class="hljs-operator">+</span><span class="hljs-operator">=</span> lpToMint;
        totalLPSupply <span class="hljs-operator">+</span><span class="hljs-operator">=</span> lpToMint;

        _updateReserves();

        <span class="hljs-keyword">emit</span> LiquidityAdded(<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>, amountA, amountB, lpToMint);
    }

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">removeLiquidity</span>(<span class="hljs-params"><span class="hljs-keyword">uint256</span> lpAmount</span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span> <span class="hljs-title">nonReentrant</span> </span>{
        <span class="hljs-built_in">require</span>(lpAmount <span class="hljs-operator">&gt;</span> <span class="hljs-number">0</span> <span class="hljs-operator">&amp;</span><span class="hljs-operator">&amp;</span> lpAmount <span class="hljs-operator">&lt;</span><span class="hljs-operator">=</span> lpBalances[<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>], <span class="hljs-string">"Invalid LP amount"</span>);

        <span class="hljs-keyword">uint256</span> amountA <span class="hljs-operator">=</span> (lpAmount <span class="hljs-operator">*</span> reserveA) <span class="hljs-operator">/</span> totalLPSupply;
        <span class="hljs-keyword">uint256</span> amountB <span class="hljs-operator">=</span> (lpAmount <span class="hljs-operator">*</span> reserveB) <span class="hljs-operator">/</span> totalLPSupply;

        lpBalances[<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>] <span class="hljs-operator">-</span><span class="hljs-operator">=</span> lpAmount;
        totalLPSupply <span class="hljs-operator">-</span><span class="hljs-operator">=</span> lpAmount;

        IERC20(tokenA).<span class="hljs-built_in">transfer</span>(<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>, amountA);
        IERC20(tokenB).<span class="hljs-built_in">transfer</span>(<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>, amountB);

        _updateReserves();

        <span class="hljs-keyword">emit</span> LiquidityRemoved(<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>, amountA, amountB, lpAmount);
    }

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getAmountOut</span>(<span class="hljs-params"><span class="hljs-keyword">uint256</span> inputAmount, <span class="hljs-keyword">address</span> inputToken</span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span> <span class="hljs-title"><span class="hljs-keyword">view</span></span> <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params"><span class="hljs-keyword">uint256</span> outputAmount</span>) </span>{
        <span class="hljs-built_in">require</span>(inputToken <span class="hljs-operator">=</span><span class="hljs-operator">=</span> tokenA <span class="hljs-operator">|</span><span class="hljs-operator">|</span> inputToken <span class="hljs-operator">=</span><span class="hljs-operator">=</span> tokenB, <span class="hljs-string">"Invalid input token"</span>);

        <span class="hljs-keyword">bool</span> isTokenA <span class="hljs-operator">=</span> inputToken <span class="hljs-operator">=</span><span class="hljs-operator">=</span> tokenA;
        (<span class="hljs-keyword">uint256</span> inputReserve, <span class="hljs-keyword">uint256</span> outputReserve) <span class="hljs-operator">=</span> isTokenA ? (reserveA, reserveB) : (reserveB, reserveA);

        <span class="hljs-keyword">uint256</span> inputWithFee <span class="hljs-operator">=</span> inputAmount <span class="hljs-operator">*</span> <span class="hljs-number">997</span>;
        <span class="hljs-keyword">uint256</span> numerator <span class="hljs-operator">=</span> inputWithFee <span class="hljs-operator">*</span> outputReserve;
        <span class="hljs-keyword">uint256</span> denominator <span class="hljs-operator">=</span> (inputReserve <span class="hljs-operator">*</span> <span class="hljs-number">1000</span>) <span class="hljs-operator">+</span> inputWithFee;

        outputAmount <span class="hljs-operator">=</span> numerator <span class="hljs-operator">/</span> denominator;
    }

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">swap</span>(<span class="hljs-params"><span class="hljs-keyword">uint256</span> inputAmount, <span class="hljs-keyword">address</span> inputToken</span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span> <span class="hljs-title">nonReentrant</span> </span>{
        <span class="hljs-built_in">require</span>(inputAmount <span class="hljs-operator">&gt;</span> <span class="hljs-number">0</span>, <span class="hljs-string">"Zero input"</span>);
        <span class="hljs-built_in">require</span>(inputToken <span class="hljs-operator">=</span><span class="hljs-operator">=</span> tokenA <span class="hljs-operator">|</span><span class="hljs-operator">|</span> inputToken <span class="hljs-operator">=</span><span class="hljs-operator">=</span> tokenB, <span class="hljs-string">"Invalid token"</span>);

        <span class="hljs-keyword">address</span> outputToken <span class="hljs-operator">=</span> inputToken <span class="hljs-operator">=</span><span class="hljs-operator">=</span> tokenA ? tokenB : tokenA;
        <span class="hljs-keyword">uint256</span> outputAmount <span class="hljs-operator">=</span> getAmountOut(inputAmount, inputToken);

        <span class="hljs-built_in">require</span>(outputAmount <span class="hljs-operator">&gt;</span> <span class="hljs-number">0</span>, <span class="hljs-string">"Insufficient output"</span>);

        IERC20(inputToken).transferFrom(<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>, <span class="hljs-keyword">address</span>(<span class="hljs-built_in">this</span>), inputAmount);
        IERC20(outputToken).<span class="hljs-built_in">transfer</span>(<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>, outputAmount);

        _updateReserves();

        <span class="hljs-keyword">emit</span> Swapped(<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>, inputToken, inputAmount, outputToken, outputAmount);
    }

    <span class="hljs-comment">// View functions</span>
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getReserves</span>(<span class="hljs-params"></span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span> <span class="hljs-title"><span class="hljs-keyword">view</span></span> <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params"><span class="hljs-keyword">uint256</span>, <span class="hljs-keyword">uint256</span></span>) </span>{
        <span class="hljs-keyword">return</span> (reserveA, reserveB);
    }

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getLPBalance</span>(<span class="hljs-params"><span class="hljs-keyword">address</span> user</span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span> <span class="hljs-title"><span class="hljs-keyword">view</span></span> <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params"><span class="hljs-keyword">uint256</span></span>) </span>{
        <span class="hljs-keyword">return</span> lpBalances[user];
    }

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getTotalLPSupply</span>(<span class="hljs-params"></span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span> <span class="hljs-title"><span class="hljs-keyword">view</span></span> <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params"><span class="hljs-keyword">uint256</span></span>) </span>{
        <span class="hljs-keyword">return</span> totalLPSupply;
    }
}</code></pre><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0"><strong>一、合约概述</strong></h2><p><code>MiniDexPair</code>合约实现了一个简化版的去中心化交易所（DEX）交易对功能，支持两种 ERC20 代币之间的流动性添加、移除以及兑换操作，并通过<code>ReentrancyGuard</code>防止重入攻击。</p><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0"><strong>二、继承与引入</strong></h2><ol><li><p><strong>继承</strong>：继承自<code>ReentrancyGuard</code>合约，为合约提供防重入保护，确保在复杂操作过程中不会因恶意重入而导致资金安全问题。</p></li><li><p><strong>引入</strong>：导入<code>@openzeppelin/contracts/token/ERC20/IERC20.sol</code>，用于实现对 ERC20 代币的基本操作，如转账、余额查询等。</p></li></ol><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0"><strong>三、状态变量</strong></h2><ol><li><p><strong>代币相关</strong></p><ul><li><p><code>tokenA</code>：类型为<code>address</code>，代表第一种 ERC20 代币的合约地址，且不可变。</p></li><li><p><code>tokenB</code>：类型为<code>address</code>，代表第二种 ERC20 代币的合约地址，且不可变。</p></li></ul></li><li><p><strong>储备量相关</strong></p><ul><li><p><code>reserveA</code>：记录<code>tokenA</code>在交易对中的储备数量。</p></li><li><p><code>reserveB</code>：记录<code>tokenB</code>在交易对中的储备数量。</p></li></ul></li><li><p><strong>流动性相关</strong></p><ul><li><p><code>totalLPSupply</code>：表示流动性提供者（LP）代币的总供应量。</p></li><li><p><code>lpBalances</code>：通过<code>mapping</code>将用户地址与对应的 LP 代币余额关联，记录每个用户持有的 LP 代币数量。</p></li></ul></li></ol><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0"><strong>四、事件</strong></h2><ol><li><p><code>LiquidityAdded</code>：当用户向交易对添加流动性时触发，记录添加流动性的提供者地址、添加的<code>tokenA</code>和<code>tokenB</code>数量以及铸造的 LP 代币数量。</p></li><li><p><code>LiquidityRemoved</code>：用户移除流动性时触发，记录移除流动性的提供者地址、取出的<code>tokenA</code>和<code>tokenB</code>数量以及销毁的 LP 代币数量。</p></li><li><p><code>Swapped</code>：代币交换时触发，记录交换的用户地址、输入代币地址和数量、输出代币地址和数量。</p></li></ol><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0"><strong>五、函数</strong></h2><ol><li><p><strong>构造函数</strong></p><ul><li><p><strong>功能</strong>：初始化交易对合约，设置两种代币地址，并确保两个代币地址不同且不为零地址。</p></li><li><p><strong>参数</strong>：<code>_tokenA</code>和<code>_tokenB</code>分别为两种 ERC20 代币的合约地址。</p></li></ul></li><li><p><strong>实用函数</strong></p><ul><li><p><code>sqrt</code>：</p><ul><li><p><strong>功能</strong>：内部纯函数，使用巴比伦法计算平方根。</p></li><li><p><strong>参数</strong>：<code>y</code>为需要计算平方根的<code>uint</code>类型值。</p></li></ul></li><li><p><code>min</code>：</p><ul><li><p><strong>功能</strong>：内部纯函数，返回两个<code>uint256</code>类型值中的较小值。</p></li><li><p><strong>参数</strong>：<code>a</code>和<code>b</code>为需要比较的两个<code>uint256</code>值。</p></li></ul></li><li><p><code>_updateReserves</code>：</p><ul><li><p><strong>功能</strong>：私有函数，更新<code>tokenA</code>和<code>tokenB</code>的储备量，通过查询合约地址的代币余额来获取最新储备。</p></li></ul></li></ul></li><li><p><strong>流动性操作</strong></p><ul><li><p><code>addLiquidity</code>：</p><ul><li><p><strong>功能</strong>：用户向交易对添加流动性。</p></li><li><p><strong>要求</strong>：添加的<code>tokenA</code>和<code>tokenB</code>数量都必须大于 0。</p></li><li><p><strong>操作</strong>：从用户处转移<code>tokenA</code>和<code>tokenB</code>到合约，根据当前总供应量计算并铸造相应数量的 LP 代币给用户，更新储备量，并触发<code>LiquidityAdded</code>事件。</p></li></ul></li><li><p><code>removeLiquidity</code>：</p><ul><li><p><strong>功能</strong>：用户从交易对移除流动性。</p></li><li><p><strong>要求</strong>：移除的 LP 代币数量必须大于 0 且不超过用户当前持有的 LP 代币余额。</p></li><li><p><strong>操作</strong>：计算应取出的<code>tokenA</code>和<code>tokenB</code>数量，销毁用户相应数量的 LP 代币，将<code>tokenA</code>和<code>tokenB</code>转移给用户，更新储备量，并触发<code>LiquidityRemoved</code>事件。</p></li></ul></li></ul></li><li><p><strong>兑换操作</strong></p><ul><li><p><code>getAmountOut</code>：</p><ul><li><p><strong>功能</strong>：计算输入一定数量的某种代币后，可兑换得到的另一种代币的数量，考虑了 0.3% 的手续费。</p></li><li><p><strong>参数</strong>：<code>inputAmount</code>为输入代币的数量，<code>inputToken</code>为输入代币的地址。</p></li><li><p><strong>返回</strong>：可兑换得到的输出代币的数量。</p></li></ul></li><li><p><code>swap</code>：</p><ul><li><p><strong>功能</strong>：用户进行代币交换。</p></li><li><p><strong>要求</strong>：输入代币数量大于 0，且输入代币地址必须是<code>tokenA</code>或<code>tokenB</code>。</p></li><li><p><strong>操作</strong>：计算输出代币数量并确保其大于 0，从用户处转移输入代币到合约，将输出代币转移给用户，更新储备量，并触发<code>Swapped</code>事件。</p></li></ul></li></ul></li><li><p><strong>视图函数</strong></p><ul><li><p><code>getReserves</code>：</p><ul><li><p><strong>功能</strong>：获取当前<code>tokenA</code>和<code>tokenB</code>的储备量。</p></li><li><p><strong>返回</strong>：<code>tokenA</code>和<code>tokenB</code>的储备量。</p></li></ul></li><li><p><code>getLPBalance</code>：</p><ul><li><p><strong>功能</strong>：获取指定用户的 LP 代币余额。</p></li><li><p><strong>参数</strong>：<code>user</code>为要查询的用户地址。</p></li><li><p><strong>返回</strong>：指定用户持有的 LP 代币数量。</p></li></ul></li><li><p><code>getTotalLPSupply</code>：</p><ul><li><p><strong>功能</strong>：获取 LP 代币的总供应量。</p></li><li><p><strong>返回</strong>：LP 代币的总供应量。</p></li></ul></li></ul></li></ol><br>]]></content:encoded>
            <author>earnest_stus@newsletter.paragraph.com (Earnest_stu)</author>
        </item>
        <item>
            <title><![CDATA[SimpleStablecoin 合约学习笔记]]></title>
            <link>https://paragraph.com/@Earnest_stus/simplestablecoin-合约学习笔记</link>
            <guid>miEIoX6mcMThtMF1uG7j</guid>
            <pubDate>Fri, 07 Nov 2025 16:21:54 GMT</pubDate>
            <description><![CDATA[// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import "@openzeppelin/contracts/access/Ownable.sol"; import "@openzeppelin/contracts/security/ReentrancyGuard.sol"; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import "@openzeppelin/contracts/access/AccessControl.sol"; import "@openzeppelin/contracts/interfaces/IERC20Metadata.sol"; import "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol"; co...]]></description>
            <content:encoded><![CDATA[<pre data-type="codeBlock" text="// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import &quot;@openzeppelin/contracts/token/ERC20/ERC20.sol&quot;;
import &quot;@openzeppelin/contracts/access/Ownable.sol&quot;;
import &quot;@openzeppelin/contracts/security/ReentrancyGuard.sol&quot;;
import &quot;@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol&quot;;
import &quot;@openzeppelin/contracts/access/AccessControl.sol&quot;;
import &quot;@openzeppelin/contracts/interfaces/IERC20Metadata.sol&quot;;
import &quot;@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol&quot;;

contract SimpleStablecoin is ERC20, Ownable, ReentrancyGuard, AccessControl {
    using SafeERC20 for IERC20;

    bytes32 public constant PRICE_FEED_MANAGER_ROLE = keccak256(&quot;PRICE_FEED_MANAGER_ROLE&quot;);
    IERC20 public immutable collateralToken;
    uint8 public immutable collateralDecimals;
    AggregatorV3Interface public priceFeed;
    uint256 public collateralizationRatio = 150; // Expressed as a percentage (150 = 150%)

    event Minted(address indexed user, uint256 amount, uint256 collateralDeposited);
    event Redeemed(address indexed user, uint256 amount, uint256 collateralReturned);
    event PriceFeedUpdated(address newPriceFeed);
    event CollateralizationRatioUpdated(uint256 newRatio);

    error InvalidCollateralTokenAddress();
    error InvalidPriceFeedAddress();
    error MintAmountIsZero();
    error InsufficientStablecoinBalance();
    error CollateralizationRatioTooLow();

    constructor(
        address _collateralToken,
        address _initialOwner,
        address _priceFeed
    ) ERC20(&quot;Simple USD Stablecoin&quot;, &quot;sUSD&quot;) Ownable(_initialOwner) {
        if (_collateralToken == address(0)) revert InvalidCollateralTokenAddress();
        if (_priceFeed == address(0)) revert InvalidPriceFeedAddress();

        collateralToken = IERC20(_collateralToken);
        collateralDecimals = IERC20Metadata(_collateralToken).decimals();
        priceFeed = AggregatorV3Interface(_priceFeed);

        _grantRole(DEFAULT_ADMIN_ROLE, _initialOwner);
        _grantRole(PRICE_FEED_MANAGER_ROLE, _initialOwner);
    }


    function getCurrentPrice() public view returns (uint256) {
        (, int256 price, , , ) = priceFeed.latestRoundData();
        require(price &gt; 0, &quot;Invalid price feed response&quot;);
        return uint256(price);
    }

    function mint(uint256 amount) external nonReentrant {
        if (amount == 0) revert MintAmountIsZero();

        uint256 collateralPrice = getCurrentPrice();
        uint256 requiredCollateralValueUSD = amount * (10 ** decimals()); // 18 decimals assumed for sUSD
        uint256 requiredCollateral = (requiredCollateralValueUSD * collateralizationRatio) / (100 * collateralPrice);
        uint256 adjustedRequiredCollateral = (requiredCollateral * (10 ** collateralDecimals)) / (10 ** priceFeed.decimals());

        collateralToken.safeTransferFrom(msg.sender, address(this), adjustedRequiredCollateral);
        _mint(msg.sender, amount);

        emit Minted(msg.sender, amount, adjustedRequiredCollateral);
    }

    function redeem(uint256 amount) external nonReentrant {
        if (amount == 0) revert MintAmountIsZero();
        if (balanceOf(msg.sender) &lt; amount) revert InsufficientStablecoinBalance();

        uint256 collateralPrice = getCurrentPrice();
        uint256 stablecoinValueUSD = amount * (10 ** decimals());
        uint256 collateralToReturn = (stablecoinValueUSD * 100) / (collateralizationRatio * collateralPrice);
        uint256 adjustedCollateralToReturn = (collateralToReturn * (10 ** collateralDecimals)) / (10 ** priceFeed.decimals());

        _burn(msg.sender, amount);
        collateralToken.safeTransfer(msg.sender, adjustedCollateralToReturn);

        emit Redeemed(msg.sender, amount, adjustedCollateralToReturn);
    }

    function setCollateralizationRatio(uint256 newRatio) external onlyOwner {
        if (newRatio &lt; 100) revert CollateralizationRatioTooLow();
        collateralizationRatio = newRatio;
        emit CollateralizationRatioUpdated(newRatio);
    }

    function setPriceFeedContract(address _newPriceFeed) external onlyRole(PRICE_FEED_MANAGER_ROLE) {
        if (_newPriceFeed == address(0)) revert InvalidPriceFeedAddress();
        priceFeed = AggregatorV3Interface(_newPriceFeed);
        emit PriceFeedUpdated(_newPriceFeed);
    }

    function getRequiredCollateralForMint(uint256 amount) public view returns (uint256) {
        if (amount == 0) return 0;

        uint256 collateralPrice = getCurrentPrice();
        uint256 requiredCollateralValueUSD = amount * (10 ** decimals());
        uint256 requiredCollateral = (requiredCollateralValueUSD * collateralizationRatio) / (100 * collateralPrice);
        uint256 adjustedRequiredCollateral = (requiredCollateral * (10 ** collateralDecimals)) / (10 ** priceFeed.decimals());

        return adjustedRequiredCollateral;
    }

    function getCollateralForRedeem(uint256 amount) public view returns (uint256) {
        if (amount == 0) return 0;

        uint256 collateralPrice = getCurrentPrice();
        uint256 stablecoinValueUSD = amount * (10 ** decimals());
        uint256 collateralToReturn = (stablecoinValueUSD * 100) / (collateralizationRatio * collateralPrice);
        uint256 adjustedCollateralToReturn = (collateralToReturn * (10 ** collateralDecimals)) / (10 ** priceFeed.decimals());

        return adjustedCollateralToReturn;
    }
    
}
"><code><span class="hljs-comment">// SPDX-License-Identifier: MIT</span>
<span class="hljs-meta"><span class="hljs-keyword">pragma</span> <span class="hljs-keyword">solidity</span> ^0.8.20;</span>

<span class="hljs-keyword">import</span> <span class="hljs-string">"@openzeppelin/contracts/token/ERC20/ERC20.sol"</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">"@openzeppelin/contracts/access/Ownable.sol"</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">"@openzeppelin/contracts/security/ReentrancyGuard.sol"</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">"@openzeppelin/contracts/access/AccessControl.sol"</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">"@openzeppelin/contracts/interfaces/IERC20Metadata.sol"</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">"@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol"</span>;

<span class="hljs-class"><span class="hljs-keyword">contract</span> <span class="hljs-title">SimpleStablecoin</span> <span class="hljs-keyword">is</span> <span class="hljs-title">ERC20</span>, <span class="hljs-title">Ownable</span>, <span class="hljs-title">ReentrancyGuard</span>, <span class="hljs-title">AccessControl</span> </span>{
    <span class="hljs-keyword">using</span> <span class="hljs-title">SafeERC20</span> <span class="hljs-title"><span class="hljs-keyword">for</span></span> <span class="hljs-title">IERC20</span>;

    <span class="hljs-keyword">bytes32</span> <span class="hljs-keyword">public</span> <span class="hljs-keyword">constant</span> PRICE_FEED_MANAGER_ROLE <span class="hljs-operator">=</span> <span class="hljs-built_in">keccak256</span>(<span class="hljs-string">"PRICE_FEED_MANAGER_ROLE"</span>);
    IERC20 <span class="hljs-keyword">public</span> <span class="hljs-keyword">immutable</span> collateralToken;
    <span class="hljs-keyword">uint8</span> <span class="hljs-keyword">public</span> <span class="hljs-keyword">immutable</span> collateralDecimals;
    AggregatorV3Interface <span class="hljs-keyword">public</span> priceFeed;
    <span class="hljs-keyword">uint256</span> <span class="hljs-keyword">public</span> collateralizationRatio <span class="hljs-operator">=</span> <span class="hljs-number">150</span>; <span class="hljs-comment">// Expressed as a percentage (150 = 150%)</span>

    <span class="hljs-function"><span class="hljs-keyword">event</span> <span class="hljs-title">Minted</span>(<span class="hljs-params"><span class="hljs-keyword">address</span> <span class="hljs-keyword">indexed</span> user, <span class="hljs-keyword">uint256</span> amount, <span class="hljs-keyword">uint256</span> collateralDeposited</span>)</span>;
    <span class="hljs-function"><span class="hljs-keyword">event</span> <span class="hljs-title">Redeemed</span>(<span class="hljs-params"><span class="hljs-keyword">address</span> <span class="hljs-keyword">indexed</span> user, <span class="hljs-keyword">uint256</span> amount, <span class="hljs-keyword">uint256</span> collateralReturned</span>)</span>;
    <span class="hljs-function"><span class="hljs-keyword">event</span> <span class="hljs-title">PriceFeedUpdated</span>(<span class="hljs-params"><span class="hljs-keyword">address</span> newPriceFeed</span>)</span>;
    <span class="hljs-function"><span class="hljs-keyword">event</span> <span class="hljs-title">CollateralizationRatioUpdated</span>(<span class="hljs-params"><span class="hljs-keyword">uint256</span> newRatio</span>)</span>;

    <span class="hljs-function"><span class="hljs-keyword">error</span> <span class="hljs-title">InvalidCollateralTokenAddress</span>(<span class="hljs-params"></span>)</span>;
    <span class="hljs-function"><span class="hljs-keyword">error</span> <span class="hljs-title">InvalidPriceFeedAddress</span>(<span class="hljs-params"></span>)</span>;
    <span class="hljs-function"><span class="hljs-keyword">error</span> <span class="hljs-title">MintAmountIsZero</span>(<span class="hljs-params"></span>)</span>;
    <span class="hljs-function"><span class="hljs-keyword">error</span> <span class="hljs-title">InsufficientStablecoinBalance</span>(<span class="hljs-params"></span>)</span>;
    <span class="hljs-function"><span class="hljs-keyword">error</span> <span class="hljs-title">CollateralizationRatioTooLow</span>(<span class="hljs-params"></span>)</span>;

    <span class="hljs-function"><span class="hljs-keyword">constructor</span>(<span class="hljs-params">
        <span class="hljs-keyword">address</span> _collateralToken,
        <span class="hljs-keyword">address</span> _initialOwner,
        <span class="hljs-keyword">address</span> _priceFeed
    </span>) <span class="hljs-title">ERC20</span>(<span class="hljs-params"><span class="hljs-string">"Simple USD Stablecoin"</span>, <span class="hljs-string">"sUSD"</span></span>) <span class="hljs-title">Ownable</span>(<span class="hljs-params">_initialOwner</span>) </span>{
        <span class="hljs-keyword">if</span> (_collateralToken <span class="hljs-operator">=</span><span class="hljs-operator">=</span> <span class="hljs-keyword">address</span>(<span class="hljs-number">0</span>)) <span class="hljs-keyword">revert</span> InvalidCollateralTokenAddress();
        <span class="hljs-keyword">if</span> (_priceFeed <span class="hljs-operator">=</span><span class="hljs-operator">=</span> <span class="hljs-keyword">address</span>(<span class="hljs-number">0</span>)) <span class="hljs-keyword">revert</span> InvalidPriceFeedAddress();

        collateralToken <span class="hljs-operator">=</span> IERC20(_collateralToken);
        collateralDecimals <span class="hljs-operator">=</span> IERC20Metadata(_collateralToken).decimals();
        priceFeed <span class="hljs-operator">=</span> AggregatorV3Interface(_priceFeed);

        _grantRole(DEFAULT_ADMIN_ROLE, _initialOwner);
        _grantRole(PRICE_FEED_MANAGER_ROLE, _initialOwner);
    }


    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getCurrentPrice</span>(<span class="hljs-params"></span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span> <span class="hljs-title"><span class="hljs-keyword">view</span></span> <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params"><span class="hljs-keyword">uint256</span></span>) </span>{
        (, <span class="hljs-keyword">int256</span> price, , , ) <span class="hljs-operator">=</span> priceFeed.latestRoundData();
        <span class="hljs-built_in">require</span>(price <span class="hljs-operator">&gt;</span> <span class="hljs-number">0</span>, <span class="hljs-string">"Invalid price feed response"</span>);
        <span class="hljs-keyword">return</span> <span class="hljs-keyword">uint256</span>(price);
    }

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">mint</span>(<span class="hljs-params"><span class="hljs-keyword">uint256</span> amount</span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span> <span class="hljs-title">nonReentrant</span> </span>{
        <span class="hljs-keyword">if</span> (amount <span class="hljs-operator">=</span><span class="hljs-operator">=</span> <span class="hljs-number">0</span>) <span class="hljs-keyword">revert</span> MintAmountIsZero();

        <span class="hljs-keyword">uint256</span> collateralPrice <span class="hljs-operator">=</span> getCurrentPrice();
        <span class="hljs-keyword">uint256</span> requiredCollateralValueUSD <span class="hljs-operator">=</span> amount <span class="hljs-operator">*</span> (<span class="hljs-number">10</span> <span class="hljs-operator">*</span><span class="hljs-operator">*</span> decimals()); <span class="hljs-comment">// 18 decimals assumed for sUSD</span>
        <span class="hljs-keyword">uint256</span> requiredCollateral <span class="hljs-operator">=</span> (requiredCollateralValueUSD <span class="hljs-operator">*</span> collateralizationRatio) <span class="hljs-operator">/</span> (<span class="hljs-number">100</span> <span class="hljs-operator">*</span> collateralPrice);
        <span class="hljs-keyword">uint256</span> adjustedRequiredCollateral <span class="hljs-operator">=</span> (requiredCollateral <span class="hljs-operator">*</span> (<span class="hljs-number">10</span> <span class="hljs-operator">*</span><span class="hljs-operator">*</span> collateralDecimals)) <span class="hljs-operator">/</span> (<span class="hljs-number">10</span> <span class="hljs-operator">*</span><span class="hljs-operator">*</span> priceFeed.decimals());

        collateralToken.safeTransferFrom(<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>, <span class="hljs-keyword">address</span>(<span class="hljs-built_in">this</span>), adjustedRequiredCollateral);
        _mint(<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>, amount);

        <span class="hljs-keyword">emit</span> Minted(<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>, amount, adjustedRequiredCollateral);
    }

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">redeem</span>(<span class="hljs-params"><span class="hljs-keyword">uint256</span> amount</span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span> <span class="hljs-title">nonReentrant</span> </span>{
        <span class="hljs-keyword">if</span> (amount <span class="hljs-operator">=</span><span class="hljs-operator">=</span> <span class="hljs-number">0</span>) <span class="hljs-keyword">revert</span> MintAmountIsZero();
        <span class="hljs-keyword">if</span> (balanceOf(<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>) <span class="hljs-operator">&lt;</span> amount) <span class="hljs-keyword">revert</span> InsufficientStablecoinBalance();

        <span class="hljs-keyword">uint256</span> collateralPrice <span class="hljs-operator">=</span> getCurrentPrice();
        <span class="hljs-keyword">uint256</span> stablecoinValueUSD <span class="hljs-operator">=</span> amount <span class="hljs-operator">*</span> (<span class="hljs-number">10</span> <span class="hljs-operator">*</span><span class="hljs-operator">*</span> decimals());
        <span class="hljs-keyword">uint256</span> collateralToReturn <span class="hljs-operator">=</span> (stablecoinValueUSD <span class="hljs-operator">*</span> <span class="hljs-number">100</span>) <span class="hljs-operator">/</span> (collateralizationRatio <span class="hljs-operator">*</span> collateralPrice);
        <span class="hljs-keyword">uint256</span> adjustedCollateralToReturn <span class="hljs-operator">=</span> (collateralToReturn <span class="hljs-operator">*</span> (<span class="hljs-number">10</span> <span class="hljs-operator">*</span><span class="hljs-operator">*</span> collateralDecimals)) <span class="hljs-operator">/</span> (<span class="hljs-number">10</span> <span class="hljs-operator">*</span><span class="hljs-operator">*</span> priceFeed.decimals());

        _burn(<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>, amount);
        collateralToken.safeTransfer(<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>, adjustedCollateralToReturn);

        <span class="hljs-keyword">emit</span> Redeemed(<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>, amount, adjustedCollateralToReturn);
    }

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">setCollateralizationRatio</span>(<span class="hljs-params"><span class="hljs-keyword">uint256</span> newRatio</span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span> <span class="hljs-title">onlyOwner</span> </span>{
        <span class="hljs-keyword">if</span> (newRatio <span class="hljs-operator">&lt;</span> <span class="hljs-number">100</span>) <span class="hljs-keyword">revert</span> CollateralizationRatioTooLow();
        collateralizationRatio <span class="hljs-operator">=</span> newRatio;
        <span class="hljs-keyword">emit</span> CollateralizationRatioUpdated(newRatio);
    }

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">setPriceFeedContract</span>(<span class="hljs-params"><span class="hljs-keyword">address</span> _newPriceFeed</span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span> <span class="hljs-title">onlyRole</span>(<span class="hljs-params">PRICE_FEED_MANAGER_ROLE</span>) </span>{
        <span class="hljs-keyword">if</span> (_newPriceFeed <span class="hljs-operator">=</span><span class="hljs-operator">=</span> <span class="hljs-keyword">address</span>(<span class="hljs-number">0</span>)) <span class="hljs-keyword">revert</span> InvalidPriceFeedAddress();
        priceFeed <span class="hljs-operator">=</span> AggregatorV3Interface(_newPriceFeed);
        <span class="hljs-keyword">emit</span> PriceFeedUpdated(_newPriceFeed);
    }

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getRequiredCollateralForMint</span>(<span class="hljs-params"><span class="hljs-keyword">uint256</span> amount</span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span> <span class="hljs-title"><span class="hljs-keyword">view</span></span> <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params"><span class="hljs-keyword">uint256</span></span>) </span>{
        <span class="hljs-keyword">if</span> (amount <span class="hljs-operator">=</span><span class="hljs-operator">=</span> <span class="hljs-number">0</span>) <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;

        <span class="hljs-keyword">uint256</span> collateralPrice <span class="hljs-operator">=</span> getCurrentPrice();
        <span class="hljs-keyword">uint256</span> requiredCollateralValueUSD <span class="hljs-operator">=</span> amount <span class="hljs-operator">*</span> (<span class="hljs-number">10</span> <span class="hljs-operator">*</span><span class="hljs-operator">*</span> decimals());
        <span class="hljs-keyword">uint256</span> requiredCollateral <span class="hljs-operator">=</span> (requiredCollateralValueUSD <span class="hljs-operator">*</span> collateralizationRatio) <span class="hljs-operator">/</span> (<span class="hljs-number">100</span> <span class="hljs-operator">*</span> collateralPrice);
        <span class="hljs-keyword">uint256</span> adjustedRequiredCollateral <span class="hljs-operator">=</span> (requiredCollateral <span class="hljs-operator">*</span> (<span class="hljs-number">10</span> <span class="hljs-operator">*</span><span class="hljs-operator">*</span> collateralDecimals)) <span class="hljs-operator">/</span> (<span class="hljs-number">10</span> <span class="hljs-operator">*</span><span class="hljs-operator">*</span> priceFeed.decimals());

        <span class="hljs-keyword">return</span> adjustedRequiredCollateral;
    }

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getCollateralForRedeem</span>(<span class="hljs-params"><span class="hljs-keyword">uint256</span> amount</span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span> <span class="hljs-title"><span class="hljs-keyword">view</span></span> <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params"><span class="hljs-keyword">uint256</span></span>) </span>{
        <span class="hljs-keyword">if</span> (amount <span class="hljs-operator">=</span><span class="hljs-operator">=</span> <span class="hljs-number">0</span>) <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;

        <span class="hljs-keyword">uint256</span> collateralPrice <span class="hljs-operator">=</span> getCurrentPrice();
        <span class="hljs-keyword">uint256</span> stablecoinValueUSD <span class="hljs-operator">=</span> amount <span class="hljs-operator">*</span> (<span class="hljs-number">10</span> <span class="hljs-operator">*</span><span class="hljs-operator">*</span> decimals());
        <span class="hljs-keyword">uint256</span> collateralToReturn <span class="hljs-operator">=</span> (stablecoinValueUSD <span class="hljs-operator">*</span> <span class="hljs-number">100</span>) <span class="hljs-operator">/</span> (collateralizationRatio <span class="hljs-operator">*</span> collateralPrice);
        <span class="hljs-keyword">uint256</span> adjustedCollateralToReturn <span class="hljs-operator">=</span> (collateralToReturn <span class="hljs-operator">*</span> (<span class="hljs-number">10</span> <span class="hljs-operator">*</span><span class="hljs-operator">*</span> collateralDecimals)) <span class="hljs-operator">/</span> (<span class="hljs-number">10</span> <span class="hljs-operator">*</span><span class="hljs-operator">*</span> priceFeed.decimals());

        <span class="hljs-keyword">return</span> adjustedCollateralToReturn;
    }
    
}
</code></pre><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0"><strong>一、合约概述</strong></h2><p><code>SimpleStablecoin</code>合约构建了一种与美元挂钩的稳定币系统。它基于 ERC20 标准，集成了可拥有者控制、防重入保护以及访问控制等功能，旨在通过抵押一定比例的其他代币（如 ETH）并借助 Chainlink 价格预言机来维持稳定币的价值稳定。</p><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0"><strong>二、继承与引入</strong></h2><ol><li><p><strong>继承</strong>：继承自<code>ERC20</code>、<code>Ownable</code>、<code>ReentrancyGuard</code>和<code>AccessControl</code>合约，分别获得 ERC20 代币功能、所有权控制、防重入保护以及访问控制功能。</p></li><li><p><strong>引入</strong>：</p><ul><li><p><code>@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol</code>提供安全的 ERC20 代币操作方法。</p></li><li><p><code>@openzeppelin/contracts/interfaces/IERC20Metadata.sol</code>用于获取 ERC20 代币的元数据，如小数位数。</p></li><li><p><code>@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol</code>引入 Chainlink 价格预言机接口，用于获取抵押代币的价格。</p></li></ul></li></ol><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0"><strong>三、状态变量</strong></h2><ol><li><p><strong>角色相关</strong></p><ul><li><p><code>PRICE_FEED_MANAGER_ROLE</code>：定义价格预言机管理角色的标识符。</p></li></ul></li><li><p><strong>抵押代币相关</strong></p><ul><li><p><code>collateralToken</code>：类型为<code>IERC20</code>，表示抵押的 ERC20 代币合约地址，且不可变。</p></li><li><p><code>collateralDecimals</code>：存储抵押代币的小数位数，不可变。</p></li></ul></li><li><p><strong>价格预言机相关</strong></p><ul><li><p><code>priceFeed</code>：类型为<code>AggregatorV3Interface</code>，代表 Chainlink 价格预言机合约地址。</p></li></ul></li><li><p><strong>比率相关</strong></p><ul><li><p><code>collateralizationRatio</code>：表示抵押率，以百分比形式存储，初始值为 150%，即每铸造 1 单位稳定币需抵押价值 1.5 倍稳定币价值的抵押代币。</p></li></ul></li></ol><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0"><strong>四、事件</strong></h2><ol><li><p><strong>铸造与赎回</strong></p><ul><li><p><code>Minted</code>：用户铸造稳定币时触发，记录铸造用户地址、铸造数量以及抵押的抵押代币数量。</p></li><li><p><code>Redeemed</code>：用户赎回稳定币时触发，记录赎回用户地址、赎回数量以及返还的抵押代币数量。</p></li></ul></li><li><p><strong>设置相关</strong></p><ul><li><p><code>PriceFeedUpdated</code>：价格预言机合约地址更新时触发，记录新的价格预言机地址。</p></li><li><p><code>CollateralizationRatioUpdated</code>：抵押率更新时触发，记录新的抵押率。</p></li></ul></li></ol><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0"><strong>五、错误</strong></h2><ol><li><p><code>InvalidCollateralTokenAddress</code>：当传入的抵押代币地址为零地址时抛出。</p></li><li><p><code>InvalidPriceFeedAddress</code>：当传入的价格预言机地址为零地址时抛出。</p></li><li><p><code>MintAmountIsZero</code>：当铸造数量为零时抛出。</p></li><li><p><code>InsufficientStablecoinBalance</code>：当用户赎回时稳定币余额不足时抛出。</p></li><li><p><code>CollateralizationRatioTooLow</code>：当设置的抵押率低于 100% 时抛出。</p></li></ol><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0"><strong>六、函数</strong></h2><ol><li><p><strong>构造函数</strong></p><ul><li><p><strong>功能</strong>：初始化稳定币合约，设置抵押代币、初始所有者、价格预言机，并授予初始所有者默认管理角色和价格预言机管理角色。</p></li><li><p><strong>参数</strong>：<code>_collateralToken</code>为抵押代币合约地址，<code>_initialOwner</code>为初始所有者地址，<code>_priceFeed</code>为价格预言机合约地址。</p></li></ul></li><li><p><strong>价格获取</strong></p><ul><li><p><code>getCurrentPrice</code>：</p><ul><li><p><strong>功能</strong>：从价格预言机获取当前抵押代币的价格。</p></li><li><p><strong>返回</strong>：当前抵押代币的价格，以<code>uint256</code>类型返回。</p></li></ul></li></ul></li><li><p><strong>稳定币铸造</strong></p><ul><li><p><code>mint</code>：</p><ul><li><p><strong>功能</strong>：用户铸造稳定币，需根据当前抵押率和抵押代币价格转移相应数量的抵押代币到合约。</p></li><li><p><strong>要求</strong>：铸造数量不能为零。</p></li><li><p><strong>操作</strong>：计算所需抵押的抵押代币数量，从用户处安全转移抵押代币到合约，铸造相应数量的稳定币给用户，并触发<code>Minted</code>事件。</p></li></ul></li></ul></li><li><p><strong>稳定币赎回</strong></p><ul><li><p><code>redeem</code>：</p><ul><li><p><strong>功能</strong>：用户赎回稳定币，合约根据当前抵押率和抵押代币价格返还相应数量的抵押代币给用户。</p></li><li><p><strong>要求</strong>：赎回数量不能为零且用户拥有足够的稳定币余额。</p></li><li><p><strong>操作</strong>：计算应返还的抵押代币数量，销毁用户相应数量的稳定币，安全转移抵押代币给用户，并触发<code>Redeemed</code>事件。</p></li></ul></li></ul></li><li><p><strong>设置函数</strong></p><ul><li><p><code>setCollateralizationRatio</code>：</p><ul><li><p><strong>功能</strong>：合约所有者设置抵押率。</p></li><li><p><strong>要求</strong>：仅合约所有者可调用，且新的抵押率不能低于 100%。</p></li><li><p><strong>操作</strong>：更新抵押率并触发<code>CollateralizationRatioUpdated</code>事件。</p></li></ul></li><li><p><code>setPriceFeedContract</code>：</p><ul><li><p><strong>功能</strong>：价格预言机管理角色的地址设置新的价格预言机合约地址。</p></li><li><p><strong>要求</strong>：仅拥有<code>PRICE_FEED_MANAGER_ROLE</code>角色的地址可调用，且新地址不能为零地址。</p></li><li><p><strong>操作</strong>：更新价格预言机合约地址并触发<code>PriceFeedUpdated</code>事件。</p></li></ul></li></ul></li><li><p><strong>查询函数</strong></p><ul><li><p><code>getRequiredCollateralForMint</code>：</p><ul><li><p><strong>功能</strong>：查询铸造指定数量稳定币所需的抵押代币数量。</p></li><li><p><strong>参数</strong>：<code>amount</code>为要铸造的稳定币数量。</p></li><li><p><strong>返回</strong>：铸造指定数量稳定币所需的抵押代币数量。</p></li></ul></li><li><p><code>getCollateralForRedeem</code>：</p><ul><li><p><strong>功能</strong>：查询赎回指定数量稳定币可获得的抵押代币数量。</p></li><li><p><strong>参数</strong>：<code>amount</code>为要赎回的稳定币数量。</p></li><li><p><strong>返回</strong>：赎回指定数量稳定币可获得的抵押代币数量。</p></li></ul></li></ul></li></ol><br>]]></content:encoded>
            <author>earnest_stus@newsletter.paragraph.com (Earnest_stu)</author>
        </item>
        <item>
            <title><![CDATA[去中心化治理系统（Decentralized Governance System）合约学习笔记]]></title>
            <link>https://paragraph.com/@Earnest_stus/去中心化治理系统（decentralized-governance-system）合约学习笔记</link>
            <guid>Kx52gSCxL5MYWaGD0F9v</guid>
            <pubDate>Fri, 07 Nov 2025 16:19:22 GMT</pubDate>
            <description><![CDATA[// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import "@openzeppelin/contracts/access/Ownable.sol"; import "@openzeppelin/contracts/security/ReentrancyGuard.sol"; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import "@openzeppelin/contracts/access/AccessControl.sol"; import "@openzeppelin/contracts/interfaces/IERC20Metadata.sol"; import "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol"; co...]]></description>
            <content:encoded><![CDATA[<pre data-type="codeBlock" text="// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import &quot;@openzeppelin/contracts/token/ERC20/ERC20.sol&quot;;
import &quot;@openzeppelin/contracts/access/Ownable.sol&quot;;
import &quot;@openzeppelin/contracts/security/ReentrancyGuard.sol&quot;;
import &quot;@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol&quot;;
import &quot;@openzeppelin/contracts/access/AccessControl.sol&quot;;
import &quot;@openzeppelin/contracts/interfaces/IERC20Metadata.sol&quot;;
import &quot;@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol&quot;;

contract SimpleStablecoin is ERC20, Ownable, ReentrancyGuard, AccessControl {
    using SafeERC20 for IERC20;

    bytes32 public constant PRICE_FEED_MANAGER_ROLE = keccak256(&quot;PRICE_FEED_MANAGER_ROLE&quot;);
    IERC20 public immutable collateralToken;
    uint8 public immutable collateralDecimals;
    AggregatorV3Interface public priceFeed;
    uint256 public collateralizationRatio = 150; // Expressed as a percentage (150 = 150%)

    event Minted(address indexed user, uint256 amount, uint256 collateralDeposited);
    event Redeemed(address indexed user, uint256 amount, uint256 collateralReturned);
    event PriceFeedUpdated(address newPriceFeed);
    event CollateralizationRatioUpdated(uint256 newRatio);

    error InvalidCollateralTokenAddress();
    error InvalidPriceFeedAddress();
    error MintAmountIsZero();
    error InsufficientStablecoinBalance();
    error CollateralizationRatioTooLow();

    constructor(
        address _collateralToken,
        address _initialOwner,
        address _priceFeed
    ) ERC20(&quot;Simple USD Stablecoin&quot;, &quot;sUSD&quot;) Ownable(_initialOwner) {
        if (_collateralToken == address(0)) revert InvalidCollateralTokenAddress();
        if (_priceFeed == address(0)) revert InvalidPriceFeedAddress();

        collateralToken = IERC20(_collateralToken);
        collateralDecimals = IERC20Metadata(_collateralToken).decimals();
        priceFeed = AggregatorV3Interface(_priceFeed);

        _grantRole(DEFAULT_ADMIN_ROLE, _initialOwner);
        _grantRole(PRICE_FEED_MANAGER_ROLE, _initialOwner);
    }


    function getCurrentPrice() public view returns (uint256) {
        (, int256 price, , , ) = priceFeed.latestRoundData();
        require(price &gt; 0, &quot;Invalid price feed response&quot;);
        return uint256(price);
    }

    function mint(uint256 amount) external nonReentrant {
        if (amount == 0) revert MintAmountIsZero();

        uint256 collateralPrice = getCurrentPrice();
        uint256 requiredCollateralValueUSD = amount * (10 ** decimals()); // 18 decimals assumed for sUSD
        uint256 requiredCollateral = (requiredCollateralValueUSD * collateralizationRatio) / (100 * collateralPrice);
        uint256 adjustedRequiredCollateral = (requiredCollateral * (10 ** collateralDecimals)) / (10 ** priceFeed.decimals());

        collateralToken.safeTransferFrom(msg.sender, address(this), adjustedRequiredCollateral);
        _mint(msg.sender, amount);

        emit Minted(msg.sender, amount, adjustedRequiredCollateral);
    }

    function redeem(uint256 amount) external nonReentrant {
        if (amount == 0) revert MintAmountIsZero();
        if (balanceOf(msg.sender) &lt; amount) revert InsufficientStablecoinBalance();

        uint256 collateralPrice = getCurrentPrice();
        uint256 stablecoinValueUSD = amount * (10 ** decimals());
        uint256 collateralToReturn = (stablecoinValueUSD * 100) / (collateralizationRatio * collateralPrice);
        uint256 adjustedCollateralToReturn = (collateralToReturn * (10 ** collateralDecimals)) / (10 ** priceFeed.decimals());

        _burn(msg.sender, amount);
        collateralToken.safeTransfer(msg.sender, adjustedCollateralToReturn);

        emit Redeemed(msg.sender, amount, adjustedCollateralToReturn);
    }

    function setCollateralizationRatio(uint256 newRatio) external onlyOwner {
        if (newRatio &lt; 100) revert CollateralizationRatioTooLow();
        collateralizationRatio = newRatio;
        emit CollateralizationRatioUpdated(newRatio);
    }

    function setPriceFeedContract(address _newPriceFeed) external onlyRole(PRICE_FEED_MANAGER_ROLE) {
        if (_newPriceFeed == address(0)) revert InvalidPriceFeedAddress();
        priceFeed = AggregatorV3Interface(_newPriceFeed);
        emit PriceFeedUpdated(_newPriceFeed);
    }

    function getRequiredCollateralForMint(uint256 amount) public view returns (uint256) {
        if (amount == 0) return 0;

        uint256 collateralPrice = getCurrentPrice();
        uint256 requiredCollateralValueUSD = amount * (10 ** decimals());
        uint256 requiredCollateral = (requiredCollateralValueUSD * collateralizationRatio) / (100 * collateralPrice);
        uint256 adjustedRequiredCollateral = (requiredCollateral * (10 ** collateralDecimals)) / (10 ** priceFeed.decimals());

        return adjustedRequiredCollateral;
    }

    function getCollateralForRedeem(uint256 amount) public view returns (uint256) {
        if (amount == 0) return 0;

        uint256 collateralPrice = getCurrentPrice();
        uint256 stablecoinValueUSD = amount * (10 ** decimals());
        uint256 collateralToReturn = (stablecoinValueUSD * 100) / (collateralizationRatio * collateralPrice);
        uint256 adjustedCollateralToReturn = (collateralToReturn * (10 ** collateralDecimals)) / (10 ** priceFeed.decimals());

        return adjustedCollateralToReturn;
    }
    
}
"><code><span class="hljs-comment">// SPDX-License-Identifier: MIT</span>
<span class="hljs-meta"><span class="hljs-keyword">pragma</span> <span class="hljs-keyword">solidity</span> ^0.8.20;</span>

<span class="hljs-keyword">import</span> <span class="hljs-string">"@openzeppelin/contracts/token/ERC20/ERC20.sol"</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">"@openzeppelin/contracts/access/Ownable.sol"</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">"@openzeppelin/contracts/security/ReentrancyGuard.sol"</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">"@openzeppelin/contracts/access/AccessControl.sol"</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">"@openzeppelin/contracts/interfaces/IERC20Metadata.sol"</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">"@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol"</span>;

<span class="hljs-class"><span class="hljs-keyword">contract</span> <span class="hljs-title">SimpleStablecoin</span> <span class="hljs-keyword">is</span> <span class="hljs-title">ERC20</span>, <span class="hljs-title">Ownable</span>, <span class="hljs-title">ReentrancyGuard</span>, <span class="hljs-title">AccessControl</span> </span>{
    <span class="hljs-keyword">using</span> <span class="hljs-title">SafeERC20</span> <span class="hljs-title"><span class="hljs-keyword">for</span></span> <span class="hljs-title">IERC20</span>;

    <span class="hljs-keyword">bytes32</span> <span class="hljs-keyword">public</span> <span class="hljs-keyword">constant</span> PRICE_FEED_MANAGER_ROLE <span class="hljs-operator">=</span> <span class="hljs-built_in">keccak256</span>(<span class="hljs-string">"PRICE_FEED_MANAGER_ROLE"</span>);
    IERC20 <span class="hljs-keyword">public</span> <span class="hljs-keyword">immutable</span> collateralToken;
    <span class="hljs-keyword">uint8</span> <span class="hljs-keyword">public</span> <span class="hljs-keyword">immutable</span> collateralDecimals;
    AggregatorV3Interface <span class="hljs-keyword">public</span> priceFeed;
    <span class="hljs-keyword">uint256</span> <span class="hljs-keyword">public</span> collateralizationRatio <span class="hljs-operator">=</span> <span class="hljs-number">150</span>; <span class="hljs-comment">// Expressed as a percentage (150 = 150%)</span>

    <span class="hljs-function"><span class="hljs-keyword">event</span> <span class="hljs-title">Minted</span>(<span class="hljs-params"><span class="hljs-keyword">address</span> <span class="hljs-keyword">indexed</span> user, <span class="hljs-keyword">uint256</span> amount, <span class="hljs-keyword">uint256</span> collateralDeposited</span>)</span>;
    <span class="hljs-function"><span class="hljs-keyword">event</span> <span class="hljs-title">Redeemed</span>(<span class="hljs-params"><span class="hljs-keyword">address</span> <span class="hljs-keyword">indexed</span> user, <span class="hljs-keyword">uint256</span> amount, <span class="hljs-keyword">uint256</span> collateralReturned</span>)</span>;
    <span class="hljs-function"><span class="hljs-keyword">event</span> <span class="hljs-title">PriceFeedUpdated</span>(<span class="hljs-params"><span class="hljs-keyword">address</span> newPriceFeed</span>)</span>;
    <span class="hljs-function"><span class="hljs-keyword">event</span> <span class="hljs-title">CollateralizationRatioUpdated</span>(<span class="hljs-params"><span class="hljs-keyword">uint256</span> newRatio</span>)</span>;

    <span class="hljs-function"><span class="hljs-keyword">error</span> <span class="hljs-title">InvalidCollateralTokenAddress</span>(<span class="hljs-params"></span>)</span>;
    <span class="hljs-function"><span class="hljs-keyword">error</span> <span class="hljs-title">InvalidPriceFeedAddress</span>(<span class="hljs-params"></span>)</span>;
    <span class="hljs-function"><span class="hljs-keyword">error</span> <span class="hljs-title">MintAmountIsZero</span>(<span class="hljs-params"></span>)</span>;
    <span class="hljs-function"><span class="hljs-keyword">error</span> <span class="hljs-title">InsufficientStablecoinBalance</span>(<span class="hljs-params"></span>)</span>;
    <span class="hljs-function"><span class="hljs-keyword">error</span> <span class="hljs-title">CollateralizationRatioTooLow</span>(<span class="hljs-params"></span>)</span>;

    <span class="hljs-function"><span class="hljs-keyword">constructor</span>(<span class="hljs-params">
        <span class="hljs-keyword">address</span> _collateralToken,
        <span class="hljs-keyword">address</span> _initialOwner,
        <span class="hljs-keyword">address</span> _priceFeed
    </span>) <span class="hljs-title">ERC20</span>(<span class="hljs-params"><span class="hljs-string">"Simple USD Stablecoin"</span>, <span class="hljs-string">"sUSD"</span></span>) <span class="hljs-title">Ownable</span>(<span class="hljs-params">_initialOwner</span>) </span>{
        <span class="hljs-keyword">if</span> (_collateralToken <span class="hljs-operator">=</span><span class="hljs-operator">=</span> <span class="hljs-keyword">address</span>(<span class="hljs-number">0</span>)) <span class="hljs-keyword">revert</span> InvalidCollateralTokenAddress();
        <span class="hljs-keyword">if</span> (_priceFeed <span class="hljs-operator">=</span><span class="hljs-operator">=</span> <span class="hljs-keyword">address</span>(<span class="hljs-number">0</span>)) <span class="hljs-keyword">revert</span> InvalidPriceFeedAddress();

        collateralToken <span class="hljs-operator">=</span> IERC20(_collateralToken);
        collateralDecimals <span class="hljs-operator">=</span> IERC20Metadata(_collateralToken).decimals();
        priceFeed <span class="hljs-operator">=</span> AggregatorV3Interface(_priceFeed);

        _grantRole(DEFAULT_ADMIN_ROLE, _initialOwner);
        _grantRole(PRICE_FEED_MANAGER_ROLE, _initialOwner);
    }


    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getCurrentPrice</span>(<span class="hljs-params"></span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span> <span class="hljs-title"><span class="hljs-keyword">view</span></span> <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params"><span class="hljs-keyword">uint256</span></span>) </span>{
        (, <span class="hljs-keyword">int256</span> price, , , ) <span class="hljs-operator">=</span> priceFeed.latestRoundData();
        <span class="hljs-built_in">require</span>(price <span class="hljs-operator">&gt;</span> <span class="hljs-number">0</span>, <span class="hljs-string">"Invalid price feed response"</span>);
        <span class="hljs-keyword">return</span> <span class="hljs-keyword">uint256</span>(price);
    }

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">mint</span>(<span class="hljs-params"><span class="hljs-keyword">uint256</span> amount</span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span> <span class="hljs-title">nonReentrant</span> </span>{
        <span class="hljs-keyword">if</span> (amount <span class="hljs-operator">=</span><span class="hljs-operator">=</span> <span class="hljs-number">0</span>) <span class="hljs-keyword">revert</span> MintAmountIsZero();

        <span class="hljs-keyword">uint256</span> collateralPrice <span class="hljs-operator">=</span> getCurrentPrice();
        <span class="hljs-keyword">uint256</span> requiredCollateralValueUSD <span class="hljs-operator">=</span> amount <span class="hljs-operator">*</span> (<span class="hljs-number">10</span> <span class="hljs-operator">*</span><span class="hljs-operator">*</span> decimals()); <span class="hljs-comment">// 18 decimals assumed for sUSD</span>
        <span class="hljs-keyword">uint256</span> requiredCollateral <span class="hljs-operator">=</span> (requiredCollateralValueUSD <span class="hljs-operator">*</span> collateralizationRatio) <span class="hljs-operator">/</span> (<span class="hljs-number">100</span> <span class="hljs-operator">*</span> collateralPrice);
        <span class="hljs-keyword">uint256</span> adjustedRequiredCollateral <span class="hljs-operator">=</span> (requiredCollateral <span class="hljs-operator">*</span> (<span class="hljs-number">10</span> <span class="hljs-operator">*</span><span class="hljs-operator">*</span> collateralDecimals)) <span class="hljs-operator">/</span> (<span class="hljs-number">10</span> <span class="hljs-operator">*</span><span class="hljs-operator">*</span> priceFeed.decimals());

        collateralToken.safeTransferFrom(<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>, <span class="hljs-keyword">address</span>(<span class="hljs-built_in">this</span>), adjustedRequiredCollateral);
        _mint(<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>, amount);

        <span class="hljs-keyword">emit</span> Minted(<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>, amount, adjustedRequiredCollateral);
    }

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">redeem</span>(<span class="hljs-params"><span class="hljs-keyword">uint256</span> amount</span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span> <span class="hljs-title">nonReentrant</span> </span>{
        <span class="hljs-keyword">if</span> (amount <span class="hljs-operator">=</span><span class="hljs-operator">=</span> <span class="hljs-number">0</span>) <span class="hljs-keyword">revert</span> MintAmountIsZero();
        <span class="hljs-keyword">if</span> (balanceOf(<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>) <span class="hljs-operator">&lt;</span> amount) <span class="hljs-keyword">revert</span> InsufficientStablecoinBalance();

        <span class="hljs-keyword">uint256</span> collateralPrice <span class="hljs-operator">=</span> getCurrentPrice();
        <span class="hljs-keyword">uint256</span> stablecoinValueUSD <span class="hljs-operator">=</span> amount <span class="hljs-operator">*</span> (<span class="hljs-number">10</span> <span class="hljs-operator">*</span><span class="hljs-operator">*</span> decimals());
        <span class="hljs-keyword">uint256</span> collateralToReturn <span class="hljs-operator">=</span> (stablecoinValueUSD <span class="hljs-operator">*</span> <span class="hljs-number">100</span>) <span class="hljs-operator">/</span> (collateralizationRatio <span class="hljs-operator">*</span> collateralPrice);
        <span class="hljs-keyword">uint256</span> adjustedCollateralToReturn <span class="hljs-operator">=</span> (collateralToReturn <span class="hljs-operator">*</span> (<span class="hljs-number">10</span> <span class="hljs-operator">*</span><span class="hljs-operator">*</span> collateralDecimals)) <span class="hljs-operator">/</span> (<span class="hljs-number">10</span> <span class="hljs-operator">*</span><span class="hljs-operator">*</span> priceFeed.decimals());

        _burn(<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>, amount);
        collateralToken.safeTransfer(<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>, adjustedCollateralToReturn);

        <span class="hljs-keyword">emit</span> Redeemed(<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>, amount, adjustedCollateralToReturn);
    }

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">setCollateralizationRatio</span>(<span class="hljs-params"><span class="hljs-keyword">uint256</span> newRatio</span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span> <span class="hljs-title">onlyOwner</span> </span>{
        <span class="hljs-keyword">if</span> (newRatio <span class="hljs-operator">&lt;</span> <span class="hljs-number">100</span>) <span class="hljs-keyword">revert</span> CollateralizationRatioTooLow();
        collateralizationRatio <span class="hljs-operator">=</span> newRatio;
        <span class="hljs-keyword">emit</span> CollateralizationRatioUpdated(newRatio);
    }

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">setPriceFeedContract</span>(<span class="hljs-params"><span class="hljs-keyword">address</span> _newPriceFeed</span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span> <span class="hljs-title">onlyRole</span>(<span class="hljs-params">PRICE_FEED_MANAGER_ROLE</span>) </span>{
        <span class="hljs-keyword">if</span> (_newPriceFeed <span class="hljs-operator">=</span><span class="hljs-operator">=</span> <span class="hljs-keyword">address</span>(<span class="hljs-number">0</span>)) <span class="hljs-keyword">revert</span> InvalidPriceFeedAddress();
        priceFeed <span class="hljs-operator">=</span> AggregatorV3Interface(_newPriceFeed);
        <span class="hljs-keyword">emit</span> PriceFeedUpdated(_newPriceFeed);
    }

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getRequiredCollateralForMint</span>(<span class="hljs-params"><span class="hljs-keyword">uint256</span> amount</span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span> <span class="hljs-title"><span class="hljs-keyword">view</span></span> <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params"><span class="hljs-keyword">uint256</span></span>) </span>{
        <span class="hljs-keyword">if</span> (amount <span class="hljs-operator">=</span><span class="hljs-operator">=</span> <span class="hljs-number">0</span>) <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;

        <span class="hljs-keyword">uint256</span> collateralPrice <span class="hljs-operator">=</span> getCurrentPrice();
        <span class="hljs-keyword">uint256</span> requiredCollateralValueUSD <span class="hljs-operator">=</span> amount <span class="hljs-operator">*</span> (<span class="hljs-number">10</span> <span class="hljs-operator">*</span><span class="hljs-operator">*</span> decimals());
        <span class="hljs-keyword">uint256</span> requiredCollateral <span class="hljs-operator">=</span> (requiredCollateralValueUSD <span class="hljs-operator">*</span> collateralizationRatio) <span class="hljs-operator">/</span> (<span class="hljs-number">100</span> <span class="hljs-operator">*</span> collateralPrice);
        <span class="hljs-keyword">uint256</span> adjustedRequiredCollateral <span class="hljs-operator">=</span> (requiredCollateral <span class="hljs-operator">*</span> (<span class="hljs-number">10</span> <span class="hljs-operator">*</span><span class="hljs-operator">*</span> collateralDecimals)) <span class="hljs-operator">/</span> (<span class="hljs-number">10</span> <span class="hljs-operator">*</span><span class="hljs-operator">*</span> priceFeed.decimals());

        <span class="hljs-keyword">return</span> adjustedRequiredCollateral;
    }

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getCollateralForRedeem</span>(<span class="hljs-params"><span class="hljs-keyword">uint256</span> amount</span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span> <span class="hljs-title"><span class="hljs-keyword">view</span></span> <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params"><span class="hljs-keyword">uint256</span></span>) </span>{
        <span class="hljs-keyword">if</span> (amount <span class="hljs-operator">=</span><span class="hljs-operator">=</span> <span class="hljs-number">0</span>) <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;

        <span class="hljs-keyword">uint256</span> collateralPrice <span class="hljs-operator">=</span> getCurrentPrice();
        <span class="hljs-keyword">uint256</span> stablecoinValueUSD <span class="hljs-operator">=</span> amount <span class="hljs-operator">*</span> (<span class="hljs-number">10</span> <span class="hljs-operator">*</span><span class="hljs-operator">*</span> decimals());
        <span class="hljs-keyword">uint256</span> collateralToReturn <span class="hljs-operator">=</span> (stablecoinValueUSD <span class="hljs-operator">*</span> <span class="hljs-number">100</span>) <span class="hljs-operator">/</span> (collateralizationRatio <span class="hljs-operator">*</span> collateralPrice);
        <span class="hljs-keyword">uint256</span> adjustedCollateralToReturn <span class="hljs-operator">=</span> (collateralToReturn <span class="hljs-operator">*</span> (<span class="hljs-number">10</span> <span class="hljs-operator">*</span><span class="hljs-operator">*</span> collateralDecimals)) <span class="hljs-operator">/</span> (<span class="hljs-number">10</span> <span class="hljs-operator">*</span><span class="hljs-operator">*</span> priceFeed.decimals());

        <span class="hljs-keyword">return</span> adjustedCollateralToReturn;
    }
    
}
</code></pre><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0"><strong>一、合约概述</strong></h2><p>该合约构建了一个基于 ERC - 20 代币的去中心化治理系统（DAO），具备加权投票、法定人数（quorum）、提案押金和时间锁（timelock）等功能，用于社区决策和治理。</p><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0"><strong>二、继承与引入</strong></h2><ol><li><p><strong>继承</strong>：继承自<code>ReentrancyGuard</code>合约，以防止重入攻击，确保合约在处理复杂操作时的安全性。</p></li><li><p><strong>引入</strong>：导入<code>@openzeppelin/contracts/token/ERC20/IERC20.sol</code>用于 ERC20 代币相关操作，<code>@openzeppelin/contracts/utils/math/SafeCast.sol</code>用于安全的类型转换，提供更安全的数学运算。</p></li></ol><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0"><strong>三、结构体</strong></h2><h3 id="h-proposal" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0"><strong>Proposal</strong></h3><p>用于存储每个提案的详细信息：</p><ul><li><p><code>id</code>：提案的唯一标识符。</p></li><li><p><code>description</code>：提案的描述。</p></li><li><p><code>deadline</code>：投票截止时间。</p></li><li><p><code>votesFor</code>：支持该提案的票数。</p></li><li><p><code>votesAgainst</code>：反对该提案的票数。</p></li><li><p><code>executed</code>：标记提案是否已执行。</p></li><li><p><code>proposer</code>：提案发起者的地址。</p></li><li><p><code>executionData</code>：执行提案所需的数据。</p></li><li><p><code>executionTargets</code>：执行提案的目标合约地址。</p></li><li><p><code>executionTime</code>：提案执行时间（如果通过，时间锁到期后执行）。</p></li></ul><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0"><strong>四、状态变量</strong></h2><ol><li><p><strong>治理代币相关</strong></p><ul><li><p><code>governanceToken</code>：类型为<code>IERC20</code>，代表治理代币合约地址，用于投票权重计算。</p></li></ul></li><li><p><strong>提案相关</strong></p><ul><li><p><code>proposals</code>：通过<code>mapping</code>将提案 ID 与<code>Proposal</code>结构体关联，存储所有提案的详细信息。</p></li><li><p><code>hasVoted</code>：二维<code>mapping</code>，记录每个地址对每个提案的投票情况，防止重复投票。</p></li><li><p><code>nextProposalId</code>：记录下一个提案的 ID，每次创建提案时递增。</p></li></ul></li><li><p><strong>时间与押金相关</strong></p><ul><li><p><code>votingDuration</code>：投票持续时间。</p></li><li><p><code>timelockDuration</code>：提案通过后到执行之间的时间锁持续时间。</p></li></ul></li><li><p><strong>管理相关</strong></p><ul><li><p><code>admin</code>：合约管理员地址，拥有特定管理权限。</p></li><li><p><code>quorumPercentage</code>：法定人数百分比，用于判断提案是否达到最低投票参与度。</p></li><li><p><code>proposalDepositAmount</code>：发起提案所需的押金数量。</p></li></ul></li></ol><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0"><strong>五、事件</strong></h2><ol><li><p><strong>提案相关</strong></p><ul><li><p><code>ProposalCreated</code>：创建提案时触发，记录提案 ID、描述、发起者和押金金额。</p></li><li><p><code>ProposalDepositPaid</code>：发起提案者支付押金时触发，记录发起者和支付金额。</p></li><li><p><code>ProposalDepositRefunded</code>：提案通过后退还发起者押金时触发，记录发起者和退还金额。</p></li></ul></li><li><p><strong>投票相关</strong></p><ul><li><p><code>Voted</code>：用户投票时触发，记录提案 ID、投票者、投票支持与否及投票权重。</p></li></ul></li><li><p><strong>执行相关</strong></p><ul><li><p><code>ProposalExecuted</code>：提案执行时触发，记录提案 ID 和执行结果（通过或失败）。</p></li><li><p><code>QuorumNotMet</code>：提案未达到法定人数时触发，记录提案 ID、总票数和所需法定人数。</p></li><li><p><code>ProposalTimelockStarted</code>：提案通过并启动时间锁时触发，记录提案 ID 和执行时间。</p></li></ul></li><li><p><strong>管理相关</strong></p><ul><li><p><code>TimelockSet</code>：设置时间锁持续时间时触发，记录设置的时间锁时长。</p></li></ul></li></ol><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0"><strong>六、修饰器</strong></h2><h3 id="h-onlyadmin" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0"><strong>onlyAdmin</strong></h3><p>限制只有管理员（<code>admin</code>）能调用被修饰的函数，确保特定管理操作的权限控制。</p><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0"><strong>七、函数</strong></h2><ol><li><p><strong>构造函数</strong></p><ul><li><p><strong>功能</strong>：初始化合约，设置治理代币、投票持续时间、时间锁持续时间，并记录管理员地址。</p></li><li><p><strong>参数</strong>：<code>_governanceToken</code>为治理代币合约地址，<code>_votingDuration</code>为投票持续时间，<code>_timelockDuration</code>为时间锁持续时间。</p></li></ul></li><li><p><strong>管理设置函数</strong></p><ul><li><p><code>setQuorumPercentage</code>：</p><ul><li><p><strong>功能</strong>：管理员设置法定人数百分比。</p></li><li><p><strong>要求</strong>：仅管理员可调用，且设置的百分比需在 0 到 100 之间。</p></li></ul></li><li><p><code>setProposalDepositAmount</code>：</p><ul><li><p><strong>功能</strong>：管理员设置提案押金金额。</p></li><li><p><strong>要求</strong>：仅管理员可调用。</p></li></ul></li><li><p><code>setTimelockDuration</code>：</p><ul><li><p><strong>功能</strong>：管理员设置时间锁持续时间，并触发<code>TimelockSet</code>事件。</p></li><li><p><strong>要求</strong>：仅管理员可调用。</p></li></ul></li></ul></li><li><p><strong>提案操作函数</strong></p><ul><li><p><code>createProposal</code>：</p><ul><li><p><strong>功能</strong>：用户创建提案，需支付押金，且目标合约地址和执行数据长度需匹配。</p></li><li><p><strong>要求</strong>：用户治理代币余额不少于提案押金金额。</p></li><li><p><strong>操作</strong>：从用户处转移押金到合约，创建提案结构体并记录相关信息，递增提案 ID 并返回新提案 ID。</p></li></ul></li><li><p><code>vote</code>：</p><ul><li><p><strong>功能</strong>：用户对提案进行投票。</p></li><li><p><strong>要求</strong>：投票时间未截止，用户拥有治理代币且未对该提案投票过。</p></li><li><p><strong>操作</strong>：根据用户投票选择增加相应票数，记录用户已投票，触发<code>Voted</code>事件。</p></li></ul></li><li><p><code>finalizeProposal</code>：</p><ul><li><p><strong>功能</strong>：结束提案投票，判断提案是否通过及是否达到法定人数，若通过则启动时间锁。</p></li><li><p><strong>要求</strong>：投票时间已截止且提案未执行。</p></li><li><p><strong>操作</strong>：计算总票数、法定人数，根据投票结果设置提案执行时间或标记提案已执行，并触发相应事件。</p></li></ul></li><li><p><code>executeProposal</code>：</p><ul><li><p><strong>功能</strong>：在时间锁到期后执行提案，若提案通过则执行相关操作并退还提案发起者押金。</p></li><li><p><strong>要求</strong>：提案未执行且时间锁已到期。</p></li><li><p><strong>操作</strong>：标记提案已执行，若提案通过则调用目标合约执行相关操作，触发<code>ProposalExecuted</code>事件并退还押金。</p></li></ul></li></ul></li><li><p><strong>查询函数</strong></p><ul><li><p><code>getProposalResult</code>：</p><ul><li><p><strong>功能</strong>：获取提案结果。</p></li><li><p><strong>要求</strong>：提案已执行。</p></li><li><p><strong>返回</strong>：根据投票结果和法定人数判断返回提案通过、失败或未达法定人数的字符串。</p></li></ul></li><li><p><code>getProposalDetails</code>：</p><ul><li><p><strong>功能</strong>：获取提案详细信息。</p></li><li><p><strong>返回</strong>：指定提案 ID 对应的<code>Proposal</code>结构体。</p></li></ul></li></ul></li></ol><br>]]></content:encoded>
            <author>earnest_stus@newsletter.paragraph.com (Earnest_stu)</author>
        </item>
        <item>
            <title><![CDATA[收益农场（Yield Farming）合约学习笔记]]></title>
            <link>https://paragraph.com/@Earnest_stus/收益农场（yield-farming）合约学习笔记</link>
            <guid>91Vmx8kjupFEgLyCydzS</guid>
            <pubDate>Thu, 06 Nov 2025 02:52:54 GMT</pubDate>
            <description><![CDATA[// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "@openzeppelin/contracts/security/ReentrancyGuard.sol"; import "@openzeppelin/contracts/utils/math/SafeCast.sol"; // For SafeCast if needed // Interface for fetching ERC-20 metadata (decimals) interface IERC20Metadata is IERC20 { function decimals() external view returns (uint8); function name() external view returns (string memory); function symbol() external view returns ...]]></description>
            <content:encoded><![CDATA[<pre data-type="codeBlock" text="// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import &quot;@openzeppelin/contracts/token/ERC20/IERC20.sol&quot;;
import &quot;@openzeppelin/contracts/security/ReentrancyGuard.sol&quot;;
import &quot;@openzeppelin/contracts/utils/math/SafeCast.sol&quot;; // For SafeCast if needed

// Interface for fetching ERC-20 metadata (decimals)
interface IERC20Metadata is IERC20 {
    function decimals() external view returns (uint8);
    function name() external view returns (string memory);
    function symbol() external view returns (string memory);
}

/// @title Yield Farming Platform
///     Stake tokens to earn rewards over time with optional emergency withdrawal and admin refill
contract YieldFarming is ReentrancyGuard {
    using SafeCast for uint256;

    IERC20 public stakingToken;
    IERC20 public rewardToken;

    uint256 public rewardRatePerSecond; // Rewards distributed per second

    address public owner;

    uint8 public stakingTokenDecimals; // Store the number of decimals for the staking token

    struct StakerInfo {
        uint256 stakedAmount;
        uint256 rewardDebt;
        uint256 lastUpdate;
    }

    mapping(address =&gt; StakerInfo) public stakers;

    event Staked(address indexed user, uint256 amount);
    event Unstaked(address indexed user, uint256 amount);
    event RewardClaimed(address indexed user, uint256 amount);
    event EmergencyWithdraw(address indexed user, uint256 amount);
    event RewardRefilled(address indexed owner, uint256 amount);

    modifier onlyOwner() {
        require(msg.sender == owner, &quot;Not the owner&quot;);
        _;
    }

    constructor(
        address _stakingToken,
        address _rewardToken,
        uint256 _rewardRatePerSecond
    ) {
        stakingToken = IERC20(_stakingToken);
        rewardToken = IERC20(_rewardToken);
        rewardRatePerSecond = _rewardRatePerSecond;
        owner = msg.sender;

        // Try fetching decimals
        try IERC20Metadata(_stakingToken).decimals() returns (uint8 decimals) {
            stakingTokenDecimals = decimals;
        } catch (bytes memory) {
            stakingTokenDecimals = 18; // Default to 18 decimals if fetching fails
        }
    }

    ///     Stake tokens to start earning rewards
    function stake(uint256 amount) external nonReentrant {
        require(amount &gt; 0, &quot;Cannot stake 0&quot;);

        updateRewards(msg.sender);

        stakingToken.transferFrom(msg.sender, address(this), amount);
        stakers[msg.sender].stakedAmount += amount;

        emit Staked(msg.sender, amount);
    }

    ///     Unstake tokens and optionally claim rewards
    function unstake(uint256 amount) external nonReentrant {
        require(amount &gt; 0, &quot;Cannot unstake 0&quot;);
        require(stakers[msg.sender].stakedAmount &gt;= amount, &quot;Not enough staked&quot;);

        updateRewards(msg.sender);

        stakers[msg.sender].stakedAmount -= amount;
        stakingToken.transfer(msg.sender, amount);

        emit Unstaked(msg.sender, amount);
    }

    ///     Claim accumulated rewards
    function claimRewards() external nonReentrant {
        updateRewards(msg.sender);

        uint256 reward = stakers[msg.sender].rewardDebt;
        require(reward &gt; 0, &quot;No rewards to claim&quot;);
        require(rewardToken.balanceOf(address(this)) &gt;= reward, &quot;Insufficient reward token balance&quot;);

        stakers[msg.sender].rewardDebt = 0;
        rewardToken.transfer(msg.sender, reward);

        emit RewardClaimed(msg.sender, reward);
    }

    ///     Emergency unstake without claiming rewards
    function emergencyWithdraw() external nonReentrant {
        uint256 amount = stakers[msg.sender].stakedAmount;
        require(amount &gt; 0, &quot;Nothing staked&quot;);

        stakers[msg.sender].stakedAmount = 0;
        stakers[msg.sender].rewardDebt = 0;
        stakers[msg.sender].lastUpdate = block.timestamp;

        stakingToken.transfer(msg.sender, amount);

        emit EmergencyWithdraw(msg.sender, amount);
    }

    ///     Admin can refill reward tokens
    function refillRewards(uint256 amount) external onlyOwner {
        rewardToken.transferFrom(msg.sender, address(this), amount);

        emit RewardRefilled(msg.sender, amount);
    }

    ///     Update rewards for a staker
    function updateRewards(address user) internal {
        StakerInfo storage staker = stakers[user];

        if (staker.stakedAmount &gt; 0) {
            uint256 timeDiff = block.timestamp - staker.lastUpdate;
            uint256 rewardMultiplier = 10 ** stakingTokenDecimals;
            uint256 pendingReward = (timeDiff * rewardRatePerSecond * staker.stakedAmount) / rewardMultiplier;
            staker.rewardDebt += pendingReward;
        }

        staker.lastUpdate = block.timestamp;
    }

    ///     View pending rewards without claiming
    function pendingRewards(address user) external view returns (uint256) {
        StakerInfo memory staker = stakers[user];

        uint256 pendingReward = staker.rewardDebt;

        if (staker.stakedAmount &gt; 0) {
            uint256 timeDiff = block.timestamp - staker.lastUpdate;
            uint256 rewardMultiplier = 10 ** stakingTokenDecimals;
            pendingReward += (timeDiff * rewardRatePerSecond * staker.stakedAmount) / rewardMultiplier;
        }

        return pendingReward;
    }

    ///     View staking token decimals
    function getStakingTokenDecimals() external view returns (uint8) {
        return stakingTokenDecimals;
    }
}"><code><span class="hljs-comment">// SPDX-License-Identifier: MIT</span>
<span class="hljs-meta"><span class="hljs-keyword">pragma</span> <span class="hljs-keyword">solidity</span> ^0.8.20;</span>

<span class="hljs-keyword">import</span> <span class="hljs-string">"@openzeppelin/contracts/token/ERC20/IERC20.sol"</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">"@openzeppelin/contracts/security/ReentrancyGuard.sol"</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">"@openzeppelin/contracts/utils/math/SafeCast.sol"</span>; <span class="hljs-comment">// For SafeCast if needed</span>

<span class="hljs-comment">// Interface for fetching ERC-20 metadata (decimals)</span>
<span class="hljs-class"><span class="hljs-keyword">interface</span> <span class="hljs-title">IERC20Metadata</span> <span class="hljs-keyword">is</span> <span class="hljs-title">IERC20</span> </span>{
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">decimals</span>(<span class="hljs-params"></span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span> <span class="hljs-title"><span class="hljs-keyword">view</span></span> <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params"><span class="hljs-keyword">uint8</span></span>)</span>;
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">name</span>(<span class="hljs-params"></span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span> <span class="hljs-title"><span class="hljs-keyword">view</span></span> <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params"><span class="hljs-keyword">string</span> <span class="hljs-keyword">memory</span></span>)</span>;
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">symbol</span>(<span class="hljs-params"></span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span> <span class="hljs-title"><span class="hljs-keyword">view</span></span> <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params"><span class="hljs-keyword">string</span> <span class="hljs-keyword">memory</span></span>)</span>;
}

<span class="hljs-comment">/// @title Yield Farming Platform</span>
<span class="hljs-comment">///     Stake tokens to earn rewards over time with optional emergency withdrawal and admin refill</span>
<span class="hljs-class"><span class="hljs-keyword">contract</span> <span class="hljs-title">YieldFarming</span> <span class="hljs-keyword">is</span> <span class="hljs-title">ReentrancyGuard</span> </span>{
    <span class="hljs-keyword">using</span> <span class="hljs-title">SafeCast</span> <span class="hljs-title"><span class="hljs-keyword">for</span></span> <span class="hljs-title"><span class="hljs-keyword">uint256</span></span>;

    IERC20 <span class="hljs-keyword">public</span> stakingToken;
    IERC20 <span class="hljs-keyword">public</span> rewardToken;

    <span class="hljs-keyword">uint256</span> <span class="hljs-keyword">public</span> rewardRatePerSecond; <span class="hljs-comment">// Rewards distributed per second</span>

    <span class="hljs-keyword">address</span> <span class="hljs-keyword">public</span> owner;

    <span class="hljs-keyword">uint8</span> <span class="hljs-keyword">public</span> stakingTokenDecimals; <span class="hljs-comment">// Store the number of decimals for the staking token</span>

    <span class="hljs-keyword">struct</span> <span class="hljs-title">StakerInfo</span> {
        <span class="hljs-keyword">uint256</span> stakedAmount;
        <span class="hljs-keyword">uint256</span> rewardDebt;
        <span class="hljs-keyword">uint256</span> lastUpdate;
    }

    <span class="hljs-keyword">mapping</span>(<span class="hljs-keyword">address</span> <span class="hljs-operator">=</span><span class="hljs-operator">&gt;</span> StakerInfo) <span class="hljs-keyword">public</span> stakers;

    <span class="hljs-function"><span class="hljs-keyword">event</span> <span class="hljs-title">Staked</span>(<span class="hljs-params"><span class="hljs-keyword">address</span> <span class="hljs-keyword">indexed</span> user, <span class="hljs-keyword">uint256</span> amount</span>)</span>;
    <span class="hljs-function"><span class="hljs-keyword">event</span> <span class="hljs-title">Unstaked</span>(<span class="hljs-params"><span class="hljs-keyword">address</span> <span class="hljs-keyword">indexed</span> user, <span class="hljs-keyword">uint256</span> amount</span>)</span>;
    <span class="hljs-function"><span class="hljs-keyword">event</span> <span class="hljs-title">RewardClaimed</span>(<span class="hljs-params"><span class="hljs-keyword">address</span> <span class="hljs-keyword">indexed</span> user, <span class="hljs-keyword">uint256</span> amount</span>)</span>;
    <span class="hljs-function"><span class="hljs-keyword">event</span> <span class="hljs-title">EmergencyWithdraw</span>(<span class="hljs-params"><span class="hljs-keyword">address</span> <span class="hljs-keyword">indexed</span> user, <span class="hljs-keyword">uint256</span> amount</span>)</span>;
    <span class="hljs-function"><span class="hljs-keyword">event</span> <span class="hljs-title">RewardRefilled</span>(<span class="hljs-params"><span class="hljs-keyword">address</span> <span class="hljs-keyword">indexed</span> owner, <span class="hljs-keyword">uint256</span> amount</span>)</span>;

    <span class="hljs-function"><span class="hljs-keyword">modifier</span> <span class="hljs-title">onlyOwner</span>(<span class="hljs-params"></span>) </span>{
        <span class="hljs-built_in">require</span>(<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span> <span class="hljs-operator">=</span><span class="hljs-operator">=</span> owner, <span class="hljs-string">"Not the owner"</span>);
        <span class="hljs-keyword">_</span>;
    }

    <span class="hljs-function"><span class="hljs-keyword">constructor</span>(<span class="hljs-params">
        <span class="hljs-keyword">address</span> _stakingToken,
        <span class="hljs-keyword">address</span> _rewardToken,
        <span class="hljs-keyword">uint256</span> _rewardRatePerSecond
    </span>) </span>{
        stakingToken <span class="hljs-operator">=</span> IERC20(_stakingToken);
        rewardToken <span class="hljs-operator">=</span> IERC20(_rewardToken);
        rewardRatePerSecond <span class="hljs-operator">=</span> _rewardRatePerSecond;
        owner <span class="hljs-operator">=</span> <span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>;

        <span class="hljs-comment">// Try fetching decimals</span>
        <span class="hljs-keyword">try</span> IERC20Metadata(_stakingToken).decimals() <span class="hljs-keyword">returns</span> (<span class="hljs-keyword">uint8</span> decimals) {
            stakingTokenDecimals <span class="hljs-operator">=</span> decimals;
        } <span class="hljs-keyword">catch</span> (<span class="hljs-keyword">bytes</span> <span class="hljs-keyword">memory</span>) {
            stakingTokenDecimals <span class="hljs-operator">=</span> <span class="hljs-number">18</span>; <span class="hljs-comment">// Default to 18 decimals if fetching fails</span>
        }
    }

    <span class="hljs-comment">///     Stake tokens to start earning rewards</span>
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">stake</span>(<span class="hljs-params"><span class="hljs-keyword">uint256</span> amount</span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span> <span class="hljs-title">nonReentrant</span> </span>{
        <span class="hljs-built_in">require</span>(amount <span class="hljs-operator">&gt;</span> <span class="hljs-number">0</span>, <span class="hljs-string">"Cannot stake 0"</span>);

        updateRewards(<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>);

        stakingToken.transferFrom(<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>, <span class="hljs-keyword">address</span>(<span class="hljs-built_in">this</span>), amount);
        stakers[<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>].stakedAmount <span class="hljs-operator">+</span><span class="hljs-operator">=</span> amount;

        <span class="hljs-keyword">emit</span> Staked(<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>, amount);
    }

    <span class="hljs-comment">///     Unstake tokens and optionally claim rewards</span>
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">unstake</span>(<span class="hljs-params"><span class="hljs-keyword">uint256</span> amount</span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span> <span class="hljs-title">nonReentrant</span> </span>{
        <span class="hljs-built_in">require</span>(amount <span class="hljs-operator">&gt;</span> <span class="hljs-number">0</span>, <span class="hljs-string">"Cannot unstake 0"</span>);
        <span class="hljs-built_in">require</span>(stakers[<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>].stakedAmount <span class="hljs-operator">&gt;</span><span class="hljs-operator">=</span> amount, <span class="hljs-string">"Not enough staked"</span>);

        updateRewards(<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>);

        stakers[<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>].stakedAmount <span class="hljs-operator">-</span><span class="hljs-operator">=</span> amount;
        stakingToken.<span class="hljs-built_in">transfer</span>(<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>, amount);

        <span class="hljs-keyword">emit</span> Unstaked(<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>, amount);
    }

    <span class="hljs-comment">///     Claim accumulated rewards</span>
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">claimRewards</span>(<span class="hljs-params"></span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span> <span class="hljs-title">nonReentrant</span> </span>{
        updateRewards(<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>);

        <span class="hljs-keyword">uint256</span> reward <span class="hljs-operator">=</span> stakers[<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>].rewardDebt;
        <span class="hljs-built_in">require</span>(reward <span class="hljs-operator">&gt;</span> <span class="hljs-number">0</span>, <span class="hljs-string">"No rewards to claim"</span>);
        <span class="hljs-built_in">require</span>(rewardToken.balanceOf(<span class="hljs-keyword">address</span>(<span class="hljs-built_in">this</span>)) <span class="hljs-operator">&gt;</span><span class="hljs-operator">=</span> reward, <span class="hljs-string">"Insufficient reward token balance"</span>);

        stakers[<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>].rewardDebt <span class="hljs-operator">=</span> <span class="hljs-number">0</span>;
        rewardToken.<span class="hljs-built_in">transfer</span>(<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>, reward);

        <span class="hljs-keyword">emit</span> RewardClaimed(<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>, reward);
    }

    <span class="hljs-comment">///     Emergency unstake without claiming rewards</span>
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">emergencyWithdraw</span>(<span class="hljs-params"></span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span> <span class="hljs-title">nonReentrant</span> </span>{
        <span class="hljs-keyword">uint256</span> amount <span class="hljs-operator">=</span> stakers[<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>].stakedAmount;
        <span class="hljs-built_in">require</span>(amount <span class="hljs-operator">&gt;</span> <span class="hljs-number">0</span>, <span class="hljs-string">"Nothing staked"</span>);

        stakers[<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>].stakedAmount <span class="hljs-operator">=</span> <span class="hljs-number">0</span>;
        stakers[<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>].rewardDebt <span class="hljs-operator">=</span> <span class="hljs-number">0</span>;
        stakers[<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>].lastUpdate <span class="hljs-operator">=</span> <span class="hljs-built_in">block</span>.<span class="hljs-built_in">timestamp</span>;

        stakingToken.<span class="hljs-built_in">transfer</span>(<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>, amount);

        <span class="hljs-keyword">emit</span> EmergencyWithdraw(<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>, amount);
    }

    <span class="hljs-comment">///     Admin can refill reward tokens</span>
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">refillRewards</span>(<span class="hljs-params"><span class="hljs-keyword">uint256</span> amount</span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span> <span class="hljs-title">onlyOwner</span> </span>{
        rewardToken.transferFrom(<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>, <span class="hljs-keyword">address</span>(<span class="hljs-built_in">this</span>), amount);

        <span class="hljs-keyword">emit</span> RewardRefilled(<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>, amount);
    }

    <span class="hljs-comment">///     Update rewards for a staker</span>
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">updateRewards</span>(<span class="hljs-params"><span class="hljs-keyword">address</span> user</span>) <span class="hljs-title"><span class="hljs-keyword">internal</span></span> </span>{
        StakerInfo <span class="hljs-keyword">storage</span> staker <span class="hljs-operator">=</span> stakers[user];

        <span class="hljs-keyword">if</span> (staker.stakedAmount <span class="hljs-operator">&gt;</span> <span class="hljs-number">0</span>) {
            <span class="hljs-keyword">uint256</span> timeDiff <span class="hljs-operator">=</span> <span class="hljs-built_in">block</span>.<span class="hljs-built_in">timestamp</span> <span class="hljs-operator">-</span> staker.lastUpdate;
            <span class="hljs-keyword">uint256</span> rewardMultiplier <span class="hljs-operator">=</span> <span class="hljs-number">10</span> <span class="hljs-operator">*</span><span class="hljs-operator">*</span> stakingTokenDecimals;
            <span class="hljs-keyword">uint256</span> pendingReward <span class="hljs-operator">=</span> (timeDiff <span class="hljs-operator">*</span> rewardRatePerSecond <span class="hljs-operator">*</span> staker.stakedAmount) <span class="hljs-operator">/</span> rewardMultiplier;
            staker.rewardDebt <span class="hljs-operator">+</span><span class="hljs-operator">=</span> pendingReward;
        }

        staker.lastUpdate <span class="hljs-operator">=</span> <span class="hljs-built_in">block</span>.<span class="hljs-built_in">timestamp</span>;
    }

    <span class="hljs-comment">///     View pending rewards without claiming</span>
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">pendingRewards</span>(<span class="hljs-params"><span class="hljs-keyword">address</span> user</span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span> <span class="hljs-title"><span class="hljs-keyword">view</span></span> <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params"><span class="hljs-keyword">uint256</span></span>) </span>{
        StakerInfo <span class="hljs-keyword">memory</span> staker <span class="hljs-operator">=</span> stakers[user];

        <span class="hljs-keyword">uint256</span> pendingReward <span class="hljs-operator">=</span> staker.rewardDebt;

        <span class="hljs-keyword">if</span> (staker.stakedAmount <span class="hljs-operator">&gt;</span> <span class="hljs-number">0</span>) {
            <span class="hljs-keyword">uint256</span> timeDiff <span class="hljs-operator">=</span> <span class="hljs-built_in">block</span>.<span class="hljs-built_in">timestamp</span> <span class="hljs-operator">-</span> staker.lastUpdate;
            <span class="hljs-keyword">uint256</span> rewardMultiplier <span class="hljs-operator">=</span> <span class="hljs-number">10</span> <span class="hljs-operator">*</span><span class="hljs-operator">*</span> stakingTokenDecimals;
            pendingReward <span class="hljs-operator">+</span><span class="hljs-operator">=</span> (timeDiff <span class="hljs-operator">*</span> rewardRatePerSecond <span class="hljs-operator">*</span> staker.stakedAmount) <span class="hljs-operator">/</span> rewardMultiplier;
        }

        <span class="hljs-keyword">return</span> pendingReward;
    }

    <span class="hljs-comment">///     View staking token decimals</span>
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getStakingTokenDecimals</span>(<span class="hljs-params"></span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span> <span class="hljs-title"><span class="hljs-keyword">view</span></span> <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params"><span class="hljs-keyword">uint8</span></span>) </span>{
        <span class="hljs-keyword">return</span> stakingTokenDecimals;
    }
}</code></pre><br>]]></content:encoded>
            <author>earnest_stus@newsletter.paragraph.com (Earnest_stu)</author>
        </item>
        <item>
            <title><![CDATA[自动化做市商（Automated Market Maker）合约学习笔记]]></title>
            <link>https://paragraph.com/@Earnest_stus/自动化做市商（automated-market-maker）合约学习笔记</link>
            <guid>xthI92Gl2qfiVYQlpasd</guid>
            <pubDate>Wed, 05 Nov 2025 16:19:59 GMT</pubDate>
            <description><![CDATA[// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; /// @title Automated Market Maker with Liquidity Token contract AutomatedMarketMaker is ERC20 { IERC20 public tokenA; IERC20 public tokenB; uint256 public reserveA; uint256 public reserveB; address public owner; event LiquidityAdded(address indexed provider, uint256 amountA, uint256 amountB, uint256 liquidity); event LiquidityRemoved(address indexed provider, uint256 amountA, uint2...]]></description>
            <content:encoded><![CDATA[<pre data-type="codeBlock" text="// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import &quot;@openzeppelin/contracts/token/ERC20/ERC20.sol&quot;;


/// @title Automated Market Maker with Liquidity Token
contract AutomatedMarketMaker is ERC20 {
    IERC20 public tokenA;
    IERC20 public tokenB;

    uint256 public reserveA;
    uint256 public reserveB;

    address public owner;

    event LiquidityAdded(address indexed provider, uint256 amountA, uint256 amountB, uint256 liquidity);
    event LiquidityRemoved(address indexed provider, uint256 amountA, uint256 amountB, uint256 liquidity);
    event TokensSwapped(address indexed trader, address tokenIn, uint256 amountIn, address tokenOut, uint256 amountOut);

    constructor(address _tokenA, address _tokenB, string memory _name, string memory _symbol) ERC20(_name, _symbol) {
        tokenA = IERC20(_tokenA);
        tokenB = IERC20(_tokenB);
        owner = msg.sender;
    }

    /// @notice Add liquidity to the pool
    function addLiquidity(uint256 amountA, uint256 amountB) external {
        require(amountA &gt; 0 &amp;&amp; amountB &gt; 0, &quot;Amounts must be &gt; 0&quot;);

        tokenA.transferFrom(msg.sender, address(this), amountA);
        tokenB.transferFrom(msg.sender, address(this), amountB);

        uint256 liquidity;
        if (totalSupply() == 0) {
            liquidity = sqrt(amountA * amountB);
        } else {
            liquidity = min(
                amountA * totalSupply() / reserveA,
                amountB * totalSupply() / reserveB
            );
        }

        _mint(msg.sender, liquidity);

        reserveA += amountA;
        reserveB += amountB;

        emit LiquidityAdded(msg.sender, amountA, amountB, liquidity);
    }

    /// @notice Remove liquidity from the pool
    function removeLiquidity(uint256 liquidityToRemove) external returns (uint256 amountAOut, uint256 amountBOut) {
        require(liquidityToRemove &gt; 0, &quot;Liquidity to remove must be &gt; 0&quot;);
        require(balanceOf(msg.sender) &gt;= liquidityToRemove, &quot;Insufficient liquidity tokens&quot;);

        uint256 totalLiquidity = totalSupply();
        require(totalLiquidity &gt; 0, &quot;No liquidity in the pool&quot;);

        amountAOut = liquidityToRemove * reserveA / totalLiquidity;
        amountBOut = liquidityToRemove * reserveB / totalLiquidity;

        require(amountAOut &gt; 0 &amp;&amp; amountBOut &gt; 0, &quot;Insufficient reserves for requested liquidity&quot;);

        reserveA -= amountAOut;
        reserveB -= amountBOut;

        _burn(msg.sender, liquidityToRemove);

        tokenA.transfer(msg.sender, amountAOut);
        tokenB.transfer(msg.sender, amountBOut);

        emit LiquidityRemoved(msg.sender, amountAOut, amountBOut, liquidityToRemove);
        return (amountAOut, amountBOut);
    }

    /// @notice Swap token A for token B
    function swapAforB(uint256 amountAIn, uint256 minBOut) external {
        require(amountAIn &gt; 0, &quot;Amount must be &gt; 0&quot;);
        require(reserveA &gt; 0 &amp;&amp; reserveB &gt; 0, &quot;Insufficient reserves&quot;);

        uint256 amountAInWithFee = amountAIn * 997 / 1000;
        uint256 amountBOut = reserveB * amountAInWithFee / (reserveA + amountAInWithFee);

        require(amountBOut &gt;= minBOut, &quot;Slippage too high&quot;);

        tokenA.transferFrom(msg.sender, address(this), amountAIn);
        tokenB.transfer(msg.sender, amountBOut);

        reserveA += amountAInWithFee;
        reserveB -= amountBOut;

        emit TokensSwapped(msg.sender, address(tokenA), amountAIn, address(tokenB), amountBOut);
    }

    /// @notice Swap token B for token A
    function swapBforA(uint256 amountBIn, uint256 minAOut) external {
        require(amountBIn &gt; 0, &quot;Amount must be &gt; 0&quot;);
        require(reserveA &gt; 0 &amp;&amp; reserveB &gt; 0, &quot;Insufficient reserves&quot;);

        uint256 amountBInWithFee = amountBIn * 997 / 1000;
        uint256 amountAOut = reserveA * amountBInWithFee / (reserveB + amountBInWithFee);

        require(amountAOut &gt;= minAOut, &quot;Slippage too high&quot;);

        tokenB.transferFrom(msg.sender, address(this), amountBIn);
        tokenA.transfer(msg.sender, amountAOut);

        reserveB += amountBInWithFee;
        reserveA -= amountAOut;

        emit TokensSwapped(msg.sender, address(tokenB), amountBIn, address(tokenA), amountAOut);
    }

    /// @notice View the current reserves
    function getReserves() external view returns (uint256, uint256) {
        return (reserveA, reserveB);
    }

    /// @dev Utility: Return the smaller of two values
    function min(uint256 a, uint256 b) internal pure returns (uint256) {
        return a &lt; b ? a : b;
    }

    /// @dev Utility: Babylonian square root
    function sqrt(uint256 y) internal pure returns (uint256 z) {
        if (y &gt; 3) {
            z = y;
            uint256 x = y / 2 + 1;
            while (x &lt; z) {
                z = x;
                x = (y / x + x) / 2;
            }
        } else if (y != 0) {
            z = 1;
        }
    }
}"><code><span class="hljs-comment">// SPDX-License-Identifier: MIT</span>
<span class="hljs-meta"><span class="hljs-keyword">pragma</span> <span class="hljs-keyword">solidity</span> ^0.8.20;</span>

<span class="hljs-keyword">import</span> <span class="hljs-string">"@openzeppelin/contracts/token/ERC20/ERC20.sol"</span>;


<span class="hljs-comment">/// @title Automated Market Maker with Liquidity Token</span>
<span class="hljs-class"><span class="hljs-keyword">contract</span> <span class="hljs-title">AutomatedMarketMaker</span> <span class="hljs-keyword">is</span> <span class="hljs-title">ERC20</span> </span>{
    IERC20 <span class="hljs-keyword">public</span> tokenA;
    IERC20 <span class="hljs-keyword">public</span> tokenB;

    <span class="hljs-keyword">uint256</span> <span class="hljs-keyword">public</span> reserveA;
    <span class="hljs-keyword">uint256</span> <span class="hljs-keyword">public</span> reserveB;

    <span class="hljs-keyword">address</span> <span class="hljs-keyword">public</span> owner;

    <span class="hljs-function"><span class="hljs-keyword">event</span> <span class="hljs-title">LiquidityAdded</span>(<span class="hljs-params"><span class="hljs-keyword">address</span> <span class="hljs-keyword">indexed</span> provider, <span class="hljs-keyword">uint256</span> amountA, <span class="hljs-keyword">uint256</span> amountB, <span class="hljs-keyword">uint256</span> liquidity</span>)</span>;
    <span class="hljs-function"><span class="hljs-keyword">event</span> <span class="hljs-title">LiquidityRemoved</span>(<span class="hljs-params"><span class="hljs-keyword">address</span> <span class="hljs-keyword">indexed</span> provider, <span class="hljs-keyword">uint256</span> amountA, <span class="hljs-keyword">uint256</span> amountB, <span class="hljs-keyword">uint256</span> liquidity</span>)</span>;
    <span class="hljs-function"><span class="hljs-keyword">event</span> <span class="hljs-title">TokensSwapped</span>(<span class="hljs-params"><span class="hljs-keyword">address</span> <span class="hljs-keyword">indexed</span> trader, <span class="hljs-keyword">address</span> tokenIn, <span class="hljs-keyword">uint256</span> amountIn, <span class="hljs-keyword">address</span> tokenOut, <span class="hljs-keyword">uint256</span> amountOut</span>)</span>;

    <span class="hljs-function"><span class="hljs-keyword">constructor</span>(<span class="hljs-params"><span class="hljs-keyword">address</span> _tokenA, <span class="hljs-keyword">address</span> _tokenB, <span class="hljs-keyword">string</span> <span class="hljs-keyword">memory</span> _name, <span class="hljs-keyword">string</span> <span class="hljs-keyword">memory</span> _symbol</span>) <span class="hljs-title">ERC20</span>(<span class="hljs-params">_name, _symbol</span>) </span>{
        tokenA <span class="hljs-operator">=</span> IERC20(_tokenA);
        tokenB <span class="hljs-operator">=</span> IERC20(_tokenB);
        owner <span class="hljs-operator">=</span> <span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>;
    }

    <span class="hljs-comment">/// @notice Add liquidity to the pool</span>
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">addLiquidity</span>(<span class="hljs-params"><span class="hljs-keyword">uint256</span> amountA, <span class="hljs-keyword">uint256</span> amountB</span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span> </span>{
        <span class="hljs-built_in">require</span>(amountA <span class="hljs-operator">&gt;</span> <span class="hljs-number">0</span> <span class="hljs-operator">&amp;</span><span class="hljs-operator">&amp;</span> amountB <span class="hljs-operator">&gt;</span> <span class="hljs-number">0</span>, <span class="hljs-string">"Amounts must be &gt; 0"</span>);

        tokenA.transferFrom(<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>, <span class="hljs-keyword">address</span>(<span class="hljs-built_in">this</span>), amountA);
        tokenB.transferFrom(<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>, <span class="hljs-keyword">address</span>(<span class="hljs-built_in">this</span>), amountB);

        <span class="hljs-keyword">uint256</span> liquidity;
        <span class="hljs-keyword">if</span> (totalSupply() <span class="hljs-operator">=</span><span class="hljs-operator">=</span> <span class="hljs-number">0</span>) {
            liquidity <span class="hljs-operator">=</span> sqrt(amountA <span class="hljs-operator">*</span> amountB);
        } <span class="hljs-keyword">else</span> {
            liquidity <span class="hljs-operator">=</span> min(
                amountA <span class="hljs-operator">*</span> totalSupply() <span class="hljs-operator">/</span> reserveA,
                amountB <span class="hljs-operator">*</span> totalSupply() <span class="hljs-operator">/</span> reserveB
            );
        }

        _mint(<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>, liquidity);

        reserveA <span class="hljs-operator">+</span><span class="hljs-operator">=</span> amountA;
        reserveB <span class="hljs-operator">+</span><span class="hljs-operator">=</span> amountB;

        <span class="hljs-keyword">emit</span> LiquidityAdded(<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>, amountA, amountB, liquidity);
    }

    <span class="hljs-comment">/// @notice Remove liquidity from the pool</span>
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">removeLiquidity</span>(<span class="hljs-params"><span class="hljs-keyword">uint256</span> liquidityToRemove</span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span> <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params"><span class="hljs-keyword">uint256</span> amountAOut, <span class="hljs-keyword">uint256</span> amountBOut</span>) </span>{
        <span class="hljs-built_in">require</span>(liquidityToRemove <span class="hljs-operator">&gt;</span> <span class="hljs-number">0</span>, <span class="hljs-string">"Liquidity to remove must be &gt; 0"</span>);
        <span class="hljs-built_in">require</span>(balanceOf(<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>) <span class="hljs-operator">&gt;</span><span class="hljs-operator">=</span> liquidityToRemove, <span class="hljs-string">"Insufficient liquidity tokens"</span>);

        <span class="hljs-keyword">uint256</span> totalLiquidity <span class="hljs-operator">=</span> totalSupply();
        <span class="hljs-built_in">require</span>(totalLiquidity <span class="hljs-operator">&gt;</span> <span class="hljs-number">0</span>, <span class="hljs-string">"No liquidity in the pool"</span>);

        amountAOut <span class="hljs-operator">=</span> liquidityToRemove <span class="hljs-operator">*</span> reserveA <span class="hljs-operator">/</span> totalLiquidity;
        amountBOut <span class="hljs-operator">=</span> liquidityToRemove <span class="hljs-operator">*</span> reserveB <span class="hljs-operator">/</span> totalLiquidity;

        <span class="hljs-built_in">require</span>(amountAOut <span class="hljs-operator">&gt;</span> <span class="hljs-number">0</span> <span class="hljs-operator">&amp;</span><span class="hljs-operator">&amp;</span> amountBOut <span class="hljs-operator">&gt;</span> <span class="hljs-number">0</span>, <span class="hljs-string">"Insufficient reserves for requested liquidity"</span>);

        reserveA <span class="hljs-operator">-</span><span class="hljs-operator">=</span> amountAOut;
        reserveB <span class="hljs-operator">-</span><span class="hljs-operator">=</span> amountBOut;

        _burn(<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>, liquidityToRemove);

        tokenA.<span class="hljs-built_in">transfer</span>(<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>, amountAOut);
        tokenB.<span class="hljs-built_in">transfer</span>(<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>, amountBOut);

        <span class="hljs-keyword">emit</span> LiquidityRemoved(<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>, amountAOut, amountBOut, liquidityToRemove);
        <span class="hljs-keyword">return</span> (amountAOut, amountBOut);
    }

    <span class="hljs-comment">/// @notice Swap token A for token B</span>
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">swapAforB</span>(<span class="hljs-params"><span class="hljs-keyword">uint256</span> amountAIn, <span class="hljs-keyword">uint256</span> minBOut</span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span> </span>{
        <span class="hljs-built_in">require</span>(amountAIn <span class="hljs-operator">&gt;</span> <span class="hljs-number">0</span>, <span class="hljs-string">"Amount must be &gt; 0"</span>);
        <span class="hljs-built_in">require</span>(reserveA <span class="hljs-operator">&gt;</span> <span class="hljs-number">0</span> <span class="hljs-operator">&amp;</span><span class="hljs-operator">&amp;</span> reserveB <span class="hljs-operator">&gt;</span> <span class="hljs-number">0</span>, <span class="hljs-string">"Insufficient reserves"</span>);

        <span class="hljs-keyword">uint256</span> amountAInWithFee <span class="hljs-operator">=</span> amountAIn <span class="hljs-operator">*</span> <span class="hljs-number">997</span> <span class="hljs-operator">/</span> <span class="hljs-number">1000</span>;
        <span class="hljs-keyword">uint256</span> amountBOut <span class="hljs-operator">=</span> reserveB <span class="hljs-operator">*</span> amountAInWithFee <span class="hljs-operator">/</span> (reserveA <span class="hljs-operator">+</span> amountAInWithFee);

        <span class="hljs-built_in">require</span>(amountBOut <span class="hljs-operator">&gt;</span><span class="hljs-operator">=</span> minBOut, <span class="hljs-string">"Slippage too high"</span>);

        tokenA.transferFrom(<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>, <span class="hljs-keyword">address</span>(<span class="hljs-built_in">this</span>), amountAIn);
        tokenB.<span class="hljs-built_in">transfer</span>(<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>, amountBOut);

        reserveA <span class="hljs-operator">+</span><span class="hljs-operator">=</span> amountAInWithFee;
        reserveB <span class="hljs-operator">-</span><span class="hljs-operator">=</span> amountBOut;

        <span class="hljs-keyword">emit</span> TokensSwapped(<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>, <span class="hljs-keyword">address</span>(tokenA), amountAIn, <span class="hljs-keyword">address</span>(tokenB), amountBOut);
    }

    <span class="hljs-comment">/// @notice Swap token B for token A</span>
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">swapBforA</span>(<span class="hljs-params"><span class="hljs-keyword">uint256</span> amountBIn, <span class="hljs-keyword">uint256</span> minAOut</span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span> </span>{
        <span class="hljs-built_in">require</span>(amountBIn <span class="hljs-operator">&gt;</span> <span class="hljs-number">0</span>, <span class="hljs-string">"Amount must be &gt; 0"</span>);
        <span class="hljs-built_in">require</span>(reserveA <span class="hljs-operator">&gt;</span> <span class="hljs-number">0</span> <span class="hljs-operator">&amp;</span><span class="hljs-operator">&amp;</span> reserveB <span class="hljs-operator">&gt;</span> <span class="hljs-number">0</span>, <span class="hljs-string">"Insufficient reserves"</span>);

        <span class="hljs-keyword">uint256</span> amountBInWithFee <span class="hljs-operator">=</span> amountBIn <span class="hljs-operator">*</span> <span class="hljs-number">997</span> <span class="hljs-operator">/</span> <span class="hljs-number">1000</span>;
        <span class="hljs-keyword">uint256</span> amountAOut <span class="hljs-operator">=</span> reserveA <span class="hljs-operator">*</span> amountBInWithFee <span class="hljs-operator">/</span> (reserveB <span class="hljs-operator">+</span> amountBInWithFee);

        <span class="hljs-built_in">require</span>(amountAOut <span class="hljs-operator">&gt;</span><span class="hljs-operator">=</span> minAOut, <span class="hljs-string">"Slippage too high"</span>);

        tokenB.transferFrom(<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>, <span class="hljs-keyword">address</span>(<span class="hljs-built_in">this</span>), amountBIn);
        tokenA.<span class="hljs-built_in">transfer</span>(<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>, amountAOut);

        reserveB <span class="hljs-operator">+</span><span class="hljs-operator">=</span> amountBInWithFee;
        reserveA <span class="hljs-operator">-</span><span class="hljs-operator">=</span> amountAOut;

        <span class="hljs-keyword">emit</span> TokensSwapped(<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>, <span class="hljs-keyword">address</span>(tokenB), amountBIn, <span class="hljs-keyword">address</span>(tokenA), amountAOut);
    }

    <span class="hljs-comment">/// @notice View the current reserves</span>
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getReserves</span>(<span class="hljs-params"></span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span> <span class="hljs-title"><span class="hljs-keyword">view</span></span> <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params"><span class="hljs-keyword">uint256</span>, <span class="hljs-keyword">uint256</span></span>) </span>{
        <span class="hljs-keyword">return</span> (reserveA, reserveB);
    }

    <span class="hljs-comment">/// @dev Utility: Return the smaller of two values</span>
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">min</span>(<span class="hljs-params"><span class="hljs-keyword">uint256</span> a, <span class="hljs-keyword">uint256</span> b</span>) <span class="hljs-title"><span class="hljs-keyword">internal</span></span> <span class="hljs-title"><span class="hljs-keyword">pure</span></span> <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params"><span class="hljs-keyword">uint256</span></span>) </span>{
        <span class="hljs-keyword">return</span> a <span class="hljs-operator">&lt;</span> b ? a : b;
    }

    <span class="hljs-comment">/// @dev Utility: Babylonian square root</span>
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">sqrt</span>(<span class="hljs-params"><span class="hljs-keyword">uint256</span> y</span>) <span class="hljs-title"><span class="hljs-keyword">internal</span></span> <span class="hljs-title"><span class="hljs-keyword">pure</span></span> <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params"><span class="hljs-keyword">uint256</span> z</span>) </span>{
        <span class="hljs-keyword">if</span> (y <span class="hljs-operator">&gt;</span> <span class="hljs-number">3</span>) {
            z <span class="hljs-operator">=</span> y;
            <span class="hljs-keyword">uint256</span> x <span class="hljs-operator">=</span> y <span class="hljs-operator">/</span> <span class="hljs-number">2</span> <span class="hljs-operator">+</span> <span class="hljs-number">1</span>;
            <span class="hljs-keyword">while</span> (x <span class="hljs-operator">&lt;</span> z) {
                z <span class="hljs-operator">=</span> x;
                x <span class="hljs-operator">=</span> (y <span class="hljs-operator">/</span> x <span class="hljs-operator">+</span> x) <span class="hljs-operator">/</span> <span class="hljs-number">2</span>;
            }
        } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (y <span class="hljs-operator">!</span><span class="hljs-operator">=</span> <span class="hljs-number">0</span>) {
            z <span class="hljs-operator">=</span> <span class="hljs-number">1</span>;
        }
    }
}</code></pre><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0"><strong>一、合约概述</strong></h2><p>该合约构建了一个自动化做市商（AMM），并集成了流动性代币功能。它基于 ERC20 标准，允许用户添加和移除流动性，以及在两种 ERC20 代币（tokenA 和 tokenB）之间进行交换。</p><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0"><strong>二、状态变量</strong></h2><ol><li><p><strong>代币相关</strong></p><ul><li><p><code>tokenA</code>：类型为<code>IERC20</code>，代表第一种 ERC20 代币合约地址。</p></li><li><p><code>tokenB</code>：类型为<code>IERC20</code>，代表第二种 ERC20 代币合约地址。</p></li></ul></li><li><p><strong>储备量相关</strong></p><ul><li><p><code>reserveA</code>：记录 tokenA 在池中储备的数量。</p></li><li><p><code>reserveB</code>：记录 tokenB 在池中储备的数量。</p></li></ul></li><li><p><strong>所有者相关</strong></p><ul><li><p><code>owner</code>：合约部署者地址，拥有一定权限。</p></li></ul></li></ol><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0"><strong>三、事件</strong></h2><ol><li><p><code>LiquidityAdded</code>：当用户向池中添加流动性时触发，记录添加流动性的提供者地址、添加的 tokenA 和 tokenB 数量以及获得的流动性代币数量。</p></li><li><p><code>LiquidityRemoved</code>：用户移除流动性时触发，记录移除流动性的提供者地址、取出的 tokenA 和 tokenB 数量以及移除的流动性代币数量。</p></li><li><p><code>TokensSwapped</code>：代币交换时触发，记录交易者地址、输入代币地址和数量、输出代币地址和数量。</p></li></ol><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0"><strong>四、函数</strong></h2><ol><li><p><strong>构造函数</strong></p><ul><li><p><strong>功能</strong>：初始化<code>AutomatedMarketMaker</code>合约，设置 tokenA、tokenB 地址，并记录合约所有者。</p></li><li><p><strong>参数</strong>：<code>_tokenA</code>和<code>_tokenB</code>为两种 ERC20 代币合约地址，<code>_name</code>和<code>_symbol</code>为流动性代币的名称和符号。</p></li></ul></li><li><p><strong>流动性操作</strong></p><ul><li><p><code>addLiquidity</code>：</p><ul><li><p><strong>功能</strong>：用户向池中添加流动性。</p></li><li><p><strong>要求</strong>：添加的 tokenA 和 tokenB 数量必须大于 0。</p></li><li><p><strong>操作</strong>：从用户处转移 tokenA 和 tokenB 到合约，根据当前总供应量计算新的流动性代币数量并铸造给用户，更新 tokenA 和 tokenB 的储备量，并触发<code>LiquidityAdded</code>事件。</p></li></ul></li><li><p><code>removeLiquidity</code>：</p><ul><li><p><strong>功能</strong>：用户从池中移除流动性。</p></li><li><p><strong>要求</strong>：移除的流动性代币数量必须大于 0，用户拥有足够的流动性代币，且池中存在流动性。</p></li><li><p><strong>操作</strong>：计算应取出的 tokenA 和 tokenB 数量，更新储备量，销毁用户的流动性代币，将 tokenA 和 tokenB 转移给用户，并触发<code>LiquidityRemoved</code>事件。</p></li></ul></li></ul></li><li><p><strong>代币交换操作</strong></p><ul><li><p><code>swapAforB</code>：</p><ul><li><p><strong>功能</strong>：用户用 tokenA 交换 tokenB。</p></li><li><p><strong>要求</strong>：输入的 tokenA 数量大于 0，且池中有足够的 tokenA 和 tokenB 储备。</p></li><li><p><strong>操作</strong>：计算扣除手续费后的 tokenA 输入量及相应的 tokenB 输出量，检查滑点是否在可接受范围内，从用户转移 tokenA 到合约，将 tokenB 转移给用户，更新储备量，并触发<code>TokensSwapped</code>事件。</p></li></ul></li><li><p><code>swapBforA</code>：</p><ul><li><p><strong>功能</strong>：用户用 tokenB 交换 tokenA。</p></li><li><p><strong>要求</strong>：与<code>swapAforB</code>类似，输入的 tokenB 数量大于 0，且池中有足够储备。</p></li><li><p><strong>操作</strong>：计算扣除手续费后的 tokenB 输入量及 tokenA 输出量，检查滑点，转移代币并更新储备量，触发<code>TokensSwapped</code>事件。</p></li></ul></li></ul></li><li><p><strong>其他函数</strong></p><ul><li><p><code>getReserves</code>：</p><ul><li><p><strong>功能</strong>：获取当前 tokenA 和 tokenB 的储备量。</p></li><li><p><strong>返回</strong>：tokenA 和 tokenB 的储备量。</p></li></ul></li><li><p><code>min</code>：</p><ul><li><p><strong>功能</strong>：内部纯函数，返回两个值中的较小值。</p></li><li><p><strong>参数</strong>：<code>a</code>和<code>b</code>为两个<code>uint256</code>类型的值。</p></li></ul></li><li><p><code>sqrt</code>：</p><ul><li><p><strong>功能</strong>：内部纯函数，使用巴比伦法计算平方根。</p></li><li><p><strong>参数</strong>：<code>y</code>为需要计算平方根的<code>uint256</code>类型值。</p></li></ul></li></ul></li></ol><br>]]></content:encoded>
            <author>earnest_stus@newsletter.paragraph.com (Earnest_stu)</author>
        </item>
        <item>
            <title><![CDATA[SimpleLending 合约学习笔记]]></title>
            <link>https://paragraph.com/@Earnest_stus/simplelending-合约学习笔记</link>
            <guid>U3GUi6u8BmpkAYjZ9FaY</guid>
            <pubDate>Sun, 02 Nov 2025 16:11:27 GMT</pubDate>
            <description><![CDATA[一、合约概述SimpleLending合约构建了一个基础的 DeFi 借贷平台，支持用户存款、取款、抵押存款、提取抵押品、借款以及还款操作，并提供计算利息、获取最大借款额度和平台总流动性等功能。 // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; /** * @title SimpleLending * @dev A basic DeFi lending and borrowing platform */ contract SimpleLending { // Token balances for each user mapping(address => uint256) public depositBalances; // Borrowed amounts for each user mapping(address => uint256) public borrowBalances; // Collateral provided by each user mapping(address => uint256) publ...]]></description>
            <content:encoded><![CDATA[<h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0"><strong>一、合约概述</strong></h2><p><code>SimpleLending</code>合约构建了一个基础的 DeFi 借贷平台，支持用户存款、取款、抵押存款、提取抵押品、借款以及还款操作，并提供计算利息、获取最大借款额度和平台总流动性等功能。</p><pre data-type="codeBlock" text=" 
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

/**
 * @title SimpleLending
 * @dev A basic DeFi lending and borrowing platform
 */
contract SimpleLending {
    // Token balances for each user
    mapping(address =&gt; uint256) public depositBalances;

    // Borrowed amounts for each user
    mapping(address =&gt; uint256) public borrowBalances;

    // Collateral provided by each user
    mapping(address =&gt; uint256) public collateralBalances;

    // Interest rate in basis points (1/100 of a percent)
    // 500 basis points = 5% interest
    uint256 public interestRateBasisPoints = 500;

    // Collateral factor in basis points (e.g., 7500 = 75%)
    // Determines how much you can borrow against your collateral
    uint256 public collateralFactorBasisPoints = 7500;

    // Timestamp of last interest accrual
    mapping(address =&gt; uint256) public lastInterestAccrualTimestamp;

    // Events
    event Deposit(address indexed user, uint256 amount);
    event Withdraw(address indexed user, uint256 amount);
    event Borrow(address indexed user, uint256 amount);
    event Repay(address indexed user, uint256 amount);
    event CollateralDeposited(address indexed user, uint256 amount);
    event CollateralWithdrawn(address indexed user, uint256 amount);

    function deposit() external payable {
        require(msg.value &gt; 0, &quot;Must deposit a positive amount&quot;);
        depositBalances[msg.sender] += msg.value;
        emit Deposit(msg.sender, msg.value);
    }

    function withdraw(uint256 amount) external {
        require(amount &gt; 0, &quot;Must withdraw a positive amount&quot;);
        require(depositBalances[msg.sender] &gt;= amount, &quot;Insufficient balance&quot;);
        depositBalances[msg.sender] -= amount;
        payable(msg.sender).transfer(amount);
        emit Withdraw(msg.sender, amount);
    }

    function depositCollateral() external payable {
        require(msg.value &gt; 0, &quot;Must deposit a positive amount as collateral&quot;);
        collateralBalances[msg.sender] += msg.value;
        emit CollateralDeposited(msg.sender, msg.value);
    }

    function withdrawCollateral(uint256 amount) external {
        require(amount &gt; 0, &quot;Must withdraw a positive amount&quot;);
        require(collateralBalances[msg.sender] &gt;= amount, &quot;Insufficient collateral&quot;);

        uint256 borrowedAmount = calculateInterestAccrued(msg.sender);
        uint256 requiredCollateral = (borrowedAmount * 10000) / collateralFactorBasisPoints;

        require(
            collateralBalances[msg.sender] - amount &gt;= requiredCollateral,
            &quot;Withdrawal would break collateral ratio&quot;
        );

        collateralBalances[msg.sender] -= amount;
        payable(msg.sender).transfer(amount);
        emit CollateralWithdrawn(msg.sender, amount);
    }

    function borrow(uint256 amount) external {
        require(amount &gt; 0, &quot;Must borrow a positive amount&quot;);
        require(address(this).balance &gt;= amount, &quot;Not enough liquidity in the pool&quot;);

        uint256 maxBorrowAmount = (collateralBalances[msg.sender] * collateralFactorBasisPoints) / 10000;
        uint256 currentDebt = calculateInterestAccrued(msg.sender);

        require(currentDebt + amount &lt;= maxBorrowAmount, &quot;Exceeds allowed borrow amount&quot;);

        borrowBalances[msg.sender] = currentDebt + amount;
        lastInterestAccrualTimestamp[msg.sender] = block.timestamp;

        payable(msg.sender).transfer(amount);
        emit Borrow(msg.sender, amount);
    }

    function repay() external payable {
        require(msg.value &gt; 0, &quot;Must repay a positive amount&quot;);

        uint256 currentDebt = calculateInterestAccrued(msg.sender);
        require(currentDebt &gt; 0, &quot;No debt to repay&quot;);

        uint256 amountToRepay = msg.value;
        if (amountToRepay &gt; currentDebt) {
            amountToRepay = currentDebt;
            payable(msg.sender).transfer(msg.value - currentDebt);
        }

        borrowBalances[msg.sender] = currentDebt - amountToRepay;
        lastInterestAccrualTimestamp[msg.sender] = block.timestamp;

        emit Repay(msg.sender, amountToRepay);
    }

    function calculateInterestAccrued(address user) public view returns (uint256) {
        if (borrowBalances[user] == 0) {
            return 0;
        }

        uint256 timeElapsed = block.timestamp - lastInterestAccrualTimestamp[user];
        uint256 interest = (borrowBalances[user] * interestRateBasisPoints * timeElapsed) / (10000 * 365 days);

        return borrowBalances[user] + interest;
    }

    function getMaxBorrowAmount(address user) external view returns (uint256) {
        return (collateralBalances[user] * collateralFactorBasisPoints) / 10000;
    }

    function getTotalLiquidity() external view returns (uint256) {
        return address(this).balance;
    }
}
"><code> 
<span class="hljs-comment">// SPDX-License-Identifier: MIT</span>
<span class="hljs-meta"><span class="hljs-keyword">pragma</span> <span class="hljs-keyword">solidity</span> ^0.8.0;</span>

<span class="hljs-comment">/**
 * @title SimpleLending
 * @dev A basic DeFi lending and borrowing platform
 */</span>
<span class="hljs-class"><span class="hljs-keyword">contract</span> <span class="hljs-title">SimpleLending</span> </span>{
    <span class="hljs-comment">// Token balances for each user</span>
    <span class="hljs-keyword">mapping</span>(<span class="hljs-keyword">address</span> <span class="hljs-operator">=</span><span class="hljs-operator">&gt;</span> <span class="hljs-keyword">uint256</span>) <span class="hljs-keyword">public</span> depositBalances;

    <span class="hljs-comment">// Borrowed amounts for each user</span>
    <span class="hljs-keyword">mapping</span>(<span class="hljs-keyword">address</span> <span class="hljs-operator">=</span><span class="hljs-operator">&gt;</span> <span class="hljs-keyword">uint256</span>) <span class="hljs-keyword">public</span> borrowBalances;

    <span class="hljs-comment">// Collateral provided by each user</span>
    <span class="hljs-keyword">mapping</span>(<span class="hljs-keyword">address</span> <span class="hljs-operator">=</span><span class="hljs-operator">&gt;</span> <span class="hljs-keyword">uint256</span>) <span class="hljs-keyword">public</span> collateralBalances;

    <span class="hljs-comment">// Interest rate in basis points (1/100 of a percent)</span>
    <span class="hljs-comment">// 500 basis points = 5% interest</span>
    <span class="hljs-keyword">uint256</span> <span class="hljs-keyword">public</span> interestRateBasisPoints <span class="hljs-operator">=</span> <span class="hljs-number">500</span>;

    <span class="hljs-comment">// Collateral factor in basis points (e.g., 7500 = 75%)</span>
    <span class="hljs-comment">// Determines how much you can borrow against your collateral</span>
    <span class="hljs-keyword">uint256</span> <span class="hljs-keyword">public</span> collateralFactorBasisPoints <span class="hljs-operator">=</span> <span class="hljs-number">7500</span>;

    <span class="hljs-comment">// Timestamp of last interest accrual</span>
    <span class="hljs-keyword">mapping</span>(<span class="hljs-keyword">address</span> <span class="hljs-operator">=</span><span class="hljs-operator">&gt;</span> <span class="hljs-keyword">uint256</span>) <span class="hljs-keyword">public</span> lastInterestAccrualTimestamp;

    <span class="hljs-comment">// Events</span>
    <span class="hljs-function"><span class="hljs-keyword">event</span> <span class="hljs-title">Deposit</span>(<span class="hljs-params"><span class="hljs-keyword">address</span> <span class="hljs-keyword">indexed</span> user, <span class="hljs-keyword">uint256</span> amount</span>)</span>;
    <span class="hljs-function"><span class="hljs-keyword">event</span> <span class="hljs-title">Withdraw</span>(<span class="hljs-params"><span class="hljs-keyword">address</span> <span class="hljs-keyword">indexed</span> user, <span class="hljs-keyword">uint256</span> amount</span>)</span>;
    <span class="hljs-function"><span class="hljs-keyword">event</span> <span class="hljs-title">Borrow</span>(<span class="hljs-params"><span class="hljs-keyword">address</span> <span class="hljs-keyword">indexed</span> user, <span class="hljs-keyword">uint256</span> amount</span>)</span>;
    <span class="hljs-function"><span class="hljs-keyword">event</span> <span class="hljs-title">Repay</span>(<span class="hljs-params"><span class="hljs-keyword">address</span> <span class="hljs-keyword">indexed</span> user, <span class="hljs-keyword">uint256</span> amount</span>)</span>;
    <span class="hljs-function"><span class="hljs-keyword">event</span> <span class="hljs-title">CollateralDeposited</span>(<span class="hljs-params"><span class="hljs-keyword">address</span> <span class="hljs-keyword">indexed</span> user, <span class="hljs-keyword">uint256</span> amount</span>)</span>;
    <span class="hljs-function"><span class="hljs-keyword">event</span> <span class="hljs-title">CollateralWithdrawn</span>(<span class="hljs-params"><span class="hljs-keyword">address</span> <span class="hljs-keyword">indexed</span> user, <span class="hljs-keyword">uint256</span> amount</span>)</span>;

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">deposit</span>(<span class="hljs-params"></span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span> <span class="hljs-title"><span class="hljs-keyword">payable</span></span> </span>{
        <span class="hljs-built_in">require</span>(<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">value</span> <span class="hljs-operator">&gt;</span> <span class="hljs-number">0</span>, <span class="hljs-string">"Must deposit a positive amount"</span>);
        depositBalances[<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>] <span class="hljs-operator">+</span><span class="hljs-operator">=</span> <span class="hljs-built_in">msg</span>.<span class="hljs-built_in">value</span>;
        <span class="hljs-keyword">emit</span> Deposit(<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>, <span class="hljs-built_in">msg</span>.<span class="hljs-built_in">value</span>);
    }

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">withdraw</span>(<span class="hljs-params"><span class="hljs-keyword">uint256</span> amount</span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span> </span>{
        <span class="hljs-built_in">require</span>(amount <span class="hljs-operator">&gt;</span> <span class="hljs-number">0</span>, <span class="hljs-string">"Must withdraw a positive amount"</span>);
        <span class="hljs-built_in">require</span>(depositBalances[<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>] <span class="hljs-operator">&gt;</span><span class="hljs-operator">=</span> amount, <span class="hljs-string">"Insufficient balance"</span>);
        depositBalances[<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>] <span class="hljs-operator">-</span><span class="hljs-operator">=</span> amount;
        <span class="hljs-keyword">payable</span>(<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>).<span class="hljs-built_in">transfer</span>(amount);
        <span class="hljs-keyword">emit</span> Withdraw(<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>, amount);
    }

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">depositCollateral</span>(<span class="hljs-params"></span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span> <span class="hljs-title"><span class="hljs-keyword">payable</span></span> </span>{
        <span class="hljs-built_in">require</span>(<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">value</span> <span class="hljs-operator">&gt;</span> <span class="hljs-number">0</span>, <span class="hljs-string">"Must deposit a positive amount as collateral"</span>);
        collateralBalances[<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>] <span class="hljs-operator">+</span><span class="hljs-operator">=</span> <span class="hljs-built_in">msg</span>.<span class="hljs-built_in">value</span>;
        <span class="hljs-keyword">emit</span> CollateralDeposited(<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>, <span class="hljs-built_in">msg</span>.<span class="hljs-built_in">value</span>);
    }

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">withdrawCollateral</span>(<span class="hljs-params"><span class="hljs-keyword">uint256</span> amount</span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span> </span>{
        <span class="hljs-built_in">require</span>(amount <span class="hljs-operator">&gt;</span> <span class="hljs-number">0</span>, <span class="hljs-string">"Must withdraw a positive amount"</span>);
        <span class="hljs-built_in">require</span>(collateralBalances[<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>] <span class="hljs-operator">&gt;</span><span class="hljs-operator">=</span> amount, <span class="hljs-string">"Insufficient collateral"</span>);

        <span class="hljs-keyword">uint256</span> borrowedAmount <span class="hljs-operator">=</span> calculateInterestAccrued(<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>);
        <span class="hljs-keyword">uint256</span> requiredCollateral <span class="hljs-operator">=</span> (borrowedAmount <span class="hljs-operator">*</span> <span class="hljs-number">10000</span>) <span class="hljs-operator">/</span> collateralFactorBasisPoints;

        <span class="hljs-built_in">require</span>(
            collateralBalances[<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>] <span class="hljs-operator">-</span> amount <span class="hljs-operator">&gt;</span><span class="hljs-operator">=</span> requiredCollateral,
            <span class="hljs-string">"Withdrawal would break collateral ratio"</span>
        );

        collateralBalances[<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>] <span class="hljs-operator">-</span><span class="hljs-operator">=</span> amount;
        <span class="hljs-keyword">payable</span>(<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>).<span class="hljs-built_in">transfer</span>(amount);
        <span class="hljs-keyword">emit</span> CollateralWithdrawn(<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>, amount);
    }

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">borrow</span>(<span class="hljs-params"><span class="hljs-keyword">uint256</span> amount</span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span> </span>{
        <span class="hljs-built_in">require</span>(amount <span class="hljs-operator">&gt;</span> <span class="hljs-number">0</span>, <span class="hljs-string">"Must borrow a positive amount"</span>);
        <span class="hljs-built_in">require</span>(<span class="hljs-keyword">address</span>(<span class="hljs-built_in">this</span>).<span class="hljs-built_in">balance</span> <span class="hljs-operator">&gt;</span><span class="hljs-operator">=</span> amount, <span class="hljs-string">"Not enough liquidity in the pool"</span>);

        <span class="hljs-keyword">uint256</span> maxBorrowAmount <span class="hljs-operator">=</span> (collateralBalances[<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>] <span class="hljs-operator">*</span> collateralFactorBasisPoints) <span class="hljs-operator">/</span> <span class="hljs-number">10000</span>;
        <span class="hljs-keyword">uint256</span> currentDebt <span class="hljs-operator">=</span> calculateInterestAccrued(<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>);

        <span class="hljs-built_in">require</span>(currentDebt <span class="hljs-operator">+</span> amount <span class="hljs-operator">&lt;</span><span class="hljs-operator">=</span> maxBorrowAmount, <span class="hljs-string">"Exceeds allowed borrow amount"</span>);

        borrowBalances[<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>] <span class="hljs-operator">=</span> currentDebt <span class="hljs-operator">+</span> amount;
        lastInterestAccrualTimestamp[<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>] <span class="hljs-operator">=</span> <span class="hljs-built_in">block</span>.<span class="hljs-built_in">timestamp</span>;

        <span class="hljs-keyword">payable</span>(<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>).<span class="hljs-built_in">transfer</span>(amount);
        <span class="hljs-keyword">emit</span> Borrow(<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>, amount);
    }

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">repay</span>(<span class="hljs-params"></span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span> <span class="hljs-title"><span class="hljs-keyword">payable</span></span> </span>{
        <span class="hljs-built_in">require</span>(<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">value</span> <span class="hljs-operator">&gt;</span> <span class="hljs-number">0</span>, <span class="hljs-string">"Must repay a positive amount"</span>);

        <span class="hljs-keyword">uint256</span> currentDebt <span class="hljs-operator">=</span> calculateInterestAccrued(<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>);
        <span class="hljs-built_in">require</span>(currentDebt <span class="hljs-operator">&gt;</span> <span class="hljs-number">0</span>, <span class="hljs-string">"No debt to repay"</span>);

        <span class="hljs-keyword">uint256</span> amountToRepay <span class="hljs-operator">=</span> <span class="hljs-built_in">msg</span>.<span class="hljs-built_in">value</span>;
        <span class="hljs-keyword">if</span> (amountToRepay <span class="hljs-operator">&gt;</span> currentDebt) {
            amountToRepay <span class="hljs-operator">=</span> currentDebt;
            <span class="hljs-keyword">payable</span>(<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>).<span class="hljs-built_in">transfer</span>(<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">value</span> <span class="hljs-operator">-</span> currentDebt);
        }

        borrowBalances[<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>] <span class="hljs-operator">=</span> currentDebt <span class="hljs-operator">-</span> amountToRepay;
        lastInterestAccrualTimestamp[<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>] <span class="hljs-operator">=</span> <span class="hljs-built_in">block</span>.<span class="hljs-built_in">timestamp</span>;

        <span class="hljs-keyword">emit</span> Repay(<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>, amountToRepay);
    }

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">calculateInterestAccrued</span>(<span class="hljs-params"><span class="hljs-keyword">address</span> user</span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span> <span class="hljs-title"><span class="hljs-keyword">view</span></span> <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params"><span class="hljs-keyword">uint256</span></span>) </span>{
        <span class="hljs-keyword">if</span> (borrowBalances[user] <span class="hljs-operator">=</span><span class="hljs-operator">=</span> <span class="hljs-number">0</span>) {
            <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;
        }

        <span class="hljs-keyword">uint256</span> timeElapsed <span class="hljs-operator">=</span> <span class="hljs-built_in">block</span>.<span class="hljs-built_in">timestamp</span> <span class="hljs-operator">-</span> lastInterestAccrualTimestamp[user];
        <span class="hljs-keyword">uint256</span> interest <span class="hljs-operator">=</span> (borrowBalances[user] <span class="hljs-operator">*</span> interestRateBasisPoints <span class="hljs-operator">*</span> timeElapsed) <span class="hljs-operator">/</span> (<span class="hljs-number">10000</span> <span class="hljs-operator">*</span> <span class="hljs-number">365</span> <span class="hljs-literal">days</span>);

        <span class="hljs-keyword">return</span> borrowBalances[user] <span class="hljs-operator">+</span> interest;
    }

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getMaxBorrowAmount</span>(<span class="hljs-params"><span class="hljs-keyword">address</span> user</span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span> <span class="hljs-title"><span class="hljs-keyword">view</span></span> <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params"><span class="hljs-keyword">uint256</span></span>) </span>{
        <span class="hljs-keyword">return</span> (collateralBalances[user] <span class="hljs-operator">*</span> collateralFactorBasisPoints) <span class="hljs-operator">/</span> <span class="hljs-number">10000</span>;
    }

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getTotalLiquidity</span>(<span class="hljs-params"></span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span> <span class="hljs-title"><span class="hljs-keyword">view</span></span> <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params"><span class="hljs-keyword">uint256</span></span>) </span>{
        <span class="hljs-keyword">return</span> <span class="hljs-keyword">address</span>(<span class="hljs-built_in">this</span>).<span class="hljs-built_in">balance</span>;
    }
}
</code></pre><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0"><strong>二、状态变量</strong></h2><ol><li><p><strong>用户资产相关</strong></p><ul><li><p><code>depositBalances</code>：映射用户地址到其存款余额，记录每个用户的存款金额。</p></li><li><p><code>borrowBalances</code>：映射用户地址到其借款余额，记录每个用户的借款金额。</p></li><li><p><code>collateralBalances</code>：映射用户地址到其抵押品余额，记录每个用户提供的抵押品金额。</p></li></ul></li><li><p><strong>利率与抵押因子相关</strong></p><ul><li><p><code>interestRateBasisPoints</code>：以基点（1 基点 = 0.01%）为单位表示的利率，当前设置为 500 基点，即 5%。</p></li><li><p><code>collateralFactorBasisPoints</code>：以基点为单位的抵押因子，用于确定用户基于抵押品可借款的额度，当前设置为 7500 基点，即 75%。</p></li></ul></li><li><p><strong>时间戳相关</strong></p><ul><li><p><code>lastInterestAccrualTimestamp</code>：映射用户地址到上次利息计算的时间戳，用于计算利息。</p></li></ul></li></ol><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0"><strong>三、事件</strong></h2><ol><li><p><code>Deposit</code>：用户存款时触发，记录存款用户地址和存款金额。</p></li><li><p><code>Withdraw</code>：用户取款时触发，记录取款用户地址和取款金额。</p></li><li><p><code>Borrow</code>：用户借款时触发，记录借款用户地址和借款金额。</p></li><li><p><code>Repay</code>：用户还款时触发，记录还款用户地址和还款金额。</p></li><li><p><code>CollateralDeposited</code>：用户存入抵押品时触发，记录存入抵押品的用户地址和金额。</p></li><li><p><code>CollateralWithdrawn</code>：用户提取抵押品时触发，记录提取抵押品的用户地址和金额。</p></li></ol><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0"><strong>四、函数</strong></h2><ol><li><p><strong>存款与取款</strong></p><ul><li><p><code>deposit</code>：</p><ul><li><p><strong>功能</strong>：用户向平台存款。</p></li><li><p><strong>要求</strong>：存款金额必须大于 0。</p></li><li><p><strong>操作</strong>：增加用户的存款余额并触发<code>Deposit</code>事件。</p></li></ul></li><li><p><code>withdraw</code>：</p><ul><li><p><strong>功能</strong>：用户从平台取款。</p></li><li><p><strong>要求</strong>：取款金额必须大于 0 且用户存款余额足够。</p></li><li><p><strong>操作</strong>：减少用户的存款余额并向用户转账取款金额，同时触发<code>Withdraw</code>事件。</p></li></ul></li></ul></li><li><p><strong>抵押品操作</strong></p><ul><li><p><code>depositCollateral</code>：</p><ul><li><p><strong>功能</strong>：用户向平台存入抵押品。</p></li><li><p><strong>要求</strong>：存入金额必须大于 0。</p></li><li><p><strong>操作</strong>：增加用户的抵押品余额并触发<code>CollateralDeposited</code>事件。</p></li></ul></li><li><p><code>withdrawCollateral</code>：</p><ul><li><p><strong>功能</strong>：用户从平台提取抵押品。</p></li><li><p><strong>要求</strong>：提取金额必须大于 0，用户抵押品余额足够且提取后抵押品仍满足借款所需抵押比例。</p></li><li><p><strong>操作</strong>：减少用户的抵押品余额并向用户转账提取金额，同时触发<code>CollateralWithdrawn</code>事件。</p></li></ul></li></ul></li><li><p><strong>借款与还款</strong></p><ul><li><p><code>borrow</code>：</p><ul><li><p><strong>功能</strong>：用户从平台借款。</p></li><li><p><strong>要求</strong>：借款金额必须大于 0，平台有足够流动性，且用户借款后总债务（含利息）不超过基于抵押品可借款的最大额度。</p></li><li><p><strong>操作</strong>：更新用户的借款余额和上次利息计算时间戳，向用户转账借款金额，并触发<code>Borrow</code>事件。</p></li></ul></li><li><p><code>repay</code>：</p><ul><li><p><strong>功能</strong>：用户向平台还款。</p></li><li><p><strong>要求</strong>：还款金额必须大于 0 且用户有债务。</p></li><li><p><strong>操作</strong>：计算用户当前债务，若还款金额大于债务，则退还多余部分；更新用户的借款余额和上次利息计算时间戳，并触发<code>Repay</code>事件。</p></li></ul></li></ul></li><li><p><strong>计算与查询</strong></p><ul><li><p><code>calculateInterestAccrued</code>：</p><ul><li><p><strong>功能</strong>：计算指定用户当前应计利息。</p></li><li><p><strong>返回</strong>：用户当前债务（本金 + 利息）。若用户无借款，则返回 0。</p></li></ul></li><li><p><code>getMaxBorrowAmount</code>：</p><ul><li><p><strong>功能</strong>：获取指定用户基于其抵押品可借款的最大额度。</p></li><li><p><strong>返回</strong>：最大借款额度。</p></li></ul></li><li><p><code>getTotalLiquidity</code>：</p><ul><li><p><strong>功能</strong>：获取平台当前的总流动性。</p></li><li><p><strong>返回</strong>：合约当前持有的 ETH 余额。</p></li></ul></li></ul></li></ol><p>通过这些功能，<code>SimpleLending</code>合约提供了一个简单但完整的 DeFi 借贷流程，用户可以在该平台上进行存款、借款以及管理抵押品等操作。</p>]]></content:encoded>
            <author>earnest_stus@newsletter.paragraph.com (Earnest_stu)</author>
        </item>
        <item>
            <title><![CDATA[去中心化彩票合约学习笔记]]></title>
            <link>https://paragraph.com/@Earnest_stus/去中心化彩票合约学习笔记</link>
            <guid>qTrApy1cwXLNIDlZr2FU</guid>
            <pubDate>Sun, 02 Nov 2025 16:05:16 GMT</pubDate>
            <description><![CDATA[一、学习内容通过构建该去中心化彩票合约，掌握了 Chainlink VRF 集成、状态机设计、ETH 处理以及智能合约与外部事件交互等技能，为开发更多与现实世界交互的智能合约奠定基础，可拓展应用于区块链游戏、NFT 铸造等多个领域。二、Chainlink VRF 原理智能合约的随机数困境：智能合约具有可预测性，无法生成真正的随机数。若使用时间戳或区块号作为随机源，易被矿工操纵。Chainlink VRF 解决方案：如同引入可信裁判，Chainlink VRF 提供随机数、加密证明其公平性，并将两者直接交付给智能合约。具体过程为合约向 Chainlink 发送请求，Chainlink 生成随机数及证明后返回给合约。三、FairChainLottery 合约详解（一）合约整体架构导入库：VRFConsumerBaseV2Plus：继承该基础合约，获得fulfillRandomWords函数，用于接收 Chainlink 返回的随机数。VRFV2PlusClient：辅助库，方便构造和格式化向 Chainlink 发送的随机数请求。合约继承：FairChainLottery合约继承自V...]]></description>
            <content:encoded><![CDATA[<h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0"><strong>一、学习内容</strong></h2><p>通过构建该去中心化彩票合约，掌握了 Chainlink VRF 集成、状态机设计、ETH 处理以及智能合约与外部事件交互等技能，为开发更多与现实世界交互的智能合约奠定基础，可拓展应用于区块链游戏、NFT 铸造等多个领域。</p><h2 id="h-chainlink-vrf" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0"><strong>二、Chainlink VRF 原理</strong></h2><ol><li><p><strong>智能合约的随机数困境</strong>：智能合约具有可预测性，无法生成真正的随机数。若使用时间戳或区块号作为随机源，易被矿工操纵。</p></li><li><p><strong>Chainlink VRF 解决方案</strong>：如同引入可信裁判，Chainlink VRF 提供随机数、加密证明其公平性，并将两者直接交付给智能合约。具体过程为合约向 Chainlink 发送请求，Chainlink 生成随机数及证明后返回给合约。</p></li></ol><h2 id="h-fairchainlottery" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0"><strong>三、FairChainLottery 合约详解</strong></h2><pre data-type="codeBlock" text=""><code></code></pre><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0"><strong>（一）合约整体架构</strong></h3><ol><li><p><strong>导入库</strong>：</p><ul><li><p><code>VRFConsumerBaseV2Plus</code>：继承该基础合约，获得<code>fulfillRandomWords</code>函数，用于接收 Chainlink 返回的随机数。</p></li><li><p><code>VRFV2PlusClient</code>：辅助库，方便构造和格式化向 Chainlink 发送的随机数请求。</p></li></ul></li><li><p><strong>合约继承</strong>：<code>FairChainLottery</code>合约继承自<code>VRFConsumerBaseV2Plus</code>，具备接收 Chainlink 随机数的能力。</p></li></ol><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0"><strong>（二）状态变量</strong></h3><ol><li><p><strong>彩票状态相关</strong>：</p><ul><li><p><code>enum LOTTERY_STATE { OPEN, CLOSED, CALCULATING }</code>：定义彩票三种状态，<code>OPEN</code>允许玩家进入，<code>CLOSED</code>为非活动状态，<code>CALCULATING</code>表示正在请求随机数选赢家。</p></li><li><p><code>LOTTERY_STATE public lotteryState</code>：记录当前彩票状态。</p></li></ul></li><li><p><strong>玩家及费用相关</strong>：</p><ul><li><p><code>address payable[] public players</code>：存储本轮参与玩家地址。</p></li><li><p><code>address public recentWinner</code>：记录上轮赢家地址。</p></li><li><p><code>uint256 public entryFee</code>：玩家参与所需支付的 ETH 费用。</p></li></ul></li><li><p><strong>Chainlink VRF 配置相关</strong>：</p><ul><li><p><code>uint256 public subscriptionId</code>：Chainlink 订阅 ID，用于支付随机数请求费用。</p></li><li><p><code>bytes32 public keyHash</code>：标识使用的 Chainlink oracle 作业。</p></li><li><p><code>uint32 public callbackGasLimit = 100000</code>：为 Chainlink 回调设置的 gas 预算。</p></li><li><p><code>uint16 public requestConfirmations = 3</code>：Chainlink 生成随机数前等待的区块确认数。</p></li><li><p><code>uint32 public numWords = 1</code>：每次请求获取的随机数个数。</p></li><li><p><code>uint256 public latestRequestId</code>：记录最新的随机数请求 ID。</p></li></ul></li></ol><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0"><strong>（三）构造函数</strong></h3><ol><li><p><strong>参数</strong>：</p><ul><li><p><code>address vrfCoordinator</code>：Chainlink VRF 协调器地址。</p></li><li><p><code>uint256 _subscriptionId</code>：Chainlink 订阅 ID。</p></li><li><p><code>bytes32 _keyHash</code>：指定的随机数作业标识。</p></li><li><p><code>uint256 _entryFee</code>：玩家参与费用。</p></li></ul></li><li><p><strong>功能</strong>：初始化 Chainlink 配置、设置参与费用并将彩票状态设为<code>CLOSED</code>，确保部署后合约初始状态可控。</p></li></ol><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0"><strong>（四）主要函数</strong></h3><ol><li><p><strong>enter</strong>：</p><ul><li><p><strong>功能</strong>：允许玩家参与彩票。</p></li><li><p><strong>条件</strong>：彩票状态为<code>OPEN</code>且玩家发送的 ETH 不少于<code>entryFee</code>。</p></li><li><p><strong>操作</strong>：将玩家地址添加到<code>players</code>数组。</p></li></ul></li><li><p><strong>startLottery</strong>：</p><ul><li><p><strong>修饰器</strong>：<code>onlyOwner</code>，仅合约所有者可调用。</p></li><li><p><strong>功能</strong>：开启彩票。</p></li><li><p><strong>条件</strong>：彩票状态为<code>CLOSED</code>。</p></li><li><p><strong>操作</strong>：将彩票状态设为<code>OPEN</code>。</p></li></ul></li><li><p><strong>endLottery</strong>：</p><ul><li><p><strong>修饰器</strong>：<code>onlyOwner</code>。</p></li><li><p><strong>功能</strong>：结束彩票并请求随机数。</p></li><li><p><strong>条件</strong>：彩票状态为<code>OPEN</code>。</p></li><li><p><strong>操作</strong>：设置彩票状态为<code>CALCULATING</code>，构造随机数请求并发送给 Chainlink。</p></li></ul></li><li><p><strong>fulfillRandomWords</strong>：</p><ul><li><p><strong>调用方式</strong>：由 Chainlink 自动调用。</p></li><li><p><strong>功能</strong>：选择赢家并发放奖金。</p></li><li><p><strong>条件</strong>：彩票状态为<code>CALCULATING</code>。</p></li><li><p><strong>操作</strong>：根据 Chainlink 返回的随机数确定赢家，记录赢家地址，清空玩家列表，将彩票状态设为<code>CLOSED</code>，并向赢家发送合约中的全部 ETH。</p></li></ul></li><li><p><strong>getPlayers</strong>：</p><ul><li><p><strong>功能</strong>：返回当前参与玩家列表。</p></li><li><p><strong>应用场景</strong>：供前端应用或区块链浏览器获取玩家信息。</p></li></ul></li></ol><h2 id="h-chainlink" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0"><strong>四、Chainlink 设置</strong></h2><ol><li><p><strong>导入库</strong>：通过导入<code>VRFConsumerBaseV2Plus</code>和<code>VRFV2PlusClient</code>库，实现与 Chainlink VRF 的交互。</p></li><li><p><strong>配置参数</strong>：在合约中设置<code>subscriptionId</code>、<code>keyHash</code>等参数，明确使用的 Chainlink 服务、支付方式、gas 限制等，确保随机数请求准确有效。</p></li></ol><h2 id="h-base-sepolia" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0"><strong>五、合约部署与测试（Base Sepolia 网络）</strong></h2><ol><li><p><strong>环境准备</strong>：在 Remix IDE 中设置环境为 “Injected Provider - MetaMask”，并在 MetaMask 中切换到 Base Sepolia 网络。</p></li><li><p><strong>获取测试 ETH</strong>：从 Chainlink 水龙头获取 Sepolia ETH，再通过 Base Bridge 或其他方式桥接到 Base Sepolia。</p></li><li><p><strong>订阅 Chainlink VRF</strong>：在 Chainlink VRF Subscription Manager 创建并资助订阅，记录 Subscription ID。</p></li><li><p><strong>添加合约为消费者</strong>：部署合约后，将合约地址添加到 VRF 订阅的消费者列表。</p></li><li><p><strong>部署合约</strong>：在 Remix 中编译并部署合约，在构造函数中填入<code>vrfCoordinator</code>、<code>subscriptionId</code>、<code>keyHash</code>和<code>entryFee</code>等参数。</p></li><li><p><strong>交互测试</strong>：调用<code>startLottery</code>开启彩票，玩家调用<code>enter</code>参与，合约所有者调用<code>endLottery</code>结束并请求随机数，Chainlink 自动调用<code>fulfillRandomWords</code>确定赢家，可通过<code>recentWinner</code>查看赢家，<code>getPlayers</code>查看玩家列表。</p></li></ol><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0"></h2><br>]]></content:encoded>
            <author>earnest_stus@newsletter.paragraph.com (Earnest_stu)</author>
        </item>
        <item>
            <title><![CDATA[ERC721 标准 NFT 合约学习笔记]]></title>
            <link>https://paragraph.com/@Earnest_stus/erc721-标准-nft-合约学习笔记</link>
            <guid>1oqrwWMMMz1ix4gQd2yK</guid>
            <pubDate>Fri, 31 Oct 2025 16:03:03 GMT</pubDate>
            <author>earnest_stus@newsletter.paragraph.com (Earnest_stu)</author>
        </item>
        <item>
            <title><![CDATA[模块化游戏插件系统学习笔记]]></title>
            <link>https://paragraph.com/@Earnest_stus/模块化游戏插件系统学习笔记</link>
            <guid>rDLcSoLd2W2OcSXIOU08</guid>
            <pubDate>Sun, 26 Oct 2025 15:07:29 GMT</pubDate>
            <description><![CDATA[一、核心合约：PluginStore（一）功能概述PluginStore是模块化玩家档案的核心合约，承担基础档案管理以及插件的注册与调用职责，通过call和staticcall与插件交互，保持自身轻量化。（二）数据结构PlayerProfile 结构体：用于存储玩家基础档案的核心信息，包含玩家昵称（name）和头像链接 / 标识（avatar）。profiles 映射：将玩家地址与对应的PlayerProfile结构体关联，方便查询和管理玩家基础档案。plugins 映射：把插件标识（如"achievements" "weapons"）与插件合约地址相对应，实现插件的注册与查找。（三）函数功能基础档案操作setProfile：玩家能够调用此函数设置或更新自身的基础档案信息，包括昵称和头像。getProfile：用于查询指定玩家的基础档案，返回其昵称和头像信息。插件管理registerPlugin：该函数用于在系统中注册插件，前提是插件合约已部署，通过传入插件标识和插件合约地址完成注册。getPlugin：根据给定的插件标识，返回对应的插件合约地址。插件执行（状态变更）runPlu...]]></description>
            <content:encoded><![CDATA[<h2 id="h-pluginstore" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0"><strong>一、核心合约：PluginStore</strong></h2><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0"><strong>（一）功能概述</strong></h3><p><code>PluginStore</code>是模块化玩家档案的核心合约，承担基础档案管理以及插件的注册与调用职责，通过<code>call</code>和<code>staticcall</code>与插件交互，保持自身轻量化。</p><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0"><strong>（二）数据结构</strong></h3><ol><li><p><strong>PlayerProfile 结构体</strong>：用于存储玩家基础档案的核心信息，包含玩家昵称（<code>name</code>）和头像链接 / 标识（<code>avatar</code>）。</p></li><li><p><strong>profiles 映射</strong>：将玩家地址与对应的<code>PlayerProfile</code>结构体关联，方便查询和管理玩家基础档案。</p></li><li><p><strong>plugins 映射</strong>：把插件标识（如<code>"achievements"</code> <code>"weapons"</code>）与插件合约地址相对应，实现插件的注册与查找。</p></li></ol><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0"><strong>（三）函数功能</strong></h3><ol><li><p><strong>基础档案操作</strong></p><ul><li><p><strong>setProfile</strong>：玩家能够调用此函数设置或更新自身的基础档案信息，包括昵称和头像。</p></li><li><p><strong>getProfile</strong>：用于查询指定玩家的基础档案，返回其昵称和头像信息。</p></li></ul></li><li><p><strong>插件管理</strong></p><ul><li><p><strong>registerPlugin</strong>：该函数用于在系统中注册插件，前提是插件合约已部署，通过传入插件标识和插件合约地址完成注册。</p></li><li><p><strong>getPlugin</strong>：根据给定的插件标识，返回对应的插件合约地址。</p></li></ul></li><li><p><strong>插件执行（状态变更）</strong></p><ul><li><p><strong>runPlugin</strong>：用于调用插件中会导致状态变更的函数。具体操作是先根据插件标识获取插件合约地址，校验插件已注册后，编码函数调用数据（含函数签名和参数），通过<code>call</code>进行低级别调用，并校验调用是否成功。</p></li></ul></li><li><p><strong>插件查询（只读）</strong></p><ul><li><p><strong>runPluginView</strong>：用于调用插件的只读函数。同样先获取插件合约地址并校验注册情况，编码函数调用数据（仅含函数签名和参数），通过<code>staticcall</code>进行只读调用，确保无状态变更，最后解码返回结果为字符串。</p></li></ul></li></ol><h2 id="h-1achievementsplugin" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0"><strong>二、插件合约 1：AchievementsPlugin</strong></h2><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0"><strong>（一）功能概述</strong></h3><p><code>AchievementsPlugin</code>是用于跟踪玩家成就的插件合约，存储玩家最新解锁的成就，并提供查询功能，需通过<code>PluginStore</code>的相关函数来调用。</p><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0"><strong>（二）数据结构</strong></h3><p><strong>latestAchievement 映射</strong>：将玩家地址与玩家最新解锁的成就名称相关联，便于存储和查询。</p><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0"><strong>（三）函数功能</strong></h3><ol><li><p><strong>setAchievement</strong>：由<code>PluginStore</code>调用，用于设置指定玩家的最新成就。</p></li><li><p><strong>getAchievement</strong>：同样由<code>PluginStore</code>调用，用于查询指定玩家的最新成就名称并返回。</p></li></ol><h2 id="h-2weaponstoreplugin" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0"><strong>三、插件合约 2：WeaponStorePlugin</strong></h2><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0"><strong>（一）功能概述</strong></h3><p><code>WeaponStorePlugin</code>是关于玩家装备武器的插件合约，负责存储玩家当前装备的武器信息，并支持查询操作，通过<code>PluginStore</code>的函数实现调用。</p><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0"><strong>（二）数据结构</strong></h3><p><strong>equippedWeapon 映射</strong>：将玩家地址与玩家当前装备的武器名称建立映射关系。</p><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0"><strong>（三）函数功能</strong></h3><ol><li><p><strong>setWeapon</strong>：在<code>PluginStore</code>调用下，为指定玩家设置当前装备的武器。</p></li><li><p><strong>getWeapon</strong>：在<code>PluginStore</code>调用时，查询并返回指定玩家当前装备的武器名称。</p></li></ol><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0"><strong>四、总结</strong></h2><p>该模块化游戏插件系统通过核心合约<code>PluginStore</code>统筹管理玩家档案与插件，各插件合约负责具体功能实现，利用<code>call</code>和<code>staticcall</code>实现核心与插件间的交互，具备良好的扩展性和维护性，为理解和构建类似的模块化区块链应用提供了范例。</p>]]></content:encoded>
            <author>earnest_stus@newsletter.paragraph.com (Earnest_stu)</author>
        </item>
        <item>
            <title><![CDATA[深入 Web3 智能合约开发 —— 从基础到进阶的学习总结]]></title>
            <link>https://paragraph.com/@Earnest_stus/深入-web3-智能合约开发-——-从基础到进阶的学习总结</link>
            <guid>FXGBEfeLB9NlNsMW9hi4</guid>
            <pubDate>Tue, 21 Oct 2025 16:04:02 GMT</pubDate>
            <description><![CDATA[在探索 Web3 世界的旅程中，深入学习 Solidity 智能合约开发，犹如开启了一扇通往去中心化应用核心的大门。从简单的计数器合约到复杂的代币合约，每一步都充满了挑战与惊喜，让我对区块链技术的底层逻辑和应用有了更为深刻的认识。一、基础合约的启蒙最初接触的ClickCounter和SaveMyInformation合约，是 Solidity 编程的基础入门。ClickCounter通过简单的状态变量counter和几个函数实现计数功能，清晰展示了状态变量与函数间如何相互作用，使我明白合约内部数据是如何动态变化的。而SaveMyInformation合约则侧重于复杂数据类型（如字符串）的存储与读取，add和retrieve函数构建起数据的输入输出机制，让我初步掌握了合约处理不同数据类型的方法。这两个合约虽功能简单，但为后续深入学习奠定了坚实基础，如同搭建高楼的基石，不可或缺。二、复杂逻辑与权限控制的探索TipJar合约的学习将我带入更为复杂的业务逻辑场景。它支持多种货币打赏，从状态变量的精心布局，如用conversionRates映射管理货币汇率，到各类函数协同实现货币添加、打赏...]]></description>
            <content:encoded><![CDATA[<p>在探索 Web3 世界的旅程中，深入学习 Solidity 智能合约开发，犹如开启了一扇通往去中心化应用核心的大门。从简单的计数器合约到复杂的代币合约，每一步都充满了挑战与惊喜，让我对区块链技术的底层逻辑和应用有了更为深刻的认识。</p><h4 id="h-" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0"><strong>一、基础合约的启蒙</strong></h4><p>最初接触的<code>ClickCounter</code>和<code>SaveMyInformation</code>合约，是 Solidity 编程的基础入门。<code>ClickCounter</code>通过简单的状态变量<code>counter</code>和几个函数实现计数功能，清晰展示了状态变量与函数间如何相互作用，使我明白合约内部数据是如何动态变化的。而<code>SaveMyInformation</code>合约则侧重于复杂数据类型（如字符串）的存储与读取，<code>add</code>和<code>retrieve</code>函数构建起数据的输入输出机制，让我初步掌握了合约处理不同数据类型的方法。这两个合约虽功能简单，但为后续深入学习奠定了坚实基础，如同搭建高楼的基石，不可或缺。</p><h4 id="h-" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0"><strong>二、复杂逻辑与权限控制的探索</strong></h4><p><code>TipJar</code>合约的学习将我带入更为复杂的业务逻辑场景。它支持多种货币打赏，从状态变量的精心布局，如用<code>conversionRates</code>映射管理货币汇率，到各类函数协同实现货币添加、打赏、提现等功能，全面展示了 Solidity 在构建实际应用方面的强大能力。特别是<code>onlyOwner</code>修饰器的应用，严格限制特定操作的访问权限，这不仅是对合约安全性的重要保障，更让我深刻体会到在区块链应用中，权限控制至关重要。它确保只有授权的主体能够执行关键操作，有效防止数据被恶意篡改或滥用。</p><h4 id="h-" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0"><strong>三、合约间交互与继承的升华</strong></h4><p><code>Calculator</code>合约调用外部合约以及<code>VaultMaster</code>合约继承<code>Ownable</code>合约的学习，为我揭示了 Solidity 更为高级的特性 —— 合约间的交互与继承。在<code>Calculator</code>合约中，通过导入并调用<code>ScientificCalculator</code>合约的功能，实现基本运算与复杂科学运算的结合，这让我掌握了不同合约间如何进行功能协作。而<code>VaultMaster</code>合约继承<code>Ownable</code>合约的示例，更是生动展示了继承的魅力。<code>VaultMaster</code>无需重写所有权相关逻辑，直接继承<code>Ownable</code>合约的功能，极大减少了重复代码，提高了代码的复用性和可维护性。继承使得合约的开发更加模块化，如同搭积木一般，开发者可以基于已有的基础合约构建出更复杂的应用，同时也方便对合约进行扩展和更新。</p><h4 id="h-erc-20" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0"><strong>四、ERC - 20 代币合约的奥秘</strong></h4><p>学习 ERC - 20 代币合约的过程，是对区块链应用中代币机制的深度探索。ERC - 20 标准的出现，统一了以太坊上代币的行为规范，使得不同的代币能够在钱包、交易所等各种平台上实现互操作性。从<code>SimpleERC20.sol</code>合约的编写中，我深入了解到代币的核心元素，如名称、符号、小数位数、总供应量、余额和授权等是如何实现的。通过分析<code>transfer</code>、<code>approve</code>、<code>transferFrom</code>等函数的逻辑，明白了代币在用户间转移以及授权第三方操作的具体实现方式。尽管自制的 ERC - 20 合约存在一些不足，如缺乏对<code>approve()</code>前置运行的保护、无铸造与销毁功能、无访问控制等，但这也促使我思考如何构建更完善、更安全的代币合约。而 OpenZeppelin 库的引入，则为解决这些问题提供了便捷而可靠的方案。它提供的一系列经过审计的合约模块，如<code>ERC20Burnable</code>、<code>ERC20Pausable</code>等，可轻松集成到自己的合约中，大大提高了开发效率和合约的安全性。</p><h4 id="h-" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0"><strong>五、学习反思与展望</strong></h4><p>回顾这段学习历程，从基础合约到复杂应用，从单一合约开发到合约间交互与继承，再到 ERC - 20 代币合约的实现，每一个阶段都充满了收获与挑战。通过不断实践和学习，我不仅掌握了 Solidity 的编程技巧，更对区块链技术的应用场景和潜力有了全新的认识。然而，我也意识到区块链技术发展迅速，仍有许多知识等待我去探索。在未来的学习中，我将继续深入研究合约安全问题，学习更多高级特性和最佳实践，尝试开发更复杂、更实用的去中心化应用。同时，我也期待能够参与到开源项目中，与其他开发者交流合作，共同推动区块链技术的发展。</p>]]></content:encoded>
            <author>earnest_stus@newsletter.paragraph.com (Earnest_stu)</author>
        </item>
        <item>
            <title><![CDATA[ERC - 20 代币合约学习笔记]]></title>
            <link>https://paragraph.com/@Earnest_stus/erc-20-代币合约学习笔记</link>
            <guid>9WIM6YOioOw2J6UlBY5p</guid>
            <pubDate>Tue, 21 Oct 2025 15:57:10 GMT</pubDate>
            <description><![CDATA[一、ERC - 20 代币标准的诞生背景早期以太坊上的代币创建热潮中，由于缺乏统一标准，各代币的实现方式差异巨大。例如，有的代币转账函数用 send()，有的叫 moveTokens()；部分代币无法查看余额，还有的在转账时不触发任何事件。这使得钱包和交易所等平台在支持新代币时，需编写大量定制化逻辑，就像为不同设计的电视制作通用遥控器一样困难。因此，以太坊社区提出 ERC - 20 标准，为代币的构建制定统一规则，使其具备通用性和互操作性。二、ERC - 20 标准解析ERC 含义：Ethereum Request for Comments，即以太坊改进提案，是以太坊社区用于达成标准共识的方式，类似区块链领域的建筑规范。ERC - 20：ERC 系列的第 20 个提案，定义了以太坊上可互换代币的行为规则。ERC - 20 定义的规则命名与显示：代币需有 name（全称）、symbol（符号）和 decimals（小数位数），便于钱包展示。余额与总量：要有 totalSupply() 函数获取总供应量，通过 balanceOf(address) 函数查询地址的代币持有量。转账：具备...]]></description>
            <content:encoded><![CDATA[<h2 id="h-erc-20" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0"><strong>一、ERC - 20 代币标准的诞生背景</strong></h2><p>早期以太坊上的代币创建热潮中，由于缺乏统一标准，各代币的实现方式差异巨大。例如，有的代币转账函数用 <code>send()</code>，有的叫 <code>moveTokens()</code>；部分代币无法查看余额，还有的在转账时不触发任何事件。这使得钱包和交易所等平台在支持新代币时，需编写大量定制化逻辑，就像为不同设计的电视制作通用遥控器一样困难。因此，以太坊社区提出 ERC - 20 标准，为代币的构建制定统一规则，使其具备通用性和互操作性。</p><h2 id="h-erc-20" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0"><strong>二、ERC - 20 标准解析</strong></h2><ol><li><p><strong>ERC 含义</strong>：<strong>Ethereum Request for Comments</strong>，即以太坊改进提案，是以太坊社区用于达成标准共识的方式，类似区块链领域的建筑规范。</p></li><li><p><strong>ERC - 20</strong>：ERC 系列的第 20 个提案，定义了以太坊上可互换代币的行为规则。</p></li><li><p><strong>ERC - 20 定义的规则</strong></p><ul><li><p><strong>命名与显示</strong>：代币需有 <code>name</code>（全称）、<code>symbol</code>（符号）和 <code>decimals</code>（小数位数），便于钱包展示。</p></li><li><p><strong>余额与总量</strong>：要有 <code>totalSupply()</code> 函数获取总供应量，通过 <code>balanceOf(address)</code> 函数查询地址的代币持有量。</p></li><li><p><strong>转账</strong>：具备 <code>transfer()</code> 函数，用于用户间的代币转账。</p></li><li><p><strong>批准与委托支出</strong>：<code>approve()</code> 函数让用户授权其他地址（如智能合约）代其支出代币，<code>transferFrom()</code> 函数执行已批准的转账操作。</p></li><li><p><strong>事件发射</strong>：在代币转移或权限授予时，需发射 <code>Transfer</code> 和 <code>Approval</code> 事件，方便外部工具追踪链上动态。</p></li></ul></li></ol><h2 id="h-erc-20-simpleerc20sol" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0"><strong>三、自制 ERC - 20 代币合约：SimpleERC20.sol</strong></h2><ol><li><p><strong>合约代码结构</strong></p></li></ol><p><strong>solidity</strong></p><pre data-type="codeBlock" text="// SPDX - License - Identifier: MIT
pragma solidity ^0.8.20;

contract SimpleERC20 {
    string public name = &quot;SimpleToken&quot;;
    string public symbol = &quot;SIM&quot;;
    uint8 public decimals = 18;
    uint256 public totalSupply;

    mapping(address =&gt; uint256) public balanceOf;
    mapping(address =&gt; mapping(address =&gt; 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 ** uint256(decimals));
        balanceOf[msg.sender] = totalSupply;
        emit Transfer(address(0), msg.sender, totalSupply);
    }

    function transfer(address _to, uint256 _value) public returns (bool) {
        require(balanceOf[msg.sender] &gt;= _value, &quot;Not enough balance&quot;);
        _transfer(msg.sender, _to, _value);
        return true;
    }

    function approve(address _spender, uint256 _value) public returns (bool) {
        allowance[msg.sender][_spender] = _value;
        emit Approval(msg.sender, _spender, _value);
        return true;
    }

    function transferFrom(address _from, address _to, uint256 _value) public returns (bool) {
        require(balanceOf[_from] &gt;= _value, &quot;Not enough balance&quot;);
        require(allowance[_from][msg.sender] &gt;= _value, &quot;Allowance too low&quot;);

        allowance[_from][msg.sender] -= _value;
        _transfer(_from, _to, _value);
        return true;
    }

    function _transfer(address _from, address _to, uint256 _value) internal {
        require(_to != address(0), &quot;Invalid address&quot;);
        balanceOf[_from] -= _value;
        balanceOf[_to] += _value;
        emit Transfer(_from, _to, _value);
    }
}
"><code><span class="hljs-comment">// SPDX - License - Identifier: MIT</span>
<span class="hljs-meta"><span class="hljs-keyword">pragma</span> <span class="hljs-keyword">solidity</span> ^0.8.20;</span>

<span class="hljs-class"><span class="hljs-keyword">contract</span> <span class="hljs-title">SimpleERC20</span> </span>{
    <span class="hljs-keyword">string</span> <span class="hljs-keyword">public</span> name <span class="hljs-operator">=</span> <span class="hljs-string">"SimpleToken"</span>;
    <span class="hljs-keyword">string</span> <span class="hljs-keyword">public</span> symbol <span class="hljs-operator">=</span> <span class="hljs-string">"SIM"</span>;
    <span class="hljs-keyword">uint8</span> <span class="hljs-keyword">public</span> decimals <span class="hljs-operator">=</span> <span class="hljs-number">18</span>;
    <span class="hljs-keyword">uint256</span> <span class="hljs-keyword">public</span> totalSupply;

    <span class="hljs-keyword">mapping</span>(<span class="hljs-keyword">address</span> <span class="hljs-operator">=</span><span class="hljs-operator">&gt;</span> <span class="hljs-keyword">uint256</span>) <span class="hljs-keyword">public</span> balanceOf;
    <span class="hljs-keyword">mapping</span>(<span class="hljs-keyword">address</span> <span class="hljs-operator">=</span><span class="hljs-operator">&gt;</span> <span class="hljs-keyword">mapping</span>(<span class="hljs-keyword">address</span> <span class="hljs-operator">=</span><span class="hljs-operator">&gt;</span> <span class="hljs-keyword">uint256</span>)) <span class="hljs-keyword">public</span> allowance;

    <span class="hljs-function"><span class="hljs-keyword">event</span> <span class="hljs-title">Transfer</span>(<span class="hljs-params"><span class="hljs-keyword">address</span> <span class="hljs-keyword">indexed</span> <span class="hljs-keyword">from</span>, <span class="hljs-keyword">address</span> <span class="hljs-keyword">indexed</span> to, <span class="hljs-keyword">uint256</span> value</span>)</span>;
    <span class="hljs-function"><span class="hljs-keyword">event</span> <span class="hljs-title">Approval</span>(<span class="hljs-params"><span class="hljs-keyword">address</span> <span class="hljs-keyword">indexed</span> owner, <span class="hljs-keyword">address</span> <span class="hljs-keyword">indexed</span> spender, <span class="hljs-keyword">uint256</span> value</span>)</span>;

    <span class="hljs-function"><span class="hljs-keyword">constructor</span>(<span class="hljs-params"><span class="hljs-keyword">uint256</span> _initialSupply</span>) </span>{
        totalSupply <span class="hljs-operator">=</span> _initialSupply <span class="hljs-operator">*</span> (<span class="hljs-number">10</span> <span class="hljs-operator">*</span><span class="hljs-operator">*</span> <span class="hljs-keyword">uint256</span>(decimals));
        balanceOf[<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>] <span class="hljs-operator">=</span> totalSupply;
        <span class="hljs-keyword">emit</span> Transfer(<span class="hljs-keyword">address</span>(<span class="hljs-number">0</span>), <span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>, totalSupply);
    }

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">transfer</span>(<span class="hljs-params"><span class="hljs-keyword">address</span> _to, <span class="hljs-keyword">uint256</span> _value</span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span> <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params"><span class="hljs-keyword">bool</span></span>) </span>{
        <span class="hljs-built_in">require</span>(balanceOf[<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>] <span class="hljs-operator">&gt;</span><span class="hljs-operator">=</span> _value, <span class="hljs-string">"Not enough balance"</span>);
        _transfer(<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>, _to, _value);
        <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>;
    }

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">approve</span>(<span class="hljs-params"><span class="hljs-keyword">address</span> _spender, <span class="hljs-keyword">uint256</span> _value</span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span> <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params"><span class="hljs-keyword">bool</span></span>) </span>{
        allowance[<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>][_spender] <span class="hljs-operator">=</span> _value;
        <span class="hljs-keyword">emit</span> Approval(<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>, _spender, _value);
        <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>;
    }

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">transferFrom</span>(<span class="hljs-params"><span class="hljs-keyword">address</span> _from, <span class="hljs-keyword">address</span> _to, <span class="hljs-keyword">uint256</span> _value</span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span> <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params"><span class="hljs-keyword">bool</span></span>) </span>{
        <span class="hljs-built_in">require</span>(balanceOf[_from] <span class="hljs-operator">&gt;</span><span class="hljs-operator">=</span> _value, <span class="hljs-string">"Not enough balance"</span>);
        <span class="hljs-built_in">require</span>(allowance[_from][<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>] <span class="hljs-operator">&gt;</span><span class="hljs-operator">=</span> _value, <span class="hljs-string">"Allowance too low"</span>);

        allowance[_from][<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>] <span class="hljs-operator">-</span><span class="hljs-operator">=</span> _value;
        _transfer(_from, _to, _value);
        <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>;
    }

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">_transfer</span>(<span class="hljs-params"><span class="hljs-keyword">address</span> _from, <span class="hljs-keyword">address</span> _to, <span class="hljs-keyword">uint256</span> _value</span>) <span class="hljs-title"><span class="hljs-keyword">internal</span></span> </span>{
        <span class="hljs-built_in">require</span>(_to <span class="hljs-operator">!</span><span class="hljs-operator">=</span> <span class="hljs-keyword">address</span>(<span class="hljs-number">0</span>), <span class="hljs-string">"Invalid address"</span>);
        balanceOf[_from] <span class="hljs-operator">-</span><span class="hljs-operator">=</span> _value;
        balanceOf[_to] <span class="hljs-operator">+</span><span class="hljs-operator">=</span> _value;
        <span class="hljs-keyword">emit</span> Transfer(_from, _to, _value);
    }
}
</code></pre><ol start="2"><li><p><strong>代码详细分析</strong></p><ul><li><p><strong>代币元数据</strong>：<code>name</code>、<code>symbol</code> 和 <code>decimals</code> 分别定义代币的名称、符号和小数位数，大多数代币小数位设为 18 位，与 ETH 一致。</p></li><li><p><strong>代币供应</strong>：<code>totalSupply</code> 跟踪代币的总供应量，在合约部署时设定。</p></li><li><p><strong>余额与授权</strong>：</p><ul><li><p><code>balanceOf</code> 映射记录每个地址持有的代币数量。</p></li><li><p><code>allowance</code> 嵌套映射用于追踪授权信息，即谁被授权可代谁支出多少代币。</p></li></ul></li><li><p><strong>事件</strong>：</p><ul><li><p><code>Transfer</code> 事件在代币转移时触发，记录转移的源地址、目标地址和数量，方便外部工具展示交易历史。</p></li><li><p><code>Approval</code> 事件在授权发生时触发，记录授权者、被授权者和授权金额。<code>indexed</code> 关键字使地址参数可在事件日志中搜索。</p></li></ul></li><li><p><strong>构造函数 - 铸造初始供应</strong>：</p><ul><li><p>计算总供应量并考虑小数位数，将初始供应量赋予合约部署者，并发射 <code>Transfer</code> 事件，以表明代币的 “铸造” 操作。</p></li></ul></li><li><p><code>transfer()</code><strong> 函数</strong>：用户用于向其他地址转账。先检查发送者余额是否足够，然后调用内部函数 <code>_transfer()</code> 执行实际转账，体现了逻辑分离的设计模式，便于代码复用和维护。</p></li><li><p><code>_transfer()</code><strong> 函数</strong>：标记为 <code>internal</code>，只能在合约内部或派生合约中调用。确保接收地址有效，更新发送者和接收者的余额，并发射 <code>Transfer</code> 事件。</p></li><li><p><code>transferFrom()</code><strong> 函数</strong>：用于经授权后代表他人转账。检查发送者余额和授权额度，减少授权额度并调用 <code>_transfer()</code> 完成转账，是实现去中心化交易所（DEX）交易和去中心化自治组织（DAO）投票等功能的基础。</p></li><li><p><code>approve()</code><strong> 函数</strong>：用户授权其他地址代其支出代币，设定授权额度并发射 <code>Approval</code> 事件，为委托代币转移操作奠定基础。</p></li></ul></li></ol><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0"><strong>四、自制合约的不足</strong></h2><ol><li><p><strong>缺乏对 </strong><code>approve()</code><strong> 前置运行的保护</strong>：存在因竞争条件导致授权滥用的风险，应采用 <code>increaseAllowance()</code> 和 <code>decreaseAllowance()</code> 等更安全的模式。</p></li><li><p><strong>无铸造与销毁功能</strong>：总供应量在部署时固定，无法增加（铸造）或减少（销毁）代币。</p></li><li><p><strong>无访问控制或所有权</strong>：任何人都能与合约交互，无法限制谁能执行铸造、暂停或升级合约等操作。</p></li><li><p><strong>无暂停或熔断机制</strong>：出现漏洞或错误时，无法暂停转账或临时关闭合约。</p></li><li><p><strong>无升级性或模块化</strong>：合约部署后无法升级逻辑，若代币已流通，重新部署会带来诸多麻烦。</p></li></ol><h2 id="h-openzeppelin-erc-20" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0"><strong>五、使用 OpenZeppelin 构建 ERC - 20 代币</strong></h2><ol><li><p><strong>OpenZeppelin 优势</strong>：安全可靠（被主要 DeFi 协议使用）、经过审计且受社区信赖、模块化可定制、易于与其他合约集成。</p></li><li><p><strong>示例代码</strong>：</p></li></ol><p><strong>solidity</strong></p><pre data-type="codeBlock" text="// SPDX - License - Identifier: MIT
pragma solidity ^0.8.20;

import &quot;@openzeppelin/contracts/token/ERC20/ERC20.sol&quot;;

contract MyToken is ERC20 {
    constructor(uint256 initialSupply) ERC20(&quot;MyToken&quot;, &quot;MTK&quot;) {
        _mint(msg.sender, initialSupply * 10 ** decimals());
    }
}
"><code><span class="hljs-comment">// SPDX - License - Identifier: MIT</span>
<span class="hljs-meta"><span class="hljs-keyword">pragma</span> <span class="hljs-keyword">solidity</span> ^0.8.20;</span>

<span class="hljs-keyword">import</span> <span class="hljs-string">"@openzeppelin/contracts/token/ERC20/ERC20.sol"</span>;

<span class="hljs-class"><span class="hljs-keyword">contract</span> <span class="hljs-title">MyToken</span> <span class="hljs-keyword">is</span> <span class="hljs-title">ERC20</span> </span>{
    <span class="hljs-function"><span class="hljs-keyword">constructor</span>(<span class="hljs-params"><span class="hljs-keyword">uint256</span> initialSupply</span>) <span class="hljs-title">ERC20</span>(<span class="hljs-params"><span class="hljs-string">"MyToken"</span>, <span class="hljs-string">"MTK"</span></span>) </span>{
        _mint(<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>, initialSupply <span class="hljs-operator">*</span> <span class="hljs-number">10</span> <span class="hljs-operator">*</span><span class="hljs-operator">*</span> decimals());
    }
}
</code></pre><p>几行代码即可创建生产级 ERC - 20 代币，还可通过其提供的插件扩展功能，如 <code>ERC20Burnable</code>（代币销毁）、<code>ERC20Pausable</code>（暂停功能）、<code>AccessControl</code> 或 <code>Ownable</code>（访问控制与所有权）、<code>ERC20Permit</code>（无 gas 授权）等。</p><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0"><strong>六、学习总结</strong></h2><p>本次学习了解了 ERC - 20 标准诞生的原因及其对以太坊代币生态的重要性，掌握了 ERC - 20 代币的核心元素（名称、符号、小数位、总供应量、余额、授权等），学会了如何编写基本的 ERC - 20 代币合约，以及合约存在的不足和改进方法。同时，认识到 OpenZeppelin 在实际开发中的便捷性和安全性，为构建真实可用的代币提供了强大工具。</p>]]></content:encoded>
            <author>earnest_stus@newsletter.paragraph.com (Earnest_stu)</author>
        </item>
        <item>
            <title><![CDATA[Solidity 智能合约学习笔记：继承与 OpenZeppelin 的应用]]></title>
            <link>https://paragraph.com/@Earnest_stus/solidity-智能合约学习笔记：继承与-openzeppelin-的应用</link>
            <guid>hQnDpd9pFAnV2Y7bMzfw</guid>
            <pubDate>Mon, 20 Oct 2025 12:02:19 GMT</pubDate>
            <description><![CDATA[一、继承的概念与作用继承的概念 继承如同现实生活中继承房屋，在 Solidity 里，一个合约（父合约）定义一系列逻辑，如函数、变量、修饰器等，另一个合约（子合约）可继承这些内容，并按需使用或修改。就像继承房屋时，不仅得到房子，还得到家具、规则等附属物。使用继承的原因避免重复代码：防止在多个地方编写相同逻辑，如onlyOwner检查逻辑。代码模块化：将代码拆分成更小、功能更聚焦的部分。功能复用：能重复使用像访问控制或实用函数这样的重要特性。易于维护：使合约更易更新和维护，是大型 Solidity 项目的基础。二、合约示例：Ownable.sol 与 VaultMaster.solOwnable.sol状态变量：address private owner;，存储合约所有者地址，private修饰使其仅在合约内部可访问。事件：event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);，用于记录所有权转移信息，indexed关键字便于按地址过滤日志。构造函数：solidityc...]]></description>
            <content:encoded><![CDATA[<h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0"><strong>一、继承的概念与作用</strong></h2><ol><li><p><strong>继承的概念</strong></p><p>继承如同现实生活中继承房屋，在 Solidity 里，一个合约（父合约）定义一系列逻辑，如函数、变量、修饰器等，另一个合约（子合约）可继承这些内容，并按需使用或修改。就像继承房屋时，不仅得到房子，还得到家具、规则等附属物。</p></li><li><p><strong>使用继承的原因</strong></p><ul><li><p><strong>避免重复代码</strong>：防止在多个地方编写相同逻辑，如<code>onlyOwner</code>检查逻辑。</p></li><li><p><strong>代码模块化</strong>：将代码拆分成更小、功能更聚焦的部分。</p></li><li><p><strong>功能复用</strong>：能重复使用像访问控制或实用函数这样的重要特性。</p></li><li><p><strong>易于维护</strong>：使合约更易更新和维护，是大型 Solidity 项目的基础。</p></li></ul></li></ol><h2 id="h-ownablesol-vaultmastersol" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0"><strong>二、合约示例：Ownable.sol 与 VaultMaster.sol</strong></h2><ol><li><p><strong>Ownable.sol</strong></p><ul><li><p><strong>状态变量</strong>：<code>address private owner;</code>，存储合约所有者地址，<code>private</code>修饰使其仅在合约内部可访问。</p></li><li><p><strong>事件</strong>：<code>event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);</code>，用于记录所有权转移信息，<code>indexed</code>关键字便于按地址过滤日志。</p></li><li><p><strong>构造函数</strong>：</p></li></ul></li></ol><p><strong>solidity</strong></p><pre data-type="codeBlock" text="constructor() {
    owner = msg.sender;
    emit OwnershipTransferred(address(0), msg.sender);
}
"><code><span class="hljs-function"><span class="hljs-keyword">constructor</span>(<span class="hljs-params"></span>) </span>{
    owner <span class="hljs-operator">=</span> <span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>;
    <span class="hljs-keyword">emit</span> OwnershipTransferred(<span class="hljs-keyword">address</span>(<span class="hljs-number">0</span>), <span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>);
}
</code></pre><p>部署合约时将部署者设为初始所有者，并触发所有权转移事件。</p><ul><li><p><strong>修饰器</strong>：</p></li></ul><p><strong>solidity</strong></p><pre data-type="codeBlock" text="modifier onlyOwner() {
    require(msg.sender == owner, &quot;Only owner can perform this action&quot;);
    _;
}
"><code><span class="hljs-function"><span class="hljs-keyword">modifier</span> <span class="hljs-title">onlyOwner</span>(<span class="hljs-params"></span>) </span>{
    <span class="hljs-built_in">require</span>(<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span> <span class="hljs-operator">=</span><span class="hljs-operator">=</span> owner, <span class="hljs-string">"Only owner can perform this action"</span>);
    <span class="hljs-keyword">_</span>;
}
</code></pre><p>确保只有所有者能调用被修饰函数。</p><ul><li><p><strong>函数</strong>：</p><ul><li><p><code>ownerAddress()</code>：因<code>owner</code>为私有，此函数让外部可查看当前所有者地址。</p></li><li><p><code>transferOwnership(address _newOwner)</code>：允许所有者转移所有权，先检查新地址有效性，更新所有者并触发事件。</p></li></ul></li><li><p><strong>总结</strong>：这是一个可复用的合约，用于跟踪所有者、限制敏感函数访问、允许所有权转移并记录所有权变更，本身不执行具体业务，供其他合约继承。</p></li></ul><ol start="2"><li><p><strong>VaultMaster.sol</strong></p><ul><li><p><strong>导入与继承</strong>：</p></li></ul></li></ol><p><strong>solidity</strong></p><pre data-type="codeBlock" text="import &quot;./ownable.sol&quot;;
contract VaultMaster is Ownable {
"><code><span class="hljs-keyword">import</span> <span class="hljs-string">"./ownable.sol"</span>;
<span class="hljs-class"><span class="hljs-keyword">contract</span> <span class="hljs-title">VaultMaster</span> <span class="hljs-keyword">is</span> <span class="hljs-title">Ownable</span> </span>{
</code></pre><p>导入<code>Ownable.sol</code>并继承，使<code>VaultMaster</code>拥有<code>Ownable</code>的所有功能。</p><ul><li><p><strong>事件</strong>：</p><ul><li><p><code>event DepositSuccessful(address indexed account, uint256 value);</code>：存款成功时触发，记录存款者地址和金额。</p></li><li><p><code>event WithdrawSuccessful(address indexed recipient, uint256 value);</code>：取款成功时触发，记录取款接收者地址和金额。</p></li></ul></li><li><p><strong>函数</strong>：</p><ul><li><p><code>getBalance()</code>：返回合约当前持有的 ETH 余额。</p></li><li><p><code>deposit()</code>：允许任何人向合约存入 ETH，检查金额大于 0 后触发存款成功事件。</p></li><li><p><code>withdraw(address _to, uint256 _amount)</code>：只有所有者能调用，检查余额足够后向指定地址转账，成功则触发取款成功事件。</p></li></ul></li></ul><h2 id="h-openzeppelin" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0"><strong>三、OpenZeppelin 的介绍与使用</strong></h2><ol><li><p><strong>OpenZeppelin 简介</strong></p><p>由顶级智能合约开发者团队创建的库，包含安全、可复用且经社区审计的合约，涵盖访问控制、令牌标准、安全模式、代理与可升级合约、实用工具和数学库等，被广泛应用于以太坊生态系统，因其经过实战检验、持续维护、审计且值得信赖。</p></li><li><p><strong>在 Remix 中使用 OpenZeppelin 的</strong><code>Ownable</code></p><ul><li><p><strong>替换导入</strong>：在<code>VaultMaster.sol</code>中，将自定义<code>Ownable</code>导入替换为<code>import "@openzeppelin/contracts/access/Ownable.sol";</code>，<code>@openzeppelin</code>用于在 Remix 及许多 Solidity 项目中引用 npm 风格包系统中的包，Remix 会自动解析并获取相关合约。</p></li><li><p><strong>传递所有者</strong>：在<code>VaultMaster</code>构造函数中添加<code>constructor() Ownable(msg.sender) {}</code>，告诉 OpenZeppelin 将部署者设为初始所有者。</p></li><li><p><strong>最终合约</strong>：使用 OpenZeppelin 的<code>Ownable</code>更新后的<code>VaultMaster.sol</code>代码如下：</p></li></ul></li></ol><p><strong>solidity</strong></p><pre data-type="codeBlock" text="// SPDX - License - Identifier: MIT
pragma solidity ^0.8.20;
import &quot;@openzeppelin/contracts/access/Ownable.sol&quot;;
contract VaultMaster is Ownable {
    event DepositSuccessful(address indexed account, uint256 value);
    event WithdrawSuccessful(address indexed recipient, uint256 value);
    constructor() Ownable(msg.sender) {}
    function getBalance() public view returns (uint256) {
        return address(this).balance;
    }
    function deposit() public payable {
        require(msg.value &gt; 0, &quot;Enter a valid amount&quot;);
        emit DepositSuccessful(msg.sender, msg.value);
    }
    function withdraw(address _to, uint256 _amount) public onlyOwner {
        require(_amount &lt;= getBalance(), &quot;Insufficient balance&quot;);
        (bool success, ) = payable(_to).call{value: _amount}(&quot;&quot;);
        require(success, &quot;Transfer failed&quot;);
        emit WithdrawSuccessful(_to, _amount);
    }
}
"><code><span class="hljs-comment">// SPDX - License - Identifier: MIT</span>
<span class="hljs-meta"><span class="hljs-keyword">pragma</span> <span class="hljs-keyword">solidity</span> ^0.8.20;</span>
<span class="hljs-keyword">import</span> <span class="hljs-string">"@openzeppelin/contracts/access/Ownable.sol"</span>;
<span class="hljs-class"><span class="hljs-keyword">contract</span> <span class="hljs-title">VaultMaster</span> <span class="hljs-keyword">is</span> <span class="hljs-title">Ownable</span> </span>{
    <span class="hljs-function"><span class="hljs-keyword">event</span> <span class="hljs-title">DepositSuccessful</span>(<span class="hljs-params"><span class="hljs-keyword">address</span> <span class="hljs-keyword">indexed</span> account, <span class="hljs-keyword">uint256</span> value</span>)</span>;
    <span class="hljs-function"><span class="hljs-keyword">event</span> <span class="hljs-title">WithdrawSuccessful</span>(<span class="hljs-params"><span class="hljs-keyword">address</span> <span class="hljs-keyword">indexed</span> recipient, <span class="hljs-keyword">uint256</span> value</span>)</span>;
    <span class="hljs-function"><span class="hljs-keyword">constructor</span>(<span class="hljs-params"></span>) <span class="hljs-title">Ownable</span>(<span class="hljs-params"><span class="hljs-built_in">msg</span>.sender</span>) </span>{}
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getBalance</span>(<span class="hljs-params"></span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span> <span class="hljs-title"><span class="hljs-keyword">view</span></span> <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params"><span class="hljs-keyword">uint256</span></span>) </span>{
        <span class="hljs-keyword">return</span> <span class="hljs-keyword">address</span>(<span class="hljs-built_in">this</span>).<span class="hljs-built_in">balance</span>;
    }
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">deposit</span>(<span class="hljs-params"></span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span> <span class="hljs-title"><span class="hljs-keyword">payable</span></span> </span>{
        <span class="hljs-built_in">require</span>(<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">value</span> <span class="hljs-operator">&gt;</span> <span class="hljs-number">0</span>, <span class="hljs-string">"Enter a valid amount"</span>);
        <span class="hljs-keyword">emit</span> DepositSuccessful(<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>, <span class="hljs-built_in">msg</span>.<span class="hljs-built_in">value</span>);
    }
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">withdraw</span>(<span class="hljs-params"><span class="hljs-keyword">address</span> _to, <span class="hljs-keyword">uint256</span> _amount</span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span> <span class="hljs-title">onlyOwner</span> </span>{
        <span class="hljs-built_in">require</span>(_amount <span class="hljs-operator">&lt;</span><span class="hljs-operator">=</span> getBalance(), <span class="hljs-string">"Insufficient balance"</span>);
        (<span class="hljs-keyword">bool</span> success, ) <span class="hljs-operator">=</span> <span class="hljs-keyword">payable</span>(_to).<span class="hljs-built_in">call</span>{<span class="hljs-built_in">value</span>: _amount}(<span class="hljs-string">""</span>);
        <span class="hljs-built_in">require</span>(success, <span class="hljs-string">"Transfer failed"</span>);
        <span class="hljs-keyword">emit</span> WithdrawSuccessful(_to, _amount);
    }
}
</code></pre><ol start="3"><li><p><strong>使用 OpenZeppelin 的优势</strong></p><p>编写更少代码、减少错误并遵循行业最佳实践，如同使用可靠的建筑模块，让开发者专注于应用的独特逻辑。</p></li></ol><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0"><strong>四、学习总结</strong></h2><p>本次学习掌握了编写控制所有权的基础合约、合约间继承的实现、修饰器和事件的使用、Solidity 项目结构优化以及 OpenZeppelin 中<code>Ownable</code>的实际应用。继承使智能合约更具模块化、可复用性和可维护性，是构建现实世界智能合约系统的重要方式。</p>]]></content:encoded>
            <author>earnest_stus@newsletter.paragraph.com (Earnest_stu)</author>
        </item>
        <item>
            <title><![CDATA[Solidity 之 SimpleFitnessTracker 合约学习笔记]]></title>
            <link>https://paragraph.com/@Earnest_stus/solidity-之-simplefitnesstracker-合约学习笔记</link>
            <guid>Kdd2JIaMkUuJ18486DzH</guid>
            <pubDate>Sun, 19 Oct 2025 15:23:32 GMT</pubDate>
            <description><![CDATA[一、合约整体功能概述SimpleFitnessTracker合约旨在为用户提供一个健身追踪功能。它能够记录用户的个人资料，包括姓名、体重及注册状态，同时跟踪用户的锻炼活动历史，并统计总的锻炼次数和总距离，还通过事件和修饰器来管理和反馈用户的操作及成就。二、数据结构定义UserProfile结构体代码：soliditystruct UserProfile{ string name; uint256 weight; bool isRegistered; } 功能：用于存储每个用户的姓名、体重以及注册状态信息。WorkoutActivity结构体代码：soliditystruct WorkoutActivity { string activityType; uint256 duration; uint256 distance; uint256 timestamp; } 功能：记录每次锻炼活动的类型、持续时间、距离以及时间戳。三、状态变量mapping(address => UserProfile) public userProfiles：通过用户地址映射到其对应的UserProfile...]]></description>
            <content:encoded><![CDATA[<h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0"><strong>一、合约整体功能概述</strong></h2><p><code>SimpleFitnessTracker</code>合约旨在为用户提供一个健身追踪功能。它能够记录用户的个人资料，包括姓名、体重及注册状态，同时跟踪用户的锻炼活动历史，并统计总的锻炼次数和总距离，还通过事件和修饰器来管理和反馈用户的操作及成就。</p><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0"><strong>二、数据结构定义</strong></h2><ol><li><p><code>UserProfile</code><strong>结构体</strong></p><ul><li><p><strong>代码</strong>：</p></li></ul></li></ol><p><strong>solidity</strong></p><pre data-type="codeBlock" text="struct UserProfile{
    string name;
    uint256 weight;
    bool isRegistered;
}
"><code><span class="hljs-keyword">struct</span> <span class="hljs-title">UserProfile</span>{
    <span class="hljs-keyword">string</span> name;
    <span class="hljs-keyword">uint256</span> weight;
    <span class="hljs-keyword">bool</span> isRegistered;
}
</code></pre><ul><li><p><strong>功能</strong>：用于存储每个用户的姓名、体重以及注册状态信息。</p></li></ul><ol start="2"><li><p><code>WorkoutActivity</code><strong>结构体</strong></p><ul><li><p><strong>代码</strong>：</p></li></ul></li></ol><p><strong>solidity</strong></p><pre data-type="codeBlock" text="struct WorkoutActivity {
    string activityType;
    uint256 duration;
    uint256 distance;
    uint256 timestamp;
}
"><code><span class="hljs-keyword">struct</span> <span class="hljs-title">WorkoutActivity</span> {
    <span class="hljs-keyword">string</span> activityType;
    <span class="hljs-keyword">uint256</span> duration;
    <span class="hljs-keyword">uint256</span> distance;
    <span class="hljs-keyword">uint256</span> timestamp;
}
</code></pre><ul><li><p><strong>功能</strong>：记录每次锻炼活动的类型、持续时间、距离以及时间戳。</p></li></ul><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0"><strong>三、状态变量</strong></h2><ol><li><p><code>mapping(address =&gt; UserProfile) public userProfiles</code>：通过用户地址映射到其对应的<code>UserProfile</code>结构体，公开状态变量，方便外部查询用户资料。</p></li><li><p><code>mapping(address =&gt; WorkoutActivity[]) public workoutHistory</code>：将用户地址与该用户的锻炼活动历史数组相关联，外部可访问，用于查看用户的锻炼记录。</p></li><li><p><code>mapping(address =&gt; uint256) public totalWorkouts</code>：记录每个用户的总锻炼次数，公开状态变量。</p></li><li><p><code>mapping(address =&gt; uint256) public totalDistance</code>：记录每个用户的总锻炼距离，公开状态变量。</p></li></ol><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0"><strong>四、事件定义</strong></h2><ol><li><p><code>event UserRegistered(address indexed userAddress, string name, uint256 timestamp)</code>：用户注册成功时触发，记录注册用户地址、姓名及时间戳。</p></li><li><p><code>event ProfileUpdated(address indexed userAddress, uint256 newWeight, uint256 timestamp)</code>：用户体重更新时触发，记录更新用户地址、新体重及时间戳。</p></li><li><p><code>event WorkoutLogged(address indexed userAddress, string activityType, uint256 duration, uint256 distance, uint256 timestamp)</code>：用户记录锻炼活动时触发，记录用户地址、锻炼类型、持续时间、距离及时间戳。</p></li><li><p><code>event MilestoneAchieved(address indexed userAddress, string milestone, uint256 timestamp)</code>：用户达成特定里程碑时触发，记录用户地址、里程碑内容及时间戳。</p></li></ol><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0"><strong>五、修饰器</strong></h2><p><code>modifier onlyRegistered()</code></p><ul><li><p><strong>代码</strong>：</p></li></ul><p><strong>solidity</strong></p><pre data-type="codeBlock" text="modifier onlyRegistered() {
    require(userProfiles[msg.sender].isRegistered, &quot;User not registered&quot;);
    _;
}
"><code><span class="hljs-function"><span class="hljs-keyword">modifier</span> <span class="hljs-title">onlyRegistered</span>(<span class="hljs-params"></span>) </span>{
    <span class="hljs-built_in">require</span>(userProfiles[<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>].isRegistered, <span class="hljs-string">"User not registered"</span>);
    <span class="hljs-keyword">_</span>;
}
</code></pre><ul><li><p><strong>功能</strong>：用于修饰特定函数，确保只有已注册用户才能调用这些函数。通过<code>require</code>语句检查调用者是否已注册，若未注册则抛出错误。</p></li></ul><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0"><strong>六、合约函数</strong></h2><ol><li><p><code>registerUser</code><strong>函数</strong></p><ul><li><p><strong>代码</strong>：</p></li></ul></li></ol><p><strong>solidity</strong></p><pre data-type="codeBlock" text="function registerUser(string memory _name, uint256 _weight) public {
    require(!userProfiles[msg.sender].isRegistered, &quot;User already registered&quot;);

    userProfiles[msg.sender] = UserProfile({
        name: _name,
        weight: _weight,
        isRegistered: true
    });

    emit UserRegistered(msg.sender, _name, block.timestamp);
}
"><code><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">registerUser</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> <span class="hljs-keyword">memory</span> _name, <span class="hljs-keyword">uint256</span> _weight</span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span> </span>{
    <span class="hljs-built_in">require</span>(<span class="hljs-operator">!</span>userProfiles[<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>].isRegistered, <span class="hljs-string">"User already registered"</span>);

    userProfiles[<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>] <span class="hljs-operator">=</span> UserProfile({
        name: _name,
        weight: _weight,
        isRegistered: <span class="hljs-literal">true</span>
    });

    <span class="hljs-keyword">emit</span> UserRegistered(<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>, _name, <span class="hljs-built_in">block</span>.<span class="hljs-built_in">timestamp</span>);
}
</code></pre><ul><li><p><strong>功能</strong>：用户注册函数，要求用户尚未注册。创建并存储用户资料，同时触发<code>UserRegistered</code>事件。</p></li></ul><ol start="2"><li><p><code>updateWeight</code><strong>函数</strong></p><ul><li><p><strong>代码</strong>：</p></li></ul></li></ol><p><strong>solidity</strong></p><pre data-type="codeBlock" text="function updateWeight(uint256 _newWeight) public onlyRegistered {
    UserProfile storage profile = userProfiles[msg.sender];

    if (_newWeight &lt; profile.weight &amp;&amp; (profile.weight - _newWeight) * 100 / profile.weight &gt;= 5) {
        emit MilestoneAchieved(msg.sender, &quot;Weight Goal Reached&quot;, block.timestamp);
    }

    profile.weight = _newWeight;
    emit ProfileUpdated(msg.sender, _newWeight, block.timestamp);
}
"><code><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">updateWeight</span>(<span class="hljs-params"><span class="hljs-keyword">uint256</span> _newWeight</span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span> <span class="hljs-title">onlyRegistered</span> </span>{
    UserProfile <span class="hljs-keyword">storage</span> profile <span class="hljs-operator">=</span> userProfiles[<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>];

    <span class="hljs-keyword">if</span> (_newWeight <span class="hljs-operator">&lt;</span> profile.weight <span class="hljs-operator">&amp;</span><span class="hljs-operator">&amp;</span> (profile.weight <span class="hljs-operator">-</span> _newWeight) <span class="hljs-operator">*</span> <span class="hljs-number">100</span> <span class="hljs-operator">/</span> profile.weight <span class="hljs-operator">&gt;</span><span class="hljs-operator">=</span> <span class="hljs-number">5</span>) {
        <span class="hljs-keyword">emit</span> MilestoneAchieved(<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>, <span class="hljs-string">"Weight Goal Reached"</span>, <span class="hljs-built_in">block</span>.<span class="hljs-built_in">timestamp</span>);
    }

    profile.weight <span class="hljs-operator">=</span> _newWeight;
    <span class="hljs-keyword">emit</span> ProfileUpdated(<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>, _newWeight, <span class="hljs-built_in">block</span>.<span class="hljs-built_in">timestamp</span>);
}
</code></pre><ul><li><p><strong>功能</strong>：仅允许已注册用户调用。更新用户体重，若体重下降幅度超过 5%，触发<code>MilestoneAchieved</code>事件，同时触发<code>ProfileUpdated</code>事件。</p></li></ul><ol start="3"><li><p><code>logWorkout</code><strong>函数</strong></p><ul><li><p><strong>代码</strong>：</p></li></ul></li></ol><p><strong>solidity</strong></p><pre data-type="codeBlock" text="function logWorkout(
    string memory _activityType,
    uint256 _duration,
    uint256 _distance
) public onlyRegistered {
    // Create new workout activity
    WorkoutActivity memory newWorkout = WorkoutActivity({
        activityType: _activityType,
        duration: _duration,
        distance: _distance,
        timestamp: block.timestamp
    });

    // Add to user's workout history
    workoutHistory[msg.sender].push(newWorkout);

    // Update total stats
    totalWorkouts[msg.sender]++;
    totalDistance[msg.sender] += _distance;

    // Emit workout logged event
    emit WorkoutLogged(
        msg.sender,
        _activityType,
        _duration,
        _distance,
        block.timestamp
    );

    // Check for workout count milestones
    if (totalWorkouts[msg.sender] == 10) {
        emit MilestoneAchieved(msg.sender, &quot;10 Workouts Completed&quot;, block.timestamp);
    } else if (totalWorkouts[msg.sender] == 50) {
        emit MilestoneAchieved(msg.sender, &quot;50 Workouts Completed&quot;, block.timestamp);
    }

    // Check for distance milestones
    if (totalDistance[msg.sender] &gt;= 100000 &amp;&amp; totalDistance[msg.sender] - _distance &lt; 100000) {
        emit MilestoneAchieved(msg.sender, &quot;100K Total Distance&quot;, block.timestamp);
    }
}
"><code><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">logWorkout</span>(<span class="hljs-params">
    <span class="hljs-keyword">string</span> <span class="hljs-keyword">memory</span> _activityType,
    <span class="hljs-keyword">uint256</span> _duration,
    <span class="hljs-keyword">uint256</span> _distance
</span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span> <span class="hljs-title">onlyRegistered</span> </span>{
    <span class="hljs-comment">// Create new workout activity</span>
    WorkoutActivity <span class="hljs-keyword">memory</span> newWorkout <span class="hljs-operator">=</span> WorkoutActivity({
        activityType: _activityType,
        duration: _duration,
        distance: _distance,
        timestamp: <span class="hljs-built_in">block</span>.<span class="hljs-built_in">timestamp</span>
    });

    <span class="hljs-comment">// Add to user's workout history</span>
    workoutHistory[<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>].<span class="hljs-built_in">push</span>(newWorkout);

    <span class="hljs-comment">// Update total stats</span>
    totalWorkouts[<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>]<span class="hljs-operator">+</span><span class="hljs-operator">+</span>;
    totalDistance[<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>] <span class="hljs-operator">+</span><span class="hljs-operator">=</span> _distance;

    <span class="hljs-comment">// Emit workout logged event</span>
    <span class="hljs-keyword">emit</span> WorkoutLogged(
        <span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>,
        _activityType,
        _duration,
        _distance,
        <span class="hljs-built_in">block</span>.<span class="hljs-built_in">timestamp</span>
    );

    <span class="hljs-comment">// Check for workout count milestones</span>
    <span class="hljs-keyword">if</span> (totalWorkouts[<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>] <span class="hljs-operator">=</span><span class="hljs-operator">=</span> <span class="hljs-number">10</span>) {
        <span class="hljs-keyword">emit</span> MilestoneAchieved(<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>, <span class="hljs-string">"10 Workouts Completed"</span>, <span class="hljs-built_in">block</span>.<span class="hljs-built_in">timestamp</span>);
    } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (totalWorkouts[<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>] <span class="hljs-operator">=</span><span class="hljs-operator">=</span> <span class="hljs-number">50</span>) {
        <span class="hljs-keyword">emit</span> MilestoneAchieved(<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>, <span class="hljs-string">"50 Workouts Completed"</span>, <span class="hljs-built_in">block</span>.<span class="hljs-built_in">timestamp</span>);
    }

    <span class="hljs-comment">// Check for distance milestones</span>
    <span class="hljs-keyword">if</span> (totalDistance[<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>] <span class="hljs-operator">&gt;</span><span class="hljs-operator">=</span> <span class="hljs-number">100000</span> <span class="hljs-operator">&amp;</span><span class="hljs-operator">&amp;</span> totalDistance[<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>] <span class="hljs-operator">-</span> _distance <span class="hljs-operator">&lt;</span> <span class="hljs-number">100000</span>) {
        <span class="hljs-keyword">emit</span> MilestoneAchieved(<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>, <span class="hljs-string">"100K Total Distance"</span>, <span class="hljs-built_in">block</span>.<span class="hljs-built_in">timestamp</span>);
    }
}
</code></pre><ul><li><p><strong>功能</strong>：仅已注册用户可调用。创建新的锻炼记录并添加到用户锻炼历史，更新总锻炼次数和总距离，触发<code>WorkoutLogged</code>事件。同时检查是否达成锻炼次数和总距离的里程碑，若达成则触发<code>MilestoneAchieved</code>事件。</p></li></ul><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0"><strong>七、学习心得</strong></h2><p>学习<code>SimpleFitnessTracker</code>合约，我对 Solidity 在实际应用场景中的开发有了更深入的认识。从结构体的设计到状态变量的使用，再到修饰器、事件以及各类函数的协同工作，整个合约构成了一个完整且功能丰富的健身追踪系统。事件的运用使得合约能够有效地记录和反馈用户的关键操作，修饰器则保证了只有合法用户才能执行特定功能，增强了合约的安全性和逻辑性。在实际开发中，这种模块化和结构化的设计方式能够提高代码的可读性和可维护性，为构建更复杂的区块链应用提供了良好的范例。同时，也让我意识到在处理用户数据和业务逻辑时，需要全面考虑各种情况，并通过合理的条件判断和事件触发来实现业务目标和用户交互。</p>]]></content:encoded>
            <author>earnest_stus@newsletter.paragraph.com (Earnest_stu)</author>
        </item>
        <item>
            <title><![CDATA[web3短期学习体会]]></title>
            <link>https://paragraph.com/@Earnest_stus/web3短期学习体会</link>
            <guid>gF9STEZu5UFev3haN4S7</guid>
            <pubDate>Sat, 18 Oct 2025 08:15:32 GMT</pubDate>
            <description><![CDATA[这几天刚接触 Web3，尤其是里头的 Solidity 智能合约，真给我一种打开新世界大门的感觉。 像ClickCounter合约，就跟个小计数器似的，counter变量记着点击数，几个函数就能实现计数、清零和减少，一下子就让我明白了合约里数据会咋变。SaveMyInformation合约呢，能存像名字、简介这些字符串数据，还能取出来，让我知道了复杂数据在合约里咋处理。 再看TipJar合约，好家伙，支持好几种货币打赏，从设置货币汇率到各种打赏、提现操作，这业务逻辑可比前面复杂多了。还有那个只能合约主人用的onlyOwner修饰器，让我知道安全这块儿在区块链里不能马虎。 今天学的Calculator合约调用外部合约，这又给我开了个新视角。通过调用ScientificCalculator合约，它不仅能做加减乘除，还能算复杂的幂运算和平方根。这让我知道合约之间还能这么配合着干活。 就这短短几天，我对 Web3 和 Solidity 合约算是有了点入门的认识。感觉这玩意儿可真有意思，以后还得接着深入学，看看能捣鼓出啥好玩的东西来。]]></description>
            <content:encoded><![CDATA[<p>这几天刚接触 Web3，尤其是里头的 Solidity 智能合约，真给我一种打开新世界大门的感觉。</p><p>像<code>ClickCounter</code>合约，就跟个小计数器似的，<code>counter</code>变量记着点击数，几个函数就能实现计数、清零和减少，一下子就让我明白了合约里数据会咋变。<code>SaveMyInformation</code>合约呢，能存像名字、简介这些字符串数据，还能取出来，让我知道了复杂数据在合约里咋处理。</p><p>再看<code>TipJar</code>合约，好家伙，支持好几种货币打赏，从设置货币汇率到各种打赏、提现操作，这业务逻辑可比前面复杂多了。还有那个只能合约主人用的<code>onlyOwner</code>修饰器，让我知道安全这块儿在区块链里不能马虎。</p><p>今天学的<code>Calculator</code>合约调用外部合约，这又给我开了个新视角。通过调用<code>ScientificCalculator</code>合约，它不仅能做加减乘除，还能算复杂的幂运算和平方根。这让我知道合约之间还能这么配合着干活。</p><p>就这短短几天，我对 Web3 和 Solidity 合约算是有了点入门的认识。感觉这玩意儿可真有意思，以后还得接着深入学，看看能捣鼓出啥好玩的东西来。</p><p><br></p>]]></content:encoded>
            <author>earnest_stus@newsletter.paragraph.com (Earnest_stu)</author>
        </item>
        <item>
            <title><![CDATA[Solidity 调用外部合约学习笔记]]></title>
            <link>https://paragraph.com/@Earnest_stus/solidity-调用外部合约学习笔记</link>
            <guid>9Nv8M01KGl2QvdTq60Wc</guid>
            <pubDate>Sat, 18 Oct 2025 08:11:50 GMT</pubDate>
            <description><![CDATA[一、合约整体结构与功能概述Calculator 合约：此合约实现了基本的四则运算功能，并通过调用外部的 ScientificCalculator 合约来实现幂运算和平方根计算。合约所有者拥有特定的管理权限。依赖引入：通过 import "./ScientificCalculator.sol"; 语句导入外部合约 ScientificCalculator，使得 Calculator 合约能够使用其功能。二、状态变量address public owner：记录合约所有者的地址，该变量为公开状态，外部可访问。address public scientificCalculatorAddress：存储外部 ScientificCalculator 合约的地址，同样为公开状态，方便外部查看合约所调用的科学计算器合约地址。三、构造函数与修饰器构造函数 constructor()代码：solidityconstructor(){ owner = msg.sender; } 功能：在合约部署时执行，将合约所有者设置为部署者。修饰器 onlyOwner()代码：soliditymodifier o...]]></description>
            <content:encoded><![CDATA[<h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0"><strong>一、合约整体结构与功能概述</strong></h2><ol><li><p><strong>Calculator 合约</strong>：此合约实现了基本的四则运算功能，并通过调用外部的 <code>ScientificCalculator</code> 合约来实现幂运算和平方根计算。合约所有者拥有特定的管理权限。</p></li><li><p><strong>依赖引入</strong>：通过 <code>import "./ScientificCalculator.sol";</code> 语句导入外部合约 <code>ScientificCalculator</code>，使得 <code>Calculator</code> 合约能够使用其功能。</p></li></ol><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0"><strong>二、状态变量</strong></h2><ol><li><p><code>address public owner</code>：记录合约所有者的地址，该变量为公开状态，外部可访问。</p></li><li><p><code>address public scientificCalculatorAddress</code>：存储外部 <code>ScientificCalculator</code> 合约的地址，同样为公开状态，方便外部查看合约所调用的科学计算器合约地址。</p></li></ol><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0"><strong>三、构造函数与修饰器</strong></h2><ol><li><p><strong>构造函数 </strong><code>constructor()</code></p><ul><li><p><strong>代码</strong>：</p></li></ul></li></ol><p><strong>solidity</strong></p><pre data-type="codeBlock" text="constructor(){
    owner = msg.sender;
}
"><code><span class="hljs-function"><span class="hljs-keyword">constructor</span>(<span class="hljs-params"></span>)</span>{
    owner <span class="hljs-operator">=</span> <span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>;
}
</code></pre><ul><li><p><strong>功能</strong>：在合约部署时执行，将合约所有者设置为部署者。</p></li></ul><ol start="2"><li><p><strong>修饰器 </strong><code>onlyOwner()</code></p><ul><li><p><strong>代码</strong>：</p></li></ul></li></ol><p><strong>solidity</strong></p><pre data-type="codeBlock" text="modifier onlyOwner() {
    require(msg.sender == owner, &quot;Only the contract owner can call this function&quot;);
    _;
}
"><code><span class="hljs-function"><span class="hljs-keyword">modifier</span> <span class="hljs-title">onlyOwner</span>(<span class="hljs-params"></span>) </span>{
    <span class="hljs-built_in">require</span>(<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span> <span class="hljs-operator">=</span><span class="hljs-operator">=</span> owner, <span class="hljs-string">"Only the contract owner can call this function"</span>);
    <span class="hljs-keyword">_</span>;
}
</code></pre><ul><li><p><strong>功能</strong>：用于修饰特定函数，确保只有合约所有者能够调用这些函数。通过 <code>require</code> 语句检查调用者地址是否与合约所有者地址一致，不一致则抛出错误。</p></li></ul><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0"><strong>四、合约自有函数</strong></h2><ol><li><p><strong>基本四则运算函数</strong></p><ul><li><p><code>add</code><strong> 函数</strong>：</p></li></ul></li></ol><p><strong>solidity</strong></p><pre data-type="codeBlock" text="function add(uint256 a, uint256 b) public pure returns (uint256) {
    return a + b;
}
"><code><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">add</span>(<span class="hljs-params"><span class="hljs-keyword">uint256</span> a, <span class="hljs-keyword">uint256</span> b</span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span> <span class="hljs-title"><span class="hljs-keyword">pure</span></span> <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params"><span class="hljs-keyword">uint256</span></span>) </span>{
    <span class="hljs-keyword">return</span> a <span class="hljs-operator">+</span> b;
}
</code></pre><ul><li><p><strong>功能</strong>：实现两个无符号 256 位整数的加法运算，为纯函数，不修改合约状态。</p></li><li><p><code>subtract</code><strong> 函数</strong>：</p></li></ul><p><strong>solidity</strong></p><pre data-type="codeBlock" text="function subtract(uint256 a, uint256 b) public pure returns (uint256) {
    return a - b;
}
"><code><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">subtract</span>(<span class="hljs-params"><span class="hljs-keyword">uint256</span> a, <span class="hljs-keyword">uint256</span> b</span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span> <span class="hljs-title"><span class="hljs-keyword">pure</span></span> <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params"><span class="hljs-keyword">uint256</span></span>) </span>{
    <span class="hljs-keyword">return</span> a <span class="hljs-operator">-</span> b;
}
</code></pre><ul><li><p><strong>功能</strong>：实现减法运算，同样为纯函数。</p></li><li><p><code>multiply</code><strong> 函数</strong>：</p></li></ul><p><strong>solidity</strong></p><pre data-type="codeBlock" text="function multiply(uint256 a, uint256 b) public pure returns (uint256) {
    return a * b;
}
"><code><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">multiply</span>(<span class="hljs-params"><span class="hljs-keyword">uint256</span> a, <span class="hljs-keyword">uint256</span> b</span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span> <span class="hljs-title"><span class="hljs-keyword">pure</span></span> <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params"><span class="hljs-keyword">uint256</span></span>) </span>{
    <span class="hljs-keyword">return</span> a <span class="hljs-operator">*</span> b;
}
</code></pre><ul><li><p><strong>功能</strong>：实现乘法运算，纯函数。</p></li><li><p><code>divide</code><strong> 函数</strong>：</p></li></ul><p><strong>solidity</strong></p><pre data-type="codeBlock" text="function divide(uint256 a, uint256 b) public pure returns (uint256) {
    require(b != 0, &quot;Division by zero&quot;);
    return a / b;
}
"><code><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">divide</span>(<span class="hljs-params"><span class="hljs-keyword">uint256</span> a, <span class="hljs-keyword">uint256</span> b</span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span> <span class="hljs-title"><span class="hljs-keyword">pure</span></span> <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params"><span class="hljs-keyword">uint256</span></span>) </span>{
    <span class="hljs-built_in">require</span>(b <span class="hljs-operator">!</span><span class="hljs-operator">=</span> <span class="hljs-number">0</span>, <span class="hljs-string">"Division by zero"</span>);
    <span class="hljs-keyword">return</span> a <span class="hljs-operator">/</span> b;
}
</code></pre><ul><li><p><strong>功能</strong>：实现除法运算，为避免除零错误，通过 <code>require</code> 语句进行检查，纯函数。</p></li></ul><ol start="2"><li><p><strong>调用外部合约函数</strong></p><ul><li><p><code>calculatePower</code><strong> 函数</strong>：</p></li></ul></li></ol><p><strong>solidity</strong></p><pre data-type="codeBlock" text="function calculatePower(uint256 base, uint256 exponent) public view returns (uint256) {
    ScientificCalculator scientificCalc = ScientificCalculator(scientificCalculatorAddress);
    uint256 result = scientificCalc.power(base, exponent);
    return result;
}
"><code><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">calculatePower</span>(<span class="hljs-params"><span class="hljs-keyword">uint256</span> base, <span class="hljs-keyword">uint256</span> exponent</span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span> <span class="hljs-title"><span class="hljs-keyword">view</span></span> <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params"><span class="hljs-keyword">uint256</span></span>) </span>{
    ScientificCalculator scientificCalc <span class="hljs-operator">=</span> ScientificCalculator(scientificCalculatorAddress);
    <span class="hljs-keyword">uint256</span> result <span class="hljs-operator">=</span> scientificCalc.power(base, exponent);
    <span class="hljs-keyword">return</span> result;
}
</code></pre><ul><li><p><strong>功能</strong>：调用外部 <code>ScientificCalculator</code> 合约的 <code>power</code> 函数来计算幂。首先将存储的外部合约地址转换为 <code>ScientificCalculator</code> 合约类型实例，然后调用其 <code>power</code> 函数，并返回结果。此函数为视图函数，不修改合约状态。</p></li><li><p><code>calculateSquareRoot</code><strong> 函数</strong>：</p></li></ul><p><strong>solidity</strong></p><pre data-type="codeBlock" text="function calculateSquareRoot(uint256 number) public returns (uint256) {
    require(number &gt;= 0, &quot;Cannot calculate square root of negative number&quot;);

    bytes memory data = abi.encodeWithSignature(&quot;squareRoot(int256)&quot;, number);
    (bool success, bytes memory returnData) = scientificCalculatorAddress.call(data);
    require(success, &quot;External call failed&quot;);

    uint256 result = abi.decode(returnData, (uint256));
    return result;
}
"><code><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">calculateSquareRoot</span>(<span class="hljs-params"><span class="hljs-keyword">uint256</span> number</span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span> <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params"><span class="hljs-keyword">uint256</span></span>) </span>{
    <span class="hljs-built_in">require</span>(number <span class="hljs-operator">&gt;</span><span class="hljs-operator">=</span> <span class="hljs-number">0</span>, <span class="hljs-string">"Cannot calculate square root of negative number"</span>);

    <span class="hljs-keyword">bytes</span> <span class="hljs-keyword">memory</span> data <span class="hljs-operator">=</span> <span class="hljs-built_in">abi</span>.<span class="hljs-built_in">encodeWithSignature</span>(<span class="hljs-string">"squareRoot(int256)"</span>, number);
    (<span class="hljs-keyword">bool</span> success, <span class="hljs-keyword">bytes</span> <span class="hljs-keyword">memory</span> returnData) <span class="hljs-operator">=</span> scientificCalculatorAddress.<span class="hljs-built_in">call</span>(data);
    <span class="hljs-built_in">require</span>(success, <span class="hljs-string">"External call failed"</span>);

    <span class="hljs-keyword">uint256</span> result <span class="hljs-operator">=</span> <span class="hljs-built_in">abi</span>.<span class="hljs-built_in">decode</span>(returnData, (<span class="hljs-keyword">uint256</span>));
    <span class="hljs-keyword">return</span> result;
}
</code></pre><ul><li><p><strong>功能</strong>：用于计算平方根，先检查输入的数字是否为非负数。接着使用 <code>abi.encodeWithSignature</code> 对要调用的外部合约函数 <code>squareRoot</code> 及其参数进行编码，通过 <code>call</code> 方法发起对外部合约的调用，并返回调用结果。若调用成功，使用 <code>abi.decode</code> 对返回数据进行解码，得到计算结果并返回。</p></li></ul><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0"><strong>五、个人学习心得</strong></h2><p>通过学习这个 <code>Calculator</code> 合约调用外部合约的示例，对 Solidity 中合约间的交互有了更深入的理解。合约间的调用不仅丰富了单个合约的功能，还展示了区块链生态中不同合约协作完成复杂任务的能力。从代码实现上看，无论是通过类型转换直接调用外部合约函数，还是使用 <code>abi.encodeWithSignature</code> 和 <code>call</code> 方法进行更底层的调用，都需要对 Solidity 的数据类型、函数签名以及调用机制有清晰的认识。同时，在调用外部合约时，错误处理非常重要，如检查调用是否成功等，以确保合约的稳定性和可靠性。这为开发更复杂的分布式应用奠定了基础，明白了如何合理地划分功能到不同合约，并实现它们之间的有效交互。</p><p>外部合约内容：</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/73ddfc85942e23212ad0da6e3f3c5b7254142642f457dd2da9d4f0f7f27f2c93.png" blurdataurl="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAASCAIAAAC1qksFAAAACXBIWXMAAA7DAAAOwwHHb6hkAAADtklEQVR4nJ2UT2jbVhzHpe42tyefBKvSW/50zN7BJzus6iCqy2zn4BQHdZAssMolVhOGEmJ0WH3q2y4zpBNt16ajmRw61MCqhkLIOixnbV8oBM2hUxNskbTwCiV9OakOLG/YYiZJlzbkcxBPAr0P7/v7vR+1/hqvv8a/z83F4wmapmVZTvWm0uk0x3EXLgyNjozcmpwcy449evyYHAhqpVJ5+eqVJEkURbW3tx/vOE5RVCgUam9voyjq3LmvIYS6rjuOc0BBteo8XVlZXl6+dvWabdvzpRKE0LIshBCEECHkNjjY7nXB0tO/q2vPr165wvN8V1eXLH8Ti33RGYlwHMfzfCAQ+DQYFARh/PI4xhhC6DiObduWZTkNbNvGGFeqleZHhFClWvHWruvWBYvlJYzxzMxMOBwOBoOdkQhFUX6/PxQKsSzb2tbKsqwoplO9vRzHjY6OhhsoitKdSJw6FQXgUn9/PwCA4zhFUXK5XCwW/+zECUXJOo5Tr8FiubxZq62vr2O8kUwmo9EoTdMsyw4MDHQnEv39X3VGIsdaWpLJpK7fcV0XY+xuY/srIWT7uh7R8xcvFstlQrYQQmurq/l8XlXVfANVVdPpNIRQ0zRBEM6nzwMAMMbeLs3ne2qwWF4qPXxICDFNk+d5Vf1R1/V8Pq8oymAmo6rq8NDQ8NDwxMRNSZI0TQMASJI0VSgAABodnAUATBWmRFFM9aa+vXixUq3uEGj69P25OUIIQkjX9Y+OHvX7/SzLUg0kSeI4ThRFmqb9fj/DMMFgkGGYQCDg83146NAHPp+P53nDMDRNm5i4aRgzCKEdgjvGvQeleUK2MMabtdrIyMiXZ8/SNH2mp+en69dlWe7r6xMEIRwO/3b3rrMNy7L+KBYdx6lUq+/Iipq+d78RUV3w5k3Ntu1isWgYhmmaXufpuq5pBcv6C+MN75+3t9us1XbV9v8FhBCe54+1tIii2NrWKgiCaZoMw5yOnh7MZCzLevZs2bbt9xZ2h+DGL4W5YqkpUOpkDx8+4vP5RFFUlGwg8IlhGN2JBMMwn588yXHcrcnJ27/e3pX1noKCPv3nwoIncF3XCxcA4OUry3Iul5udnQXgUjKZHL88nsvlxrJjAIB9Tqe6oBnRZq0WjyfO9PRomhaLxQczmX+2tvbZ7+8SNLqINIeJV16zAcYbu+7nXsXcU6De+PlBaX5tdRUh5KVECGnefvwfbwv2CfXd9z+YpUcdH3dACDHGlmXZtv1kYQFC6J3JwxuTB+BfufEdmlqK9S0AAAAASUVORK5CYII=" nextheight="795" nextwidth="1399" class="image-node embed"><figcaption htmlattributes="[object Object]" class="hide-figcaption"></figcaption></figure><p><br></p>]]></content:encoded>
            <author>earnest_stus@newsletter.paragraph.com (Earnest_stu)</author>
        </item>
        <item>
            <title><![CDATA[Solidity 智能合约之 TipJar 学习笔记]]></title>
            <link>https://paragraph.com/@Earnest_stus/solidity-智能合约之-tipjar-学习笔记</link>
            <guid>x0okklhEIhsQlEgT71Xw</guid>
            <pubDate>Fri, 17 Oct 2025 15:52:13 GMT</pubDate>
            <description><![CDATA[一、合约概述TipJar合约实现了一个小费罐功能，支持多种货币打赏，并提供了管理功能，只有合约所有者能执行特定操作。二、状态变量address public owner：记录合约所有者的地址，且该变量为公开状态，可从合约外部访问。mapping(string => uint256) public conversionRates：一个映射，将货币代码（字符串）与兑换成以太坊（ETH）的汇率（无符号 256 位整数）关联起来，公开可见。string[] public supportedCurrencies：存储支持的货币代码的字符串数组，公开可访问。uint256 public totalTipsRecieved：记录总共收到的小费金额（以 ETH 为单位），公开状态变量。mapping(address => uint256) public tipperContributions：映射，将打赏者的地址与他们的贡献金额（以 ETH 为单位）关联，公开。mapping(string => uint256) public tipsPerCurrency：映射，将货币代码与该货币收到的小费金...]]></description>
            <content:encoded><![CDATA[<h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0"><strong>一、合约概述</strong></h2><p><code>TipJar</code>合约实现了一个小费罐功能，支持多种货币打赏，并提供了管理功能，只有合约所有者能执行特定操作。</p><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0"><strong>二、状态变量</strong></h2><ol><li><p><code>address public owner</code>：记录合约所有者的地址，且该变量为公开状态，可从合约外部访问。</p></li><li><p><code>mapping(string =&gt; uint256) public conversionRates</code>：一个映射，将货币代码（字符串）与兑换成以太坊（ETH）的汇率（无符号 256 位整数）关联起来，公开可见。</p></li><li><p><code>string[] public supportedCurrencies</code>：存储支持的货币代码的字符串数组，公开可访问。</p></li><li><p><code>uint256 public totalTipsRecieved</code>：记录总共收到的小费金额（以 ETH 为单位），公开状态变量。</p></li><li><p><code>mapping(address =&gt; uint256) public tipperContributions</code>：映射，将打赏者的地址与他们的贡献金额（以 ETH 为单位）关联，公开。</p></li><li><p><code>mapping(string =&gt; uint256) public tipsPerCurrency</code>：映射，将货币代码与该货币收到的小费金额关联，公开。</p></li></ol><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0"><strong>三、修饰器</strong></h2><p><code>modifier onlyOwner()</code>：确保只有合约所有者能调用被修饰的函数。它通过<code>require</code>语句检查调用者是否为合约所有者，若不是则抛出错误。</p><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0"><strong>四、函数</strong></h2><ol><li><p><code>addCurrency</code><strong>函数</strong></p><ul><li><p><strong>代码片段</strong></p></li></ul></li></ol><p><strong>solidity</strong></p><pre data-type="codeBlock" text="function addCurrency(string memory _currencyCode, uint256 _rateToEth) public onlyOwner {
    require(_rateToEth &gt; 0, &quot;Conversion rate must be greater than 0&quot;);

    // Check if currency already exists
    bool currencyExists = false;
    for (uint i = 0; i &lt; supportedCurrencies.length; i++) {
        if (keccak256(bytes(supportedCurrencies[i])) == keccak256(bytes(_currencyCode))) {
            currencyExists = true;
            break;
        }
    }

    // Add to the list if it's new
    if (!currencyExists) {
        supportedCurrencies.push(_currencyCode);
    }

    // Set the conversion rate
    conversionRates[_currencyCode] = _rateToEth;
}
"><code><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">addCurrency</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> <span class="hljs-keyword">memory</span> _currencyCode, <span class="hljs-keyword">uint256</span> _rateToEth</span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span> <span class="hljs-title">onlyOwner</span> </span>{
    <span class="hljs-built_in">require</span>(_rateToEth <span class="hljs-operator">&gt;</span> <span class="hljs-number">0</span>, <span class="hljs-string">"Conversion rate must be greater than 0"</span>);

    <span class="hljs-comment">// Check if currency already exists</span>
    <span class="hljs-keyword">bool</span> currencyExists <span class="hljs-operator">=</span> <span class="hljs-literal">false</span>;
    <span class="hljs-keyword">for</span> (<span class="hljs-keyword">uint</span> i <span class="hljs-operator">=</span> <span class="hljs-number">0</span>; i <span class="hljs-operator">&lt;</span> supportedCurrencies.<span class="hljs-built_in">length</span>; i<span class="hljs-operator">+</span><span class="hljs-operator">+</span>) {
        <span class="hljs-keyword">if</span> (<span class="hljs-built_in">keccak256</span>(<span class="hljs-keyword">bytes</span>(supportedCurrencies[i])) <span class="hljs-operator">=</span><span class="hljs-operator">=</span> <span class="hljs-built_in">keccak256</span>(<span class="hljs-keyword">bytes</span>(_currencyCode))) {
            currencyExists <span class="hljs-operator">=</span> <span class="hljs-literal">true</span>;
            <span class="hljs-keyword">break</span>;
        }
    }

    <span class="hljs-comment">// Add to the list if it's new</span>
    <span class="hljs-keyword">if</span> (<span class="hljs-operator">!</span>currencyExists) {
        supportedCurrencies.<span class="hljs-built_in">push</span>(_currencyCode);
    }

    <span class="hljs-comment">// Set the conversion rate</span>
    conversionRates[_currencyCode] <span class="hljs-operator">=</span> _rateToEth;
}
</code></pre><ul><li><p><strong>功能</strong>：只有合约所有者能调用此函数，用于添加新的支持货币及其兑换成 ETH 的汇率。它先检查汇率是否大于 0，再检查货币是否已存在，若不存在则添加到支持货币列表，并设置其汇率。</p></li></ul><ol start="2"><li><p><code>constructor</code><strong>函数</strong></p><ul><li><p><strong>代码片段</strong></p></li></ul></li></ol><p><strong>solidity</strong></p><pre data-type="codeBlock" text="constructor() {
    owner = msg.sender;

    addCurrency(&quot;USD&quot;, 5 * 10**14);
    addCurrency(&quot;EUR&quot;, 6 * 10**14);
    addCurrency(&quot;JPY&quot;, 4 * 10**12);
    addCurrency(&quot;GBP&quot;, 7 * 10**14);
}
"><code><span class="hljs-function"><span class="hljs-keyword">constructor</span>(<span class="hljs-params"></span>) </span>{
    owner <span class="hljs-operator">=</span> <span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>;

    addCurrency(<span class="hljs-string">"USD"</span>, <span class="hljs-number">5</span> <span class="hljs-operator">*</span> <span class="hljs-number">10</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-number">14</span>);
    addCurrency(<span class="hljs-string">"EUR"</span>, <span class="hljs-number">6</span> <span class="hljs-operator">*</span> <span class="hljs-number">10</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-number">14</span>);
    addCurrency(<span class="hljs-string">"JPY"</span>, <span class="hljs-number">4</span> <span class="hljs-operator">*</span> <span class="hljs-number">10</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-number">12</span>);
    addCurrency(<span class="hljs-string">"GBP"</span>, <span class="hljs-number">7</span> <span class="hljs-operator">*</span> <span class="hljs-number">10</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-number">14</span>);
}
</code></pre><ul><li><p><strong>功能</strong>：合约部署时执行，设置合约所有者为部署者，并初始化添加几种常见货币及其汇率。</p></li></ul><ol start="3"><li><p><code>convertToEth</code><strong>函数</strong></p><ul><li><p><strong>代码片段</strong></p></li></ul></li></ol><p><strong>solidity</strong></p><pre data-type="codeBlock" text="function convertToEth(string memory _currencyCode, uint256 _amount) public view returns (uint256) {
    require(conversionRates[_currencyCode] &gt; 0, &quot;Currency not supported&quot;);
    uint256 ethAmount = _amount * conversionRates[_currencyCode];
    return ethAmount;
}
"><code><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">convertToEth</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> <span class="hljs-keyword">memory</span> _currencyCode, <span class="hljs-keyword">uint256</span> _amount</span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span> <span class="hljs-title"><span class="hljs-keyword">view</span></span> <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params"><span class="hljs-keyword">uint256</span></span>) </span>{
    <span class="hljs-built_in">require</span>(conversionRates[_currencyCode] <span class="hljs-operator">&gt;</span> <span class="hljs-number">0</span>, <span class="hljs-string">"Currency not supported"</span>);
    <span class="hljs-keyword">uint256</span> ethAmount <span class="hljs-operator">=</span> _amount <span class="hljs-operator">*</span> conversionRates[_currencyCode];
    <span class="hljs-keyword">return</span> ethAmount;
}
</code></pre><ul><li><p><strong>功能</strong>：将指定数量的某种货币根据汇率转换为 ETH 数量。先检查该货币是否被支持，然后进行转换并返回结果。</p></li></ul><ol start="4"><li><p><code>tipInEth</code><strong>函数</strong></p><ul><li><p><strong>代码片段</strong></p></li></ul></li></ol><p><strong>solidity</strong></p><pre data-type="codeBlock" text="function tipInEth() public payable {
    require(msg.value &gt; 0, &quot;Tip amount must be greater than 0&quot;);
    tipperContributions[msg.sender] += msg.value;
    totalTipsRecieved += msg.value;
    tipsPerCurrency[&quot;ETH&quot;] += msg.value;
}
"><code><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">tipInEth</span>(<span class="hljs-params"></span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span> <span class="hljs-title"><span class="hljs-keyword">payable</span></span> </span>{
    <span class="hljs-built_in">require</span>(<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">value</span> <span class="hljs-operator">&gt;</span> <span class="hljs-number">0</span>, <span class="hljs-string">"Tip amount must be greater than 0"</span>);
    tipperContributions[<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>] <span class="hljs-operator">+</span><span class="hljs-operator">=</span> <span class="hljs-built_in">msg</span>.<span class="hljs-built_in">value</span>;
    totalTipsRecieved <span class="hljs-operator">+</span><span class="hljs-operator">=</span> <span class="hljs-built_in">msg</span>.<span class="hljs-built_in">value</span>;
    tipsPerCurrency[<span class="hljs-string">"ETH"</span>] <span class="hljs-operator">+</span><span class="hljs-operator">=</span> <span class="hljs-built_in">msg</span>.<span class="hljs-built_in">value</span>;
}
</code></pre><ul><li><p><strong>功能</strong>：允许用户直接以 ETH 打赏。检查打赏金额是否大于 0，然后更新打赏者贡献、总小费金额以及 ETH 货币的小费金额。</p></li></ul><ol start="5"><li><p><code>tipInCurrency</code><strong>函数</strong></p><ul><li><p><strong>代码片段</strong></p></li></ul></li></ol><p><strong>solidity</strong></p><pre data-type="codeBlock" text="function tipInCurrency(string memory _currencyCode, uint256 _amount) public payable {
    require(conversionRates[_currencyCode] &gt; 0, &quot;Currency not supported&quot;);
    require(_amount &gt; 0, &quot;Amount must be greater than 0&quot;);
    uint256 ethAmount = convertToEth(_currencyCode, _amount);
    require(msg.value == ethAmount, &quot;Sent ETH doesn't match the converted amount&quot;);
    tipperContributions[msg.sender] += msg.value;
    totalTipsRecieved += msg.value;
    tipsPerCurrency[_currencyCode] += _amount;
}
"><code><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">tipInCurrency</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> <span class="hljs-keyword">memory</span> _currencyCode, <span class="hljs-keyword">uint256</span> _amount</span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span> <span class="hljs-title"><span class="hljs-keyword">payable</span></span> </span>{
    <span class="hljs-built_in">require</span>(conversionRates[_currencyCode] <span class="hljs-operator">&gt;</span> <span class="hljs-number">0</span>, <span class="hljs-string">"Currency not supported"</span>);
    <span class="hljs-built_in">require</span>(_amount <span class="hljs-operator">&gt;</span> <span class="hljs-number">0</span>, <span class="hljs-string">"Amount must be greater than 0"</span>);
    <span class="hljs-keyword">uint256</span> ethAmount <span class="hljs-operator">=</span> convertToEth(_currencyCode, _amount);
    <span class="hljs-built_in">require</span>(<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">value</span> <span class="hljs-operator">=</span><span class="hljs-operator">=</span> ethAmount, <span class="hljs-string">"Sent ETH doesn't match the converted amount"</span>);
    tipperContributions[<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>] <span class="hljs-operator">+</span><span class="hljs-operator">=</span> <span class="hljs-built_in">msg</span>.<span class="hljs-built_in">value</span>;
    totalTipsRecieved <span class="hljs-operator">+</span><span class="hljs-operator">=</span> <span class="hljs-built_in">msg</span>.<span class="hljs-built_in">value</span>;
    tipsPerCurrency[_currencyCode] <span class="hljs-operator">+</span><span class="hljs-operator">=</span> _amount;
}
</code></pre><ul><li><p><strong>功能</strong>：允许用户以指定货币打赏。先检查货币是否支持及金额是否大于 0，将货币转换为 ETH 后，检查用户发送的 ETH 是否与转换金额匹配，匹配则更新相关数据。</p></li></ul><ol start="6"><li><p><code>withdrawTips</code><strong>函数</strong></p><ul><li><p><strong>代码片段</strong></p></li></ul></li></ol><p><strong>solidity</strong></p><pre data-type="codeBlock" text="function withdrawTips() public onlyOwner {
    uint256 contractBalance = address(this).balance;
    require(contractBalance &gt; 0, &quot;No tips to withdraw&quot;);
    (bool success, ) = payable(owner).call{value: contractBalance}(&quot;&quot;);
    require(success, &quot;Transfer failed&quot;);
    totalTipsRecieved = 0;
}
"><code><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">withdrawTips</span>(<span class="hljs-params"></span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span> <span class="hljs-title">onlyOwner</span> </span>{
    <span class="hljs-keyword">uint256</span> contractBalance <span class="hljs-operator">=</span> <span class="hljs-keyword">address</span>(<span class="hljs-built_in">this</span>).<span class="hljs-built_in">balance</span>;
    <span class="hljs-built_in">require</span>(contractBalance <span class="hljs-operator">&gt;</span> <span class="hljs-number">0</span>, <span class="hljs-string">"No tips to withdraw"</span>);
    (<span class="hljs-keyword">bool</span> success, ) <span class="hljs-operator">=</span> <span class="hljs-keyword">payable</span>(owner).<span class="hljs-built_in">call</span>{<span class="hljs-built_in">value</span>: contractBalance}(<span class="hljs-string">""</span>);
    <span class="hljs-built_in">require</span>(success, <span class="hljs-string">"Transfer failed"</span>);
    totalTipsRecieved <span class="hljs-operator">=</span> <span class="hljs-number">0</span>;
}
</code></pre><ul><li><p><strong>功能</strong>：只有合约所有者能调用，用于提取合约中的所有小费。先检查合约余额是否大于 0，然后尝试将余额转给合约所有者，成功则重置总小费金额为 0。</p></li></ul><ol start="7"><li><p><code>transferOwnership</code><strong>函数</strong></p><ul><li><p><strong>代码片段</strong></p></li></ul></li></ol><p><strong>solidity</strong></p><pre data-type="codeBlock" text="function transferOwnership(address _newOwner) public onlyOwner {
    require(_newOwner != address(0), &quot;Invalid address&quot;);
    owner = _newOwner;
}
"><code><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">transferOwnership</span>(<span class="hljs-params"><span class="hljs-keyword">address</span> _newOwner</span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span> <span class="hljs-title">onlyOwner</span> </span>{
    <span class="hljs-built_in">require</span>(_newOwner <span class="hljs-operator">!</span><span class="hljs-operator">=</span> <span class="hljs-keyword">address</span>(<span class="hljs-number">0</span>), <span class="hljs-string">"Invalid address"</span>);
    owner <span class="hljs-operator">=</span> _newOwner;
}
</code></pre><ul><li><p><strong>功能</strong>：只有合约所有者能调用，用于转移合约所有权到新地址，先检查新地址是否有效，有效则转移。</p></li></ul><ol start="8"><li><p><code>getSupportedCurrencies</code><strong>函数</strong></p><ul><li><p><strong>代码片段</strong></p></li></ul></li></ol><p><strong>solidity</strong></p><pre data-type="codeBlock" text="function getSupportedCurrencies() public view returns (string[] memory) {
    return supportedCurrencies;
}
"><code><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getSupportedCurrencies</span>(<span class="hljs-params"></span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span> <span class="hljs-title"><span class="hljs-keyword">view</span></span> <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params"><span class="hljs-keyword">string</span>[] <span class="hljs-keyword">memory</span></span>) </span>{
    <span class="hljs-keyword">return</span> supportedCurrencies;
}
</code></pre><ul><li><p><strong>功能</strong>：返回支持的货币列表。</p></li></ul><ol start="9"><li><p><code>getContractBalance</code><strong>函数</strong></p><ul><li><p><strong>代码片段</strong></p></li></ul></li></ol><p><strong>solidity</strong></p><pre data-type="codeBlock" text="function getContractBalance() public view returns (uint256) {
    return address(this).balance;
}
"><code><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getContractBalance</span>(<span class="hljs-params"></span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span> <span class="hljs-title"><span class="hljs-keyword">view</span></span> <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params"><span class="hljs-keyword">uint256</span></span>) </span>{
    <span class="hljs-keyword">return</span> <span class="hljs-keyword">address</span>(<span class="hljs-built_in">this</span>).<span class="hljs-built_in">balance</span>;
}
</code></pre><ul><li><p><strong>功能</strong>：返回合约当前的 ETH 余额。</p></li></ul><ol start="10"><li><p><code>getTipperContribution</code><strong>函数</strong></p><ul><li><p><strong>代码片段</strong></p></li></ul></li></ol><p><strong>solidity</strong></p><pre data-type="codeBlock" text="function getTipperContribution(address _tipper) public view returns (uint256) {
    return tipperContributions[_tipper];
}
"><code><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getTipperContribution</span>(<span class="hljs-params"><span class="hljs-keyword">address</span> _tipper</span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span> <span class="hljs-title"><span class="hljs-keyword">view</span></span> <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params"><span class="hljs-keyword">uint256</span></span>) </span>{
    <span class="hljs-keyword">return</span> tipperContributions[_tipper];
}
</code></pre><ul><li><p><strong>功能</strong>：返回指定打赏者的贡献金额。</p></li></ul><ol start="11"><li><p><code>getTipsInCurrency</code><strong>函数</strong></p><ul><li><p><strong>代码片段</strong></p></li></ul></li></ol><p><strong>solidity</strong></p><pre data-type="codeBlock" text="function getTipsInCurrency(string memory _currencyCode) public view returns (uint256) {
    return tipsPerCurrency[_currencyCode];
}
"><code><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getTipsInCurrency</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> <span class="hljs-keyword">memory</span> _currencyCode</span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span> <span class="hljs-title"><span class="hljs-keyword">view</span></span> <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params"><span class="hljs-keyword">uint256</span></span>) </span>{
    <span class="hljs-keyword">return</span> tipsPerCurrency[_currencyCode];
}
</code></pre><ul><li><p><strong>功能</strong>：返回指定货币的小费金额。</p></li></ul><ol start="12"><li><p><code>getConversionRate</code><strong>函数</strong></p><ul><li><p><strong>代码片段</strong></p></li></ul></li></ol><p><strong>solidity</strong></p><pre data-type="codeBlock" text="function getConversionRate(string memory _currencyCode) public view returns (uint256) {
    require(conversionRates[_currencyCode] &gt; 0, &quot;Currency not supported&quot;);
    return conversionRates[_currencyCode];
}
"><code><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getConversionRate</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> <span class="hljs-keyword">memory</span> _currencyCode</span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span> <span class="hljs-title"><span class="hljs-keyword">view</span></span> <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params"><span class="hljs-keyword">uint256</span></span>) </span>{
    <span class="hljs-built_in">require</span>(conversionRates[_currencyCode] <span class="hljs-operator">&gt;</span> <span class="hljs-number">0</span>, <span class="hljs-string">"Currency not supported"</span>);
    <span class="hljs-keyword">return</span> conversionRates[_currencyCode];
}
</code></pre><ul><li><p><strong>功能</strong>：返回指定货币的兑换汇率，先检查货币是否支持。</p></li></ul><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0"><strong>五、个人学习心得</strong></h2><p>通过学习<code>TipJar</code>合约，深入了解了 Solidity 在实现复杂业务逻辑方面的能力。状态变量、修饰器和各类函数的协同工作，实现了一个功能丰富的小费管理系统。修饰器<code>onlyOwner</code>很好地控制了特定操作的访问权限，保证合约的安全性。不同的函数针对货币管理、打赏、提现等不同场景设计，每个函数都有严谨的条件检查，确保操作的正确性。在实际应用中，这样的合约可用于各种需要接受多种货币打赏的场景，如内容创作平台等。对合约的深入剖析也让我对 Solidity 中的数据类型、映射、数组以及函数调用和参数传递等有了更清晰的认识，为进一步开发复杂的区块链应用奠定基础。</p>]]></content:encoded>
            <author>earnest_stus@newsletter.paragraph.com (Earnest_stu)</author>
        </item>
        <item>
            <title><![CDATA[Solidity智能合约学习笔记：简单计数器与信息存储合约]]></title>
            <link>https://paragraph.com/@Earnest_stus/solidity智能合约学习笔记：简单计数器与信息存储合约</link>
            <guid>m4VvawObYFDH9yggwrV3</guid>
            <pubDate>Wed, 15 Oct 2025 04:46:35 GMT</pubDate>
            <description><![CDATA[一、合约基础设定许可证标识符：// SPDX - License - Identifier: MIT，表明该合约遵循 MIT 许可证，这种许可证较为宽松，允许代码被广泛使用、修改和分发。Solidity 版本：pragma solidity ^0.8.0;，指定合约适用的 Solidity 版本为 0.8.0 及以上版本。这样的版本指定能确保合约使用该版本及其后续兼容版本的特性与功能，同时避免因版本差异导致的语法错误或不兼容问题。二、ClickCounter 合约代码片段soliditycontract ClickCounter { uint256 public counter; function click() public { counter++; } function reset() public { counter = 0; } function decrement() public { counter--; } } 状态变量uint256 public counter;，声明了一个无符号 256 位整数类型的公共状态变量counter，用于记录点击次数。将其设置为pub...]]></description>
            <content:encoded><![CDATA[<h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0"><strong>一、合约基础设定</strong></h2><ol><li><p><strong>许可证标识符</strong>：<code>// SPDX - License - Identifier: MIT</code>，表明该合约遵循 MIT 许可证，这种许可证较为宽松，允许代码被广泛使用、修改和分发。</p></li><li><p><strong>Solidity 版本</strong>：<code>pragma solidity ^0.8.0;</code>，指定合约适用的 Solidity 版本为 0.8.0 及以上版本。这样的版本指定能确保合约使用该版本及其后续兼容版本的特性与功能，同时避免因版本差异导致的语法错误或不兼容问题。</p></li></ol><h2 id="h-clickcounter" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0"><strong>二、ClickCounter 合约</strong></h2><ol><li><p><strong>代码片段</strong></p></li></ol><p><strong>solidity</strong></p><pre data-type="codeBlock" text="contract ClickCounter {
    uint256 public counter;

    function click() public {
        counter++;
    }

    function reset() public {
        counter = 0;
    }

    function decrement() public {
        counter--;
    }
}
"><code><span class="hljs-class"><span class="hljs-keyword">contract</span> <span class="hljs-title">ClickCounter</span> </span>{
    <span class="hljs-keyword">uint256</span> <span class="hljs-keyword">public</span> counter;

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">click</span>(<span class="hljs-params"></span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span> </span>{
        counter<span class="hljs-operator">+</span><span class="hljs-operator">+</span>;
    }

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">reset</span>(<span class="hljs-params"></span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span> </span>{
        counter <span class="hljs-operator">=</span> <span class="hljs-number">0</span>;
    }

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">decrement</span>(<span class="hljs-params"></span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span> </span>{
        counter<span class="hljs-operator">-</span><span class="hljs-operator">-</span>;
    }
}
</code></pre><ol start="2"><li><p><strong>状态变量</strong></p><ul><li><p><code>uint256 public counter;</code>，声明了一个无符号 256 位整数类型的公共状态变量<code>counter</code>，用于记录点击次数。将其设置为<code>public</code>，意味着可以从合约外部访问该变量的值，方便查看点击计数。</p></li></ul></li><li><p><strong>函数</strong></p><ul><li><p><code>click</code><strong>函数</strong>：<code>function click() public { counter++; }</code>，该函数为公共函数，被调用时会使<code>counter</code>变量自增 1 ，实现点击计数功能。简单直接地体现了 Solidity 中函数对状态变量的修改操作。</p></li><li><p><code>reset</code><strong>函数</strong>：<code>function reset() public { counter = 0; }</code>，此公共函数将<code>counter</code>变量重置为 0。这展示了合约中对状态变量进行重置的常见操作，方便重新开始计数。</p></li><li><p><code>decrement</code><strong>函数</strong>：<code>function decrement() public { counter--; }</code>，公共函数，调用时会使<code>counter</code>变量自减 1 。让我们看到了不仅可以对计数器进行增加操作，还能进行减少操作。</p></li></ul></li></ol><p><strong>个人学习心得</strong>：通过<code>ClickCounter</code>合约，我深刻体会到 Solidity 在状态变量和函数设计上的简洁性与直观性。状态变量作为合约数据存储的核心，与函数紧密配合，实现了简单但实用的功能。这里函数对状态变量的修改操作让我明白了合约内部数据的动态变化机制，同时<code>public</code>关键字的使用让我认识到合约与外部交互的接口设计方式。</p><h2 id="h-savemyinformation" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0"><strong>三、SaveMyInformation 合约</strong></h2><ol><li><p><strong>代码片段</strong></p></li></ol><p><strong>solidity</strong></p><pre data-type="codeBlock" text="contract SaveMyInformation {
    string name;
    string bio;
    string age;
    string job;

    function add(string memory _name,string memory _bio,string memory _age,string memory _job) public{
        name = _name;
        bio = _bio;
        age = _age;
        job = _job;
    }
    function retrieve() public view returns (string memory,string memory,string memory,string memory){
        return (name,bio,age,job);
    }
}
"><code><span class="hljs-class"><span class="hljs-keyword">contract</span> <span class="hljs-title">SaveMyInformation</span> </span>{
    <span class="hljs-keyword">string</span> name;
    <span class="hljs-keyword">string</span> bio;
    <span class="hljs-keyword">string</span> age;
    <span class="hljs-keyword">string</span> job;

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">add</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> <span class="hljs-keyword">memory</span> _name,<span class="hljs-keyword">string</span> <span class="hljs-keyword">memory</span> _bio,<span class="hljs-keyword">string</span> <span class="hljs-keyword">memory</span> _age,<span class="hljs-keyword">string</span> <span class="hljs-keyword">memory</span> _job</span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span></span>{
        name <span class="hljs-operator">=</span> _name;
        bio <span class="hljs-operator">=</span> _bio;
        age <span class="hljs-operator">=</span> _age;
        job <span class="hljs-operator">=</span> _job;
    }
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">retrieve</span>(<span class="hljs-params"></span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span> <span class="hljs-title"><span class="hljs-keyword">view</span></span> <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params"><span class="hljs-keyword">string</span> <span class="hljs-keyword">memory</span>,<span class="hljs-keyword">string</span> <span class="hljs-keyword">memory</span>,<span class="hljs-keyword">string</span> <span class="hljs-keyword">memory</span>,<span class="hljs-keyword">string</span> <span class="hljs-keyword">memory</span></span>)</span>{
        <span class="hljs-keyword">return</span> (name,bio,age,job);
    }
}
</code></pre><ol start="2"><li><p><strong>状态变量</strong></p><ul><li><p><code>string name;</code>、<code>string bio;</code>、<code>string age;</code>、<code>string job;</code>，分别声明了用于存储姓名、个人简介、年龄和工作的字符串类型状态变量。这些变量用于持久化相关信息。</p></li></ul></li><li><p><strong>函数</strong></p><ul><li><p><code>add</code><strong>函数</strong>：<code>function add(string memory _name,string memory _bio,string memory _age,string memory _job) public{ name = _name; bio = _bio; age = _age; job = _job; }</code>，此公共函数接受四个字符串参数，将其分别赋值给对应的状态变量，实现信息的添加。这体现了 Solidity 中如何通过函数传递参数并更新状态变量，是数据写入合约的关键步骤。</p></li><li><p><code>retrieve</code><strong>函数</strong>：<code>function retrieve() public view returns (string memory,string memory,string memory,string memory){ return (name,bio,age,job); }</code>，该公共视图函数不修改状态，返回存储的姓名、个人简介、年龄和工作信息。这展示了 Solidity 中如何设计只读函数来获取合约内部存储的数据，为外部访问合约数据提供了安全的方式。</p></li></ul></li></ol><p><strong>个人学习心得</strong>：<code>SaveMyInformation</code>合约进一步拓展了我对 Solidity 的理解。它展示了如何处理复杂数据类型（字符串）的存储与读取。<code>add</code>函数和<code>retrieve</code>函数的设计让我清晰地认识到合约中数据的输入与输出机制。特别是<code>view</code>关键字在<code>retrieve</code>函数中的使用，强调了 Solidity 对于状态变量只读操作的保护机制，这对于确保合约数据完整性非常重要。同时，通过这两个合约的学习，我感受到了 Solidity 在构建不同功能智能合约方面的灵活性与强大能力。</p><br>]]></content:encoded>
            <author>earnest_stus@newsletter.paragraph.com (Earnest_stu)</author>
        </item>
    </channel>
</rss>