입력과 결과
프로그래머블 트랜잭션 블록(PTBs)는 입력을 바탕으로 동작하며 결과를 생성한다. 입력은 사용자가 PTB에 제공하는 값으로, network 위의 객체이거나 숫자와 문자열 같은 pure value이다. 결과는 PTB command가 생성하는 값이며, 같은 트랜잭션 안의 이후 command가 사용할 수 있다. 입력과 결과는 함께 PTB 실행을 통과하는 데이터 흐름을 이룬다.
Inputs
PTB의 입력 인자는 크게 객체 또는 pure value로 분류된다.
이 인자의 직접 구현은 종종 트랜잭션 builder나 SDK에 의해 가려진다.
각 Input은 사용되는 객체를 지정하는 데 필요한 메타데이터를 담는 객체인 CallArg::Object(ObjectArg)이거나, 값의 BCS bytes를 담는 pure value인 CallArg::Pure(Vec<u8>)이다.
object 입력의 경우 필요한 metadata는 ownership of the object에 따라 다르다.
ObjectArg enum의 데이터는 다음과 같다:
-
객체가 address에 의해 소유되거나 immutable이면
ObjectArg::ImmOrOwnedObject(ObjectID, SequenceNumber, ObjectDigest)를 사용한다. 이 세 값은 각각 객체의 ID, sequence 또는 version number, 객체 데이터의 digest를 지정한다. -
객체가 공유 객체이면
ObjectArg::SharedObject { id: ObjectID, initial_shared_version: SequenceNumber, mutable: bool }를 사용한다.ImmOrOwnedObject와 달리 공유 객체의 version과 digest는 network의 consensus protocol이 결정한다.initial_shared_version은 객체가 처음 공유되었을 때의 version이며, consensus가 아직 그 객체를 포함한 트랜잭션을 보지 못했을 때 사용된다. 모든 공유 객체는 수정될 수 있지만mutableflag는 이 트랜잭션에서 객체를 가변으로 사용할지를 나타낸다.mutableflag가false로 설정된 경우 객체는 읽기 전용이며, 시스템은 다른 읽기 전용 트랜잭션을 병렬로 스 케줄할 수 있다. -
객체가 다른 객체에 의해 소유되는 경우, 즉
TransferObjectscommand 또는sui::transfer::transfer함수를 통해 객체 ID로 전송된 경우에는ObjectArg::Receiving(ObjectID, SequenceNumber, ObjectDigest)를 사용한다. 객체 데이터 자체는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 stringstd::string::String이 될 수 있다. 두 경우 모두 byte가 해당 encoding에 맞는 유효한 문자열인지 검증한다. -
객체 ID
sui::object::ID. -
vector인
vector<T>. -
option인
std::option::Option<T>.
vector와 option의 경우 T는 유효한 pure 입력 타입이어야 한다.
이 규칙은 재귀적으로 적용된다.
bytes는 MoveCall이나 MakeMoveVec 같은 command에서 타입이 지정될 때까지는 검증되지 않는다.
서로 다른 각 타입은 bytes의 별도 typed copy를 만들기 때문에 bytes가 각 타입에 대해 유효하기만 하면 같은 pure 입력을 여러 타입에서 사용할 수 있 다. 각 typed copy는 고유한 입력으로 취급되므로 mutation은 해당 타입의 값에만 영향을 주고, 같은 bytes에서 생성된 모든 값에는 영향을 주지 않는다.
Results
각 트랜잭션 command는 값 배열을 하나 생성한다. 이 배열은 비어 있을 수도 있다. 값의 타입은 임의의 Move 타입이 될 수 있으므로 입력과 달리 결과 값은 객체나 pure value로 제한되지 않는다. 생성되는 결과의 개수와 타입은 각 트랜잭션 command마다 다르다:
-
MoveCall: 결과 개수와 타입은 호출되는 Move 함수에 의해 결정된다. Move call은 reference(&T또는&mut T)를 반환할 수 없으며, 이 제한은 향후 해제될 예정이다. -
SplitCoins: 하나의 코인에서 하나 이상의 코인을 생성한다. 각 코인의 타입은 분할되는 코인의 타입T에 대응하는sui::coin::Coin<T>이다. -
Publish: 하나의sui::package::UpgradeCap을 반환한다. Module bytes와 dependency ID는 command 구조에 embedded되며,Argument값이 아니다. 패키지가 staged된 뒤 각 모듈의init함수가 순서대로 실행된다. -
Upgrade: 정확히 하나의Argument, 즉sui::package::UpgradeTicket(by value)을 받는다. 하나의sui::package::UpgradeReceipt를 반환한다. Module bytes와 dependency ID는 command에 embedded되며Argument값으로 제공되지 않는다.init함수는 호출하지 않는다. -
TransferObjects와MergeCoins는 빈 결과 벡터를 생성한다.
게임용으로 Hero와 Sword를 만드는 다음 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 starts with 100 heath and 10 stamina.
public fun mint_hero(ctx: &mut TxContext): Hero {
Hero {
id: object::new(ctx),
health: 100,
stamina: 10,
}
}
또한 Hero에게 Sword를 장착하는 특수한 Move 함수도 있다:
/// Hero can equip a single sword.
/// Equiping a sword increases the `Hero`'s power by its 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에 붙이는 과정을 하나의 트랜잭션으로 결합한다:
const tx = new Transaction();
//According to Move function mint_hero, this moveCall will return an Object of Type Hero
const hero = tx.moveCall({
target: `0x123::hero::mint_hero`,
arguments: [],
typeArguments: [],
});
// According to Move function new_sword, this moveCall will return an Object of Type Sword
const sword = tx.moveCall({
target: `0x123::hero::new_sword`,
arguments: [tx.pure.u64(10)],
typeArguments: [],
});
//According to Move function equip_sword, this moveCall expects as arguments an object of type Hero and an Object of type Sword:
tx.moveCall({
target: `0x123::hero::equip_sword`,
arguments: [hero, sword],
});