1. Môi trường viết Solidity: Remix IDE
- Giao diện người dùng thân thiện: Remix IDE cung cấp một giao diện người dùng trực quan, dễ sử dụng, giúp các nhà phát triển dễ dàng viết và quản lý mã nguồn.
- Biên dịch và triển khai: Remix IDE tích hợp sẵn trình biên dịch Solidity, cho phép bạn biên dịch và triển khai hợp đồng thông minh trực tiếp từ IDE.
- Gỡ lỗi: Remix IDE cung cấp công cụ gỡ lỗi mạnh mẽ, giúp bạn theo dõi và sửa lỗi trong quá trình phát triển hợp đồng thông minh.
- Plugin mở rộng: Remix IDE hỗ trợ các plugin, cho phép bạn mở rộng chức năng của IDE theo nhu cầu phát triển của mình.
- Tích hợp với các công cụ khác: Remix IDE có thể tích hợp với các công cụ và dịch vụ khác trong hệ sinh thái Ethereum, giúp tối ưu hóa quy trình phát triển.
Tài nguyên học tập:
2. Solidity Source File Layout
Dưới đây là một ví dụ về cách bố trí tệp nguồn Solidity, kèm theo một ví dụ đơn giản về hợp đồng thông minh:
// SPDX-License-Identifier: MITpragma solidity ^0.8.0;
// Định nghĩa hợp đồng
contract SimpleStorage {
// Biến lưu trữ một số nguyên
uint256 private storedData;
// Sự kiện được phát ra khi dữ liệu được cập nhật
event DataStored(uint256 data);
// Hàm để lưu trữ một số nguyên
function set(uint256 x) public {
storedData = x;
emit DataStored(x);
}
// Hàm để lấy số nguyên đã lưu trữ
function get() public view returns (uint256) {
return storedData;
}
}
- License Identifier: Dòng đầu tiên thường là một chú thích về giấy phép, ví dụ: // SPDX-License-Identifier: MIT.
- Pragma Directive: Sử dụng để chỉ định phiên bản của trình biên dịch Solidity, ví dụ: pragma solidity ^0.8.0;.
- Import Statements: (Nếu cần) Sử dụng để nhập các tệp Solidity khác.
- Contract Definition: Định nghĩa hợp đồng bắt đầu với từ khóa contract, theo sau là tên hợp đồng.
- State Variables: Khai báo các biến trạng thái để lưu trữ dữ liệu.
- Events: Khai báo các sự kiện để phát ra thông tin khi có sự thay đổi trạng thái.
- Functions: Định nghĩa các hàm để thực hiện các hành động, bao gồm:
- Constructor: (Nếu cần) Hàm khởi tạo chạy một lần khi hợp đồng được triển khai.
- Public/External Functions: Hàm có thể được gọi từ bên ngoài hợp đồng.
- Internal/Private Functions: Hàm chỉ có thể được gọi từ bên trong hợp đồng.
- Modifiers: (Nếu cần) Được sử dụng để thay đổi hành vi của các hàm.
- Fallback/Receive Functions: (Nếu cần) Được sử dụng để xử lý các giao dịch không có dữ liệu hoặc không khớp với hàm nào.
Ví dụ trên là một hợp đồng đơn giản để lưu trữ và truy xuất một số nguyên. Bạn có thể mở rộng hợp đồng này với các tính năng phức tạp hơn tùy theo nhu cầu của mình.
3. Biến và Kiểu Dữ Liệu
Solidity cung cấp nhiều kiểu dữ liệu khác nhau để giúp bạn quản lý và lưu trữ thông tin trong các hợp đồng thông minh. Dưới đây là một số kiểu dữ liệu phổ biến và cách sử dụng chúng.
Kiểu Dữ Liệu Cơ Bản
- Boolean: Kiểu dữ liệu boolean có hai giá trị:
truevàfalse.bool isActive = true; - Integer: Solidity hỗ trợ cả số nguyên có dấu (
int) và không dấu (uint). Bạn có thể chỉ định kích thước bit từ 8 đến 256, với bước nhảy là 8.int256 signedNumber = -42;uint256 unsignedNumber = 42; - Address: Kiểu dữ liệu
addresslưu trữ địa chỉ Ethereum, có thể được sử dụng để gửi và nhận Ether.address walletAddress = 0x1234567890123456789012345678901234567890; - String: Kiểu dữ liệu
stringlưu trữ chuỗi ký tự.string memory greeting = "Hello, Solidity!"; - Bytes: Kiểu dữ liệu
byteslà một mảng byte động, trong khibytes1đếnbytes32là các mảng byte tĩnh.bytes32 fixedData = "FixedSizeData";bytes dynamicData = "DynamicSizeData";
Kiểu Dữ Liệu Phức Tạp
- Array: Solidity hỗ trợ cả mảng tĩnh và động.
uint256[] dynamicArray;uint256[5] fixedArray; - Struct:
structcho phép bạn tạo các kiểu dữ liệu phức tạp hơn bằng cách nhóm các biến lại với nhau.struct Person { string name; uint256 age;} - Mapping:
mappinglà một cấu trúc dữ liệu ánh xạ các khóa với các giá trị. Nó tương tự như một bảng băm.mapping(address => uint256) public balances;
Biến
- State Variables: Biến trạng thái được lưu trữ trên blockchain và có thể được truy cập bởi tất cả các hàm trong hợp đồng.
uint256 public storedData; - Local Variables: Biến cục bộ chỉ tồn tại trong phạm vi của một hàm và không được lưu trữ trên blockchain.
function set(uint256 x) public { uint256 temp = x + 1; storedData = temp;} - Global Variables: Solidity cung cấp một số biến toàn cục, như
msg.sender(địa chỉ của người gọi hàm) vàblock.timestamp(thời gian khối hiện tại).address public owner = msg.sender;
Các kiểu dữ liệu và biến này là nền tảng để xây dựng các hợp đồng thông minh phức tạp hơn trong Solidity. Bạn có thể kết hợp chúng để tạo ra các cấu trúc dữ liệu và logic phù hợp với nhu cầu của mình.
4. Functions
Hàm trong Solidity là các khối mã thực hiện các tác vụ cụ thể. Chúng có thể được gọi từ bên trong hợp đồng hoặc từ bên ngoài. Dưới đây là một số khái niệm cơ bản về hàm trong Solidity.
Định nghĩa Hàm
Hàm được định nghĩa bằng từ khóa function, theo sau là tên hàm, danh sách tham số, và phần thân hàm.
function set(uint256 x) public { storedData = x;}
Các Loại Hàm
- Public Functions: Có thể được gọi từ bên ngoài hợp đồng và từ các hàm khác trong hợp đồng.
function set(uint256 x) public { storedData = x;} - Private Functions: Chỉ có thể được gọi từ bên trong hợp đồng.
function _internalFunction() private { // logic} - Internal Functions: Tương tự như private, nhưng có thể được gọi từ các hợp đồng kế thừa.
function _internalFunction() internal { // logic} - External Functions: Chỉ có thể được gọi từ bên ngoài hợp đồng.
function externalFunction() external { // logic}
Các Từ Khóa Quan Trọng
- View Functions: Không sửa đổi trạng thái của hợp đồng, chỉ đọc dữ liệu.
function get() public view returns (uint256) { return storedData;} - Pure Functions: Không đọc hoặc sửa đổi trạng thái của hợp đồng.
function add(uint256 a, uint256 b) public pure returns (uint256) { return a + b;} - Payable Functions: Có thể nhận Ether khi được gọi.
function deposit() public payable { // logic}
Modifiers
Modifiers là các khối mã có thể được sử dụng để thay đổi hành vi của hàm. Chúng thường được sử dụng để kiểm tra điều kiện trước khi thực hiện hàm.
modifier onlyOwner() { require(msg.sender == owner, "Not the contract owner");
;} function restrictedFunction() public onlyOwner {// logic}
Fallback và Receive Functions
- Fallback Function: Được gọi khi một giao dịch không khớp với bất kỳ hàm nào khác hoặc khi dữ liệu không được cung cấp.
fallback() external { // logic} - Receive Function: Được gọi khi hợp đồng nhận Ether mà không có dữ liệu.
receive() external payable { // logic}
Hàm trong Solidity là một phần quan trọng của hợp đồng thông minh, cho phép bạn thực hiện các tác vụ và tương tác với dữ liệu trên blockchain. Bạn có thể sử dụng các loại hàm và từ khóa khác nhau để tối ưu hóa và bảo mật hợp đồng của mình.
5. Array & Struct
Mảng (Array)
Mảng trong Solidity có thể là mảng tĩnh hoặc mảng động. Mảng tĩnh có kích thước cố định, trong khi mảng động có thể thay đổi kích thước.
Mảng Tĩnh
Mảng tĩnh có kích thước cố định và không thể thay đổi sau khi được khai báo.
uint256[5] fixedArray; // Mảng tĩnh với 5 phần tử
Mảng Động
Mảng động có thể thay đổi kích thước bằng cách sử dụng các phương thức như push và pop.
uint256[] dynamicArray;
function addElement(uint256 element) public {
dynamicArray.push(element); // Thêm phần tử vào cuối mảng
}
function removeLastElement() public {
dynamicArray.pop(); // Xóa phần tử cuối cùng của mảng
}
function getElement(uint256 index) public view returns (uint256) {
return dynamicArray[index]; // Truy cập phần tử tại chỉ số index
}
Cấu trúc (Struct)
Struct cho phép bạn tạo các kiểu dữ liệu phức tạp hơn bằng cách nhóm các biến lại với nhau. Struct có thể được sử dụng để tổ chức dữ liệu một cách có cấu trúc.
Định nghĩa và Sử dụng Struct
struct Person {
string name;
uint256 age;
}
Person[] public people;
function addPerson(string memory name, uint256 age) public {
Person memory newPerson = Person(name, age);
people.push(newPerson);
}
function getPerson(uint256 index) public view returns (string memory, uint256) {
Person memory person = people[index];
return (person.name, person.age);
}
Truy cập và Thay đổi Dữ liệu trong Struct
Bạn có thể truy cập và thay đổi dữ liệu trong struct bằng cách sử dụng các thuộc tính của nó.
function updatePersonName(uint256 index, string memory newName) public {
people[index].name = newName;
}
Mảng và struct là các công cụ mạnh mẽ trong Solidity, cho phép bạn quản lý và tổ chức dữ liệu một cách hiệu quả. Bạn có thể kết hợp chúng để tạo ra các cấu trúc dữ liệu phức tạp hơn, phù hợp với nhu cầu của hợp đồng thông minh của bạn.
6. Mapping
Mapping là một cấu trúc dữ liệu trong Solidity cho phép ánh xạ các khóa với các giá trị. Nó tương tự như một bảng băm và rất hữu ích để lưu trữ và truy xuất dữ liệu một cách hiệu quả.
Định nghĩa Mapping
Mapping được định nghĩa bằng từ khóa mapping, theo sau là kiểu dữ liệu của khóa và kiểu dữ liệu của giá trị.
mapping(address => uint256) public balances;
Trong ví dụ trên, balances là một mapping ánh xạ từ địa chỉ Ethereum (address) đến số dư (uint256).
Sử dụng Mapping
Gán Giá Trị
Bạn có thể gán giá trị cho một khóa trong mapping bằng cách sử dụng cú pháp tương tự như mảng.
function setBalance(address user, uint256 amount) public {
balances[user] = amount;
}
Truy Xuất Giá Trị
Truy xuất giá trị từ mapping cũng sử dụng cú pháp tương tự như mảng.
function getBalance(address user) public view returns (uint256) {
return balances[user];
}
Xóa Giá Trị
Bạn có thể xóa giá trị trong mapping bằng cách gán giá trị mặc định cho khóa đó.
function resetBalance(address user) public {
delete balances[user];
}
Mapping là một công cụ mạnh mẽ trong Solidity, cho phép bạn quản lý dữ liệu một cách hiệu quả và linh hoạt. Tuy nhiên, cần lưu ý rằng mapping không hỗ trợ duyệt qua các phần tử, do đó bạn cần có cách khác để theo dõi các khóa nếu cần.
Lưu Ý
- Không thể duyệt qua mapping: Mapping không hỗ trợ duyệt qua các phần tử như mảng. Bạn không thể lấy danh sách tất cả các khóa hoặc giá trị trong mapping.
- Giá trị mặc định: Nếu một khóa không tồn tại trong mapping, nó sẽ trả về giá trị mặc định của kiểu dữ liệu giá trị (ví dụ:
0chouint256). - Sử dụng với Structs: Mapping thường được sử dụng kết hợp với structs để lưu trữ dữ liệu phức tạp hơn.
Ví dụ: Mapping với Struct
struct Person {
string name;
uint256 age;
}
mapping(address => Person) public people;
function addPerson(address user, string memory name, uint256 age) public {
people[user] = Person(name, age);
}
function getPerson(address user) public view returns (string memory, uint256) {
Person memory person = people[user];
return (person.name, person.age);
}
Mapping là một công cụ mạnh mẽ trong Solidity, cho phép bạn quản lý dữ liệu một cách hiệu quả và linh hoạt. Tuy nhiên, cần lưu ý rằng mapping không hỗ trợ duyệt qua các phần tử, do đó bạn cần có cách khác để theo dõi các khóa nếu cần.
7. Storage, Calldata & Memory
6 vị trí dùng để lưu trữ và truy cập dữ liệu trong EVM:
- Stack: Đây là nơi các giá trị được lưu trữ tạm thời trong quá trình thực thi các hàm. EVM sử dụng một stack LIFO (Last In, First Out), có thể chứa các số nguyên (integer) và địa chỉ (address). Mọi phép toán trong EVM đều được thực hiện trên stack.
- Memory: Đây là không gian lưu trữ tạm thời được sử dụng để thực hiện các phép toán phức tạp như chuỗi (string) hoặc mảng động (dynamic array). Memory trong EVM là không gian dữ liệu có thể truy cập và được phân bổ trong quá trình thực thi, xoá đi sau khi thực thi hoàn tất.
- Storage: Đây là vị trí lưu trữ dữ liệu có tính khả dụng lâu dài nhất trong EVM. Storage được sử dụng để lưu trữ các giá trị dữ liệu trên blockchain Ethereum. Các biến được lưu trữ trong storage có thể được truy cập và cập nhật trong nhiều lần gọi hàm khác nhau và tồn tại cho đến khi hợp đồng bị xoá hoặc thay đổi bởi hợp đồng khác.
- Calldata: Đây là không gian lưu trữ dữ liệu chỉ đọc được từ các hàm ngoài hợp đồng. Calldata chứa các tham số và dữ liệu được truyền tới hợp đồng thông minh từ các giao dịch. Dữ liệu trong calldata không thể thay đổi bởi hợp đồng thông minh và chỉ có thể đọc.
- Code: Đây là mã bytecode của một hợp đồng thông minh Ethereum, tức là mã máy ảo Ethereum (EVM bytecode) mà EVM sẽ thực thi khi một hợp đồng được triển khai lên blockchain. Code này được lưu trữ trong blockchain và xác định hành vi và chức năng của hợp đồng thông minh.
- Logs: Đây là một công cụ để ghi lại và truy cập các sự kiện (events) xảy ra trong một hợp đồng thông minh. Logs được lưu trữ như là một phần của bản ghi giao dịch trong blockchain. Chúng là các dữ liệu không thể thay đổi và có thể được truy cập bởi các ứng dụng và dịch vụ khác để theo dõi và phân tích các sự kiện xảy ra trong hợp đồng thông minh.
Có 6 loại nhưng memory, storage & calldata là 3 loại mà chúng ta tiếp xúc nhiều nhất khi chúng ta làm việc với smart contract.
Tìm hiểu thêm:
- The Ethereum Virtual Machine – Mastering Ethereum Book
- ETHEREUM VIRTUAL MACHINE (EVM) – Ethereum Docs
8. Deploy Contract
Bước 1: Truy cập Remix IDE
- Mở trình duyệt web và truy cập Remix IDE.
Bước 2: Tạo Tệp Hợp đồng
- Trong Remix, nhấp vào biểu tượng “File Explorer” ở bên trái.
- Nhấp vào nút “New File” và đặt tên cho tệp của bạn, ví dụ:
SimpleStorage.sol. - Nhập mã hợp đồng của bạn vào tệp. Ví dụ:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract SimpleStorage {
uint256 private storedData;
function set(uint256 x) public {
storedData = x;
}
function get() public view returns (uint256) {
return storedData;
}
}
Bước 3: Biên dịch Hợp đồng
- Nhấp vào biểu tượng “Solidity Compiler” (hình chữ nhật và mũi tên) ở bên trái.
- Chọn phiên bản trình biên dịch phù hợp với hợp đồng của bạn.
- Nhấp vào nút “Compile SimpleStorage.sol” để biên dịch hợp đồng.
Bước 4: Triển khai Hợp đồng
- Nhấp vào biểu tượng “Deploy & Run Transactions” (hình máy tính) ở bên trái.
- Chọn môi trường triển khai từ danh sách thả xuống. Bạn có thể chọn “JavaScript VM” để triển khai trên máy ảo cục bộ hoặc “Injected Web3” để triển khai trên mạng Ethereum thực tế (yêu cầu MetaMask).
- Đảm bảo hợp đồng của bạn được chọn trong danh sách thả xuống “Contract”.
- Nhấp vào nút “Deploy” để triển khai hợp đồng.
Bước 5: Tương tác với Hợp đồng
- Sau khi triển khai, bạn sẽ thấy hợp đồng của mình xuất hiện trong phần “Deployed Contracts”.
- Mở rộng hợp đồng để xem các hàm có sẵn.
- Sử dụng các nút để gọi các hàm
setvàgetđể tương tác với hợp đồng.
Lưu ý
- Chi phí Gas: Khi triển khai trên mạng Ethereum thực tế, bạn sẽ cần Ether để trả phí gas.
- Kiểm tra kỹ mã nguồn: Đảm bảo mã nguồn của bạn không có lỗi và đã được kiểm tra kỹ trước khi triển khai trên mạng chính.
Triển khai hợp đồng thông minh với Remix IDE là một quy trình đơn giản và trực quan, giúp bạn nhanh chóng đưa hợp đồng của mình lên blockchain để thử nghiệm và sử dụng.
9. Tương tác với Contract đã Deploy
Sau khi triển khai hợp đồng thông minh của bạn, bạn có thể tương tác với nó thông qua Remix IDE. Dưới đây là các bước để thực hiện điều này:
Bước 1: Xem Hợp đồng đã Triển khai
- Trong Remix IDE, chuyển đến tab “Deploy & Run Transactions”.
- Trong phần “Deployed Contracts”, bạn sẽ thấy danh sách các hợp đồng đã triển khai.
- Nhấp vào mũi tên bên cạnh tên hợp đồng để mở rộng và xem các hàm có sẵn.
Bước 2: Gọi Hàm trong Hợp đồng
- Gọi Hàm
set:- Nhập giá trị bạn muốn lưu trữ vào ô đầu vào bên cạnh hàm
set. - Nhấp vào nút
setđể gọi hàm. Điều này sẽ gửi một giao dịch để cập nhật giá trị trong hợp đồng.
- Nhập giá trị bạn muốn lưu trữ vào ô đầu vào bên cạnh hàm
- Gọi Hàm
get:- Nhấp vào nút
getđể gọi hàm. Điều này sẽ trả về giá trị hiện tại được lưu trữ trong hợp đồng mà không cần gửi giao dịch.
- Nhấp vào nút
Bước 3: Kiểm tra Kết Quả
- Sau khi gọi hàm
set, bạn có thể kiểm tra kết quả bằng cách gọi hàmgetđể đảm bảo rằng giá trị đã được cập nhật chính xác. - Remix IDE sẽ hiển thị kết quả trả về của hàm
getngay bên dưới nút gọi hàm.
Lưu ý
- Phí Gas: Khi gọi các hàm thay đổi trạng thái (như
set), bạn sẽ cần trả phí gas. Remix sẽ tự động tính toán và trừ phí này khi bạn sử dụng môi trường “JavaScript VM” hoặc “Injected Web3”. - Kiểm tra Lỗi: Nếu có lỗi xảy ra khi gọi hàm, Remix sẽ hiển thị thông báo lỗi chi tiết để bạn có thể khắc phục.
Tương tác với hợp đồng thông minh đã triển khai trong Remix IDE là một cách tuyệt vời để thử nghiệm và kiểm tra các chức năng của hợp đồng trước khi triển khai trên mạng chính.
Contract Factory
Smart Contract Factory là gì?
- Smart Contract Factory là một hợp đồng thông minh có khả năng tạo ra các hợp đồng thông minh khác.
- Được sử dụng để triển khai nhiều hợp đồng có cùng cấu trúc nhưng có dữ liệu khác nhau.
Lợi ích của Smart Contract Factory
- Tái sử dụng mã nguồn: Giảm thiểu việc viết lại mã cho các hợp đồng có cấu trúc tương tự.
- Quản lý dễ dàng: Tạo và quản lý nhiều hợp đồng từ một điểm trung tâm.
- Tiết kiệm chi phí: Giảm chi phí triển khai bằng cách sử dụng một hợp đồng gốc để tạo ra các hợp đồng con.
Cách hoạt động của Smart Contract Factory
- Định nghĩa hợp đồng gốc: Hợp đồng gốc chứa logic và cấu trúc cơ bản.
- Triển khai Factory: Hợp đồng Factory được triển khai để tạo ra các hợp đồng con.
- Tạo hợp đồng con: Sử dụng hàm trong Factory để tạo và triển khai các hợp đồng con với dữ liệu cụ thể.