[Sui Object] #2. Address-Owned Objects
목차
Sui의 Address-Owned Object 생성, 전송, 상호작용 방식과 테스트 방법을 알아봅니다. 이 글은 Sui Object 시리즈의 2번째 글입니다. 이전 글에서 Sui의 Object Model과 소유권 유형을 살펴보았습니다. 이번 글에서는 그중 가장 기본적인 소유권 형태인 Address-Owned Object를 다룹니다. Address-Owned Object는 32바이트 Address가 소유하는 Object입니다. 여기서 32바이트 Address란 Account Address 또는 다른 Object의 ID를 의미합니다. 소유자만이 해당 Object에 접근할 수 있으며, 다른 Address로 전송할 수 있습니다. Address-Owned Object를 생성하려면 Object를 만든 뒤 특정 Address로 전송해야 합니다. Sui의 이 함수는 Object를 정의한 Module 내부에서만 호출할 수 있습니다. Object의 전송 로직을 Module이 직접 제어하고 싶을 때 사용합니다. Address-Owned Object는 한 번 소유권이 설정되면 Shared Object로 변환할 수 없습니다. Object를 공유하려면 생성 시점에 Address-Owned Object는 다음과 같은 상황에서 적합합니다. Address-Owned Object의 소유자는 Account Address 또는 Object ID입니다. 소유자 유형에 따라 Object에 접근하는 방식이 달라집니다. Account Address가 소유한 Object는 해당 Address의 서명이 포함된 트랜잭션에서 직접 접근할 수 있습니다. 트랜잭션의 입력(Input)으로 Object ID를 지정하면, Sui 런타임이 소유권을 확인한 뒤 Object를 함수에 전달합니다. 이 함수를 호출할 때, 트랜잭션 서명자가 Object가 다른 Object에 의해 소유될 수도 있습니다. 이를 Child Object라고 합니다. Child Object는 트랜잭션 입력으로 직접 지정할 수 없고, 부모 Object를 통해 동적으로 접근해야 합니다. Sui CLI를 사용하면 특정 Address가 소유한 Object 목록을 조회할 수 있습니다. Sui는 앞서 작성한 Object를 생성한 뒤 특정 Address로 전송하고, 해당 Address가 Object를 소유하는지 확인합니다. Object를 다른 Address로 전송한 뒤, 소유권이 정상적으로 이전되었는지 검증합니다. 전체 목차:
개요
Address-Owned Object 생성
transfer Module은 이를 위해 두 가지 함수를 제공합니다.transfer::transfer
transfer::transfer는 key Ability만 가진 Object를 전송할 때 사용합니다.public fun transfer<T: key>(obj: T, recipient: address)module example::color_object {
use sui::transfer;
public struct ColorObject has key {
id: UID,
red: u8,
green: u8,
blue: u8,
}
public fun create(red: u8, green: u8, blue: u8, recipient: address, ctx: &mut TxContext) {
let obj = ColorObject {
id: object::new(ctx),
red,
green,
blue,
};
transfer::transfer(obj, recipient);
}
}ColorObject는 key Ability만 가지고 있으므로 transfer::transfer를 사용합니다. Module 외부에서는 이 Object를 직접 전송할 수 없고, create 같은 함수를 통해서만 전송이 이루어집니다.transfer::public_transfer
transfer::public_transfer는 key + store Ability를 가진 Object를 전송할 때 사용합니다.public fun public_transfer<T: key + store>(obj: T, recipient: address)store Ability가 있으면 어떤 Module에서든 호출할 수 있습니다. Object를 자유롭게 전송할 수 있도록 허용하고 싶을 때 사용합니다.public struct FreeObject has key, store {
id: UID,
value: u64,
}
public fun create(value: u64, recipient: address, ctx: &mut TxContext) {
transfer::public_transfer(
FreeObject { id: object::new(ctx), value },
recipient,
);
}FreeObject는 key + store Ability를 가지므로 transfer::public_transfer를 사용합니다. 이 Object는 Module 외부에서도 자유롭게 전송할 수 있습니다.공유 Object로의 변환 불가
transfer::share_object를 호출해야 합니다. 이미 특정 Address에 전송된 Object는 공유할 수 없으므로, Object의 소유권 형태는 설계 단계에서 미리 결정해야 합니다.Address-Owned Object를 사용하는 경우
Address-Owned Object와 상호작용
Account Address가 소유자인 경우
public fun update_value(obj: &mut MyObject, new_value: u64) {
obj.value = new_value;
}MyObject의 소유자여야 합니다. 소유자가 아닌 Address가 호출하면 트랜잭션이 실패합니다.Object ID가 소유자인 경우
transfer::receive를 사용하여 부모 Object의 컨텍스트에서 Child Object를 수신하고 인증합니다.public fun receive_child(parent: &mut UID, child: Receiving<MyObject>): MyObject {
transfer::receive(parent, child)
}CLI로 Object 조회
# 현재 활성 Address 확인
$ export ADDR=$(sui client active-address)
# 해당 Address가 소유한 Object 목록 조회
$ sui client objects $ADDR
# 특정 Object의 상세 정보 조회
$ sui client object <OBJECT_ID>Address-Owned Object 테스트
sui::test_scenario Module을 통해 트랜잭션 시뮬레이션 기반의 테스트를 지원합니다. Address-Owned Object의 생성, 전송, 소유권 변경을 테스트하는 방법을 살펴보겠습니다.ColorObject를 기반으로 테스트를 작성합니다.Object 생성과 전송
#[test]
fun test_create() {
let sender = @0xA;
let mut ts = ts::begin(sender);
{
ts.next_tx(sender);
let obj = new(255, 0, 255, ts.ctx());
transfer::transfer(obj, sender);
};
// sender가 ColorObject를 소유하는지 확인
{
ts.next_tx(sender);
let obj: ColorObject = ts.take_from_sender();
assert!(obj.red == 255);
assert!(obj.green == 0);
assert!(obj.blue == 255);
ts::return_to_sender(&ts, obj);
};
ts.end();
}take_from_sender를 사용하면 현재 트랜잭션 서명자가 소유한 Object를 가져올 수 있습니다. 테스트가 끝나면 return_to_sender로 Object를 반환해야 합니다.소유권 변경 검증
#[test]
fun test_transfer() {
let sender = @0xA;
let recipient = @0xB;
let mut ts = ts::begin(sender);
// ColorObject 생성 및 sender에게 전송
{
ts.next_tx(sender);
let obj = new(255, 0, 255, ts.ctx());
transfer::transfer(obj, sender);
};
// sender가 recipient에게 전송
{
ts.next_tx(sender);
let obj: ColorObject = ts.take_from_sender();
transfer::transfer(obj, recipient);
};
// sender는 더 이상 소유하지 않음
{
ts.next_tx(sender);
assert!(!ts.has_most_recent_for_sender<ColorObject>());
};
// recipient이 소유함
{
ts.next_tx(recipient);
assert!(ts.has_most_recent_for_sender<ColorObject>());
};
ts.end();
}has_most_recent_for_sender로 해당 Address가 Object를 소유하는지 확인할 수 있습니다. 전송 후 sender는 소유권을 잃고, recipient이 새로운 소유자가 됩니다.References