Solidity Programming Note

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 byte
  • byte
  • fixed/ufixed: chưa support hoàn toàn
  • enum
  • function type
  • struct
  • mapping: tương tự hashtable.
    Cấu trúc mapping(_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ào
  • array:
    • Fixed-sized array: bytesI (với 0 < I <= 32): alias for byte[I], <type>[]
    • Dynamic-sized array: string, bytes, <type>[]

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, publicprivate. 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óa this. Ví dụ ta có thể gọi phương thức được khai báo function extFunc() external bằng cách this.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ó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)

Note

  • 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ức external 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á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 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ức internal 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ệnh delegatecall

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 contract
  • view: không thay đổi thuộc tính của contract
  • constant: constant at runtime
  • payable: phải có với các phương thức sử dụng msg.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ính public thông qua getter 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
  • 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

Đố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ữ memorystorage (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.