在一定时间(deadline)内,质押(stake)一定数量(threshold)的代币。到期之后可以将代币转入(execute)到另一个合约,也可以将代币提取出来(withdraw)。
所以我们抽像出来了三个关键函数:
stake()
execute()
withdraw()
scaffold-eth也为我们提供了这样的一个脚手架,只需要把代码拉下来,我们本次就在这个基础上逐步来实现。
代码语言:javascript复制git clone https://github.com/scaffold-eth/scaffold-eth-challenges.git
cd scaffold-eth-challenges
git checkout challenge-1-decentralized-staking
yarn install
然后开三个终端窗口,执行如下三个命令:
代码语言:javascript复制yarn chain
代码语言:javascript复制yarn start
代码语言:javascript复制yarn deploy --reset
0x04 Live Coding
4.1 stake
- 关键点1,每次质押一定数量的eth。
pragma solidity 0.8.4;
import "hardhat/console.sol";
import "./ExampleExternalContract.sol";
contract Staker {
mapping(address => uint256) public balances;
event Stake(address indexed staker, uint256 amount);
function stake() public payable {
balances[msg.sender] = msg.value;
emit Stake(msg.sender, msg.value);
}
}
- 关键点2,部署脚本移除构造函数的参数
// deploy/01_deploy_staker.js
// ....
await deploy("Staker", {
// Learn more about args here: https://www.npmjs.com/package/hardhat-deploy#deploymentsdeploy
from: deployer,
// args: [exampleExternalContract.address],
log: true,
});
//...
- 关键点3,部署
yarn deploy --reset
- 关键点4,空投一些测试币
- 关键点5,测试stake
4.2 execute
筹集到的资金,在满足一定条件之后,转移给另一个合约中。
- 关键点1,另一个合约
contract ExampleExternalContract {
bool public completed;
function complete() public payable {
completed = true;
}
}
很简单,有一个flag代表是否已经结束了。
- 关键点2,构造函数
在stake合约中,要把这个合约引入进来,同时要有一个构造函数
代码语言:javascript复制ExampleExternalContract public exampleExternalContract;
constructor(address exampleExternalContractAddress) public {
exampleExternalContract = ExampleExternalContract(
exampleExternalContractAddress
);
}
- 关键点3,部署的时候要初始化
// deploy/01_deploy_staker.js
// ....
await deploy("Staker", {
// Learn more about args here: https://www.npmjs.com/package/hardhat-deploy#deploymentsdeploy
from: deployer,
args: [exampleExternalContract.address],
log: true,
});
//...
- 关键点4,质押上限
uint256 public constant threshold = 1 ether;
- 关键点5,向第二个合约转账。
function execute() public {
uint256 contractBalance = address(this).balance;
// check the contract has enough ETH to reach the treshold
require(contractBalance >= threshold, "Threshold not reached");
// Execute the external contract, transfer all the balance to the contract
// (bool sent, bytes memory data) = exampleExternalContract.complete{value: contractBalance}();
(bool sent, ) = address(exampleExternalContract).call{
value: contractBalance
}(abi.encodeWithSignature("complete()"));
require(sent, "exampleExternalContract.complete failed");
}
- 最终代码如下
// SPDX-License-Identifier: MIT
pragma solidity 0.8.4;
import "hardhat/console.sol";
import "./ExampleExternalContract.sol";
contract Staker {
ExampleExternalContract public exampleExternalContract;
mapping(address => uint256) public balances;
uint256 public constant threshold = 1 ether;
event Stake(address indexed staker, uint256 amount);
constructor(address exampleExternalContractAddress) public {
exampleExternalContract = ExampleExternalContract(
exampleExternalContractAddress
);
}
function stake() public payable {
balances[msg.sender] = msg.value;
emit Stake(msg.sender, msg.value);
}
function execute() public {
uint256 contractBalance = address(this).balance;
require(contractBalance >= threshold, "Threshold not reached");
(bool sent, ) = address(exampleExternalContract).call{
value: contractBalance
}(abi.encodeWithSignature("complete()"));
require(sent, "exampleExternalContract.complete() failed");
}
}
- 部署
yarn deploy --reset
- 空投测试币
- stake 一些币到达上限
- 测试 execute
4.3 withdraw
将质押的钱提取出来,这个比较简单,就是将钱转移出来即可。
代码语言:javascript复制 function withdraw() public {
uint256 userBalance = balances[msg.sender];
require(userBalance > 0, "You don't have balance to withdraw");
balances[msg.sender] = 0;
(bool sent, ) = msg.sender.call{value: userBalance}("");
require(sent, "Failed to send user balance back to the user");
}
- 完整代码如下
// SPDX-License-Identifier: MIT
pragma solidity 0.8.4;
import "hardhat/console.sol";
import "./ExampleExternalContract.sol";
contract Staker {
ExampleExternalContract public exampleExternalContract;
mapping(address => uint256) public balances;
uint256 public constant threshold = 1 ether;
uint256 public deadline = block.timestamp 30 seconds;
event Stake(address indexed sender, uint256 amount);
constructor(address exampleExternalContractAddress) public {
exampleExternalContract = ExampleExternalContract(
exampleExternalContractAddress
);
}
function stake() public payable {
balances[msg.sender] = msg.value;
emit Stake(msg.sender, msg.value);
}
function execute() public {
uint256 contractBalance = address(this).balance;
require(contractBalance >= threshold, "Threshold not reached");
(bool sent, ) = address(exampleExternalContract).call{
value: contractBalance
}(abi.encodeWithSignature("complete()"));
require(sent, "exampleExternalContract.complete failed");
}
function withdraw() public {
uint256 userBalance = balances[msg.sender];
require(userBalance > 0, "You don't have balance to withdraw");
balances[msg.sender] = 0;
(bool sent, ) = msg.sender.call{value: userBalance}("");
require(sent, "Failed to send user balance back to the user");
}
}
- 部署
yarn deploy --reset
- 空投测试币
- stake一些币
- 测试withdraw
4.4 加上时间或质押到期限制
这里有两个判断的标准
首先是是否到达时间,另一个就是质押是否已经完成。
- 第一个是否完成,直接去看另一个合约的标志即可
modifier stakeNotCompleted() {
bool completed = exampleExternalContract.completed();
require(!completed, "staking process is already completed");
_;
}
- 第二个是否到时间了
uint256 public deadline = block.timestamp 60 seconds;
function timeLeft() public view returns (uint256 timeleft) { if (block.timestamp >= deadline) { return 0; } else { return deadline - block.timestamp; } }
modifier deadlineReached(bool requireReached) { uint256 timeRemaining = timeLeft(); if (requireReached) { require(timeRemaining == 0, "deadline is not reached yet"); } else { require(timeRemaining > 0, "deadline has already reached"); } _; }
- 接着是deadlineReached函数
- 然后还要有一个timeLeft函数
- 首先要有一个deadline变量
- 如何修饰这些函数
function stake() public payable deadlineReached(false) stakeNotCompleted { balances[msg.sender] = msg.value; emit Stake(msg.sender, msg.value); }
function execute() public stakeNotCompleted deadlineReached(false) { uint256 contractBalance = address(this).balance; require(contractBalance >= threshold, "Threshold not reached"); (bool sent, ) = address(exampleExternalContract).call{ value: contractBalance }(abi.encodeWithSignature("complete()")); require(sent, "exampleExternalContract.complete() failed"); }
function withdraw() public deadlineReached(true) stakeNotCompleted { uint256 userBalance = balances[msg.sender]; require(userBalance > 0, "You don't have balance to withdraw"); balances[msg.sender] = 0; (bool sent, ) = msg.sender.call{value: userBalance}(""); require(sent, "Failed to send user balance back to the user"); }
- withdraw函数
- execute函数
- stake
- 可以被外部合约调用的函数
receive() external payable {
stake();
}
最终代码如下:
代码语言:javascript复制// SPDX-License-Identifier: MIT
pragma solidity 0.8.4;
import "hardhat/console.sol";
import "./ExampleExternalContract.sol";
contract Staker {
ExampleExternalContract public exampleExternalContract;
mapping(address => uint256) public balances;
uint256 public constant threshold = 1 ether;
event Stake(address indexed staker, uint256 amount);
uint256 public deadline = block.timestamp 60 seconds;
constructor(address exampleExternalContractAddress) public {
exampleExternalContract = ExampleExternalContract(
exampleExternalContractAddress
);
}
modifier stakeNotCompleted() {
bool completed = exampleExternalContract.completed();
require(!completed, "staking process is already completed");
_;
}
modifier deadlineReached(bool requireReached) {
uint256 timeRemaining = timeLeft();
if (requireReached) {
require(timeRemaining == 0, "deadline is not reached yet");
} else {
require(timeRemaining > 0, "deadline has already reached");
}
_;
}
function timeLeft() public view returns (uint256 timeleft) {
if (block.timestamp >= deadline) {
return 0;
} else {
return deadline - block.timestamp;
}
}
function stake() public payable deadlineReached(false) stakeNotCompleted {
balances[msg.sender] = msg.value;
emit Stake(msg.sender, msg.value);
}
function execute() public stakeNotCompleted deadlineReached(false) {
uint256 contractBalance = address(this).balance;
require(contractBalance >= threshold, "Threshold not reached");
(bool sent, ) = address(exampleExternalContract).call{
value: contractBalance
}(abi.encodeWithSignature("complete()"));
require(sent, "exampleExternalContract.complete() failed");
}
function withdraw() public deadlineReached(true) stakeNotCompleted {
uint256 userBalance = balances[msg.sender];
require(userBalance > 0, "You don't have balance to withdraw");
balances[msg.sender] = 0;
(bool sent, ) = msg.sender.call{value: userBalance}("");
require(sent, "Failed to send user balance back to the user");
}
receive() external payable {
stake();
}
}
- 部署
yarn deploy --reset
- 测试