[Hello Sui] #2. Smart Contract 배포하기

이 글은 Hello Sui 시리즈의 2번째 글입니다.

1.png

개요

Sui는 기존 블록체인과 달리 Object를 기본 저장 단위로 사용합니다. 키-값 저장소 기반의 다른 체인들과 달리, Sui의 모든 데이터는 고유한 ID를 가진 Object로 표현됩니다. Sui 스마트 컨트랙트 역시 다른 Object들을 조작하는 Object로 표현됩니다.

Object는 크게 두 가지 유형으로 나뉩니다.

  • Immutable Object: 전송·변경·삭제가 불가능하며 소유자가 없습니다. 누구나 공개적으로 접근할 수 있습니다.
  • Mutable Object: 전송·변경·삭제가 가능하며 Sui 주소가 소유할 수 있습니다. 공개 공유도 가능합니다.

각 Object는 고유한 ID와 버전 번호로 온체인에서 식별됩니다. 트랜잭션이 실행될 때마다 수정된 Object는 동일한 ID 하에 새로운 버전 번호가 기록됩니다.

스마트 컨트랙트는 Move 언어로 작성합니다. Move는 플랫폼 중립적인 언어로, 리소스 안전성(Resource Safety)을 언어 차원에서 강제합니다. 모든 Resource는 트랜잭션 종료 시 반드시 전역 저장소로 이동되거나 폐기되어야 하며, 임의로 복사하거나 소멸시킬 수 없습니다.

이 글에서는 Hello World 스마트 컨트랙트를 직접 작성하고, Move Package를 빌드·배포한 뒤 배포된 컨트랙트와 상호작용하는 과정을 살펴봅니다.


Move Programming Language

Move는 Sui의 스마트 컨트랙트 언어입니다. 특정 플랫폼에 종속되지 않는 플랫폼 중립적 설계로, 서로 다른 블록체인 환경에서도 공통 라이브러리와 도구를 공유할 수 있습니다.

Move에서 네트워크에 배포되는 단위를 Package라고 합니다. Package는 불변(Immutable)의 Move 바이트코드 모음으로, 한 번 배포되면 수정할 수 없습니다. Package 안에는 하나 이상의 Module이 포함되며, 각 Module이 온체인 Object와의 상호작용 로직을 정의합니다.

이 섹션에서는 Hello World 스마트 컨트랙트 코드를 직접 살펴보며 Move의 기본 구조를 확인해보겠습니다.


Clone "Hello World" Smart Contract

Sui 공식 Hello World 레포지토리를 클론합니다.

git clone https://github.com/MystenLabs/sui-stack-hello-world.git
cd sui-stack-hello-world/move/hello-world

디렉토리 구조는 다음과 같습니다.

hello-world/
├── Move.toml       # Package 설정 및 의존성
└── sources/
    └── greeting.move  # 스마트 컨트랙트 로직
  • Move.toml: Package 이름, 주소, 의존성(Sui framework 등)을 정의합니다.
  • greeting.move: 실제 컨트랙트 로직이 담긴 Move 소스 파일입니다.

Code Review

greeting.move 파일의 전체 구조는 다음과 같습니다.

module hello_world::greeting {
    use std::string::{Self, String};
    use sui::object::{Self, UID};
    use sui::transfer;
    use sui::tx_context::TxContext;

    /// A shared greeting
    public struct Greeting has key {
        id: UID,
        text: String,
    }

    /// Creates a globally shared Greeting object
    public fun new(ctx: &mut TxContext) {
        let greeting = Greeting {
            id: object::new(ctx),
            text: string::utf8(b"Hello world!"),
        };
        transfer::share_object(greeting);
    }

    /// Updates the text of a Greeting object
    public fun update_text(greeting: &mut Greeting, text: String) {
        greeting.text = text;
    }
}

module hello_world::greeting

<Package 이름>::<Module 이름> 형식으로 Module을 선언합니다.

Greeting 구조체

has key ability를 선언하면 해당 구조체가 Sui Object로 취급됩니다. key가 있는 구조체는 반드시 첫 번째 필드로 id: UID를 가져야 합니다. UID는 Sui가 Object를 온체인에서 고유하게 식별하는 데 사용하는 타입입니다.

new 함수

object::new(ctx)로 고유한 UID를 생성하고 Greeting Object를 초기화합니다. 이후 transfer::share_object()로 누구나 접근·수정할 수 있는 Shared Object로 만듭니다.

update_text 함수

&mut Greeting 참조를 인자로 받아 text 필드를 업데이트합니다. Object를 직접 소유하지 않고 참조로만 수정하기 때문에 Resource Safety 규칙을 위반하지 않습니다.


Resource Safety

Move는 Resource의 생명주기를 컴파일 타임에 강제합니다. 두 가지 핵심 규칙이 있습니다.

  1. 모든 Resource는 트랜잭션 종료 시 전역 저장소로 이동되거나 폐기되어야 합니다.
  2. Resource는 복사할 수 없습니다.

이 규칙은 Move 바이트코드 검증기가 자동으로 시행하므로, 규칙을 위반하는 코드는 컴파일 단계에서 거부됩니다.

Hello World 코드에서는 이 규칙이 다음과 같이 적용됩니다.

  • new 함수에서 생성된 Greeting Object는 transfer::share_object()를 통해 전역 저장소로 이동됩니다.
  • update_text 함수는 Greeting Object 자체가 아닌 &mut Greeting 참조를 인자로 받습니다. Object를 함수 안으로 이동시키지 않으므로 복사·소유권 이전 없이 안전하게 수정할 수 있습니다.

EVM과의 차이점

EVM은 가스(Gas) 기반의 Resource Safety 전략을 채택합니다. EVM 체인의 모든 opcode에는 연결된 가스 가격이 있으며, 이를 통해 트랜잭션 비용을 발생시켜 네트워크가 단일 트랜잭션을 무한정 실행하는 것을 방지합니다. 반면 Move는 컴파일 타임에 모든 Resource가 적절히 처리되도록 정적으로 검증합니다.


Build Move Package

다음 명령어로 Move Package를 빌드합니다.

sui move build

빌드가 성공하면 다음과 같은 출력이 나타납니다.

UPDATING GIT DEPENDENCY https://github.com/MystenLabs/sui.git
INCLUDING DEPENDENCY Sui
INCLUDING DEPENDENCY MoveStdlib
BUILDING hello_world

컴파일러는 타입 오류, 문법 오류를 검사하고 Move 소스 코드를 바이트코드로 변환합니다. 빌드 결과물은 build/ 디렉토리에 생성됩니다.


Publish Move Package

배포 전에 현재 활성화된 네트워크 환경과 잔액을 확인합니다.

sui client active-env
sui client balance

테스트넷에 배포할 경우, 잔액이 없다면 Sui Testnet Faucet에서 테스트 토큰을 받을 수 있습니다.

아래 명령어로 Package를 배포합니다.

sui client publish

배포가 성공하면 트랜잭션 결과에서 PackageID를 확인할 수 있습니다.

╭─────────────────────────────────────────────────────────────────────╮
│ Object Changes                                                       
├─────────────────────────────────────────────────────────────────────┤
│ Published Objects:                                                   
│  ┌──                                                                 
│  │ PackageID: 0x...                                                  
│  │ Modules: greeting                                                 
│  └──                                                                 
╰─────────────────────────────────────────────────────────────────────╯

Interact with Move Package

배포한 Package의 new 함수를 호출해 Greeting Object를 생성합니다.

sui client call \
  --package <PACKAGE_ID> \
  --module greeting \
  --function new

트랜잭션이 성공하면 결과에서 새로 생성된 Greeting Object의 ID를 확인할 수 있습니다.

생성된 Object의 상태는 다음 명령어로 조회합니다.

sui client object <OBJECT_ID>

text 필드에 "Hello world!"가 저장된 것을 확인할 수 있습니다.

이번엔 update_text 함수를 호출해 텍스트를 변경합니다.

sui client call \
  --package <PACKAGE_ID> \
  --module greeting \
  --function update_text \
  --args <OBJECT_ID> "Hello Sui!"

다시 sui client object <OBJECT_ID>로 조회하면 text 필드가 변경된 것을 확인할 수 있습니다.


References

전체 목차:

  1. [Hello Sui] #1. Sui Cli 설치하기
  2. [Hello Sui] #2. Smart Contract 배포하기
  3. [Hello Sui] #3. Frontend 연결하기