체인의정석

Solidity에서 자주 만나는 에러 "CompilerError: Stack too deep, try removing local variables." 그리고 에러에 대한 해결 방안 정리 , 이벤트 로그와 함수 입력값 구조체로 개수 줄이기 본문

블록체인/Solidity

Solidity에서 자주 만나는 에러 "CompilerError: Stack too deep, try removing local variables." 그리고 에러에 대한 해결 방안 정리 , 이벤트 로그와 함수 입력값 구조체로 개수 줄이기

체인의정석 2022. 7. 13. 18:31
728x90
반응형

스마트컨트렉트에서 이벤트 로그에 구조체를 가져와서 하나의 요소를 지정해준 것을 넣었더니 위와 같은 에러가 났다.

 

https://soliditydeveloper.com/stacktoodeep

 

Stack Too Deep: Three words of horror

You just have to add one tiny change in your contracts. With such a small change, you are confident your code is correct. But oh no, what's that error?

soliditydeveloper.com

찾아보니 솔리디티 개발자가 가장 싫어하는 직면하는 문제중 하나..

The reason is a limitation in how variables can be referenced in the EVM stack. While you can have more than 16 variables in it, once you try to reference a variable in slot 16 or higher, it will fail. It's therefore not always obvious why exactly some code is failing and then a few random changes just seem to fix it.

But I don't want to bore you with too much theory. This is supposed to be a practical blog post.

대충 변수가 16개 이상이면 이런 에러가 난다고 한다.

 

끔직하다..

 

아무튼 해결방법은 아래 5가지라고 한다.

1. Use less variables

 

2. Utilizing functions

3. Block scoping

4. Utilizing structs

5. Some Hacking

 
// SPDX-License-Identifier: MIT
pragma solidity 0.7.1;

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;
    }
}

예시 코드, 보다시피 엄청 많은 변수가 있는걸 볼 수있다.

이를 해결하기 위한 하나의 방법은

pragma solidity 0.7.1;

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;
    }
}

인터널 함수를 만들어서 각각 변수를 다른곳에 넘겨버리기

 

이건 이미 현재 코드에서 최적화를 진행하며 많이 해둔 상태이다.

// SPDX-License-Identifier: MIT
pragma solidity 0.7.1;

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;
    }
}

블록 스코프 만들기.

이런 방법도 되는 구나 싶지만 사실 저런식으로 짜는건 안정성이 없는거 같아서 선호하지 않는다.

 

// SPDX-License-Identifier: MIT
pragma solidity 0.7.1;
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;
    }
}

구조체로 넘겨버리기 이건 따로 서치를 하다가 찾게 되었다.

https://stackoverflow.com/questions/71908632/compilererror-stack-too-deep-try-removing-local-variables

 

CompilerError: Stack too deep, try removing local variables

I am trying to do a dapp project. I have an error about stack too deep, but I don't know how can I solve this problem. CompilerError: Stack too deep, try removing local variables. --> contracts...

stackoverflow.com

함수의 입력값으로 변수가 너무 많이 나오면 이런 에러가 나는거 같은데 구조체로 한번에 들어와야 되는거 같다.

 

이것도 지금 코드에선 이미 적용이 되어 있는 상황이다.

msg.data 파싱하기

// SPDX-License-Identifier: MIT
pragma solidity 0.7.1;

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)
        }
    }
}

다 읽어봤는데

 

내가 작성한 코드에서 난 오류의 원인은 아무래도 변수를 너무 많이 선언했기 때문인거 같다.

가독성을 위해서 변수를 정의하고 그걸 가져와서 기록하는 식으로 하였는데 

필요없는 변수는 최대한 제거하는 식으로 코드를 수정하였다.

 

그리고 나서 보니 이벤트 로그에 값이 많아서 다시한번 오류가 발생.

이벤트로 기록하는 변수를 줄이니까 역시 오류가 해결되었다.

 

그럼 구조체를 만들고 이벤트로그에 구조체를 기록시킨다면?

 

그결과 구조체가 이벤트 로그에 남으면서 오류가 해결되었다.

 

결국 변수 선언이 많을 시 에러가 나기 때문에 구조체를 활용해서 이런 부담을 줄이면 오류가 해결되고 이런 과정은 함수 뿐 만 아니라 이벤트에서도 작용함을 알 수 있었다.

 

오늘의 교훈  : 너무 많은 변수 선언은 좋지 않다. 변수 선언이 많을 경우 에러가 나게 되며 변수 선언을 최대한 줄여야 하고 이를 줄이기 위해서는 구조체를 만들어서 활용하는 방법을 사용하거나 최후의 경우 msg.Data를 사용하는것도 고려해봐야 겠다.

728x90
반응형
Comments