
Mười phương pháp tốt nhất để tối ưu hóa Gas trong hợp đồng thông minh Ethereum
Tuyển chọn TechFlowTuyển chọn TechFlow

Mười phương pháp tốt nhất để tối ưu hóa Gas trong hợp đồng thông minh Ethereum
Bằng cách tuân theo những thực hành 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, hạ thấp chi phí giao dịch và xây dựng các ứng dụng hiệu quả hơn, thân thiện với người dùng hơn.
Tác giả: Certik
Phí Gas trên mạng chính Ethereum luôn là vấn đề nan giải, đặc biệt rõ rệt trong thời điểm tắc nghẽn mạng. Vào các thời điểm cao điểm, người dùng thường phải trả mức 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 trở nên đặc biệt quan trọng. Tối ưu hóa tiêu thụ Gas không chỉ giúp giảm hiệu quả chi phí giao dịch 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ẽ trình bày 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 hành tốt nhất khi phát triển hợp đồng thông minh để tối ưu phí Gas. Mong rằng qua những nội dung này, có thể mang lại cảm hứng và hỗ trợ thiết thực cho các nhà phát triển, đồng thời giúp người dùng phổ thông hiểu rõ hơn cách thức hoạt động phí Gas của EVM, cùng nhau đối phó với những thách thức trong hệ sinh thái blockchain.
Giới thiệu cơ chế phí Gas của EVM
Trong các mạng tương thích EVM, "Gas" là đơn vị đo lường năng lực tính toán cần thiết để thực hiện một thao tác cụ thể.
Hình ảnh dưới đây minh họa bố cục cấu trúc của EVM. Trong hình, tiêu thụ Gas được chia thành ba phần: thực thi lệnh, gọi tin nhắn ngoài, và đọc/ghi bộ nhớ cùng lưu trữ.

Nguồn: Trang web chính thức Ethereum[1]
Vì mỗi giao dịch thực thi đều cần tài nguyên tính toán, nên sẽ thu một khoản phí nhất định nhằm 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). Chi phí cần thiết để hoàn tất 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ố đơn vị Gas đã dùng * (phí cơ sở + phí ưu tiên)
Phí cơ sở sẽ bị đốt cháy, còn phí ưu tiên thì làm động lực khuyến khích các trình xác thực đưa giao dịch vào blockchain. Việc đặt mức phí ưu tiên cao hơn khi gửi giao dịch có thể tăng khả năng giao dịch được đưa vào khối tiếp theo. Điều này giống như một khoản "tiền boa" người dùng trả cho trình xác thực.
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 các "mã vận hành", tức opcodes.
Mọi đoạn mã vận hành nào (ví dụ: tạo hợp đồng, gọi tin nhắn, truy cập lưu trữ tài khoản, thực thi thao tác trên máy ảo) đều có một mức tiêu thụ Gas được công nhận, các mức phí này được ghi nhận trong Sách vàng Ethereum[2].

Sau nhiều lần điều chỉnh bởi các EIP, một số mức phí Gas của opcode đã được sửa đổi và có thể khác so với trong Sách vàng. Để biết thông tin mới nhất về chi phí opcode, 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ư tưởng cốt lõi của tối ưu hóa Gas là ưu tiên các thao tác tiết kiệm chi phí trên blockchain EVM, tránh các thao tác tốn kém Gas.
Trong EVM, các thao tác sau có chi phí thấp:
-
Đọc/ghi biến bộ nhớ
-
Đọc biến hằng số và bất biến
-
Đọc/ghi biến cục bộ
-
Đọc biến calldata, ví dụ như mảng calldata và cấu trúc dữ liệu
-
Gọi hàm nội bộ
Các thao tác có chi phí cao bao gồm:
-
Đọc/ghi biến trạng thái được lưu trữ trong lưu trữ hợp đồng
-
Gọi hàm bên ngoài
-
Thao tác vòng lặp
Thực hành tốt nhất về tối ưu hóa phí Gas trên EVM
Dựa trên các khái niệm cơ bản nêu trên, chúng tôi đã tổng hợp một danh sách các thực hành tốt nhất về tối ưu hóa phí Gas dành cho cộng đồng nhà phát triển. Bằng cách tuân thủ các thực hành này, các nhà phát triển có thể giảm tiêu thụ Gas của hợp đồng thông minh, hạ thấp chi phí giao dịch, và xây dựng các ứng dụng hiệu quả hơn, thân thiện với người dùng hơn.
1. Hạn chế sử dụng lưu trữ
Trong Solidity, Storage (lưu trữ) là tài nguyên giới hạn, tiêu thụ Gas cao hơn nhiều so với Memory (bộ nhớ). Mỗi lần hợp đồng thông minh đọc hoặc ghi dữ liệu từ lưu trữ đều phát sinh chi phí Gas cao.
Theo định nghĩa trong Sách vàng Ethereum, chi phí thao tác lưu trữ cao hơn thao tác bộ nhớ hơn 100 lần. Ví dụ, các lệnh opcode mload và mstore chỉ tiêu thụ 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 lý tưởng nhất cũng cần ít nhất 100 đơn vị.

Các phương pháp hạn chế sử dụng lưu trữ bao gồm:
-
Lưu dữ liệu không cần thiết vĩnh viễn vào bộ nhớ
-
Giảm số lần sửa đổi lưu trữ: bằng cách lưu kết quả trung gian vào bộ nhớ, đợi mọi phép tính hoàn tất rồi mới gán kết quả vào biến lưu trữ.
2. Đóng gói biến (Variable packing)
Số lượng slot lưu trữ (Storage slot) được sử dụng trong hợp đồng thông minh và cách nhà phát triển biểu diễn dữ liệu ảnh hưởng lớn đến mức tiêu thụ phí 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, sử dụng slot 32 byte làm đơn vị lưu trữ cơ bản cho biến. Đóng gói biến nghĩa là sắp xếp hợp lý các biến để nhiều biến có thể vừa vào một slot lưu trữ duy nhất.
Bên trái là cách triển khai kém hiệu quả, sẽ tiêu tốn 3 slot; bên phải là cách triển khai hiệu quả hơn.

Chỉ bằng điều chỉnh nhỏ này, nhà phát triển có thể tiết kiệm 20.000 đơn vị Gas (việc lưu trữ một slot chưa dùng hết tốn 20.000 Gas), giờ chỉ cần hai slot.
Vì mỗi slot lưu trữ đều tiêu tốn Gas, đóng gói biến giúp tối ưu hóa việc sử dụng Gas bằng cách giảm số lượng slot cần thiết.
3. Tối ưu 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 chi phí thao tác của từng kiểu dữ liệu lại khác nhau. Việc lựa chọn kiểu dữ liệu phù hợp giúp tối ưu hóa việc sử dụng Gas.
Ví dụ, trong Solidity, số nguyên có thể chia nhỏ thành nhiều kích cỡ khác nhau: uint8, uint16, uint32,... Vì EVM thực hiện thao tác theo đơn vị 256 bit, việc dùng uint8 nghĩa là EVM phải chuyển nó sang 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 giữa uint8 và uint256 qua đoạn mã trong hình. Hàm UseUint() tiêu thụ 120.382 đơn vị Gas, trong khi hàm UseUInt8() tiêu thụ 166.111 đơn vị Gas.
Xét riêng lẻ, dùng uint256 rẻ hơn uint8 ở đây. Tuy nhiên, nếu áp dụng tối ưu đóng gói biến như đã đề xuất trước đó thì tình hình sẽ khác. Nếu nhà phát triển có thể đóng gói bốn biến uint8 vào một slot lưu trữ, tổng chi phí duyệt qua chúng sẽ thấp hơn bốn biến uint256. Khi đó, hợp đồng thông minh chỉ cần đọc/ghi một lần slot lưu trữ và đưa cả bốn biến uint8 vào bộ nhớ/lưu trữ trong một thao tác.
4. Dùng biến kích thước cố định thay vì biến động
Nếu dữ liệu có thể kiểm soát trong 32 byte, nên dùng kiểu dữ liệu bytes32 thay vì bytes hoặc strings. Nói chung, biến kích thước cố định tiêu thụ ít Gas hơn biến kích thước thay đổi. Nếu độ dài byte có thể giới hạn, hãy chọn độ dài nhỏ nhất từ bytes1 đến bytes32.


5. Ánh xạ (Mapping) và Mảng (Array)
Danh sách dữ liệu trong Solidity có thể được biểu diễn bằng hai kiểu dữ liệu: Arrays (mảng) và Mappings (ánh xạ), nhưng cú pháp và cấu trúc của chúng hoàn toàn khác nhau.
Ánh xạ thường hiệu quả hơn và tốn kém thấp hơn trong hầu hết trường hợp, nhưng mảng có thể lặp lại và hỗ trợ đóng gói kiểu dữ liệu. Do đó, khuyến nghị ưu tiên dùng ánh xạ khi quản lý danh sách dữ liệu, trừ khi cần lặp lại hoặc có thể tối ưu hóa Gas bằng cách đóng gói kiểu dữ liệu.
6. Dùng calldata thay vì memory
Các biến được khai báo trong tham số hàm có thể được lưu trữ trong calldata hoặc memory. Điểm khác biệt chính là memory có thể bị hàm sửa đổi, trong khi calldata thì bất biến.
Hãy nhớ nguyên tắc này: nếu tham số hàm chỉ để đọc, nên ưu tiên dùng calldata thay vì memory. Như vậy sẽ tránh được thao tác sao chép không cần thiết từ calldata sang memory.
Ví dụ 1: dùng memory

Khi dùng từ khóa memory, giá trị mảng sẽ được sao chép từ calldata mã hóa sang memory trong quá trình giải mã ABI. Khối mã này tiêu tốn 3.694 đơn vị Gas.
Ví dụ 2: dùng calldata

Khi đọc trực tiếp giá trị từ calldata, bỏ qua thao tác trung gian memory. Cách tối ưu này làm chi phí thực thi giảm xuống chỉ còn 2.413 đơn vị Gas, hiệu suất Gas tăng 35%.
7. Sử dụng từ khóa Constant/Immutable càng nhiều càng tốt
Biến Constant/Immutable sẽ không được lưu trữ trong lưu trữ hợp đồng. Những biến này sẽ được tính toán tại thời điểm biên dịch và lưu vào bytecode của hợp đồng. Do đó, chi phí truy cập thấp hơn nhiều so với lưu trữ, khuyến nghị sử dụng từ khóa Constant hoặc Immutable càng nhiều càng tốt.
8. Dùng Unchecked khi chắc chắn không xảy ra tràn số/dưới tràn
Khi nhà phát triển xác định chắc chắn rằng thao tác số học sẽ không gây tràn số hay dưới tràn, có thể dùng từ khóa unchecked được giới thiệu từ Solidity v0.8.0 để tránh kiểm tra thừa, từ đó tiết kiệm chi phí Gas.
Trong hình dưới đây, do bị ràng buộc bởi điều kiện i<length, biến i sẽ không bao giờ bị tràn. Ở đây, length được định nghĩa là uint256, nghĩa là giá trị tối đa của i là max(uint)-1. Do đó, việc tăng i trong khối unchecked được coi là an toàn và tiết kiệm Gas hơn.

Thêm nữa, từ phiên bản 0.8.0 trở đi, trình biên dịch không còn cần dùng thư viện SafeMath vì bản thân trình biên dịch đã tích hợp sẵn bảo vệ tràn số và dưới tràn.
9. Tối ưu hóa các modifier
Code của modifier được nhúng vào hàm đã được sửa đổi, mỗi lần dùng modifier thì code của nó sẽ bị nhân bản. Điều này làm tăng kích thước bytecode và làm tăng tiêu thụ Gas. Dưới đây là một phương pháp tối ưu chi phí Gas cho modifier:
Trước khi tối ưu:

Sau khi tối ưu:

Trong ví dụ này, bằng cách tái cấu trúc 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, có thể giảm kích thước bytecode và hạ thấp chi phí Gas.
10. Tối ưu đánh giá ngắn mạch (short-circuit)
Đối với toán tử || và &&, phép toán logic xảy ra đánh giá ngắn mạch, nghĩa là nếu điều kiện đầu tiên đã đủ xác định kết quả biểu thức logic thì điều kiện thứ hai sẽ không được đánh giá.
Để tối ưu tiêu thụ Gas, nên đặt các điều kiện có chi phí tính toán thấp lên trước, như vậy có thể bỏ qua các tính toán tốn kém.
Gợi ý chung bổ sung
1. Xóa mã không dùng
Nếu tồn tại các hàm hoặc biến không dùng trong hợp đồng, nên xóa chúng. Đây là cách trực tiếp nhất để giảm chi phí triển khai hợp đồng và giữ cho hợp đồng nhỏ gọn.
Dưới đây là một vài gợi ý thực tiễn:
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 bước tính toán dư thừa đó. Về bản chất, mọi tính toán không dùng đến đều nên bị xóa.
Trên Ethereum, nhà phát triển nhận được phần thưởng Gas khi giải phóng không gian lưu trữ. Khi một biến không còn cần thiết, nên dùng từ khóa delete để xóa nó hoặc đặt 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ợp nhất vòng lặp càng nhiều càng tốt, và đưa các phép tính lặp ra ngoài thân vòng lặp.
2. Sử dụng hợp đồng tiền biên dịch (precompiled contract)
Hợp đồng tiền biên dịch cung cấp các hàm thư viện phức tạp, ví dụ như thao tác mã hóa và băm. Vì code không chạy trên EVM mà chạy trực tiếp trên nút khách hàng, nên cần ít Gas hơn. Việc sử dụng hợp đồng tiền biên dịch có thể tiết kiệm Gas bằng cách giảm lượng công việc tính toán cần thiết để thực thi hợp đồng thông minh.
Các ví dụ về hợp đồng tiền biên dịch 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 tiền biên dịch này trong hợp đồng thông minh, nhà phát triển có thể giảm chi phí Gas và nâng cao hiệu suất ứng dụng.
Để biết danh sách đầy đủ các hợp đồng tiền biên dịch được hỗ trợ trên mạng Ethereum, vui lòng tham khảo tại đây[4].
3. Sử dụng mã hợp ngữ nội tuyến (inline assembly)
Mã hợp ngữ nội tuyến cho phép nhà phát triển viết mã cấp thấp hiệu quả, có thể được EVM thực thi trực tiếp mà không cần dùng các opcode Solidity tốn kém. Mã hợp ngữ nội tuyến còn 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 phí Gas. Ngoài ra, mã hợp ngữ nội tuyến có thể thực hiện một số thao tác phức tạp khó thực hiện chỉ bằng Solidity, mang lại sự linh hoạt hơn trong việc tối ưu hóa tiêu thụ Gas.
Dưới đây là ví dụ mã sử dụng hợp ngữ nội tuyến để tiết kiệm Gas:

Từ hình ảnh trên có thể thấy, trường hợp thứ hai sử dụng kỹ thuật hợp ngữ nội tuyến có hiệu suất Gas cao hơn so với trường hợp sử dụng chuẩn.
Tuy nhiên, việc dùng hợp ngữ nội tuyến cũng có thể tiềm ẩn rủi ro và dễ mắc lỗi. Do đó, cần thận trọng, chỉ nên dùng cho các nhà phát triển giàu kinh nghiệm.
4. Sử dụng giải pháp Layer 2
Sử dụng 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ính Ethereum.
Các giải pháp Layer 2 như rollups, sidechain và kênh trạng thái có thể dời xử lý giao dịch khỏi chuỗi Ethereum chính, 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ừ đó hạ thấp phí Gas. Việc sử dụng 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 dẫn đến tắc nghẽn.
5. Sử dụng công cụ và thư viện tối ưu hóa
Có nhiều công cụ tối ưu hóa có thể dùng, ví dụ như trình tối ưu solc, trình tối ưu build 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 bytecode, xóa mã không dùng, và giảm số lần thao tác cần thiết để thực thi 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ể giảm hiệu quả chi phí Gas và nâng cao hiệu suất hợp đồng thông minh.
Kết luận
Tối ưu hóa tiêu thụ Gas là 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 suất của hợp đồng thông minh trên mạng tương thích EVM. Bằng cách ưu tiên các thao tác tiết kiệm chi phí, giảm sử dụng lưu trữ, tận dụng hợp ngữ nội tuyến và tuân theo các thực hành tốt nhất được thảo luận trong bài viết này, nhà phát triển có thể giảm hiệu quả tiêu thụ Gas của hợp đồng.
Tuy nhiên, cần lưu ý rằng trong quá trình tối ưu hóa, nhà phát triển phải hành động cẩn trọng để tránh tạo ra lỗ hổng bảo mật. Việc tối ưu hóa mã và giảm tiêu thụ Gas tuyệt đối không được đánh đổi lấy sự 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
Chào mừng tham gia cộng đồng chính thức TechFlow
Nhóm Telegram:https://t.me/TechFlowDaily
Tài khoản Twitter chính thức:https://x.com/TechFlowPost
Tài khoản Twitter tiếng Anh:https://x.com/BlockFlow_News














