Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Customized masterchef smart contract (staking ETH and give reward in ETH) #12

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
/node_modules/
/artifacts/
/cache/
/data/
8 changes: 8 additions & 0 deletions .idea/.gitignore

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 15 additions & 0 deletions .idea/challenge.iml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

31 changes: 31 additions & 0 deletions .idea/inspectionProfiles/Project_Default.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions .idea/inspectionProfiles/profiles_settings.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 7 additions & 0 deletions .idea/misc.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions .idea/modules.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions .idea/vcs.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

29 changes: 29 additions & 0 deletions INSTALL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# CSPD-Staking-Contract

## Install
yarn install

After that need to add `config.js` file. Example you can find in `config.example.js` file.

## Added plugins
* [**@openzeppelin/contracts@4.2.0**](https://github.com/OpenZeppelin/openzeppelin-contracts/tree/v4.2.0/contracts) - Standart OpenZeppelin's contract library.

* [**hardhat**](https://hardhat.org/getting-started/) - Solidity development environment.

* [**hardhat-abi-exporter**](https://hardhat.org/plugins/hardhat-abi-exporter.html) - Exports smart contract ABIs on compilation. It will run automatically on compilation.

* [**hardhat-contract-sizer**](https://hardhat.org/plugins/hardhat-contract-sizer.html) - Writes bytecode sizes of smart contracts. It will run automatically on compilation.

* [**hardhat-gas-reporter**](https://hardhat.org/plugins/hardhat-gas-reporter.html) - Writes information of gas usage in tests. It will run automatically on tests.

* [**hardhat-spdx-license-identifier**](https://hardhat.org/plugins/hardhat-spdx-license-identifier.html) - Writes SPDX License Identifier into sol files. It will run automatically on compilation.

* [**hardhat-tracer**](https://hardhat.org/plugins/hardhat-tracer.html) - Prints events when running tests.

* [**solidity-coverage**](https://hardhat.org/plugins/solidity-coverage.html) - Solidity code coverage plugin.

* [**@nomiclabs/hardhat-etherscan**](https://hardhat.org/plugins/nomiclabs-hardhat-etherscan.html) - Automatic verification on etherscan, bscscan and others.

* [**@nomiclabs/hardhat-solhint**](https://hardhat.org/plugins/nomiclabs-hardhat-solhint.html) - Solidity linter

* [**prettier**](https://www.npmjs.com/package/prettier) - Formatter. Run with script `prettier` of npm.
99 changes: 65 additions & 34 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,4 @@
# Smart Contract Challenge

## A) Challenge

### 1) Setup a project and create a contract
# Ether staking and get reward per week

#### Summary

Expand All @@ -24,32 +20,67 @@ Example:
> **A** should get their deposit + all the rewards.
> **B** should only get their deposit because rewards were sent to the pool before they participated.

#### Goal

Design and code a contract for ETHPool, take all the assumptions you need to move forward.

You can use any development tools you prefer: Hardhat, Truffle, Brownie, Solidity, Vyper.

Useful resources:

- Solidity Docs: https://docs.soliditylang.org/en/v0.8.4
- Educational Resource: https://github.com/austintgriffith/scaffold-eth
- Project Starter: https://github.com/abarmat/solidity-starter

### 2) Write tests

Make sure that all your code is tested properly

### 3) Deploy your contract

Deploy the contract to any Ethereum testnet of your preference. Keep record of the deployed address.

Bonus:

- Verify the contract in Etherscan

### 4) Interact with the contract

Create a script (or a Hardhat task) to query the total amount of ETH held in the contract.

_You can use any library you prefer: Ethers.js, Web3.js, Web3.py, eth-brownie_
# Keep staking principle.
First week:
- A deposit 100, B deposit 150 and T deposit 500
- A withdraw 100 + 200 = 300
- ** B don't withdraw
- Pool Ether: 100 + 150 + 500 - 300 = 450
- B reward: 500 * 150 / 250 = 300

Second week:
- C deposit 100 and T deposit 500
- Total balance: 450 + 100 + 500 = 1050
- Then B available withdraw amount ???
- Not this: 1050 * 150 / (150 + 100) = 630

** B will be able to withdraw:
300 + 500 * 150 / (150 + 100) = 600

Test network contract: https://rinkeby.etherscan.io/address/0x00322a7E8e774157B3f160245Ac6365ae024D9f3#code

### Test Result

```solidity
·-----------------|-------------·
| Contract Name · Size (Kb) │
··················|··············
| console · 0.08 │
··················|··············
| SafeMath · 0.08 │
··················|··············
| Staking · 3.05 │
·-----------------|-------------·

Token test
√ Add Admin
√ Stake
√ Get Pending Reward
√ UnSake
√ Next week
√ Next week: Get Pending Reward

·------------------------------|---------------------------|-------------|-----------------------------·
| Solc version: 0.8.0 · Optimizer enabled: true · Runs: 200 · Block limit: 30000000 gas │
·······························|···························|·············|······························
| Methods │
·············|·················|·············|·············|·············|···············|··············
| Contract · Method · Min · Max · Avg · # calls · usd (avg) │
·············|·················|·············|·············|·············|···············|··············
| Staking · addAdmin · 47394 · 47406 · 47400 · 2 · - │
·············|·················|·············|·············|·············|···············|··············
| Staking · depositReward · 54178 · 59653 · 56916 · 2 · - │
·············|·················|·············|·············|·············|···············|··············
| Staking · stake · 54877 · 74777 · 67210 · 3 · - │
·············|·················|·············|·············|·············|···············|··············
| Staking · unStake · - · - · 50834 · 1 · - │
·············|·················|·············|·············|·············|···············|··············
| Deployments · · % of limit · │
·······························|·············|·············|·············|···············|··············
| Staking · - · - · 820165 · 2.7 % · - │
·------------------------------|-------------|-------------|-------------|---------------|-------------·

6 passing (806ms)


```
11 changes: 11 additions & 0 deletions config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
module.exports = {
// global constants
testnetAccounts: ["..."],
mainnetAccounts: ["..."],
infuraIdProject: "...",
apiKey: "...",
coinmarketcapApi: "...",

// deploy constants
initialSupply: "1000",
};
135 changes: 135 additions & 0 deletions contracts/Staking.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
// SPDX-License-Identifier: UNLICENSED

/**
* Staking contract
*/

import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/math/SafeMath.sol";

pragma solidity ^0.8.0;

/**
* @dev Staking module
* ETHPool provides a service where people can deposit ETH and they will receive weekly rewards.
* Users must be able to take out their deposits along with their portion of rewards at any time.
* New rewards are deposited manually into the pool by the ETHPool team each week using a contract function.
*/

contract Staking is Ownable, ReentrancyGuard {
using SafeMath for uint256;

// Info of each user.
struct UserInfo {
uint256 amount;
uint256 rewardDebt;
uint256 pendingAmount;
}

mapping (address => UserInfo) public userInfo;

mapping(address => bool) public admins;

uint256 public lastRewardBlock;
uint256 public accRewardPerShare;

uint256 public totalStakedAmount;

uint256 public rewardBalance;



event AddAdmin(address indexed admin);
event RemoveAdmin(address indexed admin);

event Stake(address indexed user, uint256 amount);
event ReStake(address indexed user, uint256 amount);
event DepositReward(address indexed user, uint256 amount);
event UnStake(address indexed user, uint256 unStakeAmount, uint256 rewardAmount);

/**
* @dev Throws if called by any account other than the admin.
*/
modifier onlyAdmin() {
require(admins[msg.sender], "caller is not the admin");
_;
}

constructor(
) {
lastRewardBlock = block.number;
admins[owner()] = true;
}

function addAdmin(address _address) external onlyOwner {
require(_address != address(0x0), "address error");
admins[_address] = true;
emit AddAdmin(_address);
}

function removeAdmin(address _address) external onlyOwner {
require(admins[_address], "this address is not admin!");
admins[_address] = false;
emit RemoveAdmin(_address);
}

function getETHBalanceOfPool() external view returns (uint256) {
return address(this).balance;
}

function depositReward() external payable onlyAdmin {
require(msg.value > 0, "amount is 0");
uint amount = msg.value;
if (totalStakedAmount > 0)
accRewardPerShare = accRewardPerShare.add(rewardBalance.mul(10 ** 12).div(totalStakedAmount));
rewardBalance = amount;
emit DepositReward(msg.sender, amount);
lastRewardBlock = block.number;
}

function getPending(address _user) external view returns (uint256) {
uint256 pending = _getPending(_user);
return pending;
}

function _getPending(address _user) private view returns (uint256) {
UserInfo storage user = userInfo[_user];
uint256 acc = accRewardPerShare;
if (block.number > lastRewardBlock && totalStakedAmount != 0 && rewardBalance > 0) {
acc = acc.add(rewardBalance.mul(10 ** 12).div(totalStakedAmount));
}
return user.amount.mul(acc).div(10 ** 12).sub(user.rewardDebt).add(user.pendingAmount);
}

function stake() external payable {
require(msg.value > 0, "stake amount is 0");
uint256 _amount = msg.value;
UserInfo storage user = userInfo[msg.sender];
if (user.amount > 0) {
uint256 pending = user.amount.mul(accRewardPerShare).div(10 ** 12).sub(user.rewardDebt);
user.pendingAmount = user.pendingAmount.add(pending);
}

totalStakedAmount = totalStakedAmount.add(_amount);
user.amount = user.amount.add(_amount);
user.rewardDebt = user.amount.mul(accRewardPerShare).div(10 ** 12);
emit Stake(msg.sender, _amount);
}

function unStake() external payable {
UserInfo storage user = userInfo[msg.sender];
require(user.amount > 0, "You didn't stake!");
uint256 rewardAmount = _getPending(msg.sender);
uint256 stakedAmount = user.amount;
uint256 withdrawAmount = stakedAmount + rewardAmount;
bool isSent = payable(msg.sender).send(withdrawAmount);
require(isSent, "Failed to send ETH");
rewardBalance = rewardBalance.sub(rewardAmount);
totalStakedAmount = totalStakedAmount.sub(stakedAmount);
user.pendingAmount = 0;
user.amount = 0;
user.rewardDebt = 0;
emit UnStake(msg.sender, stakedAmount, rewardAmount);
}
}
Loading