Solidity Programming Note
Solidity là ngôn ngữ lập trình hướng contract, được sử dụng để viết smart contract trong hệ sinh thái Ethereum.
Các tài liệu chính về lập trình bằng solidity có thể tìm thấy tại Solidity documentation. Trang này đi theo trình tự giới thiệu ngôn ngữ, môi trường dev, getting started with samples và cuối cùng là đi giới thiệu sâu về các thành phần của ngôn ngữ.
Bài viết này mình xin phép note lại một số điểm cần lưu ý liên quan đến ngôn ngữ Solidity.
Bài viết này được viết theo các feature của phiên bản mới nhất của Solidity 0.4.21
1. Kiểu dữ liệu
bool
int/uint
(int8
-int256
/uint8
-uint256
) (step by 8 bit)address
: ~20 bytebyte
fixed/ufixed
: chưa support hoàn toànenum
function type
struct
mapping
: tương tự hashtable.
Cấu trúcmapping(_KeyType => _ValueType)
với_KeyType
có thể là bất kì kiểu gì ngoại trừmapping
,dynamic-sized array
,contract
,enum
,struct
._ValueType
có thể là bất kì kiểu dữ liệu nàoarray
:- Fixed-sized array:
bytesI
(với0 < I <= 32
): alias forbyte[I]
,<type>[]
- Dynamic-sized array:
string
,bytes
,<type>[]
- Fixed-sized array:
2. Contract
Solidity xây dựng xoay quanh thành phần chính là contract
. Về cơ bản, contract tương tự class trong OOP với các thuộc tính (state variables
) và phương thức (functions
). Các khái niệm abstract contract (contract với ít nhất 1 phương thức chưa được thực thi), interface (chỉ gồm chữ ký thao tác) cũng tương tự OOP.
Contract trong solidity cho phép đa kế thừa. Việc này dễ đến nhiều vấn đề, trong số đó có Diamond Problem. Solidity sử dụng thuật toán C3 Linearzation tương tự python để xử lí đa kế thừa. Do vậy thứ tự khai báo kế thừa sau từ khóa is
là rất quan trọng.
Ex: Xét đoạn code dưới.
contract X {}
contract A is X {}
contract C is A, X {}
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 contract A thì A lại override X. Điều này sinh ra lỗi biên dịch
3. Phương thức
function <function name>(<parameter types>) {internal|external|public|private} [pure|constant|view|payable] [returns (<return types>)]
Có 2 cách gọi 1 phương thức:
- internal calling: con trỏ instruction nhảy đến vị trí function trong bộ nhớ để thực thi
- external calling: EVM thực hiện lệnh
call
Ứng với 2 cách gọi này có 4 mức visibility với phương thức: internal
, external
, public
và private
. Mặc định, phương thức sẽ có mức visibility là public
. Mô tả khái quát 4 mức này như sau:
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à 1 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óathis
. 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à 1 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óathis
) 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
)
Note
public
vsexternal
: phương thứcpublic
cần sao chép tham số vàomemory
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ệucalldata
. Đố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áp bộ nhớ là tốn kém hơn so với đọc trực tiếp từcalldata
(tốn gas hơn)private
vsinternal
: phương thứcprivate
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
vsdelegatecall
: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
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 có thể kể đến đó là:
pure
: không truy cập/thay đổi các thuộc tính của contractview
: không thay đổi thuộc tính của contractconstant
: constant at runtimepayable
: phải có với các phương thức sử dụngmsg.value
/ nếu muốn nhận Ether
Fallback function
Mỗi contract có duy nhất 1 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 msg.data
để lấy dữ liệu truyền theo lời gọi), cũng 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
}
}
4. Thuộc tính
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ínhpublic
thông quagetter function
.
Ex: Đối với mapping
:
contract Complex {
mapping (uint => address) public data;
}
thì getter function
tương ứng: function data(uint key) public returns (address)
private
,internal
: tương tự visibility của phương thức
Data location: memory/storage/calldata
Trong EVM, có 3 dạng lưu trữ: memory
(lưu trữ không bền lâu), storage
(lưu trữ bền vững), calldata
(lưu trữ không bên vững, chỉ áp 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:
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 1 bản sao chép độc lập. Phép gán vào 1 biến cục bộ chỉ là phép gán 1 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.