본문으로 건너뛰기

입력과 결과

Programmable transaction blocks(PTBs)는 입력을 바탕으로 동작하며 결과를 생성한다. 입력은 사용자가 PTB에 제공하는 값으로, network 위의 object이거나 숫자와 문자열 같은 pure value이다. 결과는 PTB command가 생성하는 값이며, 같은 transaction 안의 이후 command가 사용할 수 있다. 입력과 결과는 함께 PTB 실행을 통과하는 데이터 흐름을 이룬다.

Inputs

PTB의 입력 인자는 크게 object 또는 pure value로 분류된다. 이 인자의 직접 구현은 종종 transaction builder나 SDK에 의해 가려진다. 각 Input은 사용되는 object를 지정하는 데 필요한 metadata를 담는 object인 Input::Object(ObjectArg)이거나, 값의 byte를 담는 pure value인 Input::Pure(PureArg)이다.

object 입력의 경우 필요한 metadata는 ownership of the object에 따라 다르다. ObjectArg enum의 데이터는 다음과 같다:

  • object가 address에 의해 소유되거나 immutable이면 ObjectArg::ImmOrOwnedObject(ObjectID, SequenceNumber, ObjectDigest)를 사용한다. 이 세 값은 각각 object의 ID, sequence 또는 version number, object 데이터의 digest를 지정한다.

  • object가 shared object이면 ObjectArg::SharedObject { id: ObjectID, initial_shared_version: SequenceNumber, mutable: bool }를 사용한다. ImmOrOwnedObject와 달리 shared object의 version과 digest는 network의 consensus protocol이 결정한다. initial_shared_version은 object가 처음 공유되었을 때의 version이며, consensus가 아직 그 object를 포함한 transaction을 보지 못했을 때 사용된다. 모든 shared object는 수정될 수 있지만 mutable flag는 이 transaction에서 object를 가변으로 사용할지를 나타낸다. mutable flag가 false로 설정된 경우 object는 읽기 전용이며, 시스템은 다른 읽기 전용 transaction을 병렬로 스케줄할 수 있다.

  • object가 다른 object에 의해 소유되는 경우, 즉 TransferObjects command 또는 sui::transfer::transfer 함수를 통해 object ID로 전송된 경우에는 ObjectArg::Receiving(ObjectID, SequenceNumber, ObjectDigest)를 사용한다. object 데이터 자체는 ImmOrOwnedObject 경우와 같다.

pure 입력의 경우 제공되는 데이터는 BCS bytes뿐이며, 이 값은 Move 값을 구성하기 위해 deserialize된다. 모든 Move 값을 BCS bytes에서 구성할 수 있는 것은 아니다. 다음 타입은 pure value와 함께 사용할 수 있다:

  • 모든 primitive 타입: u8, u16, u32, u64, u128, u256, bool, address.

  • 문자열 하나로, ASCII string std::ascii::String 또는 UTF8 string std::string::String이 될 수 있다. 두 경우 모두 byte가 해당 encoding에 맞는 유효한 문자열인지 검증한다.

  • object ID sui::object::ID.

  • vector인 vector<T>.

  • option인 std::option::Option<T>.

vector와 option의 경우 T는 유효한 pure 입력 타입이어야 한다. 이 규칙은 재귀적으로 적용된다.

bytes는 MoveCall이나 MakeMoveVec 같은 command에서 타입이 지정될 때까지는 검증되지 않는다. 즉 하나의 pure 입력을 여러 타입의 Move 값을 생성하는 데 사용할 수 있다는 뜻이다.

Results

transaction command는 값 배열을 하나 생성한다. 이 배열은 비어 있을 수도 있다. 값의 타입은 임의의 Move 타입이 될 수 있으므로 입력과 달리 결과 값은 object나 pure value로 제한되지 않는다. 생성되는 결과의 개수와 타입은 각 transaction command마다 다르다:

  • MoveCall: 결과 개수와 타입은 호출되는 Move 함수에 의해 결정된다. reference를 반환하는 Move 함수는 현재 지원되지 않는다.

  • SplitCoins: 하나의 coin에서 하나 이상의 coin을 생성한다. 각 coin의 타입은 분할되는 coin의 타입 T에 대응하는 sui::coin::Coin<T>이다.

  • Publish: 새로 게시된 패키지에 대한 upgrade capability sui::package::UpgradeCap을 반환한다.

  • Upgrade: 업그레이드된 package에 대한 upgrade receipt sui::package::UpgradeReceipt를 반환한다.

  • TransferObjectsMergeCoins는 빈 결과 벡터를 생성한다.

게임용으로 HeroSword를 만드는 다음 Move 함수를 생각해 보자.

public struct Sword has key, store {
id: UID,
attack: u64,
}

public fun new_sword(attack: u64, ctx: &mut TxContext): Sword {
Sword {
id: object::new(ctx),
attack
}
}

public struct Hero has key, store {
id: UID,
health: u64,
stamina: u64,
}

/// Hero는 체력 100과 stamina 10으로 시작한다.
public fun mint_hero(ctx: &mut TxContext): Hero {
Hero {
id: object::new(ctx),
health: 100,
stamina: 10,
}
}

또한 Hero에게 Sword를 장착하는 특수한 Move 함수도 있다:

/// Hero는 sword를 하나만 장착할 수 있다.
/// sword를 장착하면 `Hero`의 power가 그 attack만큼 증가한다.
public fun equip_sword(self: &mut Hero, sword: Sword) {
if (df::exists_(&self.id, b"sword".to_string())) {
abort (EAlreadyEquippedSword)
};
self.add_dof(b"sword".to_string(), sword)
}

TypeScript를 사용해 Hero 생성, Sword 생성, 그리고 sword를 hero에 붙이는 과정을 하나의 transaction으로 결합한다:

const tx = new Transaction();

// Move 함수 mint_hero에 따르면 이 moveCall은 Hero 타입의 Object를 반환한다
const hero = tx.moveCall({
target: `0x123::hero::mint_hero`,
arguments: [],
typeArguments: [],
});

// Move 함수 new_sword에 따르면 이 moveCall은 Sword 타입의 Object를 반환한다
const sword = tx.moveCall({
target: `0x123::hero::new_sword`,
arguments: [tx.pure.u64(10)],
typeArguments: [],
});

// Move 함수 equip_sword에 따르면 이 moveCall은 Hero 타입의 object와 Sword 타입의 Object를 인자로 기대한다:
tx.moveCall({
target: `0x123::hero::equip_sword`,
arguments: [hero, sword],
});