Solidity là ngôn ngữ lập trình hướng hợp đồng (contract-oriented programming language), được sử dụng rộng rãi để phát triển các hợp đồng thông minh (smart contract) trên nền tảng Ethereum. Để tìm hiểu sâu hơn về ngôn ngữ này, bạn có thể tham khảo tài liệu chính thức tại Solidity documentation.
Bài viết này dựa trên các tính năng của phiên bản Solidity 0.4.21.
Mục Lục
Lập Trình Blockchain Với Solidity
1. Các Kiểu Dữ Liệu
Solidity cung cấp nhiều kiểu dữ liệu khác nhau để phù hợp với các nhu cầu lập trình, bao gồm:
-
bool: Biểu diễn giá trị đúng (true) hoặc sai (false).
-
int/uint (int8-int256/uint8-uint256): Biểu diễn số nguyên có dấu (int) và không dấu (uint) với kích thước từ 8 đến 256 bit, bước nhảy 8 bit.
-
address: Biểu diễn địa chỉ Ethereum, có kích thước khoảng 20 byte.
-
byte: Biểu diễn một byte dữ liệu.
-
fixed/ufixed: Kiểu dữ liệu số thực dấu phẩy động (chưa được hỗ trợ đầy đủ).
-
enum: Kiểu liệt kê, cho phép định nghĩa một tập hợp các giá trị được đặt tên.
-
function type: Kiểu dữ liệu biểu diễn một hàm.
-
struct: Kiểu cấu trúc, cho phép nhóm các biến có kiểu dữ liệu khác nhau thành một đơn vị duy nhất.
-
mapping: Tương tự như hashtable, cho phép ánh xạ từ một kiểu dữ liệu khóa (_KeyType) sang một kiểu dữ liệu giá trị (_ValueType). Kiểu khóa có thể là bất kỳ kiểu nào ngoại trừ mapping, dynamic-sized array, contract, enum, struct. Kiểu giá trị có thể là bất kỳ kiểu dữ liệu nào.
-
array: Mảng, có hai loại:
- Fixed-sized array: Mảng có kích thước cố định, ví dụ:
bytesI
(với 0 < I <= 32) là alias củabyte[I]
,[]
. - Dynamic-sized array: Mảng có kích thước động, ví dụ:
string
,bytes
,[]
.
- Fixed-sized array: Mảng có kích thước cố định, ví dụ:
2. Contract
Contract là thành phần cốt lõi trong Solidity. Tương tự như class trong lập trình hướng đối tượng (OOP), contract bao gồm các thuộc tính (state variables) và phương thức (functions). Các khái niệm abstract contract (contract có ít nhất một phương thức chưa được triển khai) và interface (chỉ chứa chữ ký của các phương thức) cũng tương tự như trong OOP.
Solidity hỗ trợ đa kế thừa, điều này có thể dẫn đến các vấn đề phức tạp, ví dụ như Diamond Problem. Để giải quyết vấn đề này, Solidity sử dụng thuật toán C3 Linearization, tương tự như Python, để xử lý đa kế thừa. Do đó, thứ tự khai báo kế thừa sau từ khóa is
rất quan trọng. Ví dụ:
contract X {}
contract A is X {}
contract C is A, X {}
Trong ví dụ trên, contract C
sẽ kế thừa theo thứ tự X
, A
, tức là X
sẽ override A
. Tuy nhiên, theo khai báo của contract A
, thì A
lại override X
. Điều này sẽ dẫn đến lỗi biên dịch.
3. Phương Thức (Functions)
Cú pháp khai báo phương thức trong Solidity như sau:
function () {internal|external|public|private} [pure|constant|view|payable] [returns ()]
Có hai cách gọi một phương thức:
- Internal calling: Con trỏ instruction nhảy đến vị trí của function trong bộ nhớ để thực thi.
- External calling: EVM (Ethereum Virtual Machine) thực hiện lệnh
call
.
Ứng với hai cách gọi này, có bốn mức visibility (khả năng hiển thị) cho phương thức: internal
, external
, public
và private
. Mặc định, phương thức sẽ có mức visibility là public
.
- internal: Chỉ có thể truy cập từ các phương thức bên trong contract hoặc từ contract con (kế thừa) (internal calling).
- external: Là một phần của giao diện của contract, do đó có thể được truy cập từ contract khác. Bản thân contract chứa phương thức cũng có thể gọi nó bằng cách sử dụng từ khóa
this
. Ví dụ, ta có thể gọi phương thức được khai báofunction extFunc() external
bằng cáchthis.extFunc()
(external calling). - public: Là một phần của giao diện của contract, có thể được gọi từ trong contract (mà không cần từ khóa
this
) hoặc từ contract khác (internal calling/external calling). - private: Chỉ có thể truy cập từ các phương thức bên trong contract (internal calling).
Lưu ý:
- public vs external: Phương thức
public
cần sao chép tham số vào memory trước khi thực thi (để có thể gọi từ cả trong và ngoài contract), trong khi đó phương thứcexternal
có thể đọc trực tiếp từ vùng dữ liệu calldata. Đối với các kiểu dữ liệu phức tạp (array/struct), việc sao chép và cấp phát bộ nhớ tốn kém hơn so với việc đọc trực tiếp từ calldata (tốn gas hơn). - private vs internal: Phương thức
private
chỉ có thể truy cập từ trong chính contract của nó, trong khi phương thứcinternal
có thể được gọi từ cả contract con của nó. - call vs delegatecall:
call
sử dụng context (storage) của contract được gọi. Trong khi đó,delegatecall
sử dụng context của contract gọi lệnhdelegatecall
.
Kiến trúc máy ảo Ethereum (EVM) và cách các contract tương tác.
Function Modifier
Modifier được sử dụng để kiểm soát ngữ cảnh của phương thức. Một số modifier mặc định bao gồm:
- pure: Không truy cập/thay đổi các thuộc tính của contract.
- view: Không thay đổi thuộc tính của contract.
- constant: (Đã không còn được khuyến khích sử dụng, thay bằng
view
) Hằng số tại thời điểm runtime. - payable: Bắt buộc phải có với các phương thức sử dụng
sentayho.com.vn
nếu muốn nhận Ether.
Fallback Function
Mỗi contract có duy nhất một phương thức không có tên (gọi là fallback function). Phương thức này không có tham số (tuy nhiên vẫn có thể sử dụng sentayho.com.vn
để lấy dữ liệu truyền theo lời gọi), và không có giá trị trả về. Phương thức này chỉ được gọi khi không có bất kỳ phương thức nào của contract khớp với lời gọi.
contract Sink {
// fallback function
function() public payable {
// executable code here
}
}
Minh họa hoạt động của Fallback function trong Solidity.
4. Thuộc Tính (State Variables)
Khác với phương thức, thuộc tính của contract chỉ có 3 mức visibility (mặc định là internal
):
-
public: Các contract khác có thể truy cập thuộc tính
public
thông qua getter function.Ví dụ: Đối với mapping:
contract Complex { mapping (uint => address) public data; }
thì getter function tương ứng là:
function data(uint key) public returns (address)
. -
private, internal: Tương tự như visibility của phương thức.
Data Location: Memory/Storage/Calldata
Trong EVM, có 3 dạng lưu trữ dữ liệu:
-
memory: Lưu trữ tạm thời, dữ liệu sẽ bị mất khi transaction kết thúc.
-
storage: Lưu trữ vĩnh viễn, dữ liệu được lưu trữ trên blockchain.
-
calldata: Lưu trữ tạm thời, chỉ dùng để lưu trữ tham số của phương thức
external
. -
Forced data location:
- Tham số của phương thức
external
:calldata
. - Thuộc tính (state variables):
storage
.
- Tham số của phương thức
-
Default data location:
- Thuộc tính và giá trị trả về của phương thức:
memory
. - Biến cục bộ:
storage
.
- Thuộc tính và giá trị trả về của phương thức:
Đối với các kiểu dữ liệu phức tạp (struct, array), phép gán giữa vùng lưu trữ memory
và storage
(giữa tham số và thuộc tính) luôn tạo ra một bản sao chép độc lập. Phép gán vào một biến cục bộ chỉ là phép gán một con trỏ, trỏ vào thuộc tính của contract. Hiểu được cơ chế này để kiểm soát việc gán và sao chép dữ liệu phức tạp (do chi phí cấp phát và sao chép dữ liệu khá cao) để kiểm soát gas cần cho mỗi transaction.
So sánh các loại data location trong Solidity: memory, storage, calldata.