一.试验内容
-
充值时的币种使用LETH,收取奖励和提取本金时的币种为ETH
-
每充值5个LETH,经过一个区块高度后,能够收取1个ETH的利息
-
在构造函数中设置活动起止区块高度,只要在起止区块高度内的充值才能够核算利息。起止区块高度之间的区块也称为活动期。活动期开始前的区块以及活动期结束后的区块,不核算利息
-
编写modifier,使得充值操作只能在活动期进行
-
在活动期内,用户能够随时充值、提取利息、提取本金,利息不参与复投
二. 参阅算法
-
使用depositAmount记载用户在活动期内的存款数量
-
对每个用户记载一个checkPoint,在每次用户存款或者提取本金时更新;checkPoint = depositAmount *currentBlockNumber
-
对每个用户记载一个calculatedReward,在每次用户存款/提取本金时更新
-
对每个用户记载一个claimedReward,在每次用户提取利息时记载
-
用户的待收取利息 = calculatedReward – claimedReward +从上次操作到当时区块高度新产生的利息
-
用户在提取本金时,先返还之前的一切利息
-
一切对uint256的算数运算,使用SafeMath
三.核心代码
此合约中导入了上次ERC20合约项目实践(上)的合约以及safemath
合约。
- 映射
mapping(address => uint256) public depositAmount; // 用户的存款总量
mapping(address => uint256) public checkPoint; // 每次存款或提取本金时,更新这个值
mapping(address => uint256) public calculatedReward; // 现已核算的利息
mapping(address => uint256) public claimedReward; // 现已提取的利息
- 事件
event Deposit(address indexed sender, uint256 amount); //地址=> 存款(存钱)
event Claim(address indexed sender, address recipient, uint256 amount);
event Withdraw(address indexed sender, uint256 amount);//地址=> 钱(取钱)
- 构造函数
在构造函数中设置活动起止区块高度,只要在起止区块高度内的充值才能够核算利息。起止区块高度之间的区块也称为活动期。活动期开始前的区块以及活动期结束后的区块,不核算利息
constructor(address payable _wethAddress, uint256 _period) {
// period 为从当时开始,延续多少个区块
startBlock = block.number;
endBlock = block.number + _period + 1;
_weth = _wethAddress;
}
- 函数修饰器
编写modifier,使得充值操作只能在活动期进行
// 修饰符,充值时只允许在设定的区块范围内
modifier onlyValidTime() {
require(block.number < endBlock);
_;
}
- 首要办法
1.存钱到合约
过程:
1)更新存钱之前的利息
2)调用LETH
合约的deposit
办法存在本地账户中,在此过程中,将ETH转换为LETH
3)将存在账户中的钱转到合约
4)更新checkpoint
// 存钱到合约
function deposit(uint256 _amount) public onlyValidTime returns (bool) {
// 此处编写事务逻辑
if(depositAmount[msg.sender] != 0){
calculatedReward[msg.sender] = (block.number - checkPoint[msg.sender].div(depositAmount[msg.sender])).mul(depositAmount[msg.sender].div(rewardBase)).add(calculatedReward[msg.sender]);
}
WETH(_weth).deposit();
WETH(_weth).transferFrom(msg.sender,address(this),_amount);
depositAmount[msg.sender] = depositAmount[msg.sender].add(_amount);
checkPoint[msg.sender] = depositAmount[msg.sender].mul(block.number);
emit Deposit(msg.sender, _amount);
return true;
}
2.查询利息
利息=现已核算的利息+存钱之后的利息=calculatesReward+(block.number-原来的block.number)*余额/区块
// 查询利息
function getPendingReward(address _account)public view returns (uint256 pendingReward) {
// 此处编写事务逻辑
if(checkPoint[_account] == 0){
return 0;
}
pendingReward = calculatedReward[_account].add(block.number.sub(checkPoint[_account].div(depositAmount[_account]))).mul(depositAmount[_account]).div(rewardBase);
}
3.收取利息
1)算出现在账户拥有的利息
2)合约将利息转给账户(合约就相当于银行)
最重要的过程便是以上两个,但是在该办法里边还要去维护其他特点变量。如更新calculateReward(现已核算的利息),claimedReward(现已提取的利息)
function claimReward(address payable _toAddress) public returns (bool) {
uint pendingReward = getPendingReward(msg.sender);
//总的利息=账户现在的利息+现已提取的利息
calculatedReward[msg.sender] = pendingReward.add(claimedReward[msg.sender]);
WETH(_weth).withdrawTo(_toAddress,pendingReward);
// 对每个用户记载一个claimedReward,在每次用户提取利息时记载;
claimedReward[msg.sender] = claimedReward[msg.sender].add(pendingReward);
emit Claim(msg.sender, _toAddress, pendingReward);
return true;
}
4.提取必定数量的本金
- 在提取本金之前,需求返还之前一切的利息
- 账户的余额大于提取的金额
- 合约将本金转给账户
- 更新checkpoint(因为depositAmount在变化)
提问:为什么要给msg.sender
加payable
?
function withdraw(uint256 _amount) public returns (bool) {
bool flag = claimReward(payable(msg.sender));
require(flag,"failed claimReward");
require(depositAmount[msg.sender] >= _amount);
WETH(_weth).withdrawTo(payable(msg.sender),_amount);
checkPoint[msg.sender] = depositAmount[msg.sender].mul(block.number);
emit Withdraw(msg.sender, _amount);
return true;
}
5.添加区块高度,获取区块高度
// 用于在Remix本地环境中添加区块高度
uint256 counter;
function addBlockNumber() public {
counter++;
}
// 获取当时区块高度
function getBlockNumber() public view returns (uint256) {
return block.number;
}
}
四.总结
本次试验首要是涉及单个用户和渠道的功能,如:用户存钱,取钱,查询利息;渠道核算利息,保存钱等。是一个简略的defi项目的功能。试验目的首要是理解现实defi项目的事务,合约该怎么规划。只学这么一点是不够的,能够去找复杂的defi项目合约学习。