一.试验内容

  1. 充值时的币种使用LETH,收取奖励和提取本金时的币种为ETH

  2. 每充值5个LETH,经过一个区块高度后,能够收取1个ETH的利息

  3. 在构造函数中设置活动起止区块高度,只要在起止区块高度内的充值才能够核算利息。起止区块高度之间的区块也称为活动期。活动期开始前的区块以及活动期结束后的区块,不核算利息

  4. 编写modifier,使得充值操作只能在活动期进行

  5. 在活动期内,用户能够随时充值、提取利息、提取本金,利息不参与复投

二. 参阅算法

  1. 使用depositAmount记载用户在活动期内的存款数量

  2. 对每个用户记载一个checkPoint,在每次用户存款或者提取本金时更新;checkPoint = depositAmount *currentBlockNumber

  3. 对每个用户记载一个calculatedReward,在每次用户存款/提取本金时更新

  4. 对每个用户记载一个claimedReward,在每次用户提取利息时记载

  5. 用户的待收取利息 = calculatedReward – claimedReward +从上次操作到当时区块高度新产生的利息

  6. 用户在提取本金时,先返还之前的一切利息

  7. 一切对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.提取必定数量的本金

  1. 在提取本金之前,需求返还之前一切的利息
  2. 账户的余额大于提取的金额
  3. 合约将本金转给账户
  4. 更新checkpoint(因为depositAmount在变化)

提问:为什么要给msg.senderpayable

    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项目合约学习。