{"content":{"title":"使用Foundry用Solidity编写一个Stake质押的项目","body":"在foundry中用Solidity编写一个质押挖矿的项目，实现如下功能：\r\n\r\n1. 用户随时可以质押项目方代币 RNT(自定义的ERC20+ERC2612) ，开始赚取项目方Token(esRNT)；\r\n2. 可随时解押提取已质押的 RNT；\r\n3. 可随时领取esRNT奖励，每质押1个RNT每天可奖励 1 esRNT;\r\n4. esRNT 是锁仓性的 RNT， 1 esRNT 在 30 天后可兑换 1 RNT，随时间线性释放，支持提前将 esRNT 兑换成 RNT，但锁定部分将被 burn 燃烧掉。\r\n\r\n我们需要三个合约：\r\n\r\n1、RNT合约：这是一个ERC20的代币，具有ERC2612标准，要授权给stake（uint）、stake（uint、permit）\r\n\r\n2、stakePool合约：包含stake、unstake、claim的函数\r\n\r\n代码包含：\r\n\r\n```\r\nstruct  stakeInfo{\r\n\tstaked\r\n  unClaimd\r\n  lastUpdateTime\r\n}\r\nmapping(address=>stakeInfo)\r\nstakePool.claim{\r\n//RNT.approve(esRNT,MAX RNT)\r\n\tesRNT.mint(alice){\r\n\t\ttransferFrom(msg.sender,address(this),RNT)\r\n\t\t_mint(alice,)\r\n\t\tlocks.push(...)\r\n\t}\r\n}\r\n```\r\n\r\n3、esRNT合约：包含mint、burn函数\r\n\r\n```\r\nesRNT{\r\n\tstruct LockInfo{\r\n\t\taddress user\r\n\t\tuint256 amount\r\n\t\tuint256 lockTime\r\n\t}\r\n\tLockInfo[] locks;\r\n}\r\n\r\nesRNT.burn(uint256 id){\r\n\tunlocked=amount*(now-lockTime)/30days\r\n\tRNT.transfer(user,unlocked);\r\n\tRNT.transfer(0x000000000,amount-unlocked);\r\n}\r\n```\r\n\r\n下面是一份详细的操作文档，包含各个合约部署代码、质押合约的测试代码，本项目包含三个智能合约：RNT 合约、esRNT 合约和 stakePool 合约。\r\n\r\n### 1. 安装 Foundry\r\n\r\n首先，确保你已经安装了 Foundry，工具的安装使用，请参考官网的官方文档：[https://getfoundry.sh](https://getfoundry.sh/)\r\n\r\n```bash\r\ncurl -L https://foundry.paradigm.xyz | bash\r\nfoundryup\r\n```\r\n\r\n### 2. 创建项目\r\n\r\n创建一个新的 Foundry 项目：\r\n\r\n```bash\r\nforge init stakeRNT\r\ncd stakeRNT\r\n```\r\n\r\n### 3. 添加依赖\r\n\r\n如果需要使用 OpenZeppelin 库，可以添加依赖：\r\n\r\n```bash\r\nforge install OpenZeppelin/openzeppelin-contracts\r\n```\r\n\r\n### 1. RNT 合约\r\n\r\n在src文件夹中新建一个RNT.sol 合约，这是一个ERC20代币合约，具有 ERC2612 标准（允许通过签名进行许可）。我们将使用 OpenZeppelin 的库来实现这一点。\r\n\r\n```\r\n// SPDX-License-Identifier: MIT\r\npragma solidity ^0.8.0;\r\n\r\nimport \"@openzeppelin/contracts/token/ERC20/ERC20.sol\";\r\nimport \"@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol\";\r\nimport \"@openzeppelin/contracts/access/Ownable.sol\";\r\n\r\ncontract RNT is ERC20 , ERC20Permit, Ownable{\r\n    constructor() ERC20(\"Reward Token\", \"RNT\")ERC20Permit(\"Reward Token\")Ownable(msg.sender){\r\n        _mint(msg.sender, 1000000 * 10 ** decimals());\r\n    }\r\n}\r\n```\r\n\r\n编译这个合约并部署\r\n\r\n```\r\nforge build src/RNT.sol\r\n```\r\n\r\n编译成功输出：\r\n\r\n```\r\nyhb@yhbdeMacBook-Air stakeRNT % forge build src/RNT.sol\r\n[⠊] Compiling...\r\n[⠰] Compiling 19 files with Solc 0.8.25\r\n[⠔] Solc 0.8.25 finished in 325.31ms\r\nCompiler run successful!\r\n```\r\n\r\n编写部署文件DeployRNT.s.sol\r\n\r\n```\r\n// SPDX-License-Identifier: MIT\r\npragma solidity ^0.8.0;\r\n\r\nimport \"forge-std/Script.sol\";\r\nimport \"../src/RNT.sol\";\r\n\r\ncontract DeployRNT is Script {\r\n    function setUp() public {}\r\n\r\n    function run() public {\r\n        vm.startBroadcast();\r\n\r\n        // 部署 RNT 合约\r\n        RNT rnt = new RNT();\r\n\r\n        vm.stopBroadcast();\r\n    }\r\n}\r\n\r\n```\r\n\r\n新建.env文件，写入命令参数，运行部署命令\r\n\r\n```\r\nsource .env\r\n\r\nforge script script/DeployRNT.s.sol --rpc-url ${RPC_URL} --broadcast --private-key ${PRIVATE_KEY} \r\n```\r\n\r\n部署结果：\r\n\r\n```\r\nyhb@yhbdeMacBook-Air stakeRNT % forge build\r\n[⠊] Compiling...\r\n[⠔] Compiling 47 files with Solc 0.8.25\r\n[⠒] Solc 0.8.25 finished in 1.50s\r\nCompiler run successful!\r\nyhb@yhbdeMacBook-Air stakeRNT % forge script script/DeployRNT.s.sol --rpc-url ${RPC_URL} --broadcast --private-key ${PRIVATE_KEY} \r\n[⠊] Compiling...\r\nNo files changed, compilation skipped\r\nScript ran successfully.\r\n\r\n== Logs ==\r\n  RNT deployed to: 0x2B2351b254DD6E0b292edb27f153D09a61359f46\r\n\r\n## Setting up 1 EVM.\r\n\r\n==========================\r\n\r\nChain 11155111\r\n\r\nEstimated gas price: 69.247670162 gwei\r\n\r\nEstimated total gas used for script: 1420953\r\n\r\nEstimated amount required: 0.098397684659704386 ETH\r\n\r\n==========================\r\n\r\n##### sepolia\r\n✅  [Success]Hash: 0x1c31172d50bf64dfb59a43cb93913c6333499fd550edc1859be4f74f19f188ba\r\nContract Address: 0x2B2351b254DD6E0b292edb27f153D09a61359f46\r\nBlock: 6366789\r\nPaid: 0.039294677855108214 ETH (1093419 gas * 35.937438306 gwei)\r\n\r\n✅ Sequence #1 on sepolia | Total Paid: 0.039294677855108214 ETH (1093419 gas * avg 35.937438306 gwei)\r\n                                                                                                                                        \r\n\r\n==========================\r\n\r\nONCHAIN EXECUTION COMPLETE & SUCCESSFUL.\r\n\r\nTransactions saved to: /Users/yhb/stakeRNT/broadcast/DeployRNT.s.sol/11155111/run-latest.json\r\n\r\nSensitive values saved to: /Users/yhb/stakeRNT/cache/DeployRNT.s.sol/11155111/run-latest.json\r\n\r\n```\r\n\r\n\r\n\r\n### 2. esRNT 合约\r\n\r\nesRNT.sol 合约是一个锁仓性的 RNT 合约，包含 mint 和 burn 函数。它会记录每个锁仓的记录，并支持线性释放。\r\n\r\n1、传进来的代币和要生成的代币之间是什么关系？\r\n\r\n2、传进来怎么产生锁仓与解锁的关系？\r\n\r\n用户锁仓就要开始挖币，同时的。我们要定义一个locks的集合，集合里有结构体记录锁仓的信息，锁仓的之后要对锁仓的顺序进行记录，触发事件，然后在上面对触发的事件进行定义。\r\n\r\n函数从设置各种可能的限定条件、 写参数，定义参数，到实现业务逻辑的行动，到触发行动，到记录行动的事件，是按照合约开发的逻辑进行。 \r\n\r\n在这个质押挖矿项目中，`RNT` 和 `esRNT` 合约在技术上有以下关系和作用：\r\n\r\n### RNT 合约\r\n\r\n- **类型**：标准 ERC20 代币合约，具有 ERC2612 标准（允许通过签名进行许可）。\r\n\r\n- **作用**：`RNT` 是项目方的基础代币，用户可以质押 `RNT` 来赚取 `esRNT` 。\r\n\r\n- 功能\r\n\r\n  ：\r\n\r\n  - `ERC20`：实现标准的 ERC20 功能，如转账、查询余额等。\r\n  - `ERC20Permit`：允许通过签名进行许可的功能。\r\n  - `Ownable`：添加了所有者的管理功能。\r\n\r\n### esRNT 合约\r\n\r\n- **类型**：扩展的 ERC20 合约，用于锁仓 `RNT`。\r\n- **作用**：`esRNT` 是锁仓性的 `RNT`，1 个 `esRNT` 在 30 天后可兑换 1 个 `RNT`，支持线性释放。\r\n- 功能：\r\n  - 锁仓功能：`esRNT` 代币代表锁仓的 `RNT`，并且需要记录每个锁仓的用户、数量和锁仓时间。\r\n  - `mint`：项目方可以铸造 `esRNT`，用于奖励用户。\r\n  - `burn`：用户可以将 `esRNT` 兑换回 `RNT`，支持线性释放和提前赎回。\r\n\r\n### 技术关系\r\n\r\n1. **合约之间的依赖**：\r\n   - `esRNT` 合约需要知道 `RNT` 合约的地址，以便在 `burn` 时将锁仓的 `RNT` 返还给用户。\r\n   - `StakePool` 合约在用户质押 `RNT` 时，会记录质押信息，并在用户领取奖励时铸造 `esRNT`。\r\n2. **交互流程**：\r\n   - 用户质押 `RNT` 到 `StakePool` 合约，合约记录质押信息。\r\n   - 用户可以随时领取 `esRNT` 奖励，`StakePool` 合约调用 `esRNT` 合约的 `mint` 函数铸造奖励。\r\n   - 用户可以在 30 天后通过 `esRNT` 合约的 `burn` 函数，将 `esRNT` 兑换回 `RNT`。\r\n   - 提前赎回时，未解锁的 `RNT` 将被销毁（burn）。\r\n\r\n```\r\n// SPDX-License-Identifier: MIT\r\npragma solidity ^0.8.0;\r\n\r\nimport \"@openzeppelin/contracts/token/ERC20/ERC20.sol\";\r\nimport \"@openzeppelin/contracts/access/Ownable.sol\";\r\n\r\ncontract esRNT is ERC20, Ownable {\r\n    struct LockInfo {\r\n        address user;\r\n        uint256 amount;\r\n        uint256 lockTime;\r\n    }\r\n    LockInfo[] public locks;\r\n    IERC20 public rntToken;\r\n    event Minted(address indexed _user, uint256 _amount, uint256 lockId);\r\n    constructor(IERC20 _rntToken) ERC20(\"Escrowed Reward Token\", \"esRNT\") Ownable(msg.sender){\r\n        rntToken = _rntToken;\r\n        _transferOwnership(msg.sender);  \r\n    }\r\n\r\n    function mint(address to, uint256 amount) public onlyOwner {\r\n        _mint(to, amount);\r\n        locks.push(LockInfo({\r\n            user: to,\r\n            amount: amount,\r\n            lockTime: block.timestamp\r\n        }));\r\n        uint256 lockId = locks.length - 1;\r\n\r\n        emit Minted( to, amount, lockId);\r\n    }\r\n\r\n    function burn(uint256 lockId) public {\r\n        require(lockId<locks.length, \"Invalid lockId\");\r\n        LockInfo storage lock = locks[lockId];\r\n        require(lock.user == msg.sender, \"Not the owner of the lock\");\r\n\r\n        uint256 unlocked = (lock.amount*(block.timestamp - lock.lockTime))/30 days;\r\n\r\n        uint256 burnAmount= lock.amount-unlocked;\r\n\r\n        _burn(msg.sender, lock.amount);\r\n        rntToken.transfer(msg.sender, unlocked);\r\n        rntToken.transfer(address(0), burnAmount);\r\n    }\r\n    function getLocksByUser(address user) external view returns (LockInfo[] memory) {\r\n        uint256 count = 0;\r\n        for (uint256 i = 0; i < locks.length; i++) {\r\n            if (locks[i].user == user) {\r\n                count++;\r\n            }\r\n        }\r\n\r\n        LockInfo[] memory userLocks = new LockInfo[](count);\r\n        uint256 index = 0;\r\n        for (uint256 i = 0; i < locks.length; i++) {\r\n            if (locks[i].user == user) {\r\n                userLocks[index] = locks[i];\r\n                index++;\r\n            }\r\n        }\r\n        return userLocks;\r\n    }\r\n}\r\n```\r\n\r\n通过 Foundry 部署 `esRNT` 合约，并且传入 `RNT` 合约的地址，你需要编写一个部署脚本DeployEsRNT.s.sol来实现。\r\n\r\n```\r\n// SPDX-License-Identifier: MIT\r\npragma solidity ^0.8.0;\r\n\r\nimport \"forge-std/Script.sol\";\r\nimport \"../src/esRNT.sol\";\r\nimport \"../src/RNT.sol\";  \r\n\r\ncontract DeployEsRNT is Script {\r\n    function run() external {\r\n\r\n        address rntAddress=vm.envAddress(\"RNT_ADDRESS\");\r\n\r\n        vm.startBroadcast();\r\n        esRNT esrnt = new esRNT(IERC20(rntAddress));\r\n        vm.stopBroadcast();\r\n        console.log(\"esRNT deployed at:\", address(esrnt));\r\n    }\r\n}\r\n```\r\n\r\n在 `.env` 文件，并在其中定义 `RNT_ADDRESS`，这是你之前部署的 `RNT` 合约的地址。\r\n\r\n```\r\nRNT_ADDRESS=<your_RNT_contract_address>\r\n```\r\n\r\n部署命令\r\n\r\n```\r\nforge script script/DeployEsRNT.s.sol --broadcast --rpc-url ${RPC_URL} --private-key ${PRIVATE_KEY} \r\n```\r\n\r\n部署结果为：\r\n\r\n```\r\nyhb@yhbdeMacBook-Air stakeRNT % forge script script/DeployEsRNT.s.sol --broadcast --rpc-url ${RPC_URL} --private-key ${PRIVATE_KEY} \r\n[⠊] Compiling...\r\n[⠆] Compiling 1 files with Solc 0.8.25\r\n[⠰] Solc 0.8.25 finished in 1.26s\r\nCompiler run successful!\r\nScript ran successfully.\r\n\r\n== Logs ==\r\n  esRNT deployed at: 0x7707dD2506128E330C45978AbA59DAA09bd353F4\r\n\r\n## Setting up 1 EVM.\r\n\r\n==========================\r\n\r\nChain 11155111\r\n\r\nEstimated gas price: 64.007213099 gwei\r\n\r\nEstimated total gas used for script: 1361939\r\n\r\nEstimated amount required: 0.087173919800838961 ETH\r\n\r\n==========================\r\n\r\n##### sepolia\r\n✅  [Success]Hash: 0x26ca9d0f46c56fb38f9b092ffef67307c89a84341704bb63d94514d19486e17f\r\nContract Address: 0x7707dD2506128E330C45978AbA59DAA09bd353F4\r\nBlock: 6367587\r\nPaid: 0.036420640417340484 ETH (1047954 gas * 34.754044946 gwei)\r\n\r\n✅ Sequence #1 on sepolia | Total Paid: 0.036420640417340484 ETH (1047954 gas * avg 34.754044946 gwei)\r\n                                                                                                                                        \r\n\r\n==========================\r\n\r\nONCHAIN EXECUTION COMPLETE & SUCCESSFUL.\r\n\r\nTransactions saved to: /Users/yhb/stakeRNT/broadcast/DeployEsRNT.s.sol/11155111/run-latest.json\r\n\r\nSensitive values saved to: /Users/yhb/stakeRNT/cache/DeployEsRNT.s.sol/11155111/run-latest.json\r\n\r\n```\r\n\r\n### 3. stakePool 合约\r\n\r\n新建一个StakePool.sol 合约包含 stake、unstake 和 claim 的函数，用于管理用户的质押和奖励。这个合约部署时要传入两个部署好的代币合约地址，用户质押之前要统计更新一下奖励，质押的记录需要一个结构体，质押数量，更新时间都要记录。\r\n\r\n```\r\n// SPDX-License-Identifier: MIT\r\npragma solidity ^0.8.0;\r\n\r\nimport \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\r\nimport \"@openzeppelin/contracts/access/Ownable.sol\";\r\nimport \"./RNT.sol\";\r\nimport \"./esRNT.sol\";\r\n\r\ncontract StakePool is Ownable {\r\n    IERC20 public rntToken;\r\n    esRNT public esrntToken;\r\n    mapping(address => StakeInfo) public stakes;\r\n    uint256 public rewardRate=1 ether;\r\n\r\n    struct StakeInfo {\r\n        uint256 staked;\r\n        uint256 unclaimed;\r\n        uint256 lastUpdateTime;\r\n    }\r\n\r\n    constructor(IERC20 _rntToken,esRNT _esrntToken) Ownable(msg.sender) {\r\n        rntToken = _rntToken;\r\n        esrntToken=_esrntToken;\r\n    }\r\n\r\n\r\n    function stake(uint256 amount) external {\r\n        updateReward(msg.sender);\r\n        require(amount > 0, \"Amount must be greater than 0\");\r\n        rntToken.transferFrom(msg.sender, address(this), amount);\r\n        stakes[msg.sender].staked += amount;\r\n    }\r\n\r\n    function unstake(uint256 amount) external {\r\n        updateReward(msg.sender);\r\n        require(amount > 0, \"Amount must be greater than 0\");\r\n        require(stakes[msg.sender].staked >= amount, \"Insufficient balance\");\r\n        stakes[msg.sender].staked -= amount;\r\n        rntToken.transfer(msg.sender, amount);\r\n    }\r\n\r\n    function claim() external {\r\n        updateReward(msg.sender);\r\n        uint256 reward = stakes[msg.sender].unclaimed;\r\n        stakes[msg.sender].unclaimed = 0;\r\n        esrntToken.transfer(msg.sender, reward);\r\n    }\r\n\r\n\r\n    function updateReward(address account) internal {\r\n\r\n        StakeInfo storage stakeInfo = stakes[account];\r\n        if (stakeInfo.lastUpdateTime > 0) {\r\n            uint256 timeStaked = block.timestamp - stakeInfo.lastUpdateTime;\r\n            stakeInfo.unclaimed += (timeStaked * stakeInfo.staked * rewardRate) / 1 days;\r\n        }\r\n        stakeInfo.lastUpdateTime = block.timestamp;\r\n    }\r\n}\r\n```\r\n\r\n### 质押合约的测试代码\r\n\r\n新建一个测试合约StakePool.t.sol：\r\n\r\n```\r\n// SPDX-License-Identifier: MIT\r\npragma solidity ^0.8.0;\r\n\r\nimport \"forge-std/Test.sol\";\r\nimport \"../src/StakePool.sol\";\r\nimport \"../src/RNT.sol\";\r\nimport \"../src/esRNT.sol\";\r\n\r\ncontract StakePoolTest is Test {\r\n    RNT rntToken;\r\n    esRNT esrntToken;\r\n    StakePool stakePool;\r\n    address owner;\r\n    address user1;\r\n\r\n    function setUp() public {\r\n        owner = address(this);\r\n        user1 = address(0x1);\r\n\r\n        // 部署 RNT 和 esRNT 合约\r\n        rntToken = new RNT();\r\n        esrntToken = new esRNT(IERC20(address(rntToken)));\r\n\r\n        // 部署 StakePool 合约\r\n        stakePool = new StakePool(IERC20(address(rntToken)), esrntToken);\r\n\r\n        // 将一些 RNT 分配给用户\r\n        rntToken.transfer(user1, 1000 ether);\r\n    }\r\n\r\n    function testStake() public {\r\n        vm.startPrank(user1);\r\n\r\n        // 用户授权 StakePool 合约可以花费 RNT\r\n        rntToken.approve(address(stakePool), 1000 ether);\r\n\r\n        // 用户质押 100 RNT\r\n        stakePool.stake(100 ether);\r\n\r\n        // 检查质押结果\r\n        (uint256 staked,,) = stakePool.stakes(user1);\r\n        assertEq(staked, 100 ether);\r\n        assertEq(rntToken.balanceOf(user1), 900 ether);\r\n\r\n        vm.stopPrank();\r\n    }\r\n\r\n    function testUnstake() public {\r\n        vm.startPrank(user1);\r\n\r\n        // 用户授权 StakePool 合约可以花费 RNT\r\n        rntToken.approve(address(stakePool), 1000 ether);\r\n\r\n        // 用户质押 100 RNT\r\n        stakePool.stake(100 ether);\r\n\r\n        // 用户取消质押 50 RNT\r\n        stakePool.unstake(50 ether);\r\n\r\n        // 检查取消质押结果\r\n        (uint256 staked,,) = stakePool.stakes(user1);\r\n        assertEq(staked, 50 ether);\r\n        assertEq(rntToken.balanceOf(user1), 950 ether);\r\n\r\n        vm.stopPrank();\r\n    }\r\n\r\n    // function testClaim() public {\r\n    //     vm.startPrank(user1);\r\n\r\n    //     // 用户授权 StakePool 合约可以花费 RNT\r\n    //     rntToken.approve(address(stakePool), 1000 ether);\r\n\r\n    //     // 用户质押 100 RNT\r\n    //     stakePool.stake(100 ether);\r\n\r\n    //     // 快进时间30天，获取奖励\r\n    //     vm.warp(block.timestamp + 30 days);\r\n\r\n    //     // 用户领取奖励\r\n    //     stakePool.claim();\r\n\r\n    //     // 检查领取结果\r\n    //     assertEq(esrntToken.balanceOf(user1), 100 ether);\r\n\r\n    //     vm.stopPrank();\r\n    // }\r\n}\r\n\r\n```\r\n\r\n输出测试结果为：\r\n\r\n```\r\nyhb@yhbdeMacBook-Air stakeRNT % forge test --mp test/StakePool.t.sol\r\n[⠊] Compiling...\r\n[⠔] Compiling 1 files with Solc 0.8.25\r\n[⠒] Solc 0.8.25 finished in 1.50s\r\nCompiler run successful!\r\n\r\nRan 2 tests for test/StakePool.t.sol:StakePoolTest\r\n[PASS] testStake() (gas: 124019)\r\n[PASS] testUnstake() (gas: 132477)\r\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 8.98ms (4.65ms CPU time)\r\n\r\nRan 1 test suite in 350.29ms (8.98ms CPU time): 2 tests passed, 0 failed, 0 skipped (2 total tests)\r\n```\r\n\r\n### 部署步骤\r\n\r\n1. **部署 StakePool 合约**\r\n   - 选择 `StakePool` 合约，传入 RNT 和 esRNT 合约的地址并部署。\r\n\r\n通过以下方式部署 `DeployStakePool.s.sol` 合约。\r\n\r\n```\r\n// SPDX-License-Identifier: MIT\r\npragma solidity ^0.8.0;\r\n\r\nimport \"forge-std/Script.sol\";\r\nimport \"../contracts/StakePool.sol\";\r\nimport \"../contracts/RNT.sol\";\r\nimport \"../contracts/esRNT.sol\";\r\n\r\ncontract DeployStakePool is Script {\r\n    function run() external {\r\n        address rntAddress = vm.envAddress(\"RNT_ADDRESS\");\r\n        address esrntAddress = vm.envAddress(\"ESRNT_ADDRESS\");\r\n\r\n        vm.startBroadcast();\r\n\r\n        StakePool stakePool = new StakePool(IERC20(rntAddress), esRNT(esrntAddress));\r\n\r\n        vm.stopBroadcast();\r\n\r\n        console.log(\"StakePool deployed at:\", address(stakePool));\r\n    }\r\n}\r\n\r\n```\r\n\r\n一个 `.env` 文件，并在其中定义 `RNT_ADDRESS` 和 `ESRNT_ADDRESS`。\r\n\r\n```\r\nRNT_ADDRESS=<your_RNT_contract_address>\r\nESRNT_ADDRESS=<your_esRNT_contract_address>\r\n```\r\n\r\n1. **运行部署脚本**：\r\n\r\n```\r\nforge script script/DeployStakePool.s.sol --broadcast --rpc-url  ${RPC_URL} --private-key ${PRIVATE_KEY} \r\n```\r\n\r\n替换 `<your_rpc_url>` 和 `<your_private_key>` 为你自己的 RPC URL 和私钥。\r\n\r\n输出结果为：\r\n\r\n```\r\nyhb@yhbdeMacBook-Air stakeRNT % forge script script/DeployStakePool.s.sol --broadcast --rpc-url  ${RPC_URL} --private-key ${PRIVATE_KEY} \r\n[⠊] Compiling...\r\n[⠰] Compiling 1 files with Solc 0.8.25\r\n[⠔] Solc 0.8.25 finished in 1.32s\r\nCompiler run successful!\r\nScript ran successfully.\r\n\r\n== Logs ==\r\n  StakePool deployed at: 0x1014F47eF26807EAA4ef9ae8d87F7A8be7B96aA3\r\n\r\n## Setting up 1 EVM.\r\n\r\n==========================\r\n\r\nChain 11155111\r\n\r\nEstimated gas price: 68.409996813 gwei\r\n\r\nEstimated total gas used for script: 677285\r\n\r\nEstimated amount required: 0.046333064691492705 ETH\r\n\r\n==========================\r\n\r\n##### sepolia\r\n✅  [Success]Hash: 0xc3c6e6444746c26198db05e5f158b8614c020417d829ddf1f71580bdb58e7eb5\r\nContract Address: 0x1014F47eF26807EAA4ef9ae8d87F7A8be7B96aA3\r\nBlock: 6367953\r\nPaid: 0.016827037137092529 ETH (521123 gas * 32.289952923 gwei)\r\n\r\n✅ Sequence #1 on sepolia | Total Paid: 0.016827037137092529 ETH (521123 gas * avg 32.289952923 gwei)\r\n                                                                                                                                        \r\n\r\n==========================\r\n\r\nONCHAIN EXECUTION COMPLETE & SUCCESSFUL.\r\n\r\nTransactions saved to: /Users/yhb/stakeRNT/broadcast/DeployStakePool.s.sol/11155111/run-latest.json\r\n\r\nSensitive values saved to: /Users/yhb/stakeRNT/cache/DeployStakePool.s.sol/11155111/run-latest.json\r\n\r\n```\r\n\r\n\r\n\r\n### 结论\r\n\r\n本质押挖矿项目通过 RNT、esRNT 和 StakePool 合约实现了用户随时质押、解押和领取奖励的功能。esRNT 代币具有锁仓和线性释放的特性，满足了项目方的需求。上述合约代码和测试代码可以在 Remix IDE 和 Foundry 中进行部署和测试。"},"author":{"user":"https://learnblockchain.cn/people/2184","address":"0x531247BbA4d32ED9D870bc3aBe71A2B9ce911e69"},"history":"bafkreieuovielotdgnr7epjk7idtm5mecbczseha47biyookwzyh2kjpvm","timestamp":1721830877,"version":1}