---
# System prepended metadata

title: KryptoCamp Week3 HW
tags: [KryptoCamp]

---

# KryptoCamp Week3 HW

## 基礎作業

1. 透過 remix JavaScript VM 部署合約
2. 部署任一自訂 ERC20 Token
3. 部署質押合約
    - Deposit（存入）
    - Reward（回饋）
    - TimeLock（固定鎖倉期）

### ERC20 Token
```solidity=
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";

contract HooToken is ERC20 {
    constructor() ERC20("HooToken", "HT") payable {
        _mint(msg.sender, msg.value);
    }
}

```

### Bank
```solidity=
// SPDX-License-Identifier: GPL-3.0

pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";

contract Bank { 

    IERC20 public stakingToken;
    uint256 public totalSupply;
    mapping(address => uint256) public balanceOf;


    uint256 public lockDuration = 10 seconds;
    mapping(address => uint256)  public lockStartOf;
    mapping(address => uint256)  public lockEndOf;

    uint256 public rewardRate = 1;
    mapping(address => uint256)   public rewardOf;

    constructor(IERC20 _stakingToken) {
        stakingToken = _stakingToken;
    }

    function deposit(uint256 _amount) external {

        
        stakingToken.transferFrom(msg.sender, address(this), _amount);
        totalSupply += _amount;
        balanceOf[msg.sender] += _amount;
        


    
        lockStartOf[msg.sender] = block.timestamp;
        lockEndOf[msg.sender] = block.timestamp + lockDuration;
        
    }

    function withdraw(uint256 _amount) external {

        
        require(block.timestamp > lockEndOf[msg.sender], "withdraw too fast");
        require(_amount <= balanceOf[msg.sender] , "insufficient" );

        rewardOf[msg.sender] = getReward();
        
        
        stakingToken.transfer(msg.sender, _amount);
        totalSupply -= _amount;
        balanceOf[msg.sender] -= _amount;
        
    }

    function getReward() public view returns (uint256) {
        uint256 duration = block.timestamp - lockStartOf[msg.sender];
        uint256 reward = duration * rewardRate * balanceOf[msg.sender];
        return reward;
    }

}

```

## 進階作業

1. 透過 remix Injected Web3 部署至 Rinkeby
2. 部署任一自訂 ERC20 Token
3. 部署質押合約
    - Deposit（存入）
    - Reward（回饋）
    - TimeLock（根據鎖倉期給予不同回饋加乘）

### ERC20 Token

https://rinkeby.etherscan.io/tx/0x41f3e52edc550a2bedcb4fe25d67d215b62d6df3d53928cdb0493e18180ee242

```solidity=
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";

contract HooToken is ERC20 {
    constructor() ERC20("HooToken", "HT") payable {
        _mint(msg.sender, msg.value);
    }
}
```

### Bank

https://rinkeby.etherscan.io/tx/0xf6caf66e2d36a4faab6164160a347cb657f5c29349847f260f626586744aa6b4

```solidity=
// SPDX-License-Identifier: GPL-3.0

pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";

contract Bank { 

    IERC20 public stakingToken;
    uint256 public totalSupply;
    mapping(address => uint256) public balanceOf;


    uint256 public lockDuration = 10 seconds;
    //mapping(address => uint256)  public lockStartOf;
    //mapping(address => uint256)  public lockEndOf;

    uint256 public rewardRate = 1;
    uint256 public lastTime;
    uint256 public rewardRealtime;
    mapping(address => uint256)   public rewardOf;


    struct Deposit {
        uint256 amount;
        uint256 start; // lockStartOf
        uint256 end; //lockEndOf
    }
    mapping(address => Deposit[]) public depositOf;

    constructor(IERC20 _stakingToken) {
        stakingToken = _stakingToken;
    }

    function earn() public view returns (uint256) {
        uint256 duration = block.timestamp - lastTime;
        uint256 total = rewardOf[msg.sender] + balanceOf[msg.sender] *duration * rewardRate;
        return total;
    }

    modifier updateReward() {
        rewardOf[msg.sender] = earn();
        lastTime = block.timestamp;
        _;
    }

    function deposit(uint256 _amount) external updateReward {

        // ******* comment for testing ********
        // stakingToken.transferFrom(msg.sender, address(this), _amount);
        totalSupply += _amount;
        balanceOf[msg.sender] += _amount;
            
        //lockStartOf[msg.sender] = block.timestamp;
        //lockEndOf[msg.sender] = block.timestamp + lockDuration;
        

        depositOf[msg.sender].push(
            Deposit({
                amount: _amount,
                start: block.timestamp,
                end: block.timestamp + lockDuration
            })
        );
    }

    //  *** // uint256 _amount
    function withdraw(uint256 _depositId) external updateReward {

        // storage: call by refenece
        // memory: call by value
        Deposit[] storage deposits = depositOf[msg.sender];
        uint256 amount = deposits[_depositId].amount;

        //require(block.timestamp > lockEndOf[msg.sender], "withdraw too fast");
        //require(_amount <= balanceOf[msg.sender] , "insufficient" );

        require(_depositId < deposits.length, "not exist");
        require(block.timestamp >= deposits[_depositId].end, "too soon");

        //rewardOf[msg.sender] = getReward();
        //rewardOf[msg.sender] += getRewardID(_depositId);
        
        stakingToken.transfer(msg.sender, amount);
        totalSupply -= amount;
        balanceOf[msg.sender] -= amount;
        
        // remove
        uint256 last = deposits.length -1;
        deposits[_depositId] = deposits[last];
        deposits.pop();

    }

    function getRewardDebug() external updateReward () {
        rewardRealtime = rewardOf[msg.sender];
    }

    function getReward() external updateReward returns (uint256) {
        uint256 reward = rewardOf[msg.sender];
        return reward;
    }

    /*
    function computeReward(uint256 _depositId) public view returns (uint256) {
        //uint256 duration = block.timestamp - lockStartOf[msg.sender];
        //uint256 reward = duration * rewardRate * balanceOf[msg.sender];

        uint256 start = depositOf[msg.sender][_depositId].start;
        uint256 amount = depositOf[msg.sender][_depositId].amount;
        uint256 reward = (block.timestamp - start) * amount * rewardRate;

        return reward;
    }
    */

    /*
    function getRewardID(uint256 _depositId) public view returns (uint256) {
        
        uint256 N = depositOf[msg.sender].length;
        uint256 rewards;
        for (uint256 i = 0; i < N; i++) {
            rewards += computeReward(i);
        }
        return rewards;
    }
    */

}

```

## 終極作業

1. 透過 remix Injected Web3 部署至 Rinkeby
2. 部署任兩種自訂 ERC20 Token（一個質押，一個回饋）
3. 部署質押合約
    - Deposit（存入）
    - Reward（回饋另一個 Token）
    - TimeLock（根據鎖倉期給予不同回饋加乘）
4. 利用範例 AMM 交易所進行 Reward Token 的 ETH 兌換

### ERC20 Token

#### Staking Token
https://rinkeby.etherscan.io/tx/0xa6a948f7635c2520c389d968190c3a32354d4405601c218a8413708501879ec6

#### Reward Token
https://rinkeby.etherscan.io/tx/0x4e484438ed34785658d9b1df6595d5b381a438a3e8d95d75fcaccff5c29edb4f

```solidity=
// contracts/GLDToken.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";

contract HooToken is ERC20 {
    constructor() ERC20("HooToken", "HT") payable {
        _mint(msg.sender, msg.value);
    }
}

contract QEToken is ERC20 {
    constructor() ERC20("QEToken", "QT") payable {
        _mint(msg.sender, 1 * 10**18);
    }
}
```

### Bank

https://rinkeby.etherscan.io/tx/0xdc4fdd5b5459b21d707e3bce3256929e8073ed6c6ca01aeb1b40af6baf0b213f

```solidity=
// SPDX-License-Identifier: GPL-3.0

pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";

contract Bank { 

    IERC20 public stakingToken;
    IERC20 public rewardToken;
    uint256 public totalSupply;
    mapping(address => uint256) public balanceOf;


    uint256 public lockDuration = 10 seconds;
    //mapping(address => uint256)  public lockStartOf;
    //mapping(address => uint256)  public lockEndOf;

    uint256 public rewardRate = 1;
    uint256 public lastTime;
    uint256 public rewardRealtime;
    mapping(address => uint256)   public rewardOf;


    struct Deposit {
        uint256 amount;
        uint256 start; // lockStartOf
        uint256 end; //lockEndOf
    }
    mapping(address => Deposit[]) public depositOf;

    constructor(IERC20 _stakingToken, IERC20 _rewardToken) {
        stakingToken = _stakingToken;
        rewardToken = _rewardToken;
    }

    function earn() public view returns (uint256) {
        uint256 duration = block.timestamp - lastTime;
        uint256 total = rewardOf[msg.sender] + balanceOf[msg.sender] *duration * rewardRate;
        return total;
    }

    modifier updateReward() {
        rewardOf[msg.sender] = earn();
        lastTime = block.timestamp;
        _;
    }

    function deposit(uint256 _amount) external updateReward {

        // ******* comment for testing ********
        // stakingToken.transferFrom(msg.sender, address(this), _amount);
        totalSupply += _amount;
        balanceOf[msg.sender] += _amount;
            
        //lockStartOf[msg.sender] = block.timestamp;
        //lockEndOf[msg.sender] = block.timestamp + lockDuration;
        

        depositOf[msg.sender].push(
            Deposit({
                amount: _amount,
                start: block.timestamp,
                end: block.timestamp + lockDuration
            })
        );
    }

    //  *** // uint256 _amount
    function withdraw(uint256 _depositId) external updateReward {

        // storage: call by refenece
        // memory: call by value
        Deposit[] storage deposits = depositOf[msg.sender];
        uint256 amount = deposits[_depositId].amount;

        //require(block.timestamp > lockEndOf[msg.sender], "withdraw too fast");
        //require(_amount <= balanceOf[msg.sender] , "insufficient" );

        require(_depositId < deposits.length, "not exist");
        require(block.timestamp >= deposits[_depositId].end, "too soon");

        //rewardOf[msg.sender] = getReward();
        //rewardOf[msg.sender] += getRewardID(_depositId);
        
        stakingToken.transfer(msg.sender, amount);
        totalSupply -= amount;
        balanceOf[msg.sender] -= amount;
        
        // remove
        uint256 last = deposits.length -1;
        deposits[_depositId] = deposits[last];
        deposits.pop();

    }

    function getRewardDebug() external updateReward () {
        rewardRealtime = rewardOf[msg.sender];
    }

    function getReward() external updateReward () {
        uint256 reward = rewardOf[msg.sender];
        rewardOf[msg.sender] = 0;
        rewardToken.transfer(msg.sender, reward);
    }

    /*
    function computeReward(uint256 _depositId) public view returns (uint256) {
        //uint256 duration = block.timestamp - lockStartOf[msg.sender];
        //uint256 reward = duration * rewardRate * balanceOf[msg.sender];

        uint256 start = depositOf[msg.sender][_depositId].start;
        uint256 amount = depositOf[msg.sender][_depositId].amount;
        uint256 reward = (block.timestamp - start) * amount * rewardRate;

        return reward;
    }
    */

    /*
    function getRewardID(uint256 _depositId) public view returns (uint256) {
        
        uint256 N = depositOf[msg.sender].length;
        uint256 rewards;
        for (uint256 i = 0; i < N; i++) {
            rewards += computeReward(i);
        }
        return rewards;
    }
    */

}

```

###### tags: `KryptoCamp`
