Cannot run full test
This commit is contained in:
@@ -0,0 +1,210 @@
|
||||
# Practical Sample With Hardhat + Solidity
|
||||
|
||||
A fully functional Smart Contract wrriten with ```solidity```, ```node.js``` and using ```Hardhat``` to show usage of all kinds of standard development features, including:
|
||||
* running blockchain node in localhost - ```hardhat``` & ```ganache```
|
||||
* solidity prettier code beautifier setup
|
||||
* standard of developing Smart Contract with ```solidity```
|
||||
* standard of developing ```node.js``` script to use ```hardhat``` library efficiently
|
||||
* check gas fee & gas price in real-time, with usage of ```coverage```
|
||||
* auto verify contract on Etherscan.io
|
||||
* creating automate test cases - unit test & staging test
|
||||
* creating tasks etc.
|
||||
* refactor codes to reduce gas fee
|
||||
* write code in best practice, with usage of ```solhint```
|
||||
* prettier ```solidity``` & ```node.js``` code
|
||||
* gitea version control
|
||||
|
||||
This project mainly to keep as a reference for future Web 3.0 Developments.
|
||||
|
||||
---
|
||||
### Success Deploy & Verified Of This Smart Contract To Sepolia Testnet
|
||||
|
||||
0xc2022b56eBC140B5FebCf9FBaB14c17db4C315C4
|
||||
https://sepolia.etherscan.io/address/0xc2022b56eBC140B5FebCf9FBaB14c17db4C315C4#code
|
||||
Via deploy.js
|
||||
https://sepolia.etherscan.io/address/0x3a827C119e1D746bb3C7bcbbf95c55246C8CcBdd#code
|
||||
Via yarn hardhat deploy --network sepolia
|
||||
|
||||
### Public Reported Hacked Code References:
|
||||
|
||||
This website is records of all kind previous hacked smart contract:
|
||||
|
||||
https://rekt.news/leaderboard/
|
||||
|
||||
|
||||
## 1. Git Version Control
|
||||
First time initialize:
|
||||
```
|
||||
git config --global user.name "hoelee"
|
||||
git config --global user.email "[email protected]"
|
||||
git init .
|
||||
git add .
|
||||
git checkout -b main
|
||||
git commit -m "Initial Commit"
|
||||
git remote set-url origin https://username:[email protected]/hoelee/ethers-simple-storage.git
|
||||
git credential-cache exit // Fix Credential Error
|
||||
```
|
||||
|
||||
Standard Update:
|
||||
```
|
||||
git add .
|
||||
git commit -m "Describe what changes"
|
||||
git push -u origin main
|
||||
// After set this, later easier usage via below line
|
||||
git push
|
||||
git pull
|
||||
```
|
||||
|
||||
Development need exlude file can create root file with name .gitignore
|
||||
```
|
||||
node_modules
|
||||
package.json
|
||||
img
|
||||
artifacts
|
||||
cache
|
||||
coverage
|
||||
.env
|
||||
.*
|
||||
README.md
|
||||
coverage.json
|
||||
```
|
||||
|
||||
## 2. Setup Visual Studio Code Development Environment
|
||||
|
||||
Windows need to download install WSL
|
||||
```
|
||||
wsl --set-default Ubuntu-22.04
|
||||
mkdir theProjectFolderName
|
||||
cd theProjectFolderName
|
||||
code .
|
||||
// Install nvm
|
||||
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.1/install.sh | bash
|
||||
nvm install 16.14.2
|
||||
nvm install node.js
|
||||
nvm install 18 // Update node JS to v18
|
||||
```
|
||||
Visual Studio need to update code setting
|
||||
```
|
||||
"[solidity]": {
|
||||
"editor.defaultFormatter":"NomicFoundation.hardhat-solidity"
|
||||
}
|
||||
```
|
||||
Preparing of solidity development environment:
|
||||
```
|
||||
corepack enable // Enable yarn
|
||||
yarn install solc
|
||||
yarn add [email protected]
|
||||
yarn solcjs --bin --abi --include-path node_modules/ --base-path . -o . SimpleStorage.sol
|
||||
yarn add ethers // Compiler Error, Downgraded to v5.7.2
|
||||
yarn add fs-extra
|
||||
yarn add dotenv
|
||||
yarn add prettier
|
||||
yarn add prettier-plugin-solidity
|
||||
```
|
||||
Preparing of Hardhat Development Environment
|
||||
```
|
||||
yarn init
|
||||
// Manual delete main: index.js in package.json
|
||||
yarn add --dev hardhat // Production no need --dev
|
||||
nvm install 18
|
||||
nvm use 18
|
||||
nvm alias default 18
|
||||
corepack enable // Enable yarn
|
||||
yarn hardhat
|
||||
yarn add --dev prettier prettier-plugin-solidity
|
||||
yarn add --dev dotenv
|
||||
yarn add --dev @nomiclabs/hardhat-etherscan // Auto verify Etherscan Samrt Contract
|
||||
yarn add --dev @nomiclabs/hardhat-waffle
|
||||
yarn add --dev solhint
|
||||
yarn add --dev @nomiclabs/hardhat-ethers@npm:hardhat-deploy-ethers ethers
|
||||
yarn add --dev hardhat-gas-reporter
|
||||
yarn add --dev solidity-coverage
|
||||
yarn add --dev solhint
|
||||
```
|
||||
Other Terminal Useful Command 1:
|
||||
```
|
||||
// For Debuging Hardhat
|
||||
npx hardhat --versose
|
||||
|
||||
// For Recompile
|
||||
yarn hardhat clean // Or manual delete artifacts & cache folder
|
||||
npm install
|
||||
|
||||
// For Listing Hardhat Local Blockchain node
|
||||
yarn hardhat accounts
|
||||
yarn hardhat node // Run in Dedicated Terminal, Getting Blockchain server
|
||||
yarn hardhat console --network localhost // Short Life To Test Solidity Code In Terminal
|
||||
yarn hardhat compile
|
||||
yarn hardhat run scripts/deploy.js --network localhost
|
||||
yarn hardhat custom-task-name
|
||||
// Need create file in /tasks/custom-task-name.js
|
||||
// Add import in hardhat.config.js -> requir("/tasks/custom-task-name");
|
||||
yarn hardhat test
|
||||
yarn hardhat test --grep customSearchKeyword
|
||||
// Only will run the test with describe test that contain "customSearchKeyword"
|
||||
```
|
||||
Other Terminal Useful Command 2:
|
||||
```
|
||||
// For Getting Gas Used & Gas Price
|
||||
yarn hardhat test
|
||||
// Will create a file in ./gas-report.txt
|
||||
// With .env of etherscan API key
|
||||
// For Getting Coverage
|
||||
yarn hardhat coverage
|
||||
// Checking code usage & tested percentage
|
||||
// For Checking Code Best Practice
|
||||
yarn solhint contracts/*.sol
|
||||
// For Get Fake Price Feed On Localhost & Ganache
|
||||
yarn hardhat deploy --tags mocks --network localhost
|
||||
```
|
||||
Debug ```Node.js``` need to open **Javascript Debug Terminal** first, via ```ctrl + shift + p```
|
||||
|
||||
#### Reduce Gas Used:
|
||||
* Prioritize use ```private``` instead of ```public```
|
||||
* Use ```constant``` which declare once in constructor
|
||||
* Use ```immutable ``` which declare once only
|
||||
|
||||
This is because blockchain will have higher ```read``` and ```store``` gas fee on storage block, lesser in bytes code block.
|
||||
|
||||
|
||||
## 3. Known Issue
|
||||
* Dependencies combination is old, need update...
|
||||
* ...
|
||||
|
||||
### Find a bug?
|
||||
If you found an issue or would like to submit an improvement to this demo project, please submit an issue using the issues tab above.
|
||||
|
||||
## 4. Well-known Vulnerabilities
|
||||
* Reentrancy Attack
|
||||
* Locked with modifier while running withdraw function
|
||||
* Update variable immediately before call external function
|
||||
* Integer Overflow / Underflow
|
||||
* Use compiler version >0.8.0 have check in place
|
||||
* Front-Running
|
||||
* Use average gas fee / off peak times
|
||||
* Use commit-reveal schema
|
||||
* Use submarine send
|
||||
|
||||
## 5. Other Explored Features
|
||||
* Generate Random Words
|
||||
* Create subscription at https://vrf.chain.link/ (MetaMask #1)
|
||||
* Add Fund To The Created Subsciption Contract (MetaMask #2)
|
||||
* Add Consumer
|
||||
* Get Subscription ID
|
||||
* Open Remix To Prepare Deploy Consumer Contract https://docs.chain.link/vrf/v2-5/migration-from-v2
|
||||
* Change To Correct gwei limit hash address at https://docs.chain.link/vrf/v2/subscription/supported-networks
|
||||
* Adjust Setting - random words count, confirmation blocks etc.
|
||||
* Insert Subscription ID - v2 is uint64 BUT **v2.5 is uint256**
|
||||
* Deploy And Get hash address (MetaMask #3)
|
||||
* Insert in chainlink consumer (Metamask #4)
|
||||
* Ready to use, record down the consumer contract address
|
||||
|
||||
|
||||
## 6. Looking Web 3.0 Developer For Your Project?
|
||||
**Mr Hoelee** is Welcome Web 3.0 Remote Job, Contact Me Immediately Via WhatsApp <a href="https://wa.me/60175885290">+60175885290</a>
|
||||
.
|
||||
|
||||
Or You can email <a href="mailto:me@hoelee.com">me@hoelee.com</a> now. Thanks.
|
||||
|
||||
## 7. Like this project?
|
||||
If you are feeling generous, buy me a coffee! - <a href="https://buymeacoffee.com/hoelee">buymeacoffee.com/hoelee</a>
|
||||
@@ -250,4 +250,8 @@ contract Raffle is VRFConsumerBaseV2Plus, AutomationCompatibleInterface {
|
||||
function getInterval() public view returns (uint256) {
|
||||
return i_interval;
|
||||
}
|
||||
|
||||
function getLastTimeStamp() public view returns (uint256) {
|
||||
return s_lastTimeStamp;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -105,9 +105,7 @@ module.exports = {
|
||||
},
|
||||
],
|
||||
},
|
||||
/*
|
||||
mocha: {
|
||||
timeout: 500000, // 500 seconds max for running tests
|
||||
},
|
||||
*/
|
||||
};
|
||||
|
||||
+123
-9
@@ -13,11 +13,11 @@ const isDevelopment = developmentChains.includes(network.name);
|
||||
? describe.skip
|
||||
: describe("Raffle Unit Test", function () {
|
||||
let addressMock, addressRaffle, addressDeployer, addressPlayer;
|
||||
let raffle, vrfCoordinatorV2_5Mock, vrfCoordinatorV2_5Address, player;
|
||||
let raffle, vrfCoordinatorV2_5Mock, namedAccounts, subscriptionId;
|
||||
let chainId, raffleEntranceFee, interval;
|
||||
|
||||
beforeEach(async () => {
|
||||
const namedAccounts = await getNamedAccounts();
|
||||
namedAccounts = await getNamedAccounts();
|
||||
addressDeployer = namedAccounts.deployer;
|
||||
addressPlayer = namedAccounts.player;
|
||||
await deployments.fixture(["all"]);
|
||||
@@ -31,6 +31,31 @@ const isDevelopment = developmentChains.includes(network.name);
|
||||
);
|
||||
raffle = await ethers.getContractAt("Raffle", addressRaffle);
|
||||
|
||||
// Add fund to Mock for fulfillRandomWords
|
||||
const tx = await vrfCoordinatorV2_5Mock.createSubscription();
|
||||
const txReceipt = await tx.wait(1);
|
||||
subscriptionId = txReceipt.events[0].args.subId;
|
||||
//0x8d03209e7b30987dddca60349de1dc942195aadc9d6c6b5ab324388762f3b57e
|
||||
await vrfCoordinatorV2_5Mock.addConsumer(subscriptionId, raffle.address);
|
||||
|
||||
const FUND_AMOUNT = ethers.utils.parseEther("1"); // 1 Ether
|
||||
await vrfCoordinatorV2_5Mock.fundSubscription(subscriptionId, FUND_AMOUNT);
|
||||
// Retrieve subscription details
|
||||
const subscription = await vrfCoordinatorV2_5Mock.getSubscription(subscriptionId);
|
||||
// Verify if the consumer is added correctly
|
||||
console.log(`Consumers: ${subscription.consumers}`); // Should now include raffle.address
|
||||
|
||||
// Verify the balance
|
||||
const balance = subscription.balance;
|
||||
console.log(`Subscription balance: ${ethers.utils.formatEther(balance)} Ether`);
|
||||
|
||||
// Assert the balance to ensure it matches the FUND_AMOUNT
|
||||
assert.equal(
|
||||
balance.toString(),
|
||||
FUND_AMOUNT.toString(),
|
||||
"Subscription balance should match the fund amount",
|
||||
);
|
||||
|
||||
// raffle = await deployments.get("Raffle"); // Wrong, not the contract instance
|
||||
// raffle = await ethers.getContract("Raffle"); // With hardaht-ethers dependency override
|
||||
chainId = network.config.chainId;
|
||||
@@ -53,12 +78,6 @@ const isDevelopment = developmentChains.includes(network.name);
|
||||
|
||||
describe("enterRaffle", function () {
|
||||
it("should revert if not enough payment", async () => {
|
||||
/*
|
||||
const entranceFee = networkConfig[chainId]["raffleEntranceFee"];
|
||||
const entranceFeeString = ethers.utils.formatEther(entranceFee);
|
||||
const entranceFeeWei = ethers.utils.parseEther(entranceFeeString);
|
||||
const insufficientPayment = entranceFeeWei.sub(ethers.utils.parseEther("0.01")); // Set payment less than entrance fee
|
||||
*/
|
||||
const entranceFee = await raffle.getEntranceFee();
|
||||
const entranceFeeString = ethers.utils.formatEther(entranceFee);
|
||||
console.log(entranceFeeString); // 0.01
|
||||
@@ -126,7 +145,8 @@ const isDevelopment = developmentChains.includes(network.name);
|
||||
await network.provider.send("evm_increaseTime", [interval.toNumber() - 5]); // use a higher number here if this test fails
|
||||
await network.provider.request({ method: "evm_mine", params: [] }); // Alternative to write
|
||||
const { upkeepNeeded } = await raffle.callStatic.checkUpkeep("0x"); // upkeepNeeded = (timePassed && isOpen && hasBalance && hasPlayers)
|
||||
assert(!upkeepNeeded);
|
||||
|
||||
assert.isFalse(upkeepNeeded);
|
||||
});
|
||||
it("returns true if enough time has passed, has players, eth, and is open", async () => {
|
||||
await raffle.enterRaffle({ value: raffleEntranceFee });
|
||||
@@ -179,5 +199,99 @@ const isDevelopment = developmentChains.includes(network.name);
|
||||
vrfCoordinatorV2_5Mock.fulfillRandomWords(0, raffle.address),
|
||||
).to.be.revertedWith("InvalidRequest");
|
||||
});
|
||||
|
||||
// Complete Test
|
||||
it("picks a winner, reset the raffle, and send money to the winner", async () => {
|
||||
const additionalEntrances = 3;
|
||||
const startingAccountIndex = 2; // deployer = 0, player = 1
|
||||
let accounts = await ethers.getSigners(); // Many accounts
|
||||
// getNamedAccounts() is not working here, because only 2 accounts are named
|
||||
|
||||
for (
|
||||
let i = startingAccountIndex;
|
||||
i < startingAccountIndex + additionalEntrances;
|
||||
i++
|
||||
) {
|
||||
const accountConnectedRaffle = await raffle.connect(accounts[i]);
|
||||
await accountConnectedRaffle.enterRaffle({ value: raffleEntranceFee });
|
||||
}
|
||||
const startingTimeStamp = await raffle.getLastTimeStamp();
|
||||
// This will be more important for our staging tests...
|
||||
await new Promise(async (resolve, reject) => {
|
||||
raffle.once("WinnerPicked", async () => {
|
||||
// event listener for WinnerPicked
|
||||
console.log("WinnerPicked event fired!");
|
||||
// assert throws an error if it fails, so we need to wrap
|
||||
// it in a try/catch so that the promise returns event
|
||||
// if it fails.
|
||||
try {
|
||||
// Now lets get the ending values...
|
||||
const recentWinner = await raffle.getRecentWinner();
|
||||
const raffleState = await raffle.getRaffleState();
|
||||
const winnerBalance = await accounts[2].getBalance();
|
||||
const endingTimeStamp = await raffle.getLastTimeStamp();
|
||||
await expect(raffle.getPlayer(0)).to.be.reverted;
|
||||
// Comparisons to check if our ending values are correct:
|
||||
assert.equal(recentWinner.toString(), accounts[2].address);
|
||||
assert.equal(raffleState, 0);
|
||||
assert.equal(
|
||||
winnerBalance.toString(),
|
||||
startingBalance // startingBalance + ( (raffleEntranceFee * additionalEntrances) + raffleEntranceFee )
|
||||
.add(
|
||||
raffleEntranceFee
|
||||
.mul(additionalEntrances)
|
||||
.add(raffleEntranceFee),
|
||||
)
|
||||
.toString(),
|
||||
);
|
||||
assert(endingTimeStamp > startingTimeStamp);
|
||||
resolve(); // if try passes, resolves the promise
|
||||
} catch (e) {
|
||||
reject(e); // if try fails, rejects the promise
|
||||
}
|
||||
});
|
||||
|
||||
// kicking off the event by mocking the chainlink keepers and vrf coordinator
|
||||
try {
|
||||
const tx = await raffle.performUpkeep("0x");
|
||||
const txReceipt = await tx.wait(1);
|
||||
startingBalance = await accounts[2].getBalance();
|
||||
|
||||
// Log subscription details
|
||||
const subscription =
|
||||
await vrfCoordinatorV2_5Mock.getSubscription(subscriptionId);
|
||||
const balance = subscription.balance;
|
||||
const owner = subscription.owner;
|
||||
const consumers = subscription.consumers;
|
||||
|
||||
console.log(`Subscription ID: ${subscriptionId}`);
|
||||
console.log(`Subscription Owner: ${owner}`);
|
||||
console.log(
|
||||
`Subscription Balance: ${ethers.utils.formatEther(balance)} ETH`,
|
||||
);
|
||||
console.log(`Consumers: ${consumers}`); // This should include your Raffle contract address
|
||||
|
||||
// Verify the Raffle contract address matches the consumer
|
||||
assert(
|
||||
consumers.includes(raffle.address),
|
||||
"Raffle contract not authorized as a consumer!",
|
||||
);
|
||||
|
||||
await vrfCoordinatorV2_5Mock.fulfillRandomWords(
|
||||
txReceipt.events[1].args.requestId,
|
||||
raffle.address,
|
||||
);
|
||||
|
||||
// Log post-fulfillment details
|
||||
const updatedSubscription =
|
||||
await vrfCoordinatorV2_5Mock.getSubscription(subscriptionId);
|
||||
console.log(
|
||||
`Updated Subscription Balance: ${ethers.utils.formatEther(updatedSubscription.balance)} ETH`,
|
||||
);
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user