Izprotot Web3 DApp izstrādes tehnoloģiju kopu, jums ir jābūt apguvušam galveno tehnoloģiju kopumu Web3 dApp izstrādei, RPC lomu dApp izstrādē un to, kā izmantot dRPC, lai izveidotu kontu, ģenerētu API atslēgu, galapunktus, galapunktu analīzi. , pievienojiet līdzekļus savam dRPC kontam un pārbaudiet savu bilanci.
dRPC loma viedo līgumu izvietošanā ir vienkāršot Ethereum mezgla iestatīšanas procesu, atvieglojot izstrādātāju mijiedarbību un izvietošanu, izmantojot tikai vienu koda rindiņu.
Šajā rakstā jūs rakstīsit, apkoposit, pārbaudīsit un izvietosit kafijas maksājumu viedo līgumu Ethereum Sepolia Testnet, izmantojot dRPC galapunktu un API atslēgu.
Funkcijas ietver:
Sasmērēsim rokas.
Savā saknes direktorijā izveidojiet mapi un nosauciet to par contracts
.
Izveidojiet failu mapē contracts
un nosauciet to coffee.sol
.
viedā līguma rakstīšanai izmantosit soliditāti. Solidity faili ir nosaukti ar paplašinājumu
.sol
, jo tas ir Solidity avota koda standarta faila paplašinājums.
Pievienojiet coffee.sol
šādu avota kodu:
// 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
: Šis licences identifikators norāda, ka kods ir licencēts saskaņā ar Masačūsetsas Tehnoloģiju institūta (MIT) licenci.
pragma solidity >=0.8.0 <0.9.0;
: norāda, ka kods ir rakstīts Solidity versijām no 0.8.0 (ieskaitot) līdz 0.9.0 (izņemot). uint256 public constant coffeePrice = 0.0002 ether; uint256 public totalCoffeesSold; uint256 public totalEtherReceived;
coffeePrice
: iestatīt kā nemainīgu vērtību 0.0002 ether
.totalCoffeesSold
: izseko pārdoto kafiju skaitu.totalEtherReceived
: izseko kopējo Ether, kas saņemts saskaņā ar līgumu.Solidity pielāgotās kļūdas ir kļūdu ziņojumi, kas ir pielāgoti konkrētam lietošanas gadījumam, nevis noklusējuma kļūdu ziņojumi, ko nodrošina programmēšanas valoda . Tie var palīdzēt uzlabot lietotāja pieredzi, kā arī palīdzēt atkļūdot un uzturēt viedos līgumus.
Lai Solidity definētu pielāgotu kļūdu, varat izmantot šādu sintaksi:
error
: šis atslēgvārds tiek izmantots, lai definētu pielāgotu kļūdu error QuantityMustBeGreaterThanZero(); error InsufficientEtherSent(uint256 required, uint256 sent); error DirectEtherTransferNotAllowed();
QuantityMustBeGreaterThanZero()
: nodrošina, ka daudzums ir lielāks par nulli.InsufficientEtherSent(uint256 required, uint256 sent)
: nodrošina, ka nosūtītais ēteris ir pietiekams.DirectEtherTransferNotAllowed()
: novērš tiešu ētera pārsūtīšanu uz līgumu.Notikums ir līguma daļa, kas saglabā transakciju žurnālos nodotos argumentus, kad tie tiek izvadīti. Notikumi parasti tiek izmantoti, lai informētu zvanīšanas lietojumprogrammu par līguma pašreizējo stāvokli, izmantojot EVM reģistrēšanas funkciju. Viņi paziņo lietojumprogrammām par līgumos veiktajām izmaiņām, kuras pēc tam var izmantot saistītās loģikas palaišanai.
event CoffeePurchased(address indexed buyer, uint256 quantity, uint256 totalCost);
CoffeePurchased(address indexed buyer, uint256 quantity, uint256 totalCost)
: reģistrē kafijas pirkumus.Funkcijas ir autonomi koda moduļi, kas veic noteiktu uzdevumu. Tie novērš vienu un to pašu koda fragmentu pārrakstīšanu. Tā vietā izstrādātāji var izsaukt funkciju programmā, kad tas ir nepieciešams.
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
: Apstrādā kafijas iegādi un veic šādas darbības:receive() external payable
: atgriež tiešos ētera pārskaitījumus, ja kāds tieši nosūta līdzekļus uz līguma adresi.getTotalCoffeesSold() external view returns (uint256)
: atgriež kopējo pārdoto kafiju daudzumu.getTotalEtherReceived() external view returns (uint256)
: atgriež kopējo saņemto Ether.Šeit jūs izmantosit Hardhat, lai apkopotu viedo līgumu.
Instalējiet Hardhat, izmantojot šo komandu uzvedni.
npm install --save-dev hardhat
Pēc veiksmīgas instalēšanas jūs saņemsit tālāk sniegto atbildi.
Tajā pašā direktorijā, kurā inicializējat hardhat, izmantojot šo komandu uzvedni:
npx hardhat init
Atlasiet Create a Javascript project
izmantojot lejupvērsto bultiņas pogu, un nospiediet taustiņu Enter.
Nospiediet enter, lai instalētu saknes mapē
Pieņemiet visas uzvednes, izmantojot tastatūras taustiņu y
, tostarp @nomicfoundation/hardhat-toolbox
atkarības
Tālāk ir redzama šī atbilde, kas parāda, ka esat veiksmīgi inicializējies
Jūs pamanīsit, ka jūsu projektam ir pievienotas dažas jaunas mapes un faili. piemēram,
Lock.sol
,iginition/modules
,test/Lock.js
unhardhat.config.cjs
. Neuztraucieties par viņiem.
Vienīgie noderīgie ir
iginition/modules
unhardhat.config.cjs
. Vēlāk jūs uzzināsit, kam tie tiek izmantoti. Jūtieties brīvi dzēstLock.sol
mapēcontracts
unLock.js
mapēiginition/modules
.
Sastādiet līgumu, izmantojot šādu komandu uzvedni:
npx hardhat compile
Coffee.json
ir ABI kods JSON formātā, kas tiks izsaukts, mijiedarbojoties ar viedo līgumu. { "_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": {} }
Automātiska testa skripta rakstīšana viedā līguma izveides laikā ir ļoti svarīga un ļoti ieteicama. Tas darbojas kā divu faktoru autentifikācija (2FA), nodrošinot, ka viedais līgums darbojas, kā paredzēts, pirms tā izvietošanas tiešraidē.
test
mapē izveidojiet jaunu failu un nosauciet to Coffee.
cjs. Failā ielīmējiet šo kodu zemāk:
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: "10000000000", }); 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"); }); }); });
Šis kods pārbauda Coffee viedā līguma funkcionalitāti. Tas ietver testus izvietošanai, kafijas pirkšanai un tiešo ētera pārsūtīšanas uz līgumu apstrādi.
Šeit ir sadalījums:
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: "10000000000", }); 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
un totalEtherReceived
pēc izvietošanas ir iestatītas uz nulli. 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
un totalEtherReceived
pēc pirkuma ir pareizi atjaunināti. 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"); }); });
Kad esat uzrakstījis testa skriptu, jūs :
console.log()
no sava Solidity koda. Lai to izmantotu, līguma kodā ir jāimportē hardhat/console.sol
šādi: //SPDX-License-Identifier: MIT pragma solidity >=0.8.0 <0.9.0; import "hardhat/console.sol"; contract Coffee { //... }
Lai pārbaudītu līgumu, terminālī palaidiet šādu komandu:
npx hardhat test
Jums vajadzētu būt šādai izvadei:
Tas parāda, ka jūsu viedais līgums darbojas tā, kā tas ir paredzēts.
Ja palaižat
npx hardhat test
tas automātiski apkopo un pārbauda viedo līgumu. Varat to izmēģināt un paziņot man komentāru sadaļā.
Šeit jūs izvietosit savu viedo līgumu Sepolia Testnet. Testnet ļauj pārbaudīt viedo līgumu vidē, kas atdarina Ethereum galveno tīklu, neradot ievērojamas izmaksas. Ja jums ir labas dApp funkcijas, varat to atkārtoti izvietot uz Ethereum Mainnet.
Instalējiet dotenv pakotni un šīs atkarības.
npm install dotenv npm install --save-dev @nomicfoundation/hardhat-web3-v4 'web3@4'
Tas jūsu projektam pievienos Web3.Js un Dotenv, iekļaujot tos mapē “node_modules”.
importējiet tos failā 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", } };
Savā saknes mapē izveidojiet .env
failu.
Iegūstiet sava konta privāto atslēgu no sava MetaMask maka un dRPC API atslēgas.
Saglabājiet tos savā .env
failā.
DRPC_API_KEY=your_drpc_api_key PRIVATE_KEY=your_wallet_private_key
Atjauniniet failu hardhat.config.cjs
lai iekļautu Sepolia Testnet konfigurāciju:
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: `https://lb.drpc.org/ogrpc?network=sepolia&dkey=${dRPC_API_KEY}`, accounts: [`0x${PRIVATE_KEY}`], } } };
Izveidojiet jaunu skripta failu zem ignition/module
mapē un piešķiriet tam nosaukumu deploy.cjs
. Pievienojiet šo kodu, lai izvietotu viedo līgumu:
const { buildModule } = require("@nomicfoundation/hardhat-ignition/modules"); const CoffeeModule = buildModule("CoffeeModule", (m) => { const coffee = m.contract("Coffee"); return { coffee }; }); module.exports = CoffeeModule;
Izvietojiet viedo līgumu, terminālī izpildot šādu komandu:
npx hardhat ignition deploy ./ignition/modules/deploy.cjs --network sepolia
Pēc komandu uzvednes palaišanas jums tiks lūgts Confirm deploy to network sepolia (11155111)? (y/n)
, ierakstiet y
. Pēc veiksmīgas izvietošanas terminālī vajadzētu redzēt izvietotā viedā līguma adresi.
Līguma adresei varat piekļūt arī failā deployed_addresses.json
.
Apsveicam! Jūs esat veiksmīgi izvietojis viedo līgumu Sepolia Testnet. 🎉
Šajā rakstā ir aprakstīts, kā uzrakstīt maksājumu viedo līgumu, pārbaudīt, apkopot un izvietot viedo līgumu, izmantojot hardhat CLI.
Nākamajā rakstā jūs uzzināsit, kā izveidot šīs dApp priekšējo daļu. Šī lietotāja saskarne sastāvēs no:
Papildus noklusējuma ziņojumiem: pielāgoto kļūdu apgūšana stabilitātē
Pielāgotu kļūdu apgūšana stabilitātē: paaugstiniet viedos līgumus, pārsniedzot noklusējuma ziņojumus