스마트 컨트랙트 개발/테스트에 유용한 모듈

Seungwon Go
ReturnValues
Published in
13 min readSep 27, 2018

--

npm module에 대한 이미지 검색결과

truffle-flattener

truffle-flattener 모듈은 solidity내에서 참조하고 있는 모든 파일을 가져와 하나의 파일로 만들어주는 모듈입니다.

아래의 명령어를 통해 truffle-flattener 설치하도록 하겠습니다.

npm i truffle-flattener --save

설치가 완료되었다면, truffle-flattner를 통해 참조되고 있는 모든 코드를 포함한 하나의 파일을 만들어 보도록 하겠습니다.
우선 프로젝트 루트에 flat 이라는 이름으로폴더를 생성하세요.

터미널에서 아래의 명령어를 실행하세요.

truffle-flattener ./contracts/SampleToken.sol > ./flat/SampleToken_flat.sol

명령어가 실행이 되면, 터미널에 SampleToken.sol에서 import하여 사용하는 컨트랙트 뿐만 아니라, import한 컨트랙트 파일 내에서 참조하고 있는 모든 컨트랙트 코드를 찾아와서 하나로 합친 후 flat 폴더에 SampleToken_flat.sol를 생성해 줍니다.

etherscan.io 에서 배포된 컨트랙트에 대한 Verify and Publish를 진행할때, 이렇게 하나의 파일로 작성된 전체 코드가 필요합니다.

eth-gas-reporter

테스트 케이스에서 작성된 method 실행 및 컨트랙트 배포시 가스를 얼마나 소모하는지 체크할 수 있다.

터미널에서 아래 명령어를 통해 설치 할 수 있습니다.

npm i eth-gas-reporter --save

앞서 포스팅 했던 Truffle : 스마트 컨트랙트 테스트 케이스 구현 방법 테스트 케이스를 실행시키면 아래와 같이 컨트랙트를 배포할때 드는 가스 비용과 테스트 케이스에 작성된 method를 실행시킬때 드는 가스 비용을 분석해 줍니다.

최근에 web3를 통해 웹에서 스마트 컨트랙트를 배포하는 프로그램을 실행하였는데, 테스트넷(Ropsten)의 Block Gas Limit(4712388)를 초과하여 개발된 스마트 컨트랙트를 배포하지 못한 경험을 하였습니다.

eth-gas-reporter를 통해 컨트랙트 배포 및 method 실행을 위한 가스 비용을 미리 확인한다면, 이와 같은 문제를 사전에 파악하여 프로그램을 개선할 수 있습니다.

solium

Solium은 구현된 솔리디티 코드의 코딩 스타일과 보안 이슈를 분석해주는 모듈입니다.

터미널에서 아래 명령어를 통해 설치 할 수 있습니다.

npm i solium --save

설치가 완료되면 프로젝트 루트 폴더에서 아래 명령어를 실행합니다.

solium --init

아래의 2개의 파일이 프로젝트 루트 폴더에 생성된 것을 확인 할 수 있습니다.

  • .soliumignore : contains names of files and directories to ignore while linting
  • .soliumrc.json : contains configuration that tells Solium how to lint your project. You should modify this file to configure rules, plugins and sharable configs.

.soliumrc.json 파일을 아래와 같이 되어 있습니다.

{
"extends": "solium:recommended",
"plugins": ["security"],
"rules": {
"quotes": ["error", "double"],
"indentation": ["error", 4]
}
}

코딩 Style Ruleshttps://solium.readthedocs.io/en/latest/user-guide.html#list-of-style-rules 에서 확인하실 수 있습니다.

아래 명령어를 통해 코딩 스타일 규칙이 제대로 지켜졌는지 확인해 보도록 하겠습니다.

solium -f ./contracts/SampleToken.sol

아래에 출력된 결과를 보면 솔리디티 프로그램 내에 탭간격을 4 spaces를 사용하라고 알려주고 있습니다.

SampleToken.sol 파일을 열어서 탭 간격을 공백 4칸이 되도록 수정한 후 코딩 스타일 규칙이 제대로 되었는지 다시 실행해 봅니다.

이번에는 아래와 같이 정상 메시지가 출력이 되었습니다.

Security Ruleshttps://www.npmjs.com/package/solium-plugin-security#list-of-rules 에서 확인하실 수 있습니다.

아래 명령어를 통해 Security Rule에 위배되는 내용이 있는지 확인해 보도록 하겠습니다.

solium -d ./contracts/

다행히 저희가 작성한 코드에는 아무런 문제가 없습니다.

solidity-coverage

code coverage는 소프트웨어의 테스트를 논할 때 얼마나 테스트가 충분한가를 나타내는 지표중 하나로써, 말 그대로 코드가 얼마나 커버되었는가 입니다. 즉, 소프트웨어 테스트를 진행했을 때 코드 자체가 얼마나 실행되었냐는 것입니다.

solidity-coverage는 solidity 프로그램에 대한 coverage를 측정할 수 있는 도구입니다. solidity-coverage는 Statement, Branch, Function을 기준으로 coverage를 측정해 줍니다.

Statement Coverage

  • 코드 한 줄이 한번 이상 실행된다면 충족된다.
function foo (uint x, uint y) public returns (uint)
{
uint z;
if ((x > 0) && (y > 0))
{
z = x;
}
return z;
}

foo(1,1)을 호출하게 되면 (x>0)(y>0) 조건 둘 다 만족하므로 z=x;를 실행하여 모든 Line을 실행하게 됩니다. 그러므로 테스트 foo(1,1)Statement Coverage에 만족한다고 할 수 있습니다. 반대로 foo(0,1)은 if문 만족하지 못하기 때문에 Statement Coverage에 만족한다고 할 수 없습니다.

Branch Coverage

  • 전체적인 결과가 참/거짓이면 충족된다.
function foo (uint x, uint y) public returns (uint)
{
uint z;
if ((x > 0) && (y > 0))
{
z = x;
}
return z;
}

foo(1,1), foo(0,1)은 결정 Coverage에 만족합니다. 첫번 째의 foo(1,1)은 if문을 통과하여 z=x가 실행됩니다. 하지만 두번 째의 foo(0,1)(x>0)이라는 조건에 만족하지 않기 때문에 z=x가 실행되지 않습니다. 그러므로 foo(1,1), foo(0,1)을 실행하였을 때 if문 전체 결과가 참/거짓을 만족하기 때문입니다.

Function Coverage

function내의 코드인 statement와 branch가 모두 만족하는지를 점검합니다. 즉 statement와 branch 중 하나라도 만족하지 못하는 코드가 function 내에 삽입되어 있으면, Funciton Coverage에 만족한다고 할 수 없습니다.

그럼 터미널에서 아래 명령어를 통해 solidity-coverage를 설치 하도록 하겠습니다.

npm i solidity-coverage --save

터미널에서 아래의 명령어를 실행하면 contracts 폴더에 구현되어 있는 컨트랙트에 대한 분석을 시작하고, 소스 코드를 Statements, Branches, Functions, Lines 4개 부분으로 분석한 결과를 터미널에 출력을 합니다.

./node_modules/.bin/solidity-coverage

그리고 프로젝트 루트 폴더를 보시면 아래와 같이 coverage 폴더와 coverage.json 파일이 생성된 것을 확인할 수 있습니다.

먼저 브라우저에서 coverage/contracts/index.html을 열어보시면 아래와 같이 contracts 폴더에 있는 우리가 개발한 컨트랙트 리스트와 각 컨트랙트 별 분석 결과를 확인할 수 있습니다.

SampletToken.sol을 클릭하시면 개발된 소스 코드와 함께 분석된 결과를 확인할 수 있습니다. 이때 주의해야 할 것은 import된 solidity 파일은 분석하지 않는 다는것입니다. 그래서 정확한 분석을 원한다면, 위에서 소개한 truffle-flattener를 통해 전체 코드를 하나로 만든 후 진행하셔야 합니다.

위에서 보시면 Statement, Branches, Function, Lines 별로 분석 결과를 보여주고 있습니다. 그럼 각각의 무엇을 의미하는지 한번 알아보도록 하겠습니다.

  • Statements : function 내에서 Branches을 제외한 코드 문장을 뜻합니다. 위의 코드에서는 12, 13, 14 라인이 Statements에 속합니다.
  • Branches : if, assert, require 등 특정 조건을 체크하는 영역을 뜻합니다. 위의 코드에서는 Branches 영역은 존재하지 않습니다.
  • Functions : 소스 코드 내에 function, modifier, constuctor로 선언된 영역을 뜻합니다. 위의 코드에서는 function은 1개 존재하며, 9번째 라인에서 11번째 라인입니다.
  • Lines : Statements와 Branches를 모두를 뜻합니다. 위의 코드에서는 Branches가 0이므로, Lines은 Statements와 동일한 3입니다.

프로젝트 루트에 생성된 coverage.json 파일을 보시면 아래와 같이 컨트랙트 별로 Statements, Branches, Functions, Lines에 대한 분석 결과를 json 파일로 제공을 합니다.

  • path - The path to the file. This is an absolute path, and should be the same as the key in the report object.
  • s - Hash of statement counts, where keys as statement IDs.
  • b - Hash of branch counts, where keys are branch IDs and values are arrays of counts. For an if statement, the value would have two counts; one for the if, and one for the else. Switch statements would have an array of values for each case.
  • f - Hash of function counts, where keys are function IDs.
  • fnMap - Hash of functions where keys are function IDs, and values are {name, line, loc, skip}, where name is the name of the function, line is the line the function is declared on, and loc is the Location of the function declaration (just the declaration, not the entire function body - see 'Location Objects' below.) If skip is present and true, then this indicates that this function was ignored by a ### instabul ignore ... ### pragma. Note that if a function is not ignored the skip field will be missing entirely.
  • statementMap - Hash where keys are statement IDs, and values are Location objects for each statement. The Location for a function definition is really an assignment, and should include the entire function. In addition to the normal location object fields, a statementMap entry can also have an optional skip field.
  • branchMap - Hash where keys are branch IDs, and values are {line, type, locations} objects. line is the line the branch starts on. type is the type of the branch (e.g. "if", "switch"). locations is an array of Location objects, one for each possible outcome of the branch. Note for an if statement where there is no else clause, there will still be two locations generated. Istanbul does not generate coverage for the default case of a switch statement if default is not explicitly present in the source code.
  • l - Hash of line counts, where keys are the line number.

--

--

Seungwon Go
ReturnValues

Writer, Youtuber, Investor, Programmer, Founder