[KO] CosmWasm 101 와씀 1편: Counter 컨트랙트 톺아보기

Sigrid Jin
DSRV
Published in
23 min readMay 3, 2022

--

DSRV Dev Guild에서는 더 많은 개발자들과 Web3 인프라를 만들어가기 위해, 다양한 메인넷과 스마트 컨트랙트에 대한 개발자 컨텐츠를 연재합니다.

Disclaimer: 이 글은 정보 전달을 위한 목적으로 작성되었으며, 특정 프로젝트에 대한 투자 권고, 법률적 자문 등 목적으로 하지 않습니다. 모든 투자의 책임은 개인에게 있으며, 이로 발생된 결과에 대해 어떤 부분에서도 DSRV는 책임을 지지 않습니다. 본문이 포괄하는 내용들은 특정 자산에 대한 투자를 추천하는 것이 아니며, 언제나 본문의 내용만을 통한 의사결정은 지양하시길 바랍니다.

[CosmWasm 101 시리즈]

  1. Counter 컨트랙트 톺아보기
  2. Counter 컨트랙트 배포하기 (Cliffnet, Terra Testnet)
  3. Frontend 와 통신하기

What is WebAssembly?

WebAssembly(웹 어셈블리어, Wasm이라고 줄여 표현되기도 함) 는 C++, Rust, Kotlin와 같이 다양한 프로그래밍 언어를 활용하여 컴파일이 가능한 바이트코드 포맷입니다. 바이트코드가 가상머신 실행환경에서 동작함에도, WebAssembly는 네이티브 코드 수준의 속도를 자랑할 수 있다는 점에서 각광받고 있습니다.

Wasm은 모질라 재단에서 2015년부터 개발하고 있는 기술로, 2017년에 처음 발표되었고 현재는 W3C에서 웹 표준으로서 개발되고 있습니다. 이로 인해 ‘Web’은 이제 JavaScript/TypeScript라는 고수준 언어 외에도 Wasm이라는 저수준 언어도 사용할 수 있다는 점에서 큰 의미를 갖게 되었습니다. Wasm이 등장한 배경은 모바일 시대가 대두된 것으로부터 출발합니다. 프로그램을 작성할 때 단순히 PC만 지원해야 하는 것이 아니라, 스마트폰과 태블릿 PC 등 다양한 기기를 지원해야 합니다. 개발자들은 코드를 한 번만 작성하면 다양한 기기에서 동작할 수 있기를 바랐고, 웹 브라우저 위에서 동작하는 고수준 언어인 JavaScript가 이러한 염원을 풀어주었습니다.

JavaScript 생태계가 확장되면서 AI 모델이나 WebGL과 같은 게임 엔진도 웹 브라우저 위에서 구동시키려는 움직임이 나타났습니다. 하지만, AI 모델이나 게임 엔진은 워낙 무거워 일반적인 JavaScript 엔진 위에서 동작시키는 것이 어려웠습니다. Wasm은 이러한 기술적 요구 속에서 등장했습니다. Wasm은 웹 브라우저 상에서 동작하는 저수준 언어(low-level)로서, 높은 속도와 최적화 레벨을 자랑한다는 특징이 있습니다.

그렇다면, 블록체인에서 Wasm은 왜 사용돼야 할까요?

최초의 스마트 컨트랙트 개념을 적용한 Ethereum은 바이트코드를 컴파일하는 과정에서 EVM(Ethereum Virtual Machine)이라는 가상머신을 사용합니다. 그리고 Solidity는 EVM 엔진에서 실행할 수 있는 바이트코드로 컴파일할 수 있는 고수준 프로그래밍 언어이며, 2022년 기준 압도적으로 많이 사용되고 있는 언어입니다.

각 언어별 스마트 컨트랙트에 예치돼 있는 총 토큰 가치 비율, 2022년 4월 14일, 출처: DeFillama

하지만 블록체인 인프라의 대중화가 성큼 다가오면서 새로운 가상머신 엔진이 등장할 필요성이 대두되고 있습니다. 이는 EVM과 Solidity가 갖고있는 한계 때문인데, 이를 고려했을 때 Wasm은 EVM에 비교하여 상대적인 장점들을 갖고 있습니다.

  1. Wasm은 더 많은 언어를 수용합니다.
    Ethereum 계열의 Solidity는 일반 개발자들에게 낯선 언어이므로 새롭게 학습해야 한다는 점에서 생태계의 확장을 저해하는 요소가 되고 있습니다. 하지만, Wasm은 LLVM이라는 컴파일러 엔진 위에서 만들어진 언어라면 모두 사용할 수 있습니다. 즉 앞서 언급했듯이, C++이나 Rust, Kotlin 등을 활용하여 Wasm으로 컨트랙트를 작성할 수 있다는 뜻이죠.
  2. Wasm은 보다 안전합니다.
    스마트 컨트랙트가 디파이(DeFi)에서 굉장히 활발히 쓰인다는 것을 고려한다면, 언어 수준에서 높은 수준의 보안성과 테스트 편의성을 제공해야 합니다. Solidity는 이 점에서 기존 언어수준에 미달하는 모습을 보여주고 있지만, Cosmwasm은 Solidity에 비해 상대적으로 우수한 보안성을 갖춰 DeFi 등에 사용하는데 있어 유리합니다. 예를 들어, Cosmwasm은 컨트랙트에 대한 동기적 실행 자체를 엄격히 금지시킴으로써, 재진입 공격을 방지하고 있습니다.
  3. Wasm은 빠른 속도를 자랑합니다.
    스마트 컨트랙트의 사용도가 높아지면서 바이트코드의 연산 속도를 끌어올릴 필요성이 대두되고 있습니다.

현존하는 V8 엔진이 Wasm 기반 가상머신 바이트코드를 네이티브 수준의 속도를 제공합니다. 또한, Wasm은 메모리 관리가 상대적으로 안전하다는 점과 각 컨트랙트 별로 격리된 샌드박싱 실행 환경을 제공한다는 점도 주목할 만 합니다. WebAssembly는 아직 초기 기술이지만, Figma가 웹 버전에 접목하는 등 다양한 사용 예시가 등장하고 있습니다.

물론 비판도 적지 않습니다. 블록체인 계열에서는 일부 국소적인 단위에서 성능 최적화를 위하여 비결정론적 상황 (non-deterministic) 을 허용하고 있다는 측면에서, 모든 노드에서 동일한 코드를 실행했을 때 동일한 결과를 보장해야 하는 결정론적 전제 (deterministic) 를 해칠 수 있지 않느냐는 우려의 목소리도 있습니다. 또한 블록체인 가상머신으로써 Wasm을 도입하는 것이 여전히 초기 단계이다보니, EVM과 속도를 비교했을 때 기대했던 것만큼의 성능 향상이 보이지 않는다는 비판도 있습니다. 하지만 이러한 비판에도 불구하고, 스마트 컨트랙트의 사용도가 점점 높아짐에 따라 블록체인 가상머신의 속도 향상이 불가피하다는 것은 자명한 사실이기 때문에 앞으로 WebAssembly를 주목해볼 필요가 있습니다.

CosmWasm

Cosmos SDK 위에서 WebAssembly 바이트코드를 실행할 수 있는 모듈을 CosmWasm 이라고 부릅니다. 현재 CosmWasm을 활용하여 컨트랙트 실행환경을 구축한 대표적인 네트워크로는 Terra, Osmosis (테스트넷), Secret Network, Juno Network, cliffnet (테스트넷) 등이 있습니다.

이번 글에서는 Cosmos SDK 위에 WebAssembly 바이트코드를 실행할 수 있는 모듈인 CosmWasm 위에서 간단한 컨트랙트를 작성하면서 CosmWasm 코드를 기초적인 수준에서 이해하고자 합니다.

Solidity Contract와 배포 과정의 차이

개발자가 스마트 컨트랙트를 배포하는 과정을 한 번 나누어보겠습니다.

  1. 컴파일된 바이트코드의 업로드
  2. 새로운 컨트랙트를 인스턴스화 (instantiate)
  3. 컨트랙트의 실행

Ethereum에서는 상기한 과정 중 1번과 2번 과정이 서로 통합되어 있는데, 컨트랙트가 배포될 때 constructor() 가 호출됩니다. 이더리움의 모든 계정은 balance, code, storage, nonce 로 이루어지는데, 컨트랙트의 바이트코드는 컨트랙트 자체 계정의 state에 저장되는데요. 따라서 web3.eth.getCode(someAddress) 요청을 보내면 someAddress 가 보유하고 있는 바이트코드를 불러올 수 있습니다. 그래서 서로 다른 컨트랙트가 동일한 .sol 을 컴파일 하였더라도, 동일한 바이트코드를 서로의 state에 각각 저장하고 있는 꼴이 됩니다. 이러한 설계는 풀 노드의 LevelDB 용량을 낭비할 뿐만 아니라 컨트랙트를 업그레이드(upgradable contract)하는 것도 어렵게 만듭니다.

Cosmwasm은 컨트랙트 코드를 업로드하는 과정과 새로운 컨트랙트를 초기화하고 인스턴스화 시키는 과정을 서로 분리하였습니다. 이러한 설계는 서로 다른 컨트랙트가 하나의 바이트코드를 공유할 수 있도록 하였습니다. 컨트랙트를 인스턴스화 하기 위해서는 기존에 업로드된 바이트코드의 ID를 알고 있어야 합니다. InstantiateMsg 라는 설정값을 JSON 형태의 인자로 던져 새로운 컨트랙트를 생성하고 초기 상태를 설정할 수 있습니다.

Cosmwasm에서 컨트랙트를 작성한다는 것은 크게 instantiate()execute() 그리고 query() 함수를 구현한다는 의미입니다. 이 중에서 execute()는 컨트랙트의 내부 상태를 바꾸는 트랜잭션을 핸들링하는 메소드이고, query()는 컨트랙트의 내부 상태에 쿼리를 던지는 트랜잭션을 핸들링하는 메소드입니다. 당연하겠지만 상태를 변화시키는 execute()는 별도의 gas fee를 지불해야 하며, 상태 조회가 목적인 query()의 경우 gas fee를 지불하지 않습니다. 다음 장에서 count라는 컨트랙트 내부 변수의 숫자를 +1하거나 -1하면서 상태를 변경할 수 있는 코드를 작성해보며 Cosmwasm에 대해 더욱 이해해보도록 하겠습니다.

사전 요구사항

아래 코드를 이해하려면 다음의 선수 지식이 필요해요!

1. 아래의 Solidity 수도코드를 읽고, 코드의 동작을 이해할 수 있어야 해요.
2. Rust의 기초적인 문법을 이해해야 합니다.
공식 문서에서 1 — 4장 내용을 사전에 보고 오시면 좋습니다.

Counter Example

Solidity 수도코드(Pseudocode)로 위의 요구사항을 간단하게 구현해보면 다음과 같습니다.

같이 구현해 볼 프로그램의 요구사항은 다음과 같습니다.

  1. count 라는 내부 변수를 정수 데이터 타입으로 선언한다. 컨트랙트가 인스턴스화되면, count 에 payload로 명시한 값으로 초기화된다.
  2. increment()라는 함수가 있어, 이 메소드를 실행하면 count변수가 +1 씩 증가한다.
  3. 컨트랙트를 최초 배포한 사용자가 컨트랙트의 주인이 된다. reset()이라는 함수가 있어, 컨트랙트의 소유자는 count를 payload에 명시된 값으로 초기화하는 것이 가능하다.
  4. getCount() 함수를 실행하면, 컨트랙트는 현재 count변수의 값을 반환한다.

Ethereum 생태계의 오픈제플린(OpenZeppelin)과 같이, Cosmwasm을 활용한 컨트랙트 라이브러리를 제작하고 있는 팀인 Interwasm은 cw-template 저장소에서 Boilerplate Starter Pack을 제공하고 있습니다. 해당 저장소에서 cargo-generate 을 통해 설치하면 컴파일 준비가 된 컨트랙트 코드를 맞이할 수 있습니다. 해당 boilerplate 코드는 상기한 요구사항을 이미 구현하고 있습니다.

컨트랙트 초기화

다음 명령어를 통해 컨트랙트를 로컬 저장소에 clone 받습니다. 저희가 마련한 동일한 예제 코드 다음의 저장소에서 확인하실 수 있습니다.

cargo generate --git https://github.com/CosmWasm/cw-template.git --branch 0.16 --name my-first-contract
cd my-first-contract

먼저 테스트 코드를 분석하면서 요구사항이 어떻게 구현되어 있는지 살펴보려고 합니다. src/contract.rs 파일에 들어가서 mod tests 부분을 찾아보면 테스트 코드를 발견할 수 있습니다.
테스트는 크게 1) 컨트랙트 초기화, 2) count의 증가, 3) count의 초기화, 이렇게 세 부분으로 구성되어 있는데요, cargo test 를 실행하여 테스트를 실행해보면 총 4개의 테스트가 성공적으로 수행되게 됩니다.

먼저, proper_initalization 함수를 살펴보려고 합니다. Cosmwasm은 유닛 테스트를 위하여 별도의 mocking 라이브러리를 지원하는데, 그 중 하나가 mock_dependencies_with_balance입니다. 이 메소드는 Query를 날리는 임의의 사용자 계정을 만드는데, 이 계정은 token이라는 이름의 코인을 2개 가지고 있습니다.

그리고, mock_info라는 가상의 토큰 송금 페이로드 정보를 만듭니다. creator라는 이름의 sender가 컨트랙트 초기화 시에 earth라고 하는 가상의 코인 1000개를 보내라는 메시지입니다. 컨트랙트 초기화 시에 count: 17이라고 메시지를 명시하여 초기화 함수에 보냅니다. 이를 통해 우리는 컨트랙트의 count값이 17로 초기화될 것이라 기대할 수 있습니다. contract.rs에서 instantiate()함수를 실행시켜 새로운 컨트랙트를 생성하고, query를 보낼 때 사전에 구현된 GetCount()라는 함수를 사용하여 count값을 호출합니다. 이 때 assert_eq를 통해 컨트랙트의 값이 17로 초기화가 잘 되었는지 확인하기 위해 값을 비교해봅니다.

이제 컨트랙트의 초기화 과정에 대해 살펴볼까요? 컨트랙트를 instantiate 하기 위해서는 외부에서 어느 타입의 메시지를 어떤 형태로 보내주어야 한다는 것을 명시해야 합니다. 또한, 웹에서의 Controller 패턴처럼 어떤 타입의 메시지를 받았을 때 어느 메소드로 넘겨줄 지 비즈니스 로직을 작성해야 합니다. Cosmwasm에서는 InstantiateMsg라는 메시지 타입을 구조체로 정의해야 합니다. 요구사항에는 초기 count값이 메시지에 명시되어야 하므로, 다음과 같이 InstantiateMsg를 작성하면 됩니다.

아래 InstantiateMsg구조체는 src/msg.rs에 작성되어 있습니다. InstantiateMsg는 개발자가 컨트랙트 초기화를 시도할 때 Cosmwasm의 구현체인 wasmdMsgInstantiateContract로 전달되며, 해당 메시지 구조체 내부에 있는 초기값을 활용하여 컨트랙트의 상태가 초기화됩니다. 이 과정이 굉장히 인상적일 수 있는데요, 그 이유는 Ethereum과는 달리 복수의 컨트랙트가 공통의 코드 베이스를 공유하면서도 서로 다른 파라미터의 값으로 초기화될 수 있다는 점 때문입니다. 예를 들어, 동일한 ERC20.sol의 코드 베이스를 참조하는 복수의 ERC20 토큰 컨트랙트가 있고 각각의 컨트랙트는 티커(symbol)와 발행량(totalSupply)을 다르게 초기화할 수 있는 것입니다.

다음으로, 초기화 함수를 정의합시다. 초기화 함수는 InstantiateMsg 가 호출하는데, 하기 코드를 보고 간략히 설명해 보겠습니다.

먼저, 초기화 함수가 받아내는 파라미터를 살펴보도록 합시다. DepsMut는 다른 언어에서 Context와 비슷한 의미인데요. DepsMut를 통해 다른 컨트랙트를 호출할 수 있는 APIQuerier를 사용할 수 있으며, 현재 주소에 명시된 Storage에 접근하여 읽기 및 쓰기 작업을 수행할 수 있습니다.

  • 여기서 Storage는 컨트랙트에 영구히 저장되는 메모리 공간을 이야기합니다. 개발자는 처음으로 컨트랙트를 배포할 때 Storage 의 schema를 명시할 수 있는데, 이러한 스키마를 State라고 부릅니다. State에 대한 설명은 아래에서 좀 더 상세히 설명해보도록 하겠습니다.
  • 다음으로, API는 Cosmwasm 모듈 밖에서 정의된 시스템 함수를 이야기합니다. 공식 문서를 참고해보면 여러 cryptographic helper functions가 정의되어 있는데, 대표적으로는 컨트랙트 주소를 변환하는 addr_canonicalize()같은 함수가 있습니다.
  • Querier는 외부의 사용자가 컨트랙트를 향해 query를 던질 때 해당 request를 파싱해서 쿼리 요청을 처리하는 구조체입니다. 필요에 따라서 커스텀 쿼리 타입을 구현할 수도 있습니다.

그 다음으로, Envblock과 컨트랙트와 관련된 ContractInfo정보가 담겨 있습니다. blockheight, time, chain_id를 명시하고 있으며, ContractInfo에는 컨트랙트 주소 자체만을 담고 있는 구조체입니다. MessageInfo에는 sender의 주소와 sender가 컨트랙트 초기화 요청 시 함께 보낸 네트워크 native 토큰을 몇 개 보냈는지에 대한 정보가 명시되어 있습니다.

이제 컨트랙트의 Storage 스키마를 결정 짓는 State 구조체를 초기화합니다. State 구조체는 src/state.rs에 명시되어 있는데, 해당 구조체의 코드를 살펴보면 다음과 같습니다. 아래 코드에는 count라고 하는 32비트 integer type이 명시되어 있고, owner라고 하는 컨트랙트 주소가 있습니다. cosmwasm_stdcw_storage_plus를 import 하는 코드가 상위에 보이는데, 이를 통해 Cosmwasm에서 지원하는 자료구조와 데이터 타입을 사용할 수 있습니다.

한 가지 눈에 띄는 점은 Serde를 통하여 serializedeserialize도 지원하는 점입니다. serialize를 통하여 to_string 메소드를 사용할 수 있습니다. 이 메소드는 주어진 objectstring 타입으로 변환하는 역할을 수행합니다. 마치 JavaScript에서 JSON.stringify 과정을 통해 주어진 JSON 파일을 string 타입으로 직렬화하는 과정을 생각해보면 쉽게 이해하실 수 있습니다.

다음으로, 우리가 정의한 State를 Item 타입으로 감싸준 다음에 export하는 코드를 살펴보고자 합니다. Cosmwasm은 Storage에 저장하는 자료구조로 ItemMap을 제공합니다. Item은 단일 원소를 저장할 때 사용하지만, Map은 여러 개의 원소를 key-value 형태로 저장할 때 사용합니다. MapSoliditymapping타입을 통해 직관적으로 이해해볼 수 있습니다. Item과 Map에 대한 자세한 내용은 추후 작성해보도록 하겠습니다.

위에서는 Item이라는 단일 원소를 저장하는 자료구조를 통해 State를 저장했으며, 해당 State의 storage keystate라고 정의했습니다.

다시 초기화 함수로 돌아와보면, Cosmwasm은 모든 컨트랙트가 업그레이드 가능하다는 것을 전제하고 있으므로, 이 컨트랙트의 버전이 어느 버전인지 명시하도록 하기 위해 contract_version을 할당할 수 있습니다. 이어서는 컨트랙트를 생성할 때 초기화할 state 인스턴스를 STATE에 저장하는 것을 확인할 수 있습니다.

STATEstate 구조체를 감싸고 있는 Item이라는 프록시 객체라고 생각하면 되는데, 이 Item 객체는 컨트랙트 Storagestate 구조체를 저장하는 함수를 실행합니다.

💡 DSRV’s Tip: 프록시 패턴이 무엇일까요?프록시 패턴은 어떤 다른 객체로 접근하는 것을 통제하기 위해서 그 객체의 대리자(surrogate)나 자리표시자(placeholder)의 역할을 하는 객체를 제공하는 패턴입니다. 다시 말해, 프록시 객체는 원래 객체를 감싸고 있는 객체인데, 원본 객체의 접근을 제어하고 싶거나, 부가 기능을 추가하고 싶을 때 사용합니다.예를 들어, Spring JPA에서는 지연 로딩을 구현하기 위해서 프록시 패턴을 사용합니다. 게시글에 댓글을 작성하는 기능을 구현한다고 생각해보겠습니다. Spring JPA는 내부적으로 개발자가 만든 Article의 프록시 객체를 생성하고 이 프록시 게시글 객체는 댓글에게 마치 자신이 진짜 게시글 객체인 것처럼 행동하는데요, 이 프록시 객체는 게시글 데이터가 필요한 시점에 데이터베이스에서 게시글 정보를 가져오도록 할 수 있는 초기화 기능을 가지고 있습니다.이번 예시에서는 state 구조체에 Item 자료구조가 지원하는 공통 부가 기능을 추가하고 있습니다. 그리고 state 구조체와 메시지를 주고 받기 위해서는 state를 감싸고 있는 Item 클래스로 접근하게 됩니다. 이 과정이 프록시 패턴과 유사합니다.

이 과정을 통해 우리가 정의한 state구조체의 형태로 컨트랙트가 초기화되고 컨트랙트 Storage에 값을 저장하게 됩니다.

마지막으로, returnResponse 객체를 구현해야 합니다. 이 Response 객체는 먼저 비어있는 상태로 초기화되어 있는데, add_attribute를 통해 attribute를 추가하면서, 반환값을 추가로 명시할 수 있습니다. 위의 코드에서는 방금 실행한 메소드의 이름(“method”)과 컨트랙트 보유자 주소(“owner”), 그리고 컨트랙트 Storage에 있는 count 정수 값(“count”)을 반환하고 있습니다.

컨트랙트 함수의 실행

앞서 초기화 함수에서 살펴 보았던 것처럼, 사용자가 어떤 메시지 객체를 보내오면, 컨트랙트의 어느 함수가 실행될 지 명시해야 합니다. 상기 요구사항에 따르면 increment 실행과 reset 실행 총 두 가지 실행이 가능합니다. 컨트랙트가 실행 가능한 경우는 여러 가지일 것이므로, ExecuteMsgenum 타입으로 정의해야 합니다. 쿼리를 날리는 메시지와는 달리, ExecuteMsg는 컨트랙트의 상태를 바꾸는 목적이 있으므로 가스비가 필요합니다.

이제 ExecuteMsg를 받아주는 execute 함수를 구현해보려고 합니다. matchswitch와 동일한 역할을 수행하는데, 들어온 요청을 increment 함수와 reset 함수로 분기해주는 역할을 수행합니다.

먼저, ExecuteMsgIncrement일 때 실행되는 try_increment를 살펴봅시다. 특이한 점은 try_increment의 파라미터로 storage에 대한 mutable 참조를 담아내는 deps의 소유권을 넘긴다는 점입니다.

상기 코드에는 소유권을 넘겨받은 deps를 활용하여 storage에 접근하고, STATE 라는 Item 객체에 접근하여 해당 deps에 저장된 state를 불러와 새로운 상태로 update하고 있습니다. STATE는 내부적으로 storage에 접근해 state 정보를 불러오고 이를 State 구조체로 mapping하는 역할을 수행합니다.

만약, storage 에 저장된 item이 없었다면 에러를 반환합니다. STATE.update는 인자로 “클로저”를 받습니다. 위의 코드를 살펴보면 클로저가 state.count 값에 1을 더하는 로직을 구현하고 있음을 알 수 있습니다. 참고로, JavaScript에 익숙한 독자라면 “클로저”를 reducer와 같다고 생각하면 이해가 편할 것입니다.

Item.rs에 선언된 update 함수를 살펴보면 다음과 같습니다. 즉, 클로저를 준비해주면 단 한 번만 그 클로저를 실행할 수 있도록 하고 FnOnce(), 이후에는 해당 클로저를 메모리에서 제거하게 됩니다. 유의할 점은 클로저 내부에서 소유권을 받아낸 객체가 있을 경우 단 한 번만 클로저가 실행 가능하며, 두 번 이상 실행하려는 경우 컴파일러가 사전에 에러를 반환한다는 것입니다. Rust의 “클로저”에 대한 깊이 있는 이해는 추후 ItemMap을 설명할 때 상세히 진행해보도록 하겠습니다.

Reset 역시 비슷하게 작동합니다. 특이한 점은 오직 컨트랙트 소유자만이 count를 초기화할 수 있다는 점입니다. 따라서, 트랜잭션을 보낸 sender가 컨트랙트 소유자 주소인 state.owner와 다르면 에러를 반환합니다. ContractError를 반환할 때 별도의 Custom Exception으로 Unauthorized를 선언해주었으며, 해당 exception은 error.rs에서 확인할 수 있습니다.

컨트랙트 변수 쿼리하기

https://medium.com/cudos/build-a-smart-contract-on-the-cudos-network-part-3-main-business-logic-%EF%B8%8F-fb2e7265faaa

상술했다시피, 쿼리하는 메시지는 컨트랙트의 상태를 변경하지 않기 때문에 별도의 가스비를 지불할 필요가 없습니다. 쿼리를 하기 위해서는 쿼리의 종류와 Response 구조체를 설계해야 합니다. 아래를 예시를 통해 자세히 알아보도록 하겠습니다.

위와 같이 구현한다면, 컨트랙트는 프론트엔드의 요청 페이로드를 파싱해서 GetCount가 있는지 살펴봅니다. 만약 GetCount가 있다면, 해당 메시지를 파싱해서 QueryMsg의 인스턴스를 만들어냅니다. 이 때 유의할 점은, serde 라이브러리를 사용했기에 프론트엔드 측에서는 CamelCase가 아니라 snake_case로 요청을 전송해야 한다는 점인데요, 이를 통해 각 언어의 표준에 맞추어 코딩할 수 있도록 구현했습니다. Java Spring에 익숙한 독자라면, Jackson 라이브러리의 JsonProperty와 동일한 기능을 수행한다고 생각하면 쉽게 이해해보실 수 있습니다. 따라서, 프론트엔드 측에서 넘기는 JSON 타입은 다음과 같습니다.

이제 CountResponse를 살펴볼 차례입니다. Response에서는 count의 값을 알려주어야 하므로, 상기와 같이 count를 선언하고, 타입을 32비트의 정수 타입으로 정의하면 됩니다.

마지막으로, query 기능을 구현하는 코드를 살펴보도록 합시다. query 함수는 QueryMsg에서 GetCount라는 함수가 호출되었을 경우, query_count로 실행 책임을 위임합니다. query_countcount 값을 추출하기 위하여 Item 객체의 load 함수를 사용합니다. 해당 함수는 저장된 State 객체가 없을 경우 에러를 반환합니다. 만약 State 객체가 있다면 responsequery 함수에 StdResult<Binary> 형태로 반환합니다. 여기서 반환값이 Binary인 이유는 Deserialize를 하기 위함입니다.

특이한 점은 query 함수의 파라미터에 명시된 데이터 타입인 Deps는 immutable context를 공유한다는 점입니다. 즉, Storage에 읽기는 가능하지만 쓰기는 불가능합니다. 마치 Solidity에서 읽기와 쓰기가 동시에 가능한 로우 레벨 코드인 call과 읽기 기능만 가능한 로우 레벨 코드인 staticcall의 차이와 유사하다고 볼 수 있습니다.

글을 마무리하며..

이번 시간에는 WebAssembly가 무엇인지와 어떤 의미를 갖는지 알아보고, 실제로 Cosmwasm을 활용한 간단한 컨트랙트인 Counter 예제를 살펴보았습니다. Rust의 장점으로 인해 기존의 Solidity에 비해 안전하게 코딩할 수 있다는 점, 메소드 별로 책임과 역할이 명확하게 분리되어 있어 유지보수가 용이하다는 점을 느낄 수 있었습니다. 그러나 기존 Solidity에 비하여 코드 자체가 과하게 verbose하다는 점은 단점으로 파악되었습니다.

다음 시간에는 오늘 작성한 컨트랙트 코드를 컴파일하여 생성한 wasm 파일을 활용하여, Cliffnet, Terra Testnet, 그리고 Osmosis Testnet에 각각 배포하는 과정을 설명해보고자 합니다. 이 글이 Webassembly 공부를 시작하고자 했던 많은 개발자분들께서 Cosmwasm에 쉽게 입문하는데 도움이 되었길 바라며, 다음 글로 또 찾아오도록 하겠습니다. 이 글을 읽는데 귀중한 시간을 할애해주셔서 감사합니다.

Author
Sigrid Jin | Jin Hyung Park (Twitter @jypthemiracle)

Reviewed by
Owen Yoonjae Hwang of DSRV, Research Manager (Twitter @owenhwang_dsrv)
Heesung Bae of DSRV, Frontend Developer (Twitter
@BaeHeesung25)
Heesu Shin of DSRV, Backend Developer (Twitter
@LucasShin8)

--

--