Custom Upgrade Policies
단일 키로 온체인 패키지를 업그레이드할 수 있는 권한을 보호하는 것은 여러 보안 위험을 초래할 수 있다.
이러한 단일 키 업그레이드의 보안 위험을 해결하면서도 라이브 패키지 업그레이드를 가능하게 하기 위해, Sui는 커스텀 업그레이드 정책을 제공한다. 이러한 정책은 UpgradeCap 액세스를 보호하고 케이스별로 업그레이드를 승인하는 UpgradeTicket object를 발행한다.
Compatibility
Sui는 내장된 패키지 호환성 정책 세트를 제공하며, 가장 엄격한 것부터 가장 느슨한 것까지 다음과 같이 나열된다:
| Policy | Description |
|---|---|
| Immutable | 누구도 패키 지를 업그레이드할 수 없다. |
| Dependency-only | 패키지의 의존성만 수정할 수 있다. |
| Additive | 패키지에 새로운 기능(예: 새로운 public 함수 또는 구조체)을 추가할 수 있지만 기존 기능(예: 기존 public 함수의 코드)은 변경할 수 없다. |
| Compatible | 가장 느슨한 정책이다. 더 제한적인 정책이 허용하는 것에 더하여, 패키지의 업그레이드 버전에서:
|
나열된 순서대로, 각 정책은 이전 정책들의 허용 기준이 누적되어 통합된 형태다.
패키지를 게시할 때 기본적으로 가장 느슨한 compatible 정책을 채택한다. transaction이 성공적으로 완료되기 전에 정책을 변경할 수 있는 transaction의 일부로 패키지를 게시할 수 있으며, 이를 통해 기본 정책이 아닌 원하는 정책 수준으로 패키지를 체인에 처음 사용 가능하게 만들 수 있다.
패키지의 UpgradeCap에 대해 sui::package의 함수 중 하나(only_additive_upgrades,only_dep_upgrades, make_immutable)를 호출하여 현재 정책을 변경할 수 있으며, 정책은 더 제한적으로만 변경될 수 있다. 예를 들어, sui::package::only_dep_upgrades 를 호출하여 정책을 additive로 제한한 후, 동일한 패키지의 UpgradeCap에 대해 sui::package::only_additive_upgrades를 호출하면 오류가 발생한다.
Upgrade overview
패키지 업그레이드는 단일 transaction 내에서 종단 간으로 발생해야 하며 세 가지 명령으로 구성된다:
- Authorization: 업그레이드를 진행하기 위해
UpgradeCap으로부터 권한을 얻어UpgradeTicket을 생성한다. - Execution:
UpgradeTicket을 소비하고 패키지 바이트코드와 이전 버전과의 호환성을 검증하며, 업그레이드된 패키지를 나타내는 온체인 object를 생성한다. 성공 시 결과로UpgradeReceipt를 반환한다. - Commit: 새로 생성된 패키지에 대한 정보로
UpgradeCap을 업데이트한다.
2단계는 내장 명령이지만, 1단계와 3단계는 Move 함수로 구현된다. the Sui framework는 가장 기본적인 구현을 제공한다:
이들은 sui client upgrade 가 권한 부여 및 커밋을 위해 호출하는 함수들이다. 커스텀 업그레이드 정책은 패키지 UpgradeCap(따라서 이러한 함수 호출)에 대한 액세스를 해당 정책에 특정한 추가 조건(예: 투표, 거버넌스, 권한 목록, timelock 등) 뒤에 보호함으로써 작동한다.
UpgradeCap으로부터 UpgradeTicket을 생성하고 UpgradeReceipt를 소비하여 UpgradeCap을 업데이트하는 모든 함수 쌍은 커스텀 업그레이드 정책을 구성한다.
UpgradeCap
UpgradeCap은 패키지 업그레이드를 조정하는 중심 타입이다.
패키지를 게시하면 UpgradeCap object가 생성되고 패키지를 업그레이드하면 해당 object가 업데이트된다. 이 object의 소유자는 다음을 수행할 권한이 있다:
- 향후 업그레이드에 대한 호환성 요구사항을 변경한다.
- 향후 업그레이드를 승인한다.
- 패키지를 불변으로 만든다(업그레이드 불가능)
그리고 API는 다음 속성을 보장한다:
- 패키지의 최신 버전만 업그레이드될 수 있다(선형 히스토리가 보장됨).
- 한 번에 하나의 업그레이드만 진행 중일 수 있다(여러 동시 업그레이드를 승인할 수 없음).
- 업그레이드는 단일 transaction의 범위 내에서만 승인될 수 있다; 승인을 증명하는
UpgradeTicket을store할 수 없다. - 패키지에 대한 호환성 요구사항은 시간이 지남에 따라 더 제한적으로만 만들어질 수 있다.
UpgradeTicket
UpgradeTicket 은 업그레이드가 승인되었다는 증거이다. 이 승인은 다음에 특정적이다:
- 업그레이드할 특정
package: ID, 이는cap: ID의UpgradeCap이 식별하는 패밀리의 최신 패키지여야 한다. - 업그레이드가 준수할 것으로 예상되는 호환성 보장 종류를 증명하는 특정
policy: u8. - 업그레이드 후 패키지의 내용을 식별하는 특정
digest: vector<u8>
업그레이드를 실행하려고 시도하면, validator는 수행하려는 업그레이드가 승인된 업그레이드와 모든 측면에서 일치하는지 확인하고, 이러한 기준 중 하나라도 충족되지 않으면 업그레이드를 수행하지 않는다.
UpgradeTicket을 생성한 후에는 해당 transaction 내에서 사용해야 하며(나중을 위해 저장하거나, 삭제하거나, 소각할 수 없음), 그렇지 않으면 transaction이 실패한다.
Package digest
UpgradeTicket digest 필드는 호출자가 제공해야 하는 authorize_upgrade의 digest 파라미터에서 가져온다. authorize_upgrade는 digest를 처리하지 않지만, 커스텀 정책은 이를 사용하여 미리 바이트코드 또는 소스 코드를 본 업그레이드만 승인할 수 있다. Sui는 다음과 같이 digest를 계산한다:
- 각 모듈의 바이트코드를 바이트 배열로 표현하여 가져온다.
- 패키지의 전이적 의존성 목록을 추가하며, 각각 바이트 배열로 표현된다.
- 이 바이트 배열 목록을 사전 순으로 정렬한다.
- 정렬된 목록의 각 요소를 순서대로
Blake2Bhasher에 공급한다. - 이 해시 상태로부터 digest를 계산한다.
digest 계산을 위한 구현을 참조할 수 있지만, 대부분의 경우 --dump-bytecode-as-base64 플래그를 전달할 때 빌드의 일부로 Move 툴체인이 digest를 출력하는 것에 의존할 수 있다:
$ sui move build --dump-bytecode-as-base64
FETCHING GIT DEPENDENCY https://github.com/MystenLabs/sui.git
INCLUDING DEPENDENCY Sui
INCLUDING DEPENDENCY MoveStdlib
BUILDING test
{"modules":[<MODULE-BYTES-BASE64>],"dependencies":[<DEPENDENCY-IDS>],"digest":[59,43,173,195,216,88,176,182,18,8,24,200,200,192,196,197,248,35,118,184,207,205,33,59,228,109,184,230,50,31,235,201]}
UpgradeReceipt
UpgradeReceipt는 Upgrade 명령이 성공적으로 실행되었다는 증거이며, Sui가 transaction에 대해 생성된 object 집합에 새 패키지를 추가했다는 증거이다. 이것은 패밀리의 최신 패키지 ID(package: ID)로 UpgradeCap (cap: ID로 식별됨)을 업데이트하는 데 사용된다.
Sui가 UpgradeReceipt를 생성한 후에는 동일한 transaction 내에서 UpgradeCap을 업데이트하는 데 사용해야 하며(나중을 위해 저장하거나, 삭제하거나, 소각할 수 없음), 그렇지 않으면 transaction이 실패한다.
Isolating policies
커스텀 업그레이드 정책을 작성할 때 다음을 선호한다:
- 정책을 자체 패키지로 분리한다(업그레이드 가능성을 관리하는 코드와 함께 위치하지 않음).
- 해당 패키지를 불변으로 만들고(업그레이드 불가능),
UpgradeCap의 정책을 잠궈서 나중에 정책이 덜 제한적으로 만들어질 수 없도록 한다.
이러한 모범 사례는 사용자가 값을 잠그는 순간에 패키지의 업그레이드 정책이 무엇인지 명확히 하고, 패키지 사용자가 인식하지 못하거나 새 조건을 수용하기로 선택하지 않고도 정책이 시간이 지남에 따라 더 허용적으로 변화하지 않도록 보장함으로써 정보에 입각한 사용자 동의와 제한된 위험을 지지하는 데 도움이 된다.
Example: Day of the week upgrade policy
이제 (패키지 생성자가 선택한) 특정 요일에만 업그레이드를 승인하는 토이 업그레이드 정책을 작성하여 모든 것을 실습할 시간이다.
Creating an upgrade policy
업그레이드 정책을 위한 새로운 Move 패키지를 생성하는 것으로 시작한다:
$ sui move new policy
명령은 sources 폴더와 Move.toml 선언를 가진 policy 디렉토리를 생성한다.
sources 폴더에서 day_of_week.move라는 소스 파일을 생성한다. 다음 코드를 파일에 복사하여 붙여넣는다:
module policy::day_of_week;
use sui::package;
/// Day가 주중 요일이 아니다 (0 <= day < 7 범위의 숫자).
const ENotWeekDay: u64 = 1;
public struct UpgradeCap has key, store {
id: UID,
cap: package::UpgradeCap,
day: u8,
}
public fun new_policy(
cap: package::UpgradeCap,
day: u8,
ctx: &mut TxContext,
): UpgradeCap {
assert!(day < 7, ENotWeekDay);
UpgradeCap { id: object::new(ctx), cap, day }
}
이 코드는 생성자를 포함하고 커스텀 업그레이드 정책에 대한 object 타입을 정의한다.
그런 다음 올바른 요일인 경우 업그레이드를 승인하는 함수를 추가해야 한다. 먼저, 정책이 허용하지 않는 날에 업그레이드를 시도했음을 식별하는 오류 코드를 위한 상수와 하루의 밀리초 수를 정의하는 상수(곧 사용됨) 두 개를 정의한다. 현재 ENotWeekDay 바로 아래에 이러한 정의를 추가한다.
// 잘못된 요일에 업그레이드 승인을 요청했다.
const ENotAllowedDay: u64 = 2;
const MS_IN_DAY: u64 = 24 * 60 * 60 * 1000;
new_policy 함수 다음에, 현재 요일을 가져오는 week_day 함수를 추가한다. 약속한 대로, 이 함수는 앞서 정의한 MS_IN_DAY 상수를 사용한다.
fun week_day(ctx: &TxContext): u8 {
let days_since_unix_epoch = ctx.epoch_timestamp_ms() / MS_IN_DAY;
// unix epoch(1970년 1월 1일)는 목요일이었으므로 epoch 이후의 일수를 3만큼 이동하여 0 = 월요일이 되도록 한다.
((days_since_unix_epoch + 3) % 7 as u8)
}
이 함수는 일일 단위만 필요하기 때문에 Clock 대신 TxContext의 epoch 타임스탬프를 사용하며, 이는 업그레이드 transaction이 합의를 필요로 하지 않는다는 것을 의미한다.
다음으로, 이전 함수를 호출하여 현재 요일을 가져온 다음 해당 값이 정책을 위반하는지 확인하고, 위반하는 경 우 ENotAllowedDay 오류 값을 반환하는 authorize_upgrade 함수를 추가한다.
public fun authorize_upgrade(
cap: &mut UpgradeCap,
policy: u8,
digest: vector<u8>,
ctx: &TxContext,
): package::UpgradeTicket {
assert!(week_day(ctx) == cap.day, ENotAllowedDay);
cap.cap.authorize_upgrade(policy, digest)
}
커스텀 authorize_upgrade 의 시그니처는 UpgradeTicket을 반환하는 한 sui::package::authorize_upgrade의 시그니처와 다를 수 있다.
마지막으로, sui::package의 각 함수에 위임하는 commit_upgrade및 make_immutable의 구현을 제공한다:
public fun commit_upgrade(
cap: &mut UpgradeCap,
receipt: package::UpgradeReceipt,
) {
cap.cap.commit_upgrade(receipt)
}
public fun make_immutable(cap: UpgradeCap) {
let UpgradeCap { id, cap, day: _ } = cap;
id.delete();
cap.make_immutable()
}
day_of_week.move 파일의 최종 코드는 다음과 유사해야 한다:
module policy::day_of_week;
use sui::package;
// Day가 주중 요일이 아니다 (0 <= day < 7 범위의 숫자).
const ENotWeekDay: u64 = 1;
const ENotAllowedDay: u64 = 2;
const MS_IN_DAY: u64 = 24 * 60 * 60 * 1000;
public struct UpgradeCap has key, store {
id: UID,
cap: package::UpgradeCap,
day: u8,
}
public fun new_policy(
cap: package::UpgradeCap,
day: u8,
ctx: &mut TxContext,
): UpgradeCap {
assert!(day < 7, ENotWeekDay);
UpgradeCap { id: object::new(ctx), cap, day }
}
fun week_day(ctx: &TxContext): u8 {
let days_since_unix_epoch = ctx.epoch_timestamp_ms() / MS_IN_DAY;
// unix epoch(1970년 1월 1일)는 목요일이었으므로 epoch 이후의 일수를 3만큼 이동하여 0 = 월요일이 되도록 한다.
((days_since_unix_epoch + 3) % 7 as u8)
}
public fun authorize_upgrade(
cap: &mut UpgradeCap,
policy: u8,
digest: vector<u8>,
ctx: &TxContext,
): package::UpgradeTicket {
assert!(week_day(ctx) == cap.day, ENotAllowedDay);
cap.cap.authorize_upgrade(policy, digest)
}
public fun commit_upgrade(
cap: &mut UpgradeCap,
receipt: package::UpgradeReceipt,
) {
cap.cap.commit_upgrade(receipt)
}
public fun make_immutable(cap: UpgradeCap) {
let UpgradeCap { id, cap, day: _ } = cap;
id.delete();
cap.make_immutable()
}
Publishing an upgrade policy
sui client publish 명령을 사용하여 정책을 게시한다.
$ sui client publish
Console output
성공적인 게시는 다음을 반환한다:
UPDATING GIT DEPENDENCY https://github.com/MystenLabs/sui.git
INCLUDING DEPENDENCY Sui
INCLUDING DEPENDENCY MoveStdlib
BUILDING policy
Successfully verified dependencies on-chain against source.
Transaction Digest: 5BzYX5iV6GP2RaSkZ7JPBRmListD5cEVC7REoKsNoCYc
╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ Transaction Data │
├──────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ Sender: 0x65437300e280695a40df8cf524c7bca6ad62574cac3a52d3b085ad628c797241 │
│ Gas Owner: 0x65437300e280695a40df8cf524c7bca6ad62574cac3a52d3b085ad628c797241 │
│ Gas Budget: 11773600 MIST │
│ Gas Price: 1000 MIST │
│ Gas Payment: │
│ ┌── │
│ │ ID: 0x057d71e1f7e8341c5f2b203ae5fcb33c024afcc7f1c8c264fe0fe74dddcd828c │
│ │ Version: 149516398 │
│ │ Digest: HRU5orvkMeouFUVf7MXUpJpXP6W7u8DBzhyMichbW8KP │
│ └── │
│ │
│ Transaction Kind: Programmable │
│ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │
│ │ Input Objects │ │
│ ├──────────────────────────────────────────────────────────────────────────────────────────────────────────┤ │
│ │ 0 Pure Arg: Type: address, Value: "0x65437300e280695a40df8cf524c7bca6ad62574cac3a52d3b085ad628c797241" │ │
│ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────╯ │
│ ╭─────────────────────────────────────────────────────────────────────────╮ │
│ │ Commands │ │
│ ├─────────────────────────────────────────────────────────────────────────┤ │
│ │ 0 Publish: │ │
│ │ ┌ │ │
│ │ │ Dependencies: │ │
│ │ │ 0x0000000000000000000000000000000000000000000000000000000000000001 │ │
│ │ │ 0x0000000000000000000000000000000000000000000000000000000000000002 │ │
│ │ └ │ │
│ │ │ │
│ │ 1 TransferObjects: │ │
│ │ ┌ │ │
│ │ │ Arguments: │ │
│ │ │ Result 0 │ │
│ │ │ Address: Input 0 │ │
│ │ └ │ │
│ ╰─────────────────────────────────────────────────────────────────────────╯ │
│ │
│ Signatures: │
│ ijPCo4IFqacqAN64UAaJR+J5YhE3+IiEhXA5eEJiI0LZo1y3+byq1WHb3lgU8HLLJTgp+Cuv5GYHsBN5tofYAA== │
│ │
╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
╭───────────────────────────────────────────────────────── ──────────────────────────────────────────╮
│ Transaction Effects │
├───────────────────────────────────────────────────────────────────────────────────────────────────┤
│ Digest: 5BzYX5iV6GP2RaSkZ7JPBRmListD5cEVC7REoKsNoCYc │
│ Status: Success │
│ Executed Epoch: 589 │
│ │
│ Created Objects: │
│ ┌── │
│ │ ID: 0x4de927a10f97520311239cadb7159d4b893275bc74ab4e0af1b16c41ba8275a0 │
│ │ Owner: Account Address ( 0x65437300e280695a40df8cf524c7bca6ad62574cac3a52d3b085ad628c797241 ) │
│ │ Version: 149516399 │
│ │ Digest: HLSLcEb3S8t3Zb4cjjSw8dsYhExLyLJ3ParJt2fnoZUu │
│ └── │
│ ┌── │
│ │ ID: 0xa864e0122efbd1755c387c31bf4ce45c649548d2238c8fcffab4f77cfdab7c1a │
│ │ Owner: Immutable │
│ │ Version: 1 │
│ │ Digest: C9YEPnbCs8dtMUTPrXaSNziXkLWSEnz2zaZsxp1rYYCk │
│ └── │
│ Mutated Objects: │
│ ┌── │
│ │ ID: 0x057d71e1f7e8341c5f2b203ae5fcb33c024afcc7f1c8c264fe0fe74dddcd828c │
│ │ Owner: Account Address ( 0x65437300e280695a40df8cf524c7bca6ad62574cac3a52d3b085ad628c797241 ) │
│ │ Version: 149516399 │
│ │ Digest: F4zEiy3eS1h9V2EfrV3WXWe8bktA28sbKxzNCqtTRK2T │
│ └── │
│ Gas Object: │
│ ┌── │
│ │ ID: 0x057d71e1f7e8341c5f2b203ae5fcb33c024afcc7f1c8c264fe0fe74dddcd828c │
│ │ Owner: Account Address ( 0x65437300e280695a40df8cf524c7bca6ad62574cac3a52d3b085ad628c797241 ) │
│ │ Version: 149516399 │
│ │ Digest: F4zEiy3eS1h9V2EfrV3WXWe8bktA28sbKxzNCqtTRK2T │
│ └── │
│ Gas Cost Summary: │
│ Storage Cost: 9773600 MIST │
│ Computation Cost: 1000000 MIST │
│ Storage Rebate: 978120 MIST │
│ Non-refundable Storage Fee: 9880 MIST │
│ │
│ Transaction Dependencies: │
│ F2edqX6W9HXU7KzVmfwv9fhGMB6fbjrFo3gVd73S4tK5 │
│ Gtwgse64nSVXhQvmqCpwCe5xJz9N4VypvEGJUy5DyG4e │
╰───────────────────────────────────────────────────────────────────────────────────────────────────╯
╭─────────────────────────────╮
│ No transaction block events │
╰─────────────────────────────╯
╭──────────────────────────────────────────────────────────────────────────────────────────────────╮
│ Object Changes │
├──────────────────────────────────────────────────────────────────────────────────────────────────┤
│ Created Objects: │
│ ┌── │
│ │ ObjectID: 0x4de927a10f97520311239cadb7159d4b893275bc74ab4e0af1b16c41ba8275a0 │
│ │ Sender: 0x65437300e280695a40df8cf524c7bca6ad62574cac3a52d3b085ad628c797241 │
│ │ Owner: Account Address ( 0x65437300e280695a40df8cf524c7bca6ad62574cac3a52d3b085ad628c797241 ) │
│ │ ObjectType: 0x2::package::UpgradeCap │
│ │ Version: 149516399 │
│ │ Digest: HLSLcEb3S8t3Zb4cjjSw8dsYhExLyLJ3ParJt2fnoZUu │
│ └── │
│ Mutated Objects: │
│ ┌── │
│ │ ObjectID: 0x057d71e1f7e8341c5f2b203ae5fcb33c024afcc7f1c8c264fe0fe74dddcd828c │
│ │ Sender: 0x65437300e280695a40df8cf524c7bca6ad62574cac3a52d3b085ad628c797241 │
│ │ Owner: Account Address ( 0x65437300e280695a40df8cf524c7bca6ad62574cac3a52d3b085ad628c797241 ) │
│ │ ObjectType: 0x2::coin::Coin<0x2::sui::SUI> │
│ │ Version: 149516399 │
│ │ Digest: F4zEiy3eS1h9V2EfrV3WXWe8bktA28sbKxzNCqtTRK2T │
│ └── │
│ Published Objects: │
│ ┌── │
│ │ PackageID: 0xa864e0122efbd1755c387c31bf4ce45c649548d2238c8fcffab4f77cfdab7c1a │
│ │ Version: 1 │
│ │ Digest: C9YEPnbCs8dtMUTPrXaSNziXkLWSEnz2zaZsxp1rYYCk │
│ │ Modules: day_of_week │
│ └── │
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯
╭───────────────────────────────────────────────────────────────────────────────────────────────────╮
│ Balance Changes │
├───────────────────────────────────────────────────────────────────────────────────────────────────┤
│ ┌── │
│ │ Owner: Account Address ( 0x65437300e280695a40df8cf524c7bca6ad62574cac3a52d3b085ad628c797241 ) │
│ │ CoinType: 0x2::sui::SUI │
│ │ Amount: -9795480 │
│ └── │
╰───────────────────────────────────────────────────────────────────────────────────────────────────╯
모범 사례에 따라, Sui Client CLI를 사용하여 UpgradeCap에 대해 sui::package::make_immutable을 호출하여 정책을 불변으로 만든다. 쉘에서 변수 upgradecap을 생성하고 그 값을 게시 응답의 Object Changes 섹션에 나열된 UpgradeCap object ID로 설정한다. 물론, 업그레이드 capability에 대한 object ID는 다음 예시와 다르다.
$ upgradecap=0x4de927a10f97520311239cadb7159d4b893275bc74ab4e0af1b16c41ba8275a0
$ sui client call \
--package 0x2 \
--module 'package' \
--function 'make_immutable' \
--args $upgradecap
Console output
성공적인 호출은 다음과 유사한 응답을 반환한다:
Transaction Digest: EuQYMunxnMUFedvXXPck9HrehQnNY4GJ27eFBD7ptk2H
╭─────────────────────────────────────────────────────────────────────────────────────────────────╮
│ Transaction Data │
├─────────────────────────────────────────────────────────────────────────────────────────────────┤
│ Sender: 0x65437300e280695a40df8cf524c7bca6ad62574cac3a52d3b085ad628c797241 │
│ Gas Owner: 0x65437300e280695a40df8cf524c7bca6ad62574cac3a52d3b085ad628c797241 │
│ Gas Budget: 2000000 MIST │
│ Gas Price: 1000 MIST │
│ Gas Payment: │
│ ┌── │
│ │ ID: 0x057d71e1f7e8341c5f2b203ae5fcb33c024afcc7f1c8c264fe0fe74dddcd828c │
│ │ Version: 149516399 │
│ │ Digest: F4zEiy3eS1h9V2EfrV3WXWe8bktA28sbKxzNCqtTRK2T │
│ └── │
│ │
│ Transaction Kind: Programmable │
│ ╭─────────────────────────────────────────────────────────────────────────────────────────────╮ │
│ │ Input Objects │ │
│ ├─────────────────────────────────────────────────────────────────────────────────────────────┤ │
│ │ 0 Imm/Owned Object ID: 0x4de927a10f97520311239cadb7159d4b893275bc74ab4e0af1b16c41ba8275a0 │ │
│ ╰─────────────────────────────────────────────────────────────────────────────────────────────╯ │
│ ╭──────────────────────────────────────────────────────────────────────────────────╮ │
│ │ Commands │ │
│ ├──────────────────────────────────────────────────────────────────────────────────┤ │
│ │ 0 MoveCall: │ │
│ │ ┌ │ │
│ │ │ Function: make_immutable │ │
│ │ │ Module: package │ │
│ │ │ Package: 0x0000000000000000000000000000000000000000000000000000000000000002 │ │
│ │ │ Arguments: │ │
│ │ │ Input 0 │ │
│ │ └ │ │
│ ╰──────────────────────────────────────────────────────────────────────────────────╯ │
│ │
│ Signatures: │
│ TiJ5uDmG/d8Ca9xLWY0UEx8VnNO2Va6zBwHoFonWVdQNIgj4ghv+ZrbHW85zHw0WanM8hGebANTxrf12pmHmDQ== │
│ │
╰─────────────────────────────────────────────────────────────────────────────────────────────────╯
╭───────────────────────────────────────────────────────────────────────────────────────────────────╮
│ Transaction Effects │
├───────────────────────────────────────────────────────────────────────────────────────────────────┤
│ Digest: EuQYMunxnMUFedvXXPck9HrehQnNY4GJ27eFBD7ptk2H │
│ Status: Success │
│ Executed Epoch: 589 │
│ Mutated Objects: │
│ ┌── │
│ │ ID: 0x057d71e1f7e8341c5f2b203ae5fcb33c024afcc7f1c8c264fe0fe74dddcd828c │
│ │ Owner: Account Address ( 0x65437300e280695a40df8cf524c7bca6ad62574cac3a52d3b085ad628c797241 ) │
│ │ Version: 149516400 │
│ │ Digest: GVZLEsYEDuaZoFoTnvzoHsjGLuVYfZrZEomLRnSCFkzR │
│ └── │
│ Deleted Objects: │
│ ┌── │
│ │ ID: 0x4de927a10f97520311239cadb7159d4b893275bc74ab4e0af1b16c41ba8275a0 │
│ │ Version: 149516400 │
│ │ Digest: 7gyGAp71YXQRoxmFBaHxofQXAipvgHyBKPyxmdSJxyvz │
│ └── │
│ Gas Object: │
│ ┌── │
│ │ ID: 0x057d71e1f7e8341c5f2b203ae5fcb33c024afcc7f1c8c264fe0fe74dddcd828c │
│ │ Owner: Account Address ( 0x65437300e280695a40df8cf524c7bca6ad62574cac3a52d3b085ad628c797241 ) │
│ │ Version: 149516400 │
│ │ Digest: GVZLEsYEDuaZoFoTnvzoHsjGLuVYfZrZEomLRnSCFkzR │
│ └── │
│ Gas Cost Summary: │
│ Storage Cost: 988000 MIST │
│ Computation Cost: 1000000 MIST │
│ Storage Rebate: 2595780 MIST │
│ Non-refundable Storage Fee: 26220 MIST │
│ │
│ Transaction Dependencies: │
│ 5BzYX5iV6GP2RaSkZ7JPBRmListD5cEVC7REoKsNoCYc │
│ Gtwgse64nSVXhQvmqCpwCe5xJz9N4VypvEGJUy5DyG4e │
╰───────────────────────────────────────────────────────────────────────────────────────────────────╯
╭─────────────────────────────╮
│ No transaction block events │
╰─────────────────────────────╯
╭──────────────────────────────────────────────────────────────────────────────────────────────── ──╮
│ Object Changes │
├──────────────────────────────────────────────────────────────────────────────────────────────────┤
│ Mutated Objects: │
│ ┌── │
│ │ ObjectID: 0x057d71e1f7e8341c5f2b203ae5fcb33c024afcc7f1c8c264fe0fe74dddcd828c │
│ │ Sender: 0x65437300e280695a40df8cf524c7bca6ad62574cac3a52d3b085ad628c797241 │
│ │ Owner: Account Address ( 0x65437300e280695a40df8cf524c7bca6ad62574cac3a52d3b085ad628c797241 ) │
│ │ ObjectType: 0x2::coin::Coin<0x2::sui::SUI> │
│ │ Version: 149516400 │
│ │ Digest: GVZLEsYEDuaZoFoTnvzoHsjGLuVYfZrZEomLRnSCFkzR │
│ └── │
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯
╭───────────────────────────────────────────────────────────────────────────────────────────────────╮
│ Balance Changes │
├───────────────────────────────────────────────────────────────────────────────────────────────────┤
│ ┌── │
│ │ Owner: Account Address ( 0x65437300e280695a40df8cf524c7bca6ad62574cac3a52d3b085ad628c797241 ) │
│ │ CoinType: 0x2::sui::SUI │
│ │ Amount: 607780 │
│ └── │
╰───────────────────────────────────────────────────────────────────────────────────────────────────╯
Creating a package for testing
이제 체인에서 정책을 사용할 수 있으므로 업그레이드할 패키지가 필요하다. 이 주제는 기본 패키지를 생성하고 다음 시나리오에서 참조하지만, 새로 만드는 대신 사용 가능한 다른 패키지를 사용할 수 있다.
사용 가능한 패키지가 없는 경우, sui move new 명령을 사용하여 example이라는 새 패키지의 템플릿을 생성한다.
$ sui move new example
example/sources 디렉토리에서 다음 코드로 example.move 파일을 생성한다:
module example::example {
struct Event has copy, drop { x: u64 }
entry fun nudge() {
sui::event::emit(Event { x: 41 })
}
}
다음 지침은 이 예제 패키지를 게시한 다음 업그레이드하여 내보내는 Event의 값을 변경한다. 커스텀 업그레이드 정책을 사용하고 있으므로 TypeScript SDK를 사용하여 패키지의 게시 및 업그레이드 명령을 구축해야 한다.
Using TypeScript SDK
Node.js 프로젝트를 저장할 새 디렉토리를 생성한다. npm init 함수를 사용하여 package.json을 생성하거나 수동으로 파일을 생성할 수 있다. package.json 생성 방법에 따라, 다음 JSON을 채우거나 추가한다:
{ "type": "module" }
Node.js 프로젝트의 루트에 터미널 또는 콘솔을 연다. 다음 명령을 실행하여 Sui TypeScript SDK를 의존성으로 추가한다:
$ npm install @mysten/sui
Publishing a package with custom policy
Node.js 프로젝트의 루트에서 publish.js라는 스크립트 파일을 생성한다. 파일을 편집용으로 열고 몇 가지 상수를 정의한다:
SUI:suiCLI 바이너리의 위치.POLICY_PACKAGE_ID: 게시된day_of_week패키지의 ID.
const SUI = 'sui';
const POLICY_PACKAGE_ID = '<POLICY-PACKAGE>';
다음으로, Sui Client CLI의 현재 활성 주소에 대한 서명자 키 쌍을 가져오는 보일러플레이트 코드를 추가한다:
import { execSync } from 'child_process';
import { readFileSync } from 'fs';
import { homedir } from 'os';
import path from 'path';
import { Ed25519Keypair } from '@mysten/sui/keypairs/ed25519';
import { fromBase64 } from '@mysten/sui/utils';
const sender = execSync(`${SUI} client active-address`, { encoding: 'utf8' }).trim();
const signer = (() => {
const keystore = JSON.parse(
readFileSync(path.join(homedir(), '.sui', 'sui_config', 'sui.keystore'), 'utf8'),
);
for (const priv of keystore) {
const raw = fromBase64(priv);
if (raw[0] !== 0) {
continue;
}
const pair = Ed25519Keypair.fromSecretKey(raw.slice(1));
if (pair.getPublicKey().toSuiAddress() === sender) {
return pair;
}
}
throw new Error(`key pair not found for sender: ${sender}`);
})();
다음으로, 게시할 패키지의 경로를 정의한다. 다음 스니펫은 패키지가 publish.js와 형제 디렉토리인 example에 있다고 가정한다:
import { fileURLToPath } from 'url';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
// 현재 디렉토리에 대한 상대 패키지 위치
const packagePath = path.join(__dirname, 'example');
다음으로, 패키지를 빌드한다:
const { modules, dependencies } = JSON.parse(
execSync(`${SUI} move build --dump-bytecode-as-base64 --path ${packagePath}`, {
encoding: 'utf-8',
}),
);
다음으로, 패키지를 게시하는 transaction을 구성한다. UpgradeCap을 화요일에 업그레이드를 허용하는 "day of the week" 정책으로 래핑하고 새 정책을 다시 보낸다:
import { Transaction } from '@mysten/sui/transactions';
const tx = new Transaction();
const packageUpgradeCap = tx.publish({ modules, dependencies });
const tuesdayUpgradeCap = tx.moveCall({
target: `${POLICY_PACKAGE_ID}::day_of_week::new_policy`,
arguments: [
packageUpgradeCap,
tx.pure(1), // 화요일
],
});
tx.transferObjects([tuesdayUpgradeCap], tx.pure(sender));
마지막으로, 해당 transaction을 실행하고 그 효과를 콘솔에 표시한다. 다음 스니펫은 로컬 네트워크에 대해 예제를 실행한다고 가정한다. Devnet, Testnet 또는 Mainnet에서 실행하려면 getFullnodeUrl() 함수에 각각 devnet, testnet, 또는 mainnet을 전달한다:
import { getFullnodeUrl, SuiClient } from '@mysten/sui/client';
const client = new SuiClient({ url: getFullnodeUrl('localnet') });
const result = await client.signAndExecuteTransaction({
signer,
transaction: tx,
options: {
showEffects: true,
showObjectChanges: true,
},
});
console.log(result);
Details
publish.js
import { execSync } from 'child_process';
import { readFileSync } from 'fs';
import { homedir } from 'os';
import path from 'path';
import { fileURLToPath } from 'url';
import { getFullnodeUrl, SuiClient } from '@mysten/sui/client';
import { Ed25519Keypair } from '@mysten/sui/keypairs/ed25519';
import { Transaction } from '@mysten/sui/transactions';
import { fromBase64 } from '@mysten/sui/utils';
const SUI = 'sui';
const POLICY_PACKAGE_ID = '<POLICY-PACKAGE>';
const sender = execSync(`${SUI} client active-address`, { encoding: 'utf8' }).trim();
const signer = (() => {
const keystore = JSON.parse(
readFileSync(path.join(homedir(), '.sui', 'sui_config', 'sui.keystore'), 'utf8'),
);
for (const priv of keystore) {
const raw = fromBase64(priv);
if (raw[0] !== 0) {
continue;
}
const pair = Ed25519Keypair.fromSecretKey(raw.slice(1));
if (pair.getPublicKey().toSuiAddress() === sender) {
return pair;
}
}
throw new Error(`key pair not found for sender: ${sender}`);
})();
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const packagePath = path.join(__dirname, 'example');
const { modules, dependencies } = JSON.parse(
execSync(`${SUI} move build --dump-bytecode-as-base64 --path ${packagePath}`, {
encoding: 'utf-8',
}),
);
const tx = new Transaction();
const packageUpgradeCap = tx.publish({ modules, dependencies });
const tuesdayUpgradeCap = tx.moveCall({
target: `${POLICY_PACKAGE_ID}::day_of_week::new_policy`,
arguments: [
packageUpgradeCap,
tx.pure(1), // 화요일
],
});
tx.transferObjects([tuesdayUpgradeCap], tx.pure(sender));
const client = new SuiClient({ url: getFullnodeUrl('localnet') });
const result = await client.signAndExecuteTransaction({
signer,
transaction: tx,
options: {
showEffects: true,
showObjectChanges: true,
},
});
console.log(result);
publish.js 파일을 저장한 다음 Node.js를 사용하여 스크립트를 실행한다:
$ node publish.js
Console output
스크립트가 성공하면 콘솔은 다음 응답을 출력한다:
INCLUDING DEPENDENCY Sui
INCLUDING DEPENDENCY MoveStdlib
BUILDING example
{
digest: '9NBLe61sRqe7wS6y8mMVt6vhwA9W5Sz5YVEmuCwNMT64',
effects: {
messageVersion: 'v1',
status: { status: 'success' },
executedEpoch: '0',
gasUsed: {
computationCost: '1000000',
storageCost: '6482800',
storageRebate: '978120',
nonRefundableStorageFee: '9880'
},
modifiedAtVersions: [ [Object] ],
transactionDigest: '9NBLe61sRqe7wS6y8mMVt6vhwA9W5Sz5YVEmuCwNMT64',
created: [ [Object], [Object] ],
mutated: [ [Object] ],
gasObject: { owner: [Object], reference: [Object] },
dependencies: [
'BMVXjS7GG3d5W4Prg7gMVyvKTzEk1Hazx7Tq4WCcbcz9',
'CAFFD2HHnULQMCycL9xgad5JJpjFu2nuftf2xyugQu4t',
'GGDUeVkDoNFcyGibGNeiaGSiKsxf9QLzbjqPzdqi3dNJ'
]
},
objectChanges: [
{
type: 'mutated',
sender: '<SENDER>',
owner: [Object],
objectType: '0x2::coin::Coin<0x2::sui::SUI>',
objectId: '<GAS>',
version: '10',
previousVersion: '9',
digest: 'Dz38faAzFsRzKQyT7JTkVydCcvNNxbUdZiutGmA2Eyy6'
},
{
type: 'published',
packageId: '<EXAMPLE-PACKAGE>',
version: '1',
digest: '5JdU8hkFTjyqg4fHyC8JtdHBV11yCCKdFuyf9j4kKY3o',
modules: [Array]
},
{
type: 'created',
sender: '<SENDER>',
owner: [Object],
objectType: '<POLICY-PACKAGE>::day_of_week::UpgradeCap',
objectId: '<EXAMPLE-UPGRADE-CAP>',
version: '10',
digest: '3uAMFHFKunX9XrufMe27MHDbeLpgHBSsCPN3gSa93H3v'
}
],
confirmedLocalExecution: true
}
ReferenceError: fetch is not defined 오류를 받으면 Node.js 버전 18 이상을 사용한다.
CLI를 사용하여 새로 게시된 패키지가 작동하는지 테스트한다:
$ sui client call --gas-budget 10000000 \
--package '<EXAMPLE-PACKAGE-ID>' \
--module 'example' \
--function 'nudge' \
Console output
성공적인 호출은 다음으로 응답한다:
----- Transaction Digest ----
Bx1GA8EsBjoLKvXV2GG92DC5Jt58dbytf6jFcLg18dDR
----- Transaction Data ----
Transaction Signature: [Signature(Ed25519SuiSignature(Ed25519SuiSignature([0, 92, 22, 253, 150, 35, 134, 140, 185, 239, 72, 194, 25, 250, 153, 98, 134, 26, 219, 232, 199, 122, 56, 189, 186, 56, 126, 184, 147, 148, 184, 4, 17, 177, 156, 231, 198, 74, 118, 28, 187, 132, 94, 141, 44, 55, 70, 207, 157, 143, 182, 83, 59, 156, 116, 226, 22, 65, 211, 179, 187, 18, 76, 245, 4, 92, 225, 85, 204, 230, 61, 45, 147, 106, 193, 13, 195, 116, 230, 99, 61, 161, 251, 251, 68, 154, 46, 172, 143, 122, 101, 212, 120, 80, 164, 214, 54])))]
Transaction Kind : Programmable
Inputs: []
Commands: [
MoveCall(<EXAMPLE-PACKAGE>::example::nudge()),
]
Sender: <SENDER>
Gas Payment: Object ID: <GAS>, version: 0xb, digest: 93nZ3uLmLfJdHWoSHMuHsjFstEf45EM2pfovu3ibo4iH
Gas Owner: <SENDER>
Gas Price: 1000
Gas Budget: 10000000
----- Transaction Effects ----
Status : Success
Mutated Objects:
- ID: <GAS> , Owner: Account Address ( <SENDER> )
----- Events ----
Array [
Object {
"id": Object {
"txDigest": String("Bx1GA8EsBjoLKvXV2GG92DC5Jt58dbytf6jFcLg18dDR"),
"eventSeq": String("0"),
},
"packageId": String("<EXAMPLE-PACKAGE>"),
"transactionModule": String("example"),
"sender": String("<SENDER>"),
"type": String("<EXAMPLE-PACKAGE>::example::Event"),
"parsedJson": Object {
"x": String("41"),
},
"bcs": String("7rkaa6aDvyD"),
},
]
----- Object changes ----
Array [
Object {
"type": String("mutated"),
"sender": String("<SENDER>"),
"owner": Object {
"AddressOwner": String("<SENDER>"),
},
"objectType": String("0x2::coin::Coin<0x2::sui::SUI>"),
"objectId": String("<GAS>"),
"version": String("12"),
"previousVersion": String("11"),
"digest": String("9aNuZF63uBVaWF9L6cVmk7geimmpP9h9StigdNDPSiy3"),
},
]
----- Balance changes ----
Array [
Object {
"owner": Object {
"AddressOwner": String("<SENDER>"),
},
"coinType": String("0x2::sui::SUI"),
"amount": String("-1009880"),
},
]
제공된 예제 패키지를 사용한 경우, 값이 41인 필드 x가 포함된 Events 섹션이 있음을 알 수 있다.
Upgrading a package with custom policy
패키지가 게시되었으므로 새 정책을 사용하여 업그레이드를 수행하는 upgrade.js 스크립트를 준비할 수 있다. 이것은 패키지를 빌드하기 전까지 publish.js 와 동일하게 동작한다. 패키지를 빌드할 때 스크립트는 digest도 캡처하고, transaction은 이제 세 가지 업그레이드 명령(authorize, execute, commit)을 수행한다. upgrade.js의 전체 스크립트는 다음과 같다:
import { execSync } from 'child_process';
import { readFileSync } from 'fs';
import { homedir } from 'os';
import path from 'path';
import { fileURLToPath } from 'url';
import { getFullnodeUrl, SuiClient } from '@mysten/sui/client';
import { Ed25519Keypair } from '@mysten/sui/keypairs/ed25519';
import { Transaction, UpgradePolicy } from '@mysten/sui/transactions';
import { fromBase64 } from '@mysten/sui/utils';
const SUI = 'sui';
const POLICY_PACKAGE_ID = '<POLICY-PACKAGE>';
const EXAMPLE_PACKAGE_ID = '<EXAMPLE-PACKAGE>';
const CAP_ID = '<EXAMPLE-UPGRADE-CAP>';
const sender = execSync(`${SUI} client active-address`, { encoding: 'utf8' }).trim();
const signer = (() => {
const keystore = JSON.parse(
readFileSync(path.join(homedir(), '.sui', 'sui_config', 'sui.keystore'), 'utf8'),
);
for (const priv of keystore) {
const raw = fromBase64(priv);
if (raw[0] !== 0) {
continue;
}
const pair = Ed25519Keypair.fromSecretKey(raw.slice(1));
if (pair.getPublicKey().toSuiAddress() === sender) {
return pair;
}
}
throw new Error(`key pair not found for sender: ${sender}`);
})();
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const packagePath = path.join(__dirname, 'example');
const { modules, dependencies, digest } = JSON.parse(
execSync(`${SUI} move build --dump-bytecode-as-base64 --path ${packagePath}`, {
encoding: 'utf-8',
}),
);
const tx = new Transaction();
const cap = tx.object(CAP_ID);
const ticket = tx.moveCall({
target: `${POLICY_PACKAGE_ID}::day_of_week::authorize_upgrade`,
arguments: [cap, tx.pure(UpgradePolicy.COMPATIBLE), tx.pure(digest)],
});
const receipt = tx.upgrade({
modules,
dependencies,
packageId: EXAMPLE_PACKAGE_ID,
ticket,
});
tx.moveCall({
target: `${POLICY_PACKAGE_ID}::day_of_week::commit_upgrade`,
arguments: [cap, receipt],
});
const client = new SuiClient({ url: getFullnodeUrl('localnet') });
const result = await client.signAndExecuteTransaction({
signer,
transaction: tx,
options: {
showEffects: true,
showObjectChanges: true,
},
});
console.log(result);
오늘이 화요일이 아닌 경우, 정책이 업그레이드를 수행할 수 있도록 허용하는 다음 화요일까지 스크립트를 실행하기를 기다린다. 그 시점에서, example.move를 업데이트하여 다른 상수로 이벤트를 내보내고 Node.js를 사용하여 업그레이드 스크립트를 실행한다:
$ node upgrade.js
Console output
스크립트가 성공하면(그리고 오늘이 화요일이면), 콘솔은 다음 응답을 표시한다:
INCLUDING DEPENDENCY Sui
INCLUDING DEPENDENCY MoveStdlib
BUILDING example
{
digest: 'EzJyH6BX231sw4jY6UZ6r9Dr28SKsiB2hg3zw4Jh4D5P',
effects: {
messageVersion: 'v1',
status: { status: 'success' },
executedEpoch: '0',
gasUsed: {
computationCost: '1000000',
storageCost: '6482800',
storageRebate: '2874168',
nonRefundableStorageFee: '29032'
},
modifiedAtVersions: [ [Object], [Object] ],
transactionDigest: 'EzJyH6BX231sw4jY6UZ6r9Dr28SKsiB2hg3zw4Jh4D5P',
created: [ [Object] ],
mutated: [ [Object], [Object] ],
gasObject: { owner: [Object], reference: [Object] },
dependencies: [
'62BxVq24tgaRrFTXR3i944RRZ6x8sgTGbjFzpFDe2RAB',
'BMVXjS7GG3d5W4Prg7gMVyvKTzEk1Hazx7Tq4WCcbcz9',
'Bx1GA8EsBjoLKvXV2GG92DC5Jt58dbytf6jFcLg18dDR',
'CAFFD2HHnULQMCycL9xgad5JJpjFu2nuftf2xyugQu4t'
]
},
objectChanges: [
{
type: 'mutated',
sender: '<SENDER>',
owner: [Object],
objectType: '0x2::coin::Coin<0x2::sui::SUI>',
objectId: '<GAS>',
version: '13',
previousVersion: '12',
digest: 'DF4aebHRYrVdxtfAaFfET3hLHn5hqsoty4joMYxLDBuc'
},
{
type: 'mutated',
sender: '<SENDER>',
owner: [Object],
objectType: '<POLICY-PACKAGE>::day_of_week::UpgradeCap',
objectId: '<EXAMPLE-UPGRADE-CAP>',
version: '13',
previousVersion: '11',
digest: '5Wtuw9mAGBuP5qFdTzDCRxBF9LqJ7uZbpxk2UXhAkrXL'
},
{
type: 'published',
packageId: '<UPGRADED-EXAMPLE-PACKAGE>',
version: '2',
digest: '7mvnMEXezAGcWqYSt6R4QUpPjY8nqTSmb5Dv2SqkVq7a',
modules: [Array]
}
],
confirmedLocalExecution: true
}
Sui Client CLI를 사용하여 업그레이드된 패키지를 테스트한다(패키지 ID는 원래 예제 패키지 버전과 다르다 ):
$ sui client call --gas-budget 10000000 \
--package '<UPGRADED-EXAMPLE-PACKAGE>' \
--module 'example' \
--function 'nudge'
Console output
성공하면 콘솔은 다음 응답을 출력한다:
----- Transaction Digest ----
EF2rQzWHmtjPvkqzFGyFvANA8e4ETULSBqDMkzqVoshi
----- Transaction Data ----
Transaction Signature: [Signature(Ed25519SuiSignature(Ed25519SuiSignature([0, 88, 98, 118, 173, 218, 55, 4, 48, 166, 42, 106, 193, 210, 159, 75, 233, 95, 77, 201, 38, 0, 234, 183, 77, 252, 178, 22, 221, 106, 202, 42, 166, 29, 130, 164, 97, 110, 201, 153, 91, 149, 50, 72, 6, 213, 183, 70, 83, 55, 5, 190, 182, 5, 98, 212, 134, 103, 181, 204, 247, 90, 28, 125, 14, 92, 225, 85, 204, 230, 61, 45, 147, 106, 193, 13, 195, 116, 230, 99, 61, 161, 251, 251, 68, 154, 46, 172, 143, 122, 101, 212, 120, 80, 164, 214, 54])))]
Transaction Kind : Programmable
Inputs: []
Commands: [
MoveCall(<UPGRADE-EXAMPLE-PACKAGE>::example::nudge()),
]
Sender: <SENDER>
Gas Payment: Object ID: <GAS>, version: 0xd, digest: DF4aebHRYrVdxtfAaFfET3hLHn5hqsoty4joMYxLDBuc
Gas Owner: <SENDER>
Gas Price: 1000
Gas Budget: 10000000
----- Transaction Effects ----
Status : Success
Mutated Objects:
- ID: <GAS> , Owner: Account Address ( <SENDER> )
----- Events ----
Array [
Object {
"id": Object {
"txDigest": String("EF2rQzWHmtjPvkqzFGyFvANA8e4ETULSBqDMkzqVoshi"),
"eventSeq": String("0"),
},
"packageId": String("<UPGRADE-EXAMPLE-PACKAGE>"),
"transactionModule": String("example"),
"sender": String("<SENDER>"),
"type": String("<EXAMPLE-PACKAGE>::example::Event"),
"parsedJson": Object {
"x": String("42"),
},
"bcs": String("82TFauPiYEj"),
},
]
----- Object changes ----
Array [
Object {
"type": String("mutated"),
"sender": String("<SENDER>"),
"owner": Object {
"AddressOwner": String("<SENDER>"),
},
"objectType": String("0x2::coin::Coin<0x2::sui::SUI>"),
"objectId": String("<GAS>"),
"version": String("14"),
"previousVersion": String("13"),
"digest": String("AmGocCxy6cHvCuGG3izQ8a7afp6qWWt14yhowAzBYa44"),
},
]
----- Balance changes ----
Array [
Object {
"owner": Object {
"AddressOwner": String("<SENDER>"),
},
"coinType": String("0x2::sui::SUI"),
"amount": String("-1009880"),
},
]
이제 x 필드에 대해 내보낸 Events섹션의 값은 42이다(원래 41에서 변경됨).
화요일 이전에 첫 번째 업그레이드를 시도하거나 상수를 다시 변경하고 다음 날 업그레이드를 시도하면, 스크립트는 업그레이드가 코드 2 (ENotAllowedDay)로 중단되었음을 나타내는 다음과 유사한 오류가 포함된 응답을 받는다:
...
status: {
status: 'failure',
error: 'MoveAbort(MoveLocation { module: ModuleId { address: <POLICY-PACKAGE>, name: Identifier("day_of_week") }, function: 1, instruction: 11, function_name: Some("authorize_upgrade") }, 2) in command 0'
},
...