Để chỉnh vài thay đổi nhỏ vào hợp đồng, bạn nghĩ rằng chỉ tốn vài giây mà thôi. Và sau khi tốn vài phút để chỉnh, bạn nhấp compile, chuyện gì xảy ra sau đó, một dòng thông báo xuất hiện:
InternalCompilerError: Stack too deep, try removing local variables.
Nếu đã từng code solidity lâu, bạn có thể đã gặp trường hợp này nhiều lần, Lỗi này thường xuất hiện vào lúc,,, ta không ngờ nhất, và đặc biệt là lúc… đang bị dí deadline 🙂
Đừng lo lắng, bạn không có lỗi, bạn không phải là người duy nhất rơi vào trường hợp này. Trên thực tế, lỗi này thuộc top 5 lỗi Solidity gây khó chịu nhất.
Table of Contents
Tại sao lỗi Stack Too Deep này lại xuất hiện?
Lý do là sự hạn chế trong cách variables (biến) được tham chiếu trong EVM stack. Chỉ có thể có 16 biến (lý do thì hỏi EVM nha), khi thử tham chiếu đến một biến trong slot có 16 biến hoặc cao hơn sẽ gặp lỗi. Và cũng khá khó để biết trước được đoạn code nào sẽ gặp lỗi.
Giải pháp
Vậy những phương pháp chung để khắc phục là gì? Hãy xem xét 5 phương án sau:
- Sử dụng ít biến hơn (tất nhiên, không cần giải thích thêm rồi)
- Sử dụng các hàm
- Scoping theo khối
- Sử dụng cấu trúc
- Một chút Hack
Đối với cách đầu tiên, hãy cố gắng tối ưu mã của bạn để chỉ sử dụng ít biến hơn.
Đối với bốn cách còn lại, hãy xem xét một ví dụ về Stack Too Deep bên dưới và bốn cách khác nhau để khắc phục.
// SPDX-License-Identifier: MIT
pragma solidity 0.8.21;
contract StackTooDeepTest1 {
function addUints(
uint256 a,uint256 b,uint256 c,uint256 d,uint256 e,uint256 f,uint256 g,uint256 h,uint256 i
) external pure returns(uint256) {
return a+b+c+d+e+f+g+h+i;
}
}
1. Sử dụng một hàm nội bộ
Sử dụng một hàm nội bộ sẽ làm cho lỗi biến mất. Ví dụ, chúng ta có thể chia nó thành ba cuộc gọi hàm, mỗi cuộc gọi thêm ba giá trị uint. Khá kỳ lạ là lỗi Stack Too Deep này buộc chúng ta viết mã tốt hơn.
// SPDX-License-Identifier: MIT
pragma solidity 0.8.21;
contract StackTooDeepTest1 {
function addUints(
uint256 a,uint256 b,uint256 c,uint256 d,uint256 e,uint256 f,uint256 g,uint256 h,uint256 i
) external pure returns(uint256) {
return _addThreeUints(a,b,c) + _addThreeUints(d,e,f) + _addThreeUints(g,h,i);
}
function _addThreeUints(uint256 a, uint256 b, uint256 c) private pure returns(uint256) {
return a+b+c;
}
}
2. Sử dụng việc phân khối theo khối
Lấy cảm hứng từ Uniswap, bạn cũng có thể sử dụng block scoping (phân khối). Đơn giản là đặt dấu ngoặc nhọn quanh các phần mã:
// SPDX-License-Identifier: MIT
pragma solidity 0.8.21;
contract StackTooDeepTest2 {
function addUints(
uint256 a,uint256 b,uint256 c,uint256 d,uint256 e,uint256 f,uint256 g,uint256 h,uint256 i
) external pure returns(uint256) {
uint256 result = 0;
{
result = a+b+c+d+e;
}
{
result = result+f+g+h+i;
}
return result;
}
}
3. Sử dụng cấu trúc thay thế
Hơi giống cách chỉ sử dụng ít biến hơn. Đặt dữ liệu vào cấu trúc. Cách này thì thuận tiện hơn cho việc đọc hiểu.
// SPDX-License-Identifier: MIT
pragma solidity 0.8.21;
pragma experimental ABIEncoderV2;
contract StackTooDeepTest3 {
struct UintPair {
uint256 value1;
uint256 value2;
}
function addUints(
UintPair memory a, UintPair memory b, UintPair memory c, UintPair memory d, uint256 e
) external pure returns(uint256) {
return a.value1+a.value2+b.value1+b.value2+c.value1+c.value2+d.value1+d.value2+e;
}
}
4. Phân tích msg.data
Ý tưởng gốc cho cách tiếp cận này đến từ người dùng k06a tại Stackexchange. Đây là một giải pháp hơi hack, nên không được khuyến nghị. Nhưng nếu đã thử tất cả các phương pháp khác mà không thành công thì có thể thử cách này:
// SPDX-License-Identifier: MIT
pragma solidity 0.8.21;
contract StackTooDeepTest4 {
function addUints(
uint256 /*a*/,uint256 /*b*/,uint256 c,uint256 d,uint256 e,uint256 f,uint256 g,uint256 h,uint256 i
) external pure returns(uint256) {
return _fromUint(msg.data)+c+d+e+f+g+h+i;
}
function _fromUint(bytes memory data) internal pure returns(uint256 value) {
uint256 value1;
uint256 value2;
assembly {
value1 := mload(add(data, 36))
value2 := mload(add(data, 68))
value := add(value1, value2)
}
}
}
Cách hoạt động của nó là bằng cách phân tích msg.data. Tất cả dữ liệu được gửi đến hợp đồng được lưu trữ ở đây, vì vậy chúng ta có thể chú thích biến a và b, nhưng vẫn nhận giá trị của chúng. 4 byte đầu tiên của msg.data là dữ liệu bộ chọn chức năng. Sau đó là hai uint256 đầu tiên của chúng ta, mỗi cái với 32 byte.
Điều này chỉ hoạt động cho các hàm bên ngoài sử dụng msg.data. Một giải pháp tạm thời để sử dụng nó cũng với các hàm công khai sẽ là gọi các hàm công khai đó thông qua this.myPublicFunction().
Cuối cùng, dù vấn đề “Stack Too Deep” có thể khiến bạn cảm thấy bất ngờ và khó khăn, hãy nhớ rằng bạn không phải một mình trong việc đối mặt với nó. Cả thế giới lập trình hợp đồng thông minh đều đã từng gặp phải nó và tìm cách vượt qua. Với những gợi ý và phương pháp mà chúng tôi đã đề cập, hy vọng bạn đã có cái nhìn tổng quan về cách giải quyết vấn đề này.
Hãy luôn kiên trì, sáng tạo và không ngừng học hỏi để trở thành một lập trình viên Solidity xuất sắc. Lỗi “Stack Too Deep” chỉ là một trong hàng ngàn thách thức mà bạn sẽ gặp phải trong hành trình của mình. Bằng cách nắm vững kiến thức và kỹ năng, bạn sẽ có khả năng tạo ra các hợp đồng thông minh mạnh mẽ và ổn định hơn, giúp xây dựng một tương lai blockchain tốt hơn.ShareSave
- If you’d like to invest in blockchain advertising companies, just BUY token Saigon (SGN) at Pancakeswap: https://t.co/KJbk71cFe8 (do not worry about low liquidity)
- Backed by Click Digital Company
- Enhancing blockchain knowledge
- BSC address: 0xa29c5da6673fd66e96065f44da94e351a3e2af65
- Twitter: https://twitter.com/SaigonSGN135
- Staking SGN: http://135web.net
Digital Marketing Specialist