Dari pemahaman tumpukan teknologi untuk pengembangan DApp Web3, Anda pasti telah mempelajari tumpukan teknologi inti untuk pengembangan dApp web3, peran RPC dalam pengembangan dApp, dan cara menggunakan dRPC untuk membuat akun, menghasilkan kunci API, titik akhir, analisis titik akhir, menambahkan dana ke Akun dRPC Anda, dan memeriksa saldo Anda.
Peran dRPC dalam penerapan kontrak pintar adalah untuk menyederhanakan proses penyiapan node Ethereum, sehingga memudahkan pengembang untuk berinteraksi dan menerapkan hanya dengan satu baris kode.
Dalam artikel ini, Anda akan menulis, mengompilasi, menguji, dan menyebarkan kontrak pintar pembayaran kopi ke Ethereum Sepolia Testnet menggunakan titik akhir dRPC dan kunci API.
Fitur-fiturnya meliputi:
Ayo kita kotori tanganmu.
Buat Folder di bawah direktori root Anda, dan beri nama contracts
.
Buat File di bawah folder contracts
, dan beri nama coffee.sol
.
Anda akan menggunakan solidity untuk menulis kontrak pintar. File solidity diberi nama dengan ekstensi
.sol
karena merupakan ekstensi file standar untuk kode sumber Solidity.
Tambahkan kode sumber berikut ke 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
: Pengidentifikasi lisensi ini menunjukkan bahwa kode tersebut dilisensikan di bawah Lisensi Massachusetts Institute of Technology (MIT) .
pragma solidity >=0.8.0 <0.9.0;
: Menentukan bahwa kode ditulis untuk versi Solidity antara 0.8.0 (inklusif) dan 0.9.0 (eksklusif). uint256 public constant coffeePrice = 0.0002 ether; uint256 public totalCoffeesSold; uint256 public totalEtherReceived;
coffeePrice
: Ditetapkan sebagai nilai konstan 0.0002 ether
.totalCoffeesSold
: Melacak jumlah kopi yang terjual.totalEtherReceived
: Melacak total Ether yang diterima oleh kontrak.Kesalahan khusus dalam Solidity adalah pesan kesalahan yang disesuaikan dengan kasus penggunaan tertentu, bukan pesan kesalahan default yang disediakan oleh bahasa pemrograman . Pesan kesalahan ini dapat membantu meningkatkan pengalaman pengguna, dan juga dapat membantu dalam debugging dan pemeliharaan kontrak pintar.
Untuk menentukan kesalahan khusus di Solidity, Anda dapat menggunakan sintaks berikut:
error
: Kata kunci ini digunakan untuk menentukan kesalahan khusus error QuantityMustBeGreaterThanZero(); error InsufficientEtherSent(uint256 required, uint256 sent); error DirectEtherTransferNotAllowed();
QuantityMustBeGreaterThanZero()
: Memastikan kuantitas lebih besar dari nol.InsufficientEtherSent(uint256 required, uint256 sent)
: Memastikan Ether yang dikirim mencukupi.DirectEtherTransferNotAllowed()
: Mencegah transfer Ether langsung ke kontrak.Peristiwa adalah bagian dari kontrak yang menyimpan argumen yang diteruskan dalam log transaksi saat dipancarkan. Peristiwa biasanya digunakan untuk memberi tahu aplikasi pemanggil tentang status kontrak saat ini menggunakan fitur pencatatan EVM. Peristiwa memberi tahu aplikasi tentang perubahan yang dibuat pada kontrak, yang kemudian dapat digunakan untuk menjalankan logika terkait.
event CoffeePurchased(address indexed buyer, uint256 quantity, uint256 totalCost);
CoffeePurchased(address indexed buyer, uint256 quantity, uint256 totalCost)
: Mencatat pembelian kopi.Fungsi adalah modul kode mandiri yang menyelesaikan tugas tertentu. Fungsi menghilangkan redundansi penulisan ulang bagian kode yang sama. Sebaliknya, pengembang dapat memanggil fungsi dalam program saat diperlukan.
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
: Menangani pembelian kopi dan melakukan operasi berikut:receive() external payable
: Mengembalikan transfer Ether langsung jika seseorang mengirim dana ke alamat kontrak secara langsung.getTotalCoffeesSold() external view returns (uint256)
: Mengembalikan total kopi yang terjual.getTotalEtherReceived() external view returns (uint256)
: Mengembalikan total Ether yang diterima.Di sini, Anda akan menggunakan Hardhat untuk mengkompilasi kontrak pintar.
Instal Hardhat menggunakan prompt perintah berikut.
npm install --save-dev hardhat
Anda akan mendapatkan respons di bawah ini setelah instalasi berhasil.
Di direktori yang sama tempat Anda menginisialisasi hardhat menggunakan command prompt ini:
npx hardhat init
Pilih Create a Javascript project
dengan menggunakan tombol panah bawah dan tekan enter.
Tekan enter untuk menginstal di folder root
Terima semua perintah menggunakan y
pada keyboard Anda termasuk dependensi @nomicfoundation/hardhat-toolbox
Anda melihat respons di bawah ini yang menunjukkan bahwa Anda telah berhasil menginisialisasi
Anda akan melihat beberapa folder dan file baru telah ditambahkan ke proyek Anda. Misalnya,
Lock.sol
,iginition/modules
,test/Lock.js
, danhardhat.config.cjs
. Jangan khawatir tentang hal itu.
Satu-satunya yang berguna adalah
iginition/modules
danhardhat.config.cjs
. Anda akan tahu kegunaannya nanti. Jangan ragu untuk menghapusLock.sol
di foldercontracts
danLock.js
di folderiginition/modules
.
Kompilasi kontrak menggunakan prompt perintah berikut:
npx hardhat compile
Coffee.json
terdapat kode ABI dalam format JSON yang akan Anda panggil saat berinteraksi dengan kontrak pintar. { "_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": {} }
Menulis skrip pengujian otomatis saat membangun kontrak pintar Anda sangat penting dan sangat direkomendasikan. Skrip ini berfungsi seperti autentikasi dua faktor (2FA), yang memastikan bahwa kontrak pintar Anda berfungsi seperti yang diharapkan sebelum menerapkannya ke jaringan aktif.
Di bawah folder test
buat file baru, dan beri nama Coffee.
. Di dalam file tersebut, tempel kode berikut ini:
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"); }); }); });
Kode ini menguji fungsionalitas kontrak pintar Coffee. Kode ini mencakup pengujian untuk penerapan, pembelian kopi, dan penanganan transfer Ether langsung ke kontrak.
Berikut rinciannya:
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
dan totalEtherReceived
ditetapkan ke nol setelah penerapan. 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
dan totalEtherReceived
diperbarui dengan benar setelah pembelian. 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"); }); });
Setelah Anda menulis skrip pengujian, Anda akan :
console.log()
dari kode Solidity Anda. Untuk menggunakannya, Anda harus mengimpor hardhat/console.sol
dalam kode kontrak Anda seperti ini: //SPDX-License-Identifier: MIT pragma solidity >=0.8.0 <0.9.0; import "hardhat/console.sol"; contract Coffee { //... }
Untuk menguji kontrak, jalankan perintah berikut di terminal Anda:
npx hardhat test
Anda seharusnya mendapatkan keluaran seperti di bawah ini:
Ini menunjukkan bahwa kontrak pintar Anda berfungsi sebagaimana diharapkan.
Jika Anda menjalankan
npx hardhat test
ia akan secara otomatis mengkompilasi dan menguji kontrak pintar. Anda dapat mencobanya dan memberi tahu saya di bagian komentar.
Di sini, Anda akan menerapkan kontrak pintar Anda ke Sepolia Testnet. Testnet memungkinkan Anda menguji kontrak pintar Anda di lingkungan yang menyerupai mainnet Ethereum tanpa menimbulkan biaya yang signifikan. Jika Anda merasa nyaman dengan fungsi dApp, Anda dapat menerapkannya kembali ke Ethereum Mainnet.
Instal paket dotenv dan dependensi ini.
npm install dotenv npm install --save-dev @nomicfoundation/hardhat-web3-v4 'web3@4'
Ini akan menambahkan Web3.Js dan Dotenv ke proyek Anda dengan memasukkannya dalam folder 'node_modules'.
impor mereka ke file hardhat.config.cjs
Anda
require('dotenv').config(); require("@nomicfoundation/hardhat-toolbox"); require("@nomicfoundation/hardhat-web3-v4"); const HardhatUserConfig = require("hardhat/config"); module.exports = { solidity: "0.8.24", } };
Buat file .env
di folder root Anda.
Dapatkan kunci pribadi akun Anda dari dompet MetaMask dan kunci API dRPC Anda.
Simpan dalam file .env
Anda .
DRPC_API_KEY=your_drpc_api_key PRIVATE_KEY=your_wallet_private_key
Perbarui file hardhat.config.cjs
untuk menyertakan Konfigurasi Testnet Sepolia:
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}`], } } };
Buat file skrip baru di folder ignition/module
, dan beri nama deploy.cjs
. Tambahkan kode berikut untuk menerapkan kontrak pintar Anda:
const { buildModule } = require("@nomicfoundation/hardhat-ignition/modules"); const CoffeeModule = buildModule("CoffeeModule", (m) => { const coffee = m.contract("Coffee"); return { coffee }; }); module.exports = CoffeeModule;
Terapkan kontrak pintar dengan menjalankan perintah berikut di terminal Anda:
npx hardhat ignition deploy ./ignition/modules/deploy.cjs --network sepolia
Setelah menjalankan command prompt, Anda akan diminta untuk Confirm deploy to network sepolia (11155111)? (y/n)
, ketik y
. Anda akan melihat alamat kontrak pintar yang Anda deploy di terminal setelah deploy berhasil.
Anda juga dapat mengakses alamat kontrak di file deployed_addresses.json
.
Selamat, Anda telah berhasil menerapkan kontrak pintar Anda ke Sepolia Testnet.
Artikel ini mengajarkan Anda cara menulis kontrak pintar pembayaran, menguji, mengompilasi, dan menyebarkan kontrak pintar menggunakan hardhat CLI.
Pada artikel berikutnya, Anda akan mempelajari cara membangun front end untuk dApp ini. UI ini akan terdiri dari:
Melampaui Pesan Default: Menguasai Kesalahan Kustom di Solidity
Menguasai Kesalahan Kustom di Solidity: Tingkatkan Kontrak Cerdas Anda Melampaui Pesan Default