# 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`