Mục lục
Tại sao lại có giới hạn cho kích thước của một contract
Vào ngày 22 tháng 11 năm 2016, hard-fork Spurious Dragon đã giới thiệu EIP-170, bổ sung giới hạn kích thước hợp đồng thông minh là 24,576 kb. Nếu bạn là nhà phát triển Solidity, điều này có nghĩa là khi bạn thêm ngày càng nhiều chức năng vào hợp đồng của mình, đến một lúc nào đó bạn sẽ đạt đến giới hạn và khi triển khai sẽ thấy lỗi:
Warning: Contract code size exceeds 24576 bytes (a limit introduced in Spurious Dragon). This contract may not be deployable on Mainnet. Consider enabling the optimizer (with a low "runs" value!), turning off revert strings, or using libraries.
Giới hạn này được đưa ra để ngăn chặn các cuộc tấn công từ chối dịch vụ (DOS). Bất kỳ cuộc gọi đến hợp đồng nào đều có giá tương đối rẻ về mặt Gas. Tuy nhiên, tác động của lệnh gọi hợp đồng đối với các nút Ethereum tăng lên một cách không cân xứng tùy thuộc vào kích thước của mã hợp đồng được gọi (đọc mã từ đĩa, xử lý trước mã, thêm dữ liệu vào bằng chứng Merkle). Bất cứ khi nào bạn gặp tình huống mà kẻ tấn công yêu cầu ít tài nguyên để gây ra nhiều công việc cho người khác, bạn sẽ có nguy cơ bị tấn công DOS.
Ban đầu, đây không phải là vấn đề vì một giới hạn kích thước hợp đồng tự nhiên là giới hạn gas khối. Rõ ràng, hợp đồng phải được triển khai trong giao dịch chứa tất cả mã byte của hợp đồng. Nếu bạn chỉ đưa một giao dịch đó vào một khối, bạn có thể sử dụng hết lượng gas đó, nhưng nó không phải là vô hạn. Nhưng kể từ khi nâng cấp London, giới hạn gas khối có thể thay đổi trong khoảng từ 15 triệu đến 30 triệu đơn vị tùy thuộc vào nhu cầu của mạng.
Công cụ hỗ trợ
Thật không may, không có cách nào dễ dàng để có được kích thước mã byte trong hợp đồng của bạn. Một công cụ tuyệt vời có thể giúp bạn đó là plugin truffle-contract-size nếu bạn đang sử dụng Truffle.
Đầu tiên bạn cài đặt thư viện:
npm install truffle-contract-size
Sau đó thêm dòng sau vào tệp truffle-config.js:
plugins: ["truffle-contract-size"]
Cuối cùng chạy lệnh sau để xem kích thước các contract:
truffle run contract-size
Bạn sẽ thấy kết quả như hình, dựa vào thông tin này bạn sẽ biết mình cần ưu tiên tối ưu ở contract nào:

Một số phương pháp giúp giảm kích thước Contract
Phương pháp có tác động lớn
Tách thành nhiều contract nhỏ hơn
Đây luôn là cách tiếp cận đầu tiên của bạn cần ưu tiên thực hiện trước. Làm thế nào bạn có thể tách hợp đồng thành nhiều hợp đồng nhỏ hơn?
Nó thường buộc bạn phải đưa ra một kiến trúc tốt cho hợp đồng của mình. Các hợp đồng nhỏ hơn luôn được ưu tiên từ góc độ dễ đọc mã. Để chia tách hợp đồng, hãy tự hỏi:
- Những chức năng nào thuộc về nhau? Mỗi bộ chức năng có thể hoạt động tốt nhất trong hợp đồng riêng của nó.
- Những hàm nào không yêu cầu đọc trạng thái hợp đồng?
- Bạn có thể chia theo storage hoặc theo chức năng không?
Sử dụng Thư viện
Một cách đơn giản để di chuyển mã chức năng ra khỏi bộ lưu trữ đó là sử dụng thư viện. Không khai báo các hàm trong thư viện dưới dạng internal, vì nếu khai báo internal code của chúng sẽ được thêm trực tiếp vào hợp đồng trong quá trình biên dịch. Nhưng nếu bạn khai báo dạng public hoặc external, thì trên thực tế, những chức năng đó sẽ nằm trong một hợp đồng thư viện riêng.
Hãy cân nhắc sử dụng using for để sử dụng thư viện thuận tiện hơn.
Sử dụng Proxies
Một chiến lược tiên tiến hơn sẽ là một hệ thống proxy. Các thư viện sử dụng DELEGATECALL ở phía sau để thực hiện chức năng của hợp đồng khác với trạng thái của hợp đồng gọi. Hãy xem bài đăng trên blog này để tìm hiểu thêm về hệ thống proxy.
Chúng cung cấp cho bạn nhiều chức năng hơn, ví dụ: chúng cho phép khả năng nâng cấp, nhưng chúng cũng gây thêm nhiều sự phức tạp và đặc biệt làm tăng Gas. Tôi sẽ không thêm những thứ đó chỉ để giảm quy mô hợp đồng trừ khi đó là lựa chọn duy nhất của bạn vì bất kỳ lý do gì.
Phương pháp có tác động trung bình
Giảm bớt các function
Điều này thì hiển nhiên rồi. Function sẽ làm tăng quy mô hợp đồng khá nhiều.
- External function: Thông thường chúng ta thêm rất nhiều chức năng xem (View function) vì lý do thuận tiện. Điều đó hoàn toàn ổn cho đến khi bạn đạt đến giới hạn kích thước. Sau đó, bạn có thể thực sự muốn nghĩ đến việc loại bỏ tất cả trừ những function thực sự cần thiết.
- Internal function: Bạn cũng có thể xóa các hàm internal/private thay vào đó là nhúng trực tiếp code nếu hàm đó chỉ được gọi một lần.
Hạn chế sử dụng các biến
Ví dụ mã ban đầu là:
function get(uint id) returns (address,address) {
MyStruct memory myStruct = myStructs[id];
return (myStruct.addr1, myStruct.addr2);
}
tối ưu thành:
function get(uint id) returns (address,address) {
return (myStructs[id].addr1, myStructs[id].addr2);
}
đã giúp chúng ta giảm kích thước Contract đi 0,28kb.
Rất có thể bạn có thể tìm thấy nhiều tình huống tương tự trong hợp đồng của mình và những tình huống đó thực sự có thể cộng lại thành lượng đáng kể.
Rút gọn thông báo lỗi
Các thông báo hoàn nguyên (revert message) dài và đặc biệt là nhiều thông báo hoàn nguyên khác nhau có thể làm tăng thêm kích thước hợp đồng. Thay vào đó hãy sử dụng mã lỗi ngắn và giải mã chúng trong hợp đồng của bạn. Một tin nhắn dài có thể trở nên ngắn hơn nhiều. Ví dụ:
require(msg.sender == owner, "Only the owner of this contract can call this function");
Thay bằng:
require(msg.sender == owner, "OW1");
Sử dụng lỗi tùy chỉnh thay vì thông báo lỗi
Lỗi tùy chỉnh đã được giới thiệu trong Solidity 0.8.4. Chúng là một cách tuyệt vời để giảm quy mô hợp đồng của bạn vì chúng được mã hóa ABI dưới dạng selector.
error Unauthorized();
if (msg.sender != owner) {
revert Unauthorized();
}
Xem xét bật chế độ tối ưu hóa khi biên dịch: “Enable optimization: 200”
Bạn cũng có thể bất chế độ tối ưu hóa. Giá trị mặc định là 200 có nghĩa là nó đang cố gắng tối ưu hóa mã byte như thể một hàm được gọi 200 lần. Nếu bạn thay đổi nó thành 1, về cơ bản, bạn yêu cầu trình tối ưu hóa tối ưu hóa trong trường hợp chỉ chạy mỗi chức năng một lần. Chức năng được tối ưu hóa để chỉ chạy một lần có nghĩa là nó được tối ưu hóa cho chính việc triển khai.
Xin lưu ý rằng điều này sẽ làm tăng chi phí gas khi chạy các chức năng, vì vậy bạn có thể không muốn thực hiện điều đó.
Phương pháp có tác động nhỏ
Hạn chế truyền cấu trúc cho hàm
Nếu bạn đang sử dụng ABIEncodeV2, nó sẽ giúp bạn truyền struct tới hàm:
function get(uint id) returns (address,address) {
return _get(myStruct);
}
function _get(MyStruct memory myStruct) private view returns(address,address) {
return (myStruct.addr1, myStruct.addr2);
}
Nhưng nếu bạn truyền tham số trực tiếp như dưới, kích thước sẽ giảm 0.1Kb
function get(uint id) returns(address,address) {
return _get(myStructs[id].addr1, myStructs[id].addr2);
}
function _get(address addr1, address addr2) private view returns(address,address) {
return (addr1, addr2);
}
Khai báo mức độ hiển thị chính xác cho các hàm và biến
Hàm hoặc biến chỉ được gọi từ bên ngoài? Khai báo chúng là external thay vì public.
Các hàm hoặc biến chỉ được gọi từ trong hợp đồng? Khai báo chúng là private hoặc internal thay vì public.
Loại bỏ các modifier
Các modifier, đặc biệt là khi được sử dụng nhiều, có thể có tác động đáng kể đến kích thước của hợp đồng. Hãy cân nhắc việc loại bỏ chúng và thay vào đó hãy sử dụng hàm function.
modifier checkStuff() {}
function doSomething() checkStuff {}
function checkStuff() private {}
function doSomething() { checkStuff(); }
Nguồn: DOWNSIZING CONTRACTS TO FIGHT THE CONTRACT SIZE LIMIT
Trả lời