WELLDONE Code를 통해 NEAR에서 Rust 카운터 컨트랙트 배포하고 실행하기
Introduction
이번 튜토리얼에서 우리는 WELLDONE Code를 사용하여 NEAR 테스트넷에 간단한 카운터 스마트 컨트랙트를 배포하고 배포한 컨트랙트를 실행해보도록 하겠습니다. WELLDONE Code는 Web3 개발자가 더욱 쉽게 멀티 체인 상에서 컨트랙트를 배포하고 테스트할 수 있도록 도와주는 멀티체인을 지원하는 최초의 Remix IDE 플러그인 입니다.
새로운 네트워크에서 개발을 하고자 할 때마다 해당 네트워크 환경에 맞는 개발 환경을 다시 구축해야 하는 어려움을 대신 해결해주고, 개발자가 개발과 테스트에 집중할 수 있는 환경을 제공합니다. WELLDONE Code에 대한 보다 자세한 설명은 다음 포스팅을 참고해주세요.
현재(2022.09.01) 기준으로 WELLDONE Code는 Celo, Klaytn, NEAR 을 지원하고 있으며 Juno, Sui, Aptos 등을 추후 지원할 예정입니다.
- WELLDONE Code는 현재 WELLDONE Wallet을 지원합니다. WELLDONE Wallet 익스텐션을 크롬 브라우저에 설치해주세요. WELLDONE Wallet에 대한 자세한 설명은 다음 링크에서 확인할 수 있습니다.
- Rust에 대한 기본적인 이해가 있다면 컨트랙트를 이해하는 데 도움이 됩니다.
Remix IDE에서 WELLDONE Code 추가하기
먼저 Remix IDE 사이트에 접속한 후, 다음의 가이드를 따라 WELLDONE Code 플러그인을 추가합니다.
왼쪽 바의
Plugin Manager
아이콘을 클릭합니다.Url
필드에 https://remix-plugin.welldonestudio.io/ 를 추가하고,Plugin Name
을 입력해줍니다.
WELLDONE Code 플러그인이 성공적으로 추가되었다면 왼쪽 바에 나타난 아이콘을 클릭하여 WELLDONE Code를 실행할 수 있습니다.
Documentation
버튼을 클릭하면 WELLDONE Studio Docs로 이동하여 WELLDONE Code에 대한 자세한 설명과 사용법을 알아볼 수 있습니다. 사용하시면서 제보하고 싶은 이슈가 생긴다면 Make an issue
버튼을 클릭하여 자유롭게 이슈를 제보해주세요.
WELLDONE Code를 사용하여 NEAR 스마트 컨트랙트 작성하기
WELLDONE Wallet 연결하기
Select a Chain
섹션에서 NEAR를 클릭하여 들어가면 다음과 같은 화면이 나타납니다.
WELLDONE Code를 사용하기 위해서는 Connect to WELLDONE
버튼을 눌러서 WELLDONE Wallet과 먼저 연결해야 합니다. WELLDONE Wallet을 설치하지 않은 상태에서 버튼을 누른다면 터미널에 아래와 같은 로그가 표시될 것입니다. 로그의 설명을 따라 WELLDONE Wallet 익스텐션을 설치해주세요.
Please Install WELLDONE Wallet https://chrome.google.com/webstore/detail/welldone-wallet/bmkakpenjmcpfhhjadflneinmhboecjf . If you have installed it, please press the refresh button.
WELLDONE Wallet 익스텐션을 설치한 후에는 지갑과 NEAR 계정을 생성해야 합니다. 만약 계정을 생성하지 않고 Connect to WELLDONE 버튼을 클릭한다면 Please Unlock your WELLDONE Wallet OR Create Account
이라는 로그가 터미널에 출력될 것입니다. WELLDONE Wallet과 계정을 생성하는 방법은 다음 매뉴얼을 참고해주세요.
WELLDONE Wallet에서 NEAR 계정을 생성했다면, Connect to Wallet
버튼을 클릭하여 WELLDONE Code와 지갑을 연결합니다. 그러면 아래와 같이 접근을 요청하는 창이 뜨고 Accept
버튼을 클릭하면 Account 섹션에 연결된 계정의 ID와 잔액이 표시됩니다.
카운터 예제 템플릿 코드 생성하기
TEMPLATE CODE 섹션에서는 Rust, AssemblyScript, JavaScript, TypeScript로 작성된 간단한 Counter 예제와 Rust로 작성된 FT, NFT 예제 코드를 생성할 수 있습니다. 우리는 Rust로 작성된 Counter 예제를 함께 살펴보며 배포를 진행할 예정이므로 rs_counter
를 선택한 후 Create Template
버튼을 클릭합니다. 파일에 접근하는 권한을 요청하는 창에서 Accept
를 클릭한 후, 성공적으로 파일이 생성되었다면 터미널에 rs_counter is created successfully.
라는 로그가 출력될 것입니다.
워크스페이스로 이동해서 살펴보면 다음과 같이 near
폴더 안에 rs_counter
라는 폴더가 생성된 것을 확인할 수 있습니다.
만약, 제공하는 템플릿 코드가 아니라 직접 스마트 컨트랙트를 작성하고 싶다면 Project 섹션에서 사용하고자 하는 언어를 선택한 후 Project Name을 입력한 후 New Project
버튼을 클릭하면 다음과 같이 기본 구조가 자동으로 만들어집니다.
WELLDONE Code를 통해 NEAR 에서 컨트랙트를 컴파일 및 배포하기 위해서는 near/
폴더 내부에 컨트랙트를 작성해야 합니다. 언어 별로 더 자세한 구조는 Code 문서를 참고해주세요.
카운터 컨트랙트 톺아보기
이제 rs_counter 폴더 안의 src/lib.rs 파일에서 컨트랙트 코드를 함께 보면서 NEAR에서 Rust로 스마트 컨트랙트를 어떻게 작성하는 지 살펴보겠습니다.
src/lib.rs 코드의 제일 상단에서는 /Cargo.toml 파일에 선언된 near_sdk
에 접근해서 스마트 컨트랙트 작성에 필요한 모듈을 import 했습니다.
use near_sdk::borsh::{self, BorshDeserialize, BorshSerialize};
use near_sdk::{log, near_bindgen};
self
:BorshDeseiralize
와BorshSerialize
가 작동하기 위해 필요합니다. 스마트 컨트랙트 자기 자신을 참조합니다.BorshDeserialize
: 스마트 컨트랙트의 함수를 호출하면서 인자를 함께 전달할 때, JSON 타입의 인자를 역직렬화합니다.BorshSerialize
: 스마트 컨트랙트에서 결과를 다시 보낼 때 JSON으로 직렬화합니다.near_bindgen
: 스마트 컨트랙트라는 것을 알려주는 어노테이션으로 각 컨트랙트마다near_bindgen
어노테이션과 함께 선언된 struct가 적어도 하나씩은 있어야 합니다.
다음으로 struct
를 선언합니다. struct 선언자 위의 attributes 들은 WebAssembly로 컴파일된 파일이 NEAR 블록체인에서 호환될 수 있도록 해줍니다. Default
는 컨트랙트의 기본 생성자로, 컨트랙트를 배포한 후 함수를 실행하려 할 때 함수 실행 전에 NEAR의 가상머신이 기본값으로 컨트랙트를 초기화합니다.
아래 코드의 경우 val
은 i8
타입이기 때문에 기본값인 0으로 초기화가 될 것입니다. pub
는 스마트 컨트랙트가 public 하게 접근 가능하다는 것을 의미합니다.
#[near_bindgen]
#[derive(Default, BorshDeserialize, BorshSerialize)]
pub struct Counter {
val: i8,
}
만약 기본값이 아닌 다른 값으로 컨트랙트를 초기화하고 싶다면 아래와 같이 구현할 수 있습니다.
#[near_bindgen]
#[derive(BorshDeserialize, BorshSerialize)]
pub struct Counter {
val: i8,
}
impl Default for Counter{
fn default() -> Self {
Counter {val: 8}
}
}
이제 impl
을 사용해서 컨트랙트의 실제 함수들을 구현할 것입니다. NEAR의 스마트 컨트랙트 내부에 속하는 함수라는 사실을 명시하기 위해, 앞서 Struct에서 보았던 것처럼 near_bindgen
어노테이션을 추가해 줍니다.
#[near_bindgen]
impl Counter {
// ...
}
Rust에서 스마트 컨트랙트를 작성할 때는 이렇게 struct
와 실제 함수를 구현하는 impl
을 가지는 패턴으로 작성하는 것이 일반적입니다. 함수를 더 자세히 들여다보도록 하겠습니다.
카운터 컨트랙트에는 get_num
, increment
, decrement
, reset
네 가지 함수가 구현되어 있습니다. 먼저 get_num
함수 코드는 다음과 같습니다.
인자로 전달하는 &self
는 struct
의 인스턴스를 참조하고, i8
타입의 카운터 값을 반환하는 함수이기 때문에 리턴 타입을 명시해줍니다.
pub fn get_num(&self) -> i8 {
return self.val;
}
다음으로 storage
의 카운터 값에 1을 더해주는 increment
함수와 1을 빼주는 decrement
함수를 살펴보겠습니다. get_num
과는 다르게 increment
, decrement
함수는 컨트랙트 내부의 값을 변경합니다. &mut
은 mutable이라는 뜻으로 &mut self
는 self
변수에 대한 mutable한 참조를 생성합니다.
pub fn increment(&mut self) {
self.val += 1;
log!("Increased number to {}", self.val);
}
pub fn decrement(&mut self) {
self.val -= 1;
log!("Decreased number to {}", self.val);
}
마지막으로 컨트랙트의 카운터 값을 0으로 초기화하는 reset
함수입니다. increment
, decrement
함수와 마찬가지로 컨트랙트 내부의 값을 변경하기 때문에 &mut self
를 이용합니다.
pub fn reset(&mut self) {
self.val = 0;
log!("Reset counter to zero");
}
이렇게 스마트 컨트랙트 코드를 모두 살펴보았는데요. 다음으로 테스트 코드를 살펴보도록 하겠습니다.
mod tests
는 말 그대로 tests
라는 이름의 모듈을 의미합니다. cfg
는 괄호 안의 조건이 true
인 경우에만 모듈을 컴파일하라는 컴파일 명령어입니다. 아래의 경우에는 cargo test
명령을 실행할 때만 (test)
조건이 true
가 되기 때문에 테스트를 실행하지 않으면 해당 모듈은 컴파일되지 않습니다.
#[cfg(test)]
mod tests {
테스트 코드는 총 5개로 increment
, decrement
함수의 경우에는 먼저 counter 값을 0으로 초기화한 Counter 인스턴스를 생성한 후, increment
와 decrement
를 각각 실행하고 다시 get_num
으로 값을 가져와서 값이 잘 변경되었는지 확인합니다.
#[test]
fn increment() {
// instantiate a contract variable with the counter at zero
let mut contract = Counter { val: 0 };
contract.increment();
assert_eq!(1, contract.get_num());
}
#[test]
fn decrement() {
let mut contract = Counter { val: 0 };
contract.decrement();
assert_eq!(-1, contract.get_num());
}
increment_and_reset
함수는 위와 동일하게 Counter 인스턴스를 생성한 후 increment
와 reset
을 차례로 실행시켜서 마지막에 get_num
으로 가져온 값이 0으로 잘 초기화되었는 지 확인합니다.
#[test]
fn increment_and_reset() {
let mut contract = Counter { val: 0 };
contract.increment(1);
contract.reset();
assert_eq!(0, contract.get_num());
}
마지막 panics_on_overflow
와 panics_on_underflow
함수는 should_panic
이라는 attributes를 가지고 있는데요. 해당 테스트는 오류가 예상되는 테스트로 오류가 발생하면 테스트를 통과합니다.
#[test]
#[should_panic]
fn panics_on_overflow() {
let mut contract = Counter { val: 127 };
contract.increment();
}
#[test]
#[should_panic]
fn panics_on_underflow() {
let mut contract = Counter { val: -128 };
contract.decrement();
}
WELLDONE Code를 사용하여 스마트 컨트랙트 배포하기
컨트랙트 컴파일하기
WELLDONE Code는 NEAR에서 제공하는 기본 컴파일을 이용합니다. 이는 안정적인 컴파일을 제공하지만, 컨트랙트 실행 시 메소드의 파라미터를 직접 입력해야 하는 불편함이 있어, 추후 raen build
를 이용한 컴파일 옵션도 제공할 예정입니다. raen build
에 대한 자세한 정보는 다음 링크를 참고해주세요.
스마트 컨트랙트를 컴파일하기 위해서 먼저 PROJECT TO COMPILE 섹션에서 컴파일하고자 하는 디렉토리와 컨트랙트가 작성된 언어를 선택합니다. 우리는 near/rs_counter와 Rust를 선택한 후, 컴파일 버튼을 클릭합니다.
컴파일이 진행되면 터미널에 진행상황이 로그로 표시됩니다. 컴파일이 완료되면 near/rs_counter/out/counter_contract.wasm 경로에서 컴파일된 wasm 파일이 생성된 것을 확인할 수 있습니다.
만약 컴파일 이후에 코드에 수정 사항이 생겨 새로 컴파일을 진행해야 한다면, 기존에 생성되었던 out
디렉토리를 삭제하고 다시 컴파일해야 합니다.
컨트랙트 배포하기
컴파일이 완료되면 Deploy
버튼이 활성화될 것입니다. 해당 칸에 컨트랙트를 배포할 계정의 ID를 입력한 후 Deploy
버튼을 눌러 배포를 진행합니다. 이때 컨트랙트를 배포하는 계정은 연결된 지갑이 권한을 갖고 있는 계정이어야 합니다.
Deploy
버튼을 누르면 WELLDONE Wallet 익스텐션이 활성화되면서 다음과 같은 창이 나타납니다. 사용자가 SendTx
를 누르면 트랜잭션을 전송하고, 트랜잭션 해시값이 리턴됩니다.
터미널에 {"type":"error","value":"User denied transaction signature"}
라는 로그가 출력된다면 열려져 있는 WELLDONE Wallet 익스텐션 창을 닫은 후 다시 시도해주세요.
이후 트랜잭션 검증이 완료되고 성공적으로 Receipt를 가져온다면 터미널에 다음과 같은 로그가 출력되고, 좌측 패널에 배포된 컨트랙트의 정보를 보여줍니다.
WELLDONE Code를 사용하여 배포한 스마트 컨트랙트와 통신하기
이제 WELLDONE Code에서 배포한 컨트랙트와 통신을 해보겠습니다. Methods 섹션에서는 컨트랙트에서 접근할 수 있는 메소드의 목록을 보여줍니다. 만약 인자가 필요하다면 Add Argument 버튼을 눌러 필요한 인자의 이름과 타입, 값을 추가할 수 있습니다. Deposit에는 트랜잭션과 함께 보낼 토큰량을 NEAR 혹은 yoctoNEAR 두 가지 단위로 지정할 수 있습니다. 이후 메소드의 종류에 맞게 Call
혹은 View
버튼을 눌러 컨트랙트를 실행할 수 있습니다.
NEAR에서 컨트랙트를 호출하는 방법에는 view
와 call
두 가지가 있습니다. view
는 컨트랙트의 상태를 단순히 조회하는 함수를 호출할 때 사용하는 것으로서 가스 비용을 지불할 필요가 없습니다. 카운터 컨트랙트에서는 get_num
메소드가 view
함수에 해당합니다.
call
은 컨트랙트 내부의 상태를 변화시키는 함수를 호출할 때 사용하는 것으로, 가스 비용을 지불하는 트랜잭션이기 때문에 WELLDONE Wallet을 통한 서명이 필요합니다.
get_num
메소드 실행하기
먼저 view
함수인 get_num
메소드를 실행해보겠습니다. Methods에서 get_num
을 선택하고 View
버튼을 클릭합니다. view
함수는 지갑을 통해 서명할 필요가 없기 때문에 지갑과의 인터랙션이 필요하지 않습니다. 메소드 실행이 성공한다면 터미널에 메소드를 실행한 결과가 출력되는 것을 확인할 수 있습니다.
imcrement
, decrement
, reset
메소드 실행하기
다음으로 change
함수인 increment
와 decrement
메소드를 실행해보겠습니다. 먼저 Methods에서 increment
를 선택하고 해당 메소드는 다른 인자를 필요로 하지 않으므로 인자를 추가하지 않고 Call
버튼을 클릭합니다. 그러면 WELLDONE Wallet 익스텐션 창이 나타나고, SendTx
버튼을 누르면 트랜잭션 해시가 반환됩니다. 트랜잭션이 성공하면 터미널에 다음과 같이 트랜잭션 결과가 나타납니다.
이후 다시 get_num
메소드를 실행해서 값을 가져와보면 성공적으로 카운터가 1로 증가한 것을 확인할 수 있습니다. decrement
와 reset
메소드도 동일한 방법으로 실행할 수 있습니다.
Wrap-Up
우리는 WELLDONE Code를 사용해서 NEAR에서 컨트랙트를 작성하고, 배포하고, 실행하는 방법을 배웠습니다. WELLDONE Code를 사용하면 추가적인 환경 설정 없이 WELLDONE Wallet만을 이용해서 스마트 컨트랙트를 개발할 수 있습니다. Universal Provider를 사용해서 프론트엔드와 스마트 컨트랙트가 통신하는 방법을 알고 싶다면 다음 튜토리얼을 참고해주세요.