
Phí Gas trên mạng chủ Ethereum luôn là vấn đề khó giải quyết, đặc biệt là trong thời gian mạng bị quá tải. Trong thời điểm cao điểm, người dùng thường phải trả khoản phí giao dịch rất cao. Do đó, việc tối ưu hóa phí Gas trong giai đoạn phát triển hợp đồng thông minh là rất quan trọng. Tối ưu hóa mức tiêu thụ Gas không chỉ giúp giảm chi phí giao dịch hiệu quả, mà còn nâng cao hiệu suất giao dịch, mang lại trải nghiệm sử dụng blockchain kinh tế và hiệu quả hơn cho người dùng.
Bài viết này sẽ tổng quan về cơ chế phí Gas của Máy ảo Ethereum (EVM), các khái niệm cốt lõi liên quan đến tối ưu hóa phí Gas, cũng như các thực tiễn tốt nhất khi tối ưu hóa phí Gas khi phát triển hợp đồng thông minh. Hy vọng thông qua những nội dung này, có thể mang lại những gợi ý và hỗ trợ thiết thực cho các nhà phát triển, đồng thời cũng giúp người dùng thông thường hiểu rõ hơn về cách vận hành phí Gas của EVM, cùng nhau đối mặt với những thách thức trong hệ sinh thái blockchain.
Giới thiệu về cơ chế phí Gas của EVM
Trong các mạng tương thích EVM, "Gas" là đơn vị đo lường khả năng tính toán cần thiết để thực hiện một hoạt động cụ thể.
Sơ đồ bên dưới minh họa cấu trúc của EVM. Trong đó, tiêu thụ Gas được chia thành ba phần: thực thi các thao tác, gọi tin nhắn bên ngoài và đọc/ghi bộ nhớ và lưu trữ.

Nguồn: Trang web chính thức của Ethereum[1]
Do mỗi giao dịch đều cần tài nguyên tính toán để thực hiện, nên sẽ phải trả một khoản phí nhất định để ngăn chặn vòng lặp vô hạn và các cuộc tấn công từ chối dịch vụ (DoS). Khoản phí cần thiết để hoàn thành một giao dịch được gọi là "phí Gas".
Kể từ khi EIP-1559 (Hard Fork London) có hiệu lực, phí Gas được tính theo công thức sau:
Phí Gas = số lượng Gas sử dụng * (phí cơ bản + phí ưu tiên)
Phí cơ bản sẽ bị đốt, còn phí ưu tiên sẽ được dùng như một khoản thưởng để khuyến khích các trình xác minh thêm giao dịch vào blockchain. Khi gửi giao dịch, nếu đặt phí ưu tiên cao hơn, sẽ tăng khả năng giao dịch được đưa vào khối tiếp theo. Đây giống như một "tiền tip" mà người dùng trả cho các trình xác minh.
1. Hiểu về tối ưu hóa Gas trong EVM
Khi biên dịch hợp đồng thông minh bằng Solidity, hợp đồng sẽ được chuyển đổi thành một chuỗi "mã lệnh" (opcodes).
Bất kỳ đoạn mã lệnh nào (ví dụ: tạo hợp đồng, thực hiện lời gọi tin nhắn, truy cập bộ nhớ tài khoản và thực hiện các thao tác trên máy ảo) đều có một mức tiêu thụ Gas được công nhận, và những chi phí này được ghi trong sách vàng Ethereum[2].

Sau nhiều lần sửa đổi EIP, chi phí Gas của một số mã lệnh đã được điều chỉnh, có thể khác với sách vàng. Để biết thông tin chi tiết về chi phí mã lệnh mới nhất, vui lòng tham khảo tại đây[3].
2. Các khái niệm cơ bản về tối ưu hóa Gas
Ý tưởng cốt lõi của tối ưu hóa Gas là ưu tiên sử dụng các thao tác có hiệu quả chi phí cao trên blockchain EVM, tránh các thao tác có chi phí Gas đắt đỏ.
Trong EVM, các thao tác sau có chi phí thấp:
- Đọc/ghi biến trong bộ nhớ
- Đọc các hằng số và biến không thể thay đổi
- Đọc/ghi biến cục bộ
- Đọc các biến calldata, chẳng hạn như mảng calldata và cấu trúc
- Gọi hàm nội bộ
Các thao tác có chi phí cao bao gồm:
- Đọc/ghi các biến trạng thái lưu trong bộ nhớ hợp đồng
- Gọi hàm bên ngoài
- Các thao tác vòng lặp
Các thực tiễn tốt nhất để tối ưu hóa phí Gas của EVM
Dựa trên các khái niệm cơ bản trên, chúng tôi đã tổng hợp một danh sách các thực tiễn tốt nhất để tối ưu hóa phí Gas cho cộng đồng nhà phát triển. Bằng cách tuân thủ các thực tiễn này, các nhà phát triển có thể giảm mức tiêu thụ Gas của hợp đồng thông minh, giảm chi phí giao dịch và xây dựng các ứng dụng hiệu quả và thân thiện với người dùng hơn.
1. Giảm thiểu việc sử dụng lưu trữ
Trong Solidity, Storage (lưu trữ) là một tài nguyên hạn chế, chi phí Gas của nó cao hơn rất nhiều so với Memory (bộ nhớ). Mỗi khi hợp đồng thông minh đọc hoặc ghi dữ liệu từ lưu trữ, đều sẽ phát sinh chi phí Gas đáng kể.
Theo định nghĩa trong sách vàng Ethereum, chi phí của các thao tác lưu trữ cao hơn hơn 100 lần so với các thao tác bộ nhớ. Ví dụ, các chỉ thị mload và mstore chỉ tiêu tốn 3 đơn vị Gas, trong khi các thao tác lưu trữ như sload và sstore, ngay cả trong trường hợp tối ưu nhất, cũng cần ít nhất 100 đơn vị.

Các cách để giới hạn việc sử dụng lưu trữ bao gồm:
- Lưu trữ dữ liệu tạm thời trong bộ nhớ
- Giảm số lần sửa đổi lưu trữ: Bằng cách lưu trữ các kết quả trung gian trong bộ nhớ, sau đó chỉ gán kết quả cuối cùng cho biến lưu trữ.
2. Đóng gói biến
Số lượng Storage slot (ô lưu trữ) được sử dụng trong hợp đồng thông minh và cách mà nhà phát triển biểu diễn dữ liệu sẽ ảnh hưởng rất lớn đến mức tiêu thụ Gas.
Trình biên dịch Solidity sẽ đóng gói các biến lưu trữ liên tiếp trong quá trình biên dịch, và sử dụng ô lưu trữ 32 byte làm đơn vị cơ bản để lưu trữ các biến. Đóng gói biến có nghĩa là sắp xếp các biến một cách hợp lý để nhiều biến có thể phù hợp với một ô lưu trữ duy nhất.
Bên trái là một cách triển khai kém hiệu quả, sẽ tiêu tốn 3 ô lưu trữ; bên phải là một cách triển khai hiệu quả hơn.

Với việc điều chỉnh nhỏ này, nhà phát triển có thể tiết kiệm 20.000 đơn vị Gas (cần 20.000 Gas để lưu trữ một ô lưu trữ chưa sử dụng), nhưng bây giờ chỉ cần hai ô lưu trữ.
Vì mỗi ô lưu trữ đều tiêu tốn Gas, nên việc đóng gói biến bằng cách giảm số ô lưu trữ cần thiết sẽ giúp tối ưu hóa việc sử dụng Gas.
3. Tối ưu hóa kiểu dữ liệu
Một biến có thể được biểu diễn bằng nhiều kiểu dữ liệu khác nhau, nhưng các kiểu dữ liệu khác nhau cũng có chi phí thao tác khác nhau. Lựa chọn kiểu dữ liệu phù hợp sẽ giúp tối ưu hóa việc sử dụng Gas.
Ví dụ, trong Solidity, số nguyên có thể được phân thành các kích thước khác nhau: uint8, uint16, uint32 và các loại khác. Vì EVM thực hiện các thao tác theo đơn vị 256 bit, nên sử dụng uint8 có nghĩa là EVM phải chuyển đổi nó thành uint256 trước, và việc chuyển đổi này sẽ tiêu tốn thêm Gas.

Chúng ta có thể so sánh chi phí Gas của uint8 và uint256 thông qua đoạn mã trong hình. Hàm UseUint() tiêu tốn 120.382 đơn vị Gas, trong khi hàm UseUInt8() tiêu tốn 166.111 đơn vị Gas.
Xét riêng, việc sử dụng uint256 rẻ hơn uint8. Tuy nhiên, nếu kết hợp với tối ưu hóa đóng gói biến như chúng tôi đề xuất trước đó, thì lại khác. Nếu nhà phát triển có thể đóng gói bốn biến uint8 vào một ô lưu trữ, thì tổng chi phí lặp lại chúng sẽ thấp hơn bốn biến uint256. Như vậy, hợp đồng thông minh có thể đọc/ghi một ô lưu trữ và đưa bốn biến uint8 vào bộ nhớ/lưu trữ trong một lần thao tác.
4. Sử dụng biến cố định thay vì biến động
Nếu dữ liệu có thể kiểm soát được trong 32 byte, hãy sử dụng kiểu dữ liệu bytes32 thay vì bytes hoặc strings. Nhìn chung

Khi đọc trực tiếp giá trị từ calldata, bỏ qua thao tác memory trung gian. Cách tối ưu này làm giảm chi phí thực thi xuống chỉ 2.413 đơn vị Gas, cải thiện hiệu quả Gas lên 35%.
7. Sử dụng từ khóa Constant/Immutable khi có thể
Constant/Immutable biến không được lưu trữ trong bộ nhớ hợp đồng. Các biến này được tính toán tại thời điểm biên dịch và lưu trữ trong mã byte của hợp đồng. Do đó, chi phí truy cập của chúng thấp hơn nhiều so với bộ nhớ, vì vậy nên sử dụng từ khóa Constant hoặc Immutable khi có thể.
8. Sử dụng Unchecked khi chắc chắn không xảy ra tràn/đọc dưới
Khi nhà phát triển có thể xác định rằng các phép toán số học sẽ không gây tràn hoặc đọc dưới, họ có thể sử dụng từ khóa unchecked được giới thiệu trong Solidity v0.8.0 để tránh các kiểm tra tràn hoặc đọc dưới không cần thiết, từ đó tiết kiệm chi phí Gas.
Trong hình dưới, do bị giới hạn bởi điều kiện ikhối không kiểm tra được coi là an toàn và tiết kiệm Gas hơn.

Ngoài ra, trình biên dịch từ phiên bản 0.8.0 trở lên không còn cần thư viện SafeMath vì nó đã tích hợp sẵn bảo vệ chống tràn và đọc dưới.
9. Tối ưu hóa Modifier
Mã của Modifier được nhúng vào các hàm được sửa đổi, và mỗi lần sử dụng Modifier, mã của nó sẽ được sao chép. Điều này làm tăng kích thước mã byte và tăng chi phí Gas. Dưới đây là một cách để tối ưu hóa chi phí Gas của Modifier:
Trước khi tối ưu:

Sau khi tối ưu:

Trong ví dụ này, bằng cách cấu trúc lại logic thành hàm nội bộ _checkOwner(), cho phép tái sử dụng hàm nội bộ này trong Modifier, giúp giảm kích thước mã byte và chi phí Gas.
10. Tối ưu hóa ngắn mạch
Đối với các toán tử ||và &&, việc tính toán logic sẽ xảy ra ngắn mạch, nghĩa là nếu điều kiện đầu tiên đủ để xác định kết quả của biểu thức logic, thì điều kiện thứ hai sẽ không được đánh giá.
Để tối ưu hóa chi phí Gas, nên đặt các điều kiện có chi phí tính toán thấp ở phía trước, để có thể bỏ qua các tính toán tốn kém hơn.
Các lời khuyên chung bổ sung
1. Xóa mã không sử dụng
Nếu hợp đồng có các hàm hoặc biến không được sử dụng, hãy xóa chúng. Đây là cách trực tiếp nhất để giảm chi phí triển khai và giữ cho hợp đồng gọn nhẹ.
Dưới đây là một số lời khuyên hữu ích:
- Sử dụng thuật toán hiệu quả nhất để tính toán. Nếu hợp đồng trực tiếp sử dụng kết quả của một số tính toán, thì nên loại bỏ các quy trình tính toán d冗余.
- Trong Ethereum, nhà phát triển có thể nhận được phần thưởng Gas bằng cách giải phóng không gian lưu trữ. Khi không còn cần một biến nào đó, hãy sử dụng từ khóa delete để xóa nó hoặc đặt nó về giá trị mặc định.
- Tối ưu vòng lặp: Tránh các thao tác vòng lặp tốn kém, hãy kết hợp các vòng lặp khi có thể và di chuyển các tính toán lặp đi.
2. Sử dụng hợp đồng được biên dịch trước
Các hợp đồng được biên dịch trước cung cấp các hàm thư viện phức tạp, chẳng hạn như mã hóa và băm. Vì mã không chạy trên EVM mà chạy trên nút máy khách cục bộ, nên cần ít Gas hơn. Sử dụng các hợp đồng được biên dịch trước có thể giảm lượng tính toán cần thiết để thực thi hợp đồng thông minh, do đó tiết kiệm Gas.
Ví dụ về các hợp đồng được biên dịch trước bao gồm thuật toán chữ ký số đường cong elliptic (ECDSA) và thuật toán băm SHA2-256. Bằng cách sử dụng các hợp đồng được biên dịch trước này trong hợp đồng thông minh, nhà phát triển có thể giảm chi phí Gas và cải thiện hiệu suất ứng dụng.
Để xem danh sách đầy đủ các hợp đồng được biên dịch trước được hỗ trợ bởi mạng Ethereum, vui lòng tham khảo tại đây[4].
3. Sử dụng mã汇编nội tuyến
Mã汇编nội tuyến (in-line assembly) cho phép nhà phát triển viết mã cấp thấp nhưng hiệu quả có thể được EVM thực thi trực tiếp, mà không cần sử dụng các mã lệnh tốn kém của Solidity. Mã汇编nội tuyến cũng cho phép kiểm soát chính xác hơn việc sử dụng bộ nhớ và lưu trữ, từ đó giảm thêm chi phí Gas. Ngoài ra, mã汇编nội tuyến có thể thực hiện một số thao tác phức tạp mà chỉ sử dụng Solidity khó thực hiện, cung cấp thêm linh hoạt để tối ưu hóa chi phí Gas.
Dưới đây là ví dụ về mã sử dụng mã汇编nội tuyến để tiết kiệm Gas:

Từ hình trên, có thể thấy trường hợp sử dụng mã汇编nội tuyến thứ hai có hiệu quả Gas cao hơn so với trường hợp sử dụng tiêu chuẩn.
Tuy nhiên, sử dụng mã汇编nội tuyến cũng có thể mang lại rủi ro và dễ gây lỗi. Do đó, cần cẩn thận khi sử dụng và chỉ dành cho những nhà phát triển có kinh nghiệm.
4. Sử dụng các giải pháp Layer 2
Sử dụng các giải pháp Layer 2 có thể giảm lượng dữ liệu cần lưu trữ và tính toán trên mạng chủ Ethereum.
Các giải pháp Layer 2 như rollups, sidechain và state channels có thể chuyển giao xử lý giao dịch từ chuỗi chính Ethereum, từ đó đạt được giao dịch nhanh hơn và rẻ hơn. Bằng cách đóng gói nhiều giao dịch lại với nhau, các giải pháp này giảm số lượng giao dịch trên chuỗi, từ đó giảm phí Gas. Sử dụng các giải pháp Layer 2 cũng có thể cải thiện khả năng mở rộng của Ethereum, cho phép nhiều người dùng và ứng dụng tham gia mạng mà không gây quá tải và tắc nghẽn.
5. Sử dụng các công cụ và thư viện tối ưu
Có nhiều công cụ tối ưu hóa có sẵn, chẳng hạn như trình tối ưu hóa solc, trình tối ưu hóa xây dựng của Truffle và trình biên dịch Solidity của Remix.

Các công cụ này có thể giúp giảm thiểu kích thước mã byte, xóa mã không sử dụng và giảm số lần thực thi cần thiết để chạy hợp đồng thông minh. Kết hợp với các thư viện tối ưu hóa Gas khác như "solmate", nhà phát triển có thể hiệu quả giảm chi phí Gas và cải thiện hiệu suất của hợp đồng thông minh.
Kết luận
Tối ưu hóa chi phí Gas là một bước quan trọng đối với nhà phát triển, vừa giúp giảm thiểu chi phí giao dịch vừa nâng cao hiệu quả của hợp đồng thông minh trên các mạng tương thích EVM. Bằng cách ưu tiên thực hiện các thao tác tiết kiệm chi phí, giảm thiểu việc sử dụng bộ nhớ, sử dụng mã汇编nội tuyến và tuân theo các thực hành tốt khác được thảo luận trong bài viết này, nhà phát triển có thể hiệu quả giảm chi phí Gas của hợp đồng.
Tuy nhiên, cần lưu ý rằng trong quá trình tối ưu, nhà phát triển phải hết sức cẩn trọng để tránh các lỗ hổng bảo mật. Quá trình tối ưu hóa mã và giảm chi phí Gas không bao giờ được đánh đổi bằng an toàn vốn có của hợp đồng thông minh.
[1] : https://ethereum.org/en/developers/docs/gas/
[2] : https://ethereum.github.io/yellowpaper/paper.pdf
[3] : https://www.evm.codes/
[4] : https://www.evm.codes/precompiled


