Хайде да си изцапаме ръцете.
Създайте папка във вашата основна директория и я наименувайте contracts
.
Създайте файл в папката contracts
и го наименувайте coffee.sol
.
ще използвате солидността, за да напишете интелигентния договор. Файловете на Solidity са именувани с разширението
.sol
, защото това е стандартното файлово разширение за изходния код на Solidity.
Добавете следния изходен код към coffee.sol
:
// SPDX-License-Identifier: MIT pragma solidity >=0.8.0 <0.9.0; contract Coffee { uint256 public constant coffeePrice = 0.0002 ether; uint256 public totalCoffeesSold; uint256 public totalEtherReceived; // Custom error definitions error QuantityMustBeGreaterThanZero(); error InsufficientEtherSent(uint256 required, uint256 sent); error DirectEtherTransferNotAllowed(); // Event to log coffee purchases event CoffeePurchased(address indexed buyer, uint256 quantity, uint256 totalCost); // Function to buy coffee function buyCoffee(uint256 quantity) external payable { if (quantity <= 0) { revert QuantityMustBeGreaterThanZero(); } uint256 totalCost = coffeePrice * quantity; if (msg.value > totalCost) { revert InsufficientEtherSent(totalCost, msg.value); } // Update the total coffees sold and total ether received totalCoffeesSold += quantity; totalEtherReceived += totalCost; console.log("Total ether received updated:", totalEtherReceived); console.log("Total coffee sold updated:", totalCoffeesSold); // Emit the purchase event emit CoffeePurchased(msg.sender, quantity, totalCost); // Refund excess Ether sent if (msg.value > totalCost) { uint256 refundAmount = msg.value - totalCost; payable(msg.sender).transfer(refundAmount); } } // Fallback function to handle Ether sent directly to the contract receive() external payable { revert DirectEtherTransferNotAllowed(); } // Public view functions to get totals function getTotalCoffeesSold() external view returns (uint256) { console.log("getTotalCoffeesSold :", totalCoffeesSold); return totalCoffeesSold; } function getTotalEtherReceived() external view returns (uint256) { console.log("getTotalEtherReceived :", totalEtherReceived); return totalEtherReceived; } }
//SPDX-License-Identifier: MIT
: Този идентификатор на лиценза показва, че кодът е лицензиран съгласно лиценза .
pragma solidity >=0.8.0 <0.9.0;
: Указва, че кодът е написан за версии на Solidity между 0.8.0 (включително) и 0.9.0 (изключително). uint256 public constant coffeePrice = 0.0002 ether; uint256 public totalCoffeesSold; uint256 public totalEtherReceived;
coffeePrice
: Задайте като постоянна стойност от 0.0002 ether
.totalCoffeesSold
: Проследява броя на продадените кафета.totalEtherReceived
: Проследява общия етер, получен от договора.Персонализираните грешки в Solidity са съобщения за грешка, които са пригодени за конкретен случай на употреба, а не съобщенията за грешка по подразбиране, които се предоставят от езика за програмиране . Те могат да помогнат за подобряване на потребителското изживяване и също могат да помогнат при отстраняване на грешки и поддържане на интелигентни договори.
error
: Тази ключова дума се използва за дефиниране на персонализирана грешка error QuantityMustBeGreaterThanZero(); error InsufficientEtherSent(uint256 required, uint256 sent); error DirectEtherTransferNotAllowed();
QuantityMustBeGreaterThanZero()
: Гарантира, че количеството е по-голямо от нула.InsufficientEtherSent(uint256 required, uint256 sent)
: Гарантира, че изпратеният Ether е достатъчен.DirectEtherTransferNotAllowed()
: Предотвратява директни прехвърляния на Ether към договора. event CoffeePurchased(address indexed buyer, uint256 quantity, uint256 totalCost);
CoffeePurchased(address indexed buyer, uint256 quantity, uint256 totalCost)
: Регистрира покупките на кафе.Функциите са самостоятелни модули от код, които изпълняват конкретна задача. Те елиминират излишъка от пренаписване на една и съща част от кода. Вместо това, разработчиците могат да извикат функция в програмата, когато е необходимо.
function buyCoffee(uint256 quantity) external payable { if (quantity <= 0) { revert QuantityMustBeGreaterThanZero(); } uint256 totalCost = coffeePrice * quantity; if (msg.value > totalCost) { revert InsufficientEtherSent(totalCost, msg.value); } // Update the total coffees sold and total ether received totalCoffeesSold += quantity; totalEtherReceived += totalCost; console.log("Total ether received updated:", totalEtherReceived); console.log("Total coffee sold updated:", totalCoffeesSold); // Emit the purchase event emit CoffeePurchased(msg.sender, quantity, totalCost); // Refund excess Ether sent if (msg.value > totalCost) { uint256 refundAmount = msg.value - totalCost; payable(msg.sender).transfer(refundAmount); } } receive() external payable { revert DirectEtherTransferNotAllowed(); } function getTotalCoffeesSold() external view returns (uint256) { console.log("getTotalCoffeesSold :", totalCoffeesSold); return totalCoffeesSold; } function getTotalEtherReceived() external view returns (uint256) { console.log("getTotalEtherReceived :", totalEtherReceived); return totalEtherReceived; }
buyCoffee(uint256 quantity) external payable
: Обработва покупките на кафе и извършва следните операции:receive() external payable
: Връща директните Ether трансфери, в случай че някой изпрати средства директно на адреса на договора.getTotalCoffeesSold() external view returns (uint256)
: Връща общия брой продадени кафета.getTotalEtherReceived() external view returns (uint256)
: Връща общия получен етер.
Инсталирайте Hardhat, като използвате следния команден ред.
npm install --save-dev hardhat
В същата директория, където инициализирате hardhat с помощта на този команден ред:
npx hardhat init
Изберете Create a Javascript project
като използвате бутона със стрелка надолу и натиснете enter.
Натиснете Enter, за да инсталирате в основната папка
Приемете всички подкани, като използвате y
на клавиатурата, включително зависимостите @nomicfoundation/hardhat-toolbox
Виждате този отговор по-долу, показващ, че сте инициализирали успешно
Ще забележите, че някои нови папки и файлове са добавени към вашия проект. напр.
Lock.sol
,iginition/modules
,test/Lock.js
иhardhat.config.cjs
. Не се тревожете за тях.
Единственият полезен са
iginition/modules
иhardhat.config.cjs
. По-късно ще разберете за какво се използват. Чувствайте се свободни да изтриетеLock.sol
в папкатаcontracts
иLock.js
в папкатаiginition/modules
.
Компилирайте договора, като използвате следния команден ред:
npx hardhat compile
Coffee.json
е ABI кодът във формат JSON, който ще извикате, когато взаимодействате с интелигентния договор. { "_format": "hh-sol-artifact-1", "contractName": "Coffee", "sourceName": "contracts/coffee.sol", "abi": [ { "inputs": [], "name": "DirectEtherTransferNotAllowed", "type": "error" }, { "inputs": [ { "internalType": "uint256", "name": "required", "type": "uint256" }, { "internalType": "uint256", "name": "sent", "type": "uint256" } ], "name": "InsufficientEtherSent", "type": "error" }, { "inputs": [], "name": "QuantityMustBeGreaterThanZero", "type": "error" }, { "anonymous": false, "inputs": [ { "indexed": true, "internalType": "address", "name": "buyer", "type": "address" }, { "indexed": false, "internalType": "uint256", "name": "quantity", "type": "uint256" }, { "indexed": false, "internalType": "uint256", "name": "totalCost", "type": "uint256" } ], "name": "CoffeePurchased", "type": "event" }, { "inputs": [ { "internalType": "uint256", "name": "quantity", "type": "uint256" } ], "name": "buyCoffee", "outputs": [], "stateMutability": "payable", "type": "function" }, { "inputs": [], "name": "coffeePrice", "outputs": [ { "internalType": "uint256", "name": "", "type": "uint256" } ], "stateMutability": "view", "type": "function" }, { "inputs": [], "name": "getTotalCoffeesSold", "outputs": [ { "internalType": "uint256", "name": "", "type": "uint256" } ], "stateMutability": "view", "type": "function" }, { "inputs": [], "name": "getTotalEtherReceived", "outputs": [ { "internalType": "uint256", "name": "", "type": "uint256" } ], "stateMutability": "view", "type": "function" }, { "inputs": [], "name": "totalCoffeesSold", "outputs": [ { "internalType": "uint256", "name": "", "type": "uint256" } ], "stateMutability": "view", "type": "function" }, { "inputs": [], "name": "totalEtherReceived", "outputs": [ { "internalType": "uint256", "name": "", "type": "uint256" } ], "stateMutability": "view", "type": "function" }, { "stateMutability": "payable", "type": "receive" } ], "bytecode": "", "deployedBytecode": "", "linkReferences": {}, "deployedLinkReferences": {} }
Под test
папка създайте нов файл и го наречете Coffee.
cjs. Във файла поставете този код по-долу:
const { loadFixture } = require("@nomicfoundation/hardhat-toolbox/network-helpers.js"); const { expect } = require("chai"); const pkg = require("hardhat"); const ABI = require('../artifacts/contracts/coffee.sol/Coffee.json'); const { web3 } = pkg; describe("Coffee Contract", function () { // Fixture to deploy the Coffee contract async function deployCoffeeFixture() { const coffeeContract = new web3.eth.Contract(ABI.abi); coffeeContract.handleRevert = true; const [deployer, buyer] = await web3.eth.getAccounts(); const rawContract = coffeeContract.deploy({ data: ABI.bytecode, }); // Estimate gas for the deployment const estimateGas = await rawContract.estimateGas({ from: deployer }); // Deploy the contract const coffee = await rawContract.send({ from: deployer, gas: estimateGas.toString(), gasPrice: "", }); console.log("Coffee contract deployed to: ", coffee.options.address); return { coffee, deployer, buyer, rawContract }; } describe("Deployment", function () { // Test to check initial values after deployment it("Should set the initial values correctly", async function () { const { coffee } = await loadFixture(deployCoffeeFixture); const totalCoffeesSold = await coffee.methods.totalCoffeesSold().call(); const totalEtherReceived = await coffee.methods.totalEtherReceived().call(); expect(totalCoffeesSold).to.equal("0"); expect(totalEtherReceived).to.equal("0"); }); }); describe("Buying Coffee", function () { // Test to check coffee purchase and event emission it("Should purchase coffee and emit an event", async function () { const { coffee, buyer } = await loadFixture(deployCoffeeFixture); const quantity = 3; const totalCost = web3.utils.toWei("0.0006", "ether"); // Buyer purchases coffee const receipt = await coffee.methods.buyCoffee(quantity).send({ from: buyer, value: totalCost }); // Check event const event = receipt.events.CoffeePurchased; expect(event).to.exist; expect(event.returnValues.buyer).to.equal(buyer); expect(event.returnValues.quantity).to.equal(String(quantity)); expect(event.returnValues.totalCost).to.equal(totalCost); }); // Test to check revert when quantity is zero it("Should revert if the quantity is zero", async function () { const { coffee, buyer } = await loadFixture(deployCoffeeFixture); expect( coffee.methods.buyCoffee(0).send({ from: buyer, value: web3.utils.toWei("0.0002", "ether") }) ).to.be.revertedWith("QuantityMustBeGreaterThanZero"); }); // Test to check if totalCoffeesSold and totalEtherReceived are updated correctly it("Should update totalCoffeesSold and totalEtherReceived correctly", async function () { const { coffee, buyer } = await loadFixture(deployCoffeeFixture); const quantity = 5; const totalCost = web3.utils.toWei("0.001", "ether"); await coffee.methods.buyCoffee(quantity).send({ from: buyer, value: totalCost }); const totalCoffeesSold = await coffee.methods.totalCoffeesSold().call(); const totalEtherReceived = await coffee.methods.totalEtherReceived().call(); expect(totalCoffeesSold).to.equal(String(quantity)); expect(totalEtherReceived).to.equal(totalCost); }); }); describe("Fallback function", function () { // Test to check revert when ether is sent directly to the contract it("Should revert if ether is sent directly to the contract", async function () { const { coffee, buyer } = await loadFixture(deployCoffeeFixture); expect( web3.eth.sendTransaction({ from: buyer, to: coffee.options.address, value: web3.utils.toWei("0.001", "ether"), }) ).to.be.revertedWith("DirectEtherTransferNotAllowed"); }); }); });
Този код тества функционалността на интелигентния договор за кафе. Той включва тестове за внедряване, закупуване на кафе и обработка на директни преводи на Ether към договора.
deployCoffeeFixture
async function deployCoffeeFixture() { const coffeeContract = new web3.eth.Contract(ABI.abi); coffeeContract.handleRevert = true; const [deployer, buyer] = await web3.eth.getAccounts(); const rawContract = coffeeContract.deploy({ data: ABI.bytecode, }); const estimateGas = await rawContract.estimateGas({ from: deployer }); const coffee = await rawContract.send({ from: deployer, gas: estimateGas.toString(), gasPrice: "", }); console.log("Coffee contract deployed to: ", coffee.options.address); return { coffee, deployer, buyer, rawContract }; }
describe("Deployment", function () { it("Should set the initial values correctly", async function () { const { coffee } = await loadFixture(deployCoffeeFixture); const totalCoffeesSold = await coffee.methods.totalCoffeesSold().call(); const totalEtherReceived = await coffee.methods.totalEtherReceived().call(); expect(totalCoffeesSold).to.equal("0"); expect(totalEtherReceived).to.equal("0"); }); });
totalCoffeesSold
и totalEtherReceived
са зададени на нула след внедряването. describe("Buying Coffee", function () { it("Should purchase coffee and emit an event", async function () { const { coffee, buyer } = await loadFixture(deployCoffeeFixture); const quantity = 3; const totalCost = web3.utils.toWei("0.0006", "ether"); const receipt = await coffee.methods.buyCoffee(quantity).send({ from: buyer, value: totalCost }); const event = receipt.events.CoffeePurchased; expect(event).to.exist; expect(event.returnValues.buyer).to.equal(buyer); expect(event.returnValues.quantity).to.equal(String(quantity)); expect(event.returnValues.totalCost).to.equal(totalCost); }); it("Should revert if the quantity is zero", async function () { const { coffee, buyer } = await loadFixture(deployCoffeeFixture); expect( coffee.methods.buyCoffee(0).send({ from: buyer, value: web3.utils.toWei("0.0002", "ether") }) ).to.be.revertedWith("QuantityMustBeGreaterThanZero"); }); it("Should update totalCoffeesSold and totalEtherReceived correctly", async function () { const { coffee, buyer } = await loadFixture(deployCoffeeFixture); const quantity = 5; const totalCost = web3.utils.toWei("0.001", "ether"); await coffee.methods.buyCoffee(quantity).send({ from: buyer, value: totalCost }); const totalCoffeesSold = await coffee.methods.totalCoffeesSold().call(); const totalEtherReceived = await coffee.methods.totalEtherReceived().call(); expect(totalCoffeesSold).to.equal(String(quantity)); expect(totalEtherReceived).to.equal(totalCost); }); });
CoffeePurchased
.totalCoffeesSold
и totalEtherReceived
се актуализират правилно след покупка. describe("Fallback function", function () { it("Should revert if ether is sent directly to the contract", async function () { const { coffee, buyer } = await loadFixture(deployCoffeeFixture); expect( web3.eth.sendTransaction({ from: buyer, to: coffee.options.address, value: web3.utils.toWei("0.001", "ether"), }) ).to.be.revertedWith("DirectEtherTransferNotAllowed"); }); });
console.log()
от вашия код на Solidity. За да го използвате, трябва да импортирате hardhat/console.sol
в кода на вашия договор по следния начин: //SPDX-License-Identifier: MIT pragma solidity >=0.8.0 <0.9.0; import "hardhat/console.sol"; contract Coffee { //... }
За да тествате договора, изпълнете следната команда във вашия терминал:
npx hardhat test
Трябва да имате резултат като този по-долу:
Ако стартирате
npx hardhat test
той автоматично компилира и тества интелигентния договор. Можете да го изпробвате и да ме уведомите в секцията за коментари.
Инсталирайте пакета dotenv и тези зависимости.
npm install dotenv npm install --save-dev @nomicfoundation/hardhat-web3-v4 'web3@4'
Това ще добави Web3.Js и Dotenv към вашия проект, като го включи в папката 'node_modules'.
импортирайте ги във вашия файл hardhat.config.cjs
require('dotenv').config(); require("@nomicfoundation/hardhat-toolbox"); require("@nomicfoundation/hardhat-web3-v4"); const HardhatUserConfig = require("hardhat/config"); module.exports = { solidity: "0.8.24", } };
Създайте .env
файл в основната папка.
Вземете частния ключ на вашия акаунт от вашия портфейл MetaMask и dRPC API ключ.
Съхранявайте ги във вашия .env
файл.
DRPC_API_KEY=your_drpc_api_key PRIVATE_KEY=your_wallet_private_key
Актуализирайте файла hardhat.config.cjs
за да включите конфигурацията на Sepolia Testnet:
require('dotenv').config(); require("@nomicfoundation/hardhat-toolbox"); require("@nomicfoundation/hardhat-web3-v4"); const HardhatUserConfig = require("hardhat/config"); const dRPC_API_KEY = process.env.VITE_dRPC_API_KEY; const PRIVATE_KEY = process.env.VITE_PRIVATE_KEY; module.exports = { solidity: "0.8.24", networks: { sepolia: { url: `//lb.drpc.org/ogrpc?network=sepolia&dkey=${dRPC_API_KEY}`, accounts: [`0x${PRIVATE_KEY}`], } } };
Създайте нов скриптов файл в папката ignition/module
и го наречете deploy.cjs
. Добавете следния код, за да внедрите вашия интелигентен договор:
const { buildModule } = require("@nomicfoundation/hardhat-ignition/modules"); const CoffeeModule = buildModule("CoffeeModule", (m) => { const coffee = m.contract("Coffee"); return { coffee }; }); module.exports = CoffeeModule;
Разположете интелигентния договор, като изпълните следната команда във вашия терминал:
npx hardhat ignition deploy ./ignition/modules/deploy.cjs --network sepolia
След като изпълните командния ред, ще бъдете помолени да Confirm deploy to network sepolia (11155111)? (y/n)
, въведете y
. Трябва да видите адреса на вашия внедрен интелигентен договор в терминала при успешно внедряване.
Можете също да получите достъп до адреса на договора във файла deployed_addresses.json
.
Поздравления, успешно внедрихте своя интелигентен договор в Sepolia Testnet. 🎉