Sui Move 合约升级与权限定制

一:猜数设计

让我们来设计一个简单的猜数程序,用户猜测一个数字并传入函数,判断与程序随机而成的数字是否相同,如果相同就给予一定奖励。

这里的奖励可以是链上流通的货币或是其它有价值的虚拟物品,不过作为一篇适合 <math xmlns="http://www.w3.org/1998/Math/MathML"> S u i M o v e \mathit {Sui\ Move} </math>Sui Move 初学者的简单用例,直接牵扯到高昂物品似乎有所不妥,所以这里的奖励就用一个整型的 <math xmlns="http://www.w3.org/1998/Math/MathML"> p r i z e \mathit {prize} </math>prize 来表示,将含有奖金的 <math xmlns="http://www.w3.org/1998/Math/MathML"> o b j e c t \mathit {object} </math>object 发送给猜中数字的玩家,以此来作为激励手段。

同时,我们还可以用 <math xmlns="http://www.w3.org/1998/Math/MathML"> E v e n t s \mathit {Events} </math>Events 来记录链上发生的重大事件。在这个例子里,重大事件无疑就是 <math xmlns="http://www.w3.org/1998/Math/MathML"> W i n n e r \mathit {Winner} </math>Winner 了,我们可以用事件来记录胜者玩家的地址,为了让数据有一定的分析价值,还可以记录到这一次猜中数之前的所有玩家的尝试总数。

那么,根据分析,我们可以用三个 <math xmlns="http://www.w3.org/1998/Math/MathML"> s t r u c t \mathit {struct} </math>struct 来存储这些信息。

move 复制代码
struct Count has key {
    id: UID,
    total: u64,
}

struct Prize has key {
    id: UID,
    prize: u8,
}

struct GuessEvent has copy, drop {
    total_count: u64,
    final_winner: address,
}
  • <math xmlns="http://www.w3.org/1998/Math/MathML"> C o u n t \mathit {Count} </math>Count 会作为一个共享对象,让所有玩家都可以访问并修改,其中的 <math xmlns="http://www.w3.org/1998/Math/MathML"> t o t a l \mathit {total} </math>total 将存储一共的尝试猜数的次数,直到猜中时清零重置。
  • <math xmlns="http://www.w3.org/1998/Math/MathML"> P r i z e \mathit {Prize} </math>Prize 直到被猜中数时才会被创建,同时被发送给此次交易的发起者,也就是猜中数字的玩家,其中的 <math xmlns="http://www.w3.org/1998/Math/MathML"> p r i z e \mathit {prize} </math>prize 可以简单设定为猜中的数等值的金额,由于猜数的范围不能太大,不妨就以 <math xmlns="http://www.w3.org/1998/Math/MathML"> [ 0 , 10 ] [\text 0,\ \text {10}] </math>[0, 10] 这个闭区间为前置条件。
  • <math xmlns="http://www.w3.org/1998/Math/MathML"> G u e s s E v e n t \mathit {GuessEvent} </math>GuessEvent 是定义的一个事件,我们只关心事件当中的值,同时让它在作用域结束时自我消亡,所以设定了 <math xmlns="http://www.w3.org/1998/Math/MathML"> c o p y , d r o p \mathit {copy},\ \mathit {drop} </math>copy, drop 这两个能力。
    触发事件时只需要使用sui::event::emit(<ObjectEvent>);
    而交易产生的事件详情可以在 <math xmlns="http://www.w3.org/1998/Math/MathML"> S u i e x p l o r e r \mathit {Sui\ explorer} </math>Sui explorer 中的 <math xmlns="http://www.w3.org/1998/Math/MathML"> E v e n t s \mathit {Events} </math>Events 标签页查看。

根据上述分析,我们可以很轻松地编写三个函数。

move 复制代码
fun init(ctx: &mut TxContext) {
    let count = Count {
        id: object::new(ctx),
        total: 0,
    };
    transfer::share_object(count);
}

fun send_prize(count: u64, prize: u8, ctx: &mut TxContext) {
    transfer::transfer(Prize {
        id: object::new(ctx),
        prize,
    }, tx_context::sender(ctx));

    event::emit(GuessEvent {
        total_count: count,
        final_winner: tx_context::sender(ctx),
    });
}

public entry fun guess_between_zero_and_hundred(count: &mut Count, number: u8, clock: &Clock, ctx: &mut TxContext) {
    let des_number = ((clock::timestamp_ms(clock) % 11) as u8);
    if (number == des_number) {
        send_prize(count.total, number, ctx);
        count.total = 0;
    } else {
        count.total = count.total + 1;
    }
}
  • <math xmlns="http://www.w3.org/1998/Math/MathML"> i n i t \mathit {init} </math>init 函数只会在发布时被调用一次,用来创建 <math xmlns="http://www.w3.org/1998/Math/MathML"> C o u n t \mathit {Count} </math>Count 并将其共享再好不过。
  • <math xmlns="http://www.w3.org/1998/Math/MathML"> s e n d _ p r i z e \mathit {send\_prize} </math>send_prize 函数是在数字被猜中时调用的,作用是将奖励 <math xmlns="http://www.w3.org/1998/Math/MathML"> P r i z e \mathit {Prize} </math>Prize 发送给中奖者,同时触发一个事件。
  • <math xmlns="http://www.w3.org/1998/Math/MathML"> e n t r y \mathit {entry} </math>entry 函数是指在交易过程中能够被直接调用的,它不应该有返回值,因为即使有也没什么作用,入口函数被不被 <math xmlns="http://www.w3.org/1998/Math/MathML"> p u b l i c \mathit {public} </math>public 修饰的区别在于能否被其它模块调用,以及合约升级时能否被更改等。
    <math xmlns="http://www.w3.org/1998/Math/MathML"> g u e s s _ b e t w e e n _ z e r o _ a n d _ h u n d r e d \mathit {guess\_between\_zero\_and\_hundred} </math>guess_between_zero_and_hundred 函数名就暗示了猜数的范围(一开始是想 [ <math xmlns="http://www.w3.org/1998/Math/MathML"> 0 , 100 \text 0,\ \text {100} </math>0, 100] 的,但一想到测试时会测死,就缩小到了 <math xmlns="http://www.w3.org/1998/Math/MathML"> 1 0 \text 10 </math>10,但函数名忘改成 <math xmlns="http://www.w3.org/1998/Math/MathML"> t e n \mathit {ten} </math>ten 了┭┮﹏┭┮),其中有个参数是 <math xmlns="http://www.w3.org/1998/Math/MathML"> C l o c k \mathit {Clock} </math>Clock,我们可以用它来获得时间戳,用这个时间戳来转化成目标数字。
    这种"随机"方式其实并不安全,因为这个值是可控的,真实的交易环境也不会如此草率,不过这并不如此篇的重点,也就不再赘述。
    <math xmlns="http://www.w3.org/1998/Math/MathML"> t i m e s t a m p _ m s \mathit {timestamp\_ms} </math>timestamp_ms 得到的值是 <math xmlns="http://www.w3.org/1998/Math/MathML"> u 64 \mathit u\text{64} </math>u64 类型的,我们可以用 <math xmlns="http://www.w3.org/1998/Math/MathML"> a s \mathit {as} </math>as 关键词简单地将其转化成 <math xmlns="http://www.w3.org/1998/Math/MathML"> u 8 \mathit u\text 8 </math>u8。(这里只是为了顺带提一句 <math xmlns="http://www.w3.org/1998/Math/MathML"> a s \mathit {as} </math>as 的作用才设定成这个类型的整数的,前篇里还提到了它可以为导入的模块取别名,如果忘记了的可以去翻一翻)

好,至此,简单的猜数就设计完毕,但是别忘了这一篇章的主题合约升级与权限定制

二:修改代码以支持版本更迭

发布的合约 <math xmlns="http://www.w3.org/1998/Math/MathML"> p a c k a g e \mathit {package} </math>package 是不可变的 <math xmlns="http://www.w3.org/1998/Math/MathML"> o b j e c t \mathit {object} </math>object​,不可撤回也无法修改。智能合约升级的本质是在新的地址上重新发布新的合约,并且把旧版合约的数据迁移过去。

当然,合约升级并不意味着可以对代码进行肆无忌惮地修改,需要满足如下几条规则:

  • 现有的 <math xmlns="http://www.w3.org/1998/Math/MathML"> p u b l i c \mathit {public} </math>public 函数的输入输出参数格式保持不变
  • 可以添加新的 <math xmlns="http://www.w3.org/1998/Math/MathML"> s t r u c t \mathit {struct} </math>struct 和函数
  • 可以给现有的 <math xmlns="http://www.w3.org/1998/Math/MathML"> s t r u c t \mathit {struct} </math>struct 添加新的能力
  • 可以把现有函数中对泛型参数的约束去掉
  • 可以改变函数的实现
  • 可以修改非 <math xmlns="http://www.w3.org/1998/Math/MathML"> p u b l i c \mathit {public} </math>public 函数的输入输出参数格式,包括 <math xmlns="http://www.w3.org/1998/Math/MathML"> f r i e n d \mathit {friend} </math>friend 和 <math xmlns="http://www.w3.org/1998/Math/MathML"> e n t r y \mathit {entry} </math>entry 函数
  • 可以让非 <math xmlns="http://www.w3.org/1998/Math/MathML"> p u b l i c \mathit {public} </math>public 函数变为 <math xmlns="http://www.w3.org/1998/Math/MathML"> p u b l i c \mathit {public} </math>public 函数

除此之外还需要注意的是, <math xmlns="http://www.w3.org/1998/Math/MathML"> i n i t \mathit {init} </math>init 函数只会在第一次发布合约时执行,后续合约升级时不会被重复执行。同时,如果你的 <math xmlns="http://www.w3.org/1998/Math/MathML"> p a c k a g e \mathit {package} </math>package 依赖了一个外部的 <math xmlns="http://www.w3.org/1998/Math/MathML"> p a c k a g e \mathit {package} </math>package​,你需要手动把它指向新依赖的合约地址,这一过程在升级合约时不会自动进行。

根据上述内容,我们来思考,想要让一份合约支持后续迭代升级,需要添加一些什么?

  1. <math xmlns="http://www.w3.org/1998/Math/MathML"> V E R S I O N \mathit {VERSION} </math>VERSION,定义一个常量来表示版本号。因为升级合约并不会将旧的合约地址销毁,所以我们需要明确版本,非对应版本的调用都不应执行。
  2. 共享对象 <math xmlns="http://www.w3.org/1998/Math/MathML"> C o u n t \mathit {Count} </math>Count 中添加版本号字段,用来判断数据是否已经迁移。(还没来得及迁移的数据,可以接受旧的对应版本的合约的函数调用,但它不能接受新的)
  3. 为了实现数据迁移,自然也就需要添加一个函数来实现这一点,在这个例子当中的主要作用就是更新 <math xmlns="http://www.w3.org/1998/Math/MathML"> C o u n t \mathit {Count} </math>Count 对象当中的版本号字段。
  4. 相关的升级迭代的操作应该都只授权给发布者,其他人不该有权限调用。为了达到这一目的,不妨在共享对象当中再添加一个字段,用来存储发布者的 <math xmlns="http://www.w3.org/1998/Math/MathML"> I D \mathit {ID} </math>ID。我们已经知道, <math xmlns="http://www.w3.org/1998/Math/MathML"> i n i t \mathit {init} </math>init 只会在第一次合约发布时被调用,当时的 <math xmlns="http://www.w3.org/1998/Math/MathML"> T x C o n t e x t \mathit {TxContext} </math>TxContext 一定是发布者,那么该阶段存储进去的 <math xmlns="http://www.w3.org/1998/Math/MathML"> I D \mathit {ID} </math>ID 也将是发布者的。
    那么如何在调用数据迁移时传入这个 <math xmlns="http://www.w3.org/1998/Math/MathML"> I D \mathit {ID} </math>ID?不如再定义一个 <math xmlns="http://www.w3.org/1998/Math/MathML"> s t r u c t \mathit {struct} </math>struct,拥有 <math xmlns="http://www.w3.org/1998/Math/MathML"> k e y \mathit {key} </math>key 能力,将其交给我们的发布者,届时只需要传入该 <math xmlns="http://www.w3.org/1998/Math/MathML"> o b j e c t \mathit {object} </math>object 的地址即可作为凭据。
  5. 至于怎么在版本不对应,或者非授权操作情况下中断并退出,可以使用assert!(<bool>, <error code>),这在前面的篇章当中已有涉及。为了适应更多变的情况,同时提高代码的可读性,可以将 <math xmlns="http://www.w3.org/1998/Math/MathML"> e r r o r c o d e \mathit {error\ code} </math>error code 定义成一个常量再作为参数传入。

修改并整合后的代码如下:

move 复制代码
module guess_number::guess_number {
    use sui::object::{Self, ID, UID};
    use sui::tx_context::{Self, TxContext};
    use sui::transfer;
    use sui::clock::{Self, Clock};
    use sui::event;

    const VERSION: u64 = 1;

    const ENOTVERSION: u64 = 0;
    const ENOTADMIN: u64 = 1;
    const ENOTUPGRADE: u64 = 2;

    struct Count has key {
        id: UID,
        version: u64,
        admin: ID,
        total: u64,
    }

    struct AdminCap has key {
        id: UID,
    }

    struct Prize has key {
        id: UID,
        prize: u8,
    }

    struct GuessEvent has copy, drop {
        total_count: u64,
        final_winner: address,
    }

    fun init(ctx: &mut TxContext) {
        let admin_cap = AdminCap {id: object::new(ctx)};

        let count = Count {
            id: object::new(ctx),
            version: VERSION,
            admin: object::id(&admin_cap),
            total: 0,
        };

        transfer::transfer(admin_cap, tx_context::sender(ctx));
        transfer::share_object(count);
    }

    fun send_prize(count: u64, prize: u8, ctx: &mut TxContext) {
        transfer::transfer(Prize {
            id: object::new(ctx),
            prize,
        }, tx_context::sender(ctx));

        event::emit(GuessEvent {
            total_count: count,
            final_winner: tx_context::sender(ctx),
        });
    }

    public entry fun guess_between_zero_and_hundred(count: &mut Count, number: u8, clock: &Clock, ctx: &mut TxContext) {
        assert!(count.version == VERSION, ENOTVERSION);

        let des_number = ((clock::timestamp_ms(clock) % 11) as u8);
        if (number == des_number) {
            send_prize(count.total, number, ctx);
            count.total = 0;
        } else {
            count.total = count.total + 1;
        }
    }

    entry fun migrate(count: &mut Count, admin_cap: &AdminCap) {
        assert!(count.admin == object::id(admin_cap), ENOTADMIN);
        assert!(count.version != VERSION, ENOTUPGRADE);

        count.version = VERSION;
    }
}

别着急,配置文件 <math xmlns="http://www.w3.org/1998/Math/MathML"> M o v e . t o m l \mathit {Move.toml} </math>Move.toml 也需要做更改,主要是在 [ <math xmlns="http://www.w3.org/1998/Math/MathML"> p a c k a g e \mathit {package} </math>package] 下添加版本号。(这里的 <math xmlns="http://www.w3.org/1998/Math/MathML"> v e r s i o n \mathit {version} </math>version 和代码中的可以不一致)

ini 复制代码
[package]
name = "guess_number"
version = "0.0.0"

[addresses]
guess_number = "0x0"

三:原始合约发布

scss 复制代码
sui move build
sui client publish --gas-budget 100000000

发布成功后在信息里找到跟 <math xmlns="http://www.w3.org/1998/Math/MathML"> p a c k a g e \mathit {package} </math>package​ 相关的:

所发布的包的 <math xmlns="http://www.w3.org/1998/Math/MathML"> P a c k a g e I D \mathit {PackageID} </math>PackageID,以及 <math xmlns="http://www.w3.org/1998/Math/MathML"> A d m i n C a p , C o u n t , U p g r a d e C a p \mathit {AdminCap},\ \mathit {Count},\ \mathit {UpgradeCap} </math>AdminCap, Count, UpgradeCap 的 <math xmlns="http://www.w3.org/1998/Math/MathML"> P a c k a g e I D \mathit {PackageID} </math>PackageID 都是后续会用到的,可以用 <math xmlns="http://www.w3.org/1998/Math/MathML"> e x p o r t \mathit {export} </math>export 来赋予一个别名,方便取用。

ini 复制代码
export PACKAGE_ID=0x628f33fcf96ebc82f275bddb7ad927b0e260e989f2131e0e1dc844ab931b57f5
export ADMIN_CAP=0x2160bdabbe0321d418cd6572ab24092554c3fbea0b5f24fd32295eaf2b8fa63a
export COUNT=0x415f0255873919cc68c217a715454b54537ad3c0677b7d3b925c777131dbf19c
export UPGRADE_CAP=0x57ae075216a7996c944af979a7fe8057f6d1b2165055cdddce9f7898579fc8cd

接下来,我们就可以用下述命令来猜数了:
sui client call --package $PACKAGE_ID --module guess_number --function guess_between_zero_and_hundred --args $COUNT 6 0x6 --gas-budget 100000000

其中0x6作为 <math xmlns="http://www.w3.org/1998/Math/MathML"> C l o c k \mathit {Clock} </math>Clock 参数的地址传递。

通过sui client object $COUNT得到的信息可以发现,字段 <math xmlns="http://www.w3.org/1998/Math/MathML"> t o t a l \mathit {total} </math>total 的值为 <math xmlns="http://www.w3.org/1998/Math/MathML"> 1 \text 1 </math>1,说明一共尝试了一次没有成功,此时,重复猜数,直到猜中。

尝试了好几次(永远的 <math xmlns="http://www.w3.org/1998/Math/MathML"> 6 6 </math>6),终于在猜数后得到的信息里, <math xmlns="http://www.w3.org/1998/Math/MathML"> O b j e c t C h a n g e s \mathit {Object\ Changes} </math>Object Changes 看到了它新建了一个 <math xmlns="http://www.w3.org/1998/Math/MathML"> P r i z e \mathit {Prize} </math>Prize 对象,它的拥有者是玩家本人。分别查看 <math xmlns="http://www.w3.org/1998/Math/MathML"> C o u n t \mathit {Count} </math>Count 和 <math xmlns="http://www.w3.org/1998/Math/MathML"> P r i z e \mathit {Prize} </math>Prize 当中存储的内容,如下图所示,发现符合预期。

别忘了事件,除了本文最开始提到的查看方式,其实在执行猜中数的那一次给出的信息当中就有相应的信息:

四:合约升级

在上述代价的基础上,首先来思考,除了功能上的升级之外,需要修改什么?

  1. <math xmlns="http://www.w3.org/1998/Math/MathML"> V E R S I O N \mathit {VERSION} </math>VERSION 作为版本的标志,势必要扩大。
  2. <math xmlns="http://www.w3.org/1998/Math/MathML"> M o v e . t o m l \mathit {Move.toml} </math>Move.toml 中 [ <math xmlns="http://www.w3.org/1998/Math/MathML"> p a c k a g e \mathit {package} </math>package] 中的 <math xmlns="http://www.w3.org/1998/Math/MathML"> v e r s i o n \mathit {version} </math>version 也需要更改,同时添加新的一行published-at = "<ORIGINAL-PACKAGE-ID>",用来标注旧合约地址是哪儿。如果不知道这个信息,又何来数据迁移和升级。
ini 复制代码
// guess_number.move
const VERSION: u64 = 2;

// Move.toml
[package]
name = "guess_number"
version = "0.0.1"
published-at = "0x628f33fcf96ebc82f275bddb7ad927b0e260e989f2131e0e1dc844ab931b57f5" //这里需要替换成你们自己的旧合约地址

接下来,我们来思考哪些功能存在优化的空间?

真实情况下,奖励的东西应该更加吸引人,这样才能诱使人与之进行交易(猜数),所以我们扩大 <math xmlns="http://www.w3.org/1998/Math/MathML"> p r i z e \mathit {prize} </math>prize,让它等于猜中的那个数乘上一共所猜的次数的十倍,为了不超过 <math xmlns="http://www.w3.org/1998/Math/MathML"> u 8 \mathit u \text 8 </math>u8 的数据范围,还需要跟 <math xmlns="http://www.w3.org/1998/Math/MathML"> 255 \text {255} </math>255 取个最小值。

本次示例就以这一点为代表进行,只要是符合之前所列举的修改规则的都是可以的,你可以自由发挥(๑•̀ㅂ•́)و✧

经过优化后的代码如下:

move 复制代码
module guess_number::guess_number {
    use sui::object::{Self, ID, UID};
    use sui::tx_context::{Self, TxContext};
    use sui::transfer;
    use sui::clock::{Self, Clock};
    use sui::event;
    use sui::math;

    const VERSION: u64 = 2;

    const ENOTVERSION: u64 = 0;
    const ENOTADMIN: u64 = 1;
    const ENOTUPGRADE: u64 = 2;

    struct Count has key {
        id: UID,
        version: u64,
        admin: ID,
        total: u64,
    }

    struct AdminCap has key {
        id: UID,
    }

    struct Prize has key {
        id: UID,
        prize: u8,
    }

    struct GuessEvent has copy, drop {
        total_count: u64,
        final_winner: address,
    }

    fun init(ctx: &mut TxContext) {
        let admin_cap = AdminCap {id: object::new(ctx)};

        let count = Count {
            id: object::new(ctx),
            version: VERSION,
            admin: object::id(&admin_cap),
            total: 0,
        };

        transfer::transfer(admin_cap, tx_context::sender(ctx));
        transfer::share_object(count);
    }

    fun send_prize(count: u64, prize: u8, ctx: &mut TxContext) {
        transfer::transfer(Prize {
            id: object::new(ctx),
            prize,
        }, tx_context::sender(ctx));

        event::emit(GuessEvent {
            total_count: count,
            final_winner: tx_context::sender(ctx),
        });
    }

    public entry fun guess_between_zero_and_hundred(count: &mut Count, number: u8, clock: &Clock, ctx: &mut TxContext) {
        assert!(count.version == VERSION, ENOTVERSION);

        let des_number = ((clock::timestamp_ms(clock) % 11) as u8);
        if (number == des_number) {
            let prize = (math::min((number as u64) * (count.total + 1) * 10, 255) as u8);
            send_prize(count.total, prize, ctx);
            count.total = 0;
        } else {
            count.total = count.total + 1;
        }
    }

    entry fun migrate(count: &mut Count, admin_cap: &AdminCap) {
        assert!(count.admin == object::id(admin_cap), ENOTADMIN);
        assert!(count.version != VERSION, ENOTUPGRADE);

        count.version = VERSION;
    }
}

sui client upgrade --gas-budget 100000000 --upgrade-capability $UPGRADE_CAP尝试升级合约。

如果出现了如下错误,请考虑修改是否完全遵循了那七条规则。

css 复制代码
Error executing transaction: Failure {
    error: "PackageUpgradeError { upgrade_error: IncompatibleUpgrade } in command 1",
}

如果升级成功,我们也将得到一长串信息,同样的,将新的 <math xmlns="http://www.w3.org/1998/Math/MathML"> P a c k a g e I D \mathit {PackageID} </math>PackageID 记录一下:
export NEW_PACKAGE_ID=0x5a3974941fc000890f312c538c47f98c787a89776d6da1762129a4b0379e855b

在调用函数 <math xmlns="http://www.w3.org/1998/Math/MathML"> m i g r a t e \mathit {migrate} </math>migrate 之前,旧合约可以正常使用,但是新合约不行,因为 <math xmlns="http://www.w3.org/1998/Math/MathML"> C o u n t \mathit {Count} </math>Count 中的 <math xmlns="http://www.w3.org/1998/Math/MathML"> v e r s i o n \mathit {version} </math>version 还是 <math xmlns="http://www.w3.org/1998/Math/MathML"> 1 \text 1 </math>1,如果此时调用新合约,会出现如下报错:

swift 复制代码
Error executing transaction: Failure {
    error: "MoveAbort(MoveLocation { module: ModuleId { address: 628f33fcf96ebc82f275bddb7ad927b0e260e989f2131e0e1dc844ab931b57f5, name: Identifier(\"guess_number\") }, function: 2, instruction: 14, function_name: Some(\"guess_between_zero_and_hundred\") }, 0) in command 0",
}

如果想要调用 <math xmlns="http://www.w3.org/1998/Math/MathML"> m i g r a t e \mathit {migrate} </math>migrate 的话,就需要之前存储的 <math xmlns="http://www.w3.org/1998/Math/MathML"> A D M I N _ C A P \mathit {ADMIN\_CAP} </math>ADMIN_CAP 的地址,命令如下:
sui client call --package $NEW_PACKAGE_ID --module guess_number --function migrate --args $COUNT $ADMIN_CAP --gas-budget 100000000

如果重复调用则会报错,因为 <math xmlns="http://www.w3.org/1998/Math/MathML"> a s s e r t \mathit {assert} </math>assert 不通过,从错误代码 <math xmlns="http://www.w3.org/1998/Math/MathML"> 2 \text 2 </math>2 也可以看出,是assert!(count.version != VERSION, ENOTUPGRADE);出了问题,版本已经不需要更新了。

现在,调用旧合约的话就会出错,新合约就没有问题。不断猜数,观察次数和猜中的数之间的关系,可以发现 <math xmlns="http://www.w3.org/1998/Math/MathML"> p r i z e \mathit {prize} </math>prize 的奖励情况变得更诱人了。

五:权限定制

如果只是按照之前所叙述的七条修改规则,只要用户想(不考虑代码优美)几乎可以重写所有功能,有的时候这并不是开发者想要的,所以, <math xmlns="http://www.w3.org/1998/Math/MathML"> S u i M o v e \mathit {Sui\ Move} </math>Sui Move 也提供了不同的合约升级权限。

  • <math xmlns="http://www.w3.org/1998/Math/MathML"> C o m p a t i b l e : \mathit {Compatible}: </math>Compatible: 最宽松的权限,也是最初发布合约时的默认权限,只要满足那七条规定就可以。
  • <math xmlns="http://www.w3.org/1998/Math/MathML"> A d d i t i v e : \mathit {Additive}: </math>Additive: 可以添加新的函数,比如新的 <math xmlns="http://www.w3.org/1998/Math/MathML"> p u b l i c \mathit {public} </math>public 函数和 <math xmlns="http://www.w3.org/1998/Math/MathML"> s t r u c t \mathit {struct} </math>struct,但不能对现有的函数代码做任何修改。
  • <math xmlns="http://www.w3.org/1998/Math/MathML"> D e p e n d e n c y \mathit {Dependency} </math>Dependency- <math xmlns="http://www.w3.org/1998/Math/MathML"> o n l y : \mathit {only}: </math>only: 只能修改该 <math xmlns="http://www.w3.org/1998/Math/MathML"> p a c k a g e \mathit {package} </math>package 的依赖项。
  • <math xmlns="http://www.w3.org/1998/Math/MathML"> I m m u t a b l e : \mathit {Immutable}: </math>Immutable: 无法再升级该 <math xmlns="http://www.w3.org/1998/Math/MathML"> p a c k a g e \mathit {package} </math>package。

我们尝试执行以下命令,调用0x2地址(也就是 <math xmlns="http://www.w3.org/1998/Math/MathML"> s u i \mathit {sui} </math>sui )下的 <math xmlns="http://www.w3.org/1998/Math/MathML"> p a c k a g e \mathit {package} </math>package 模块中的 <math xmlns="http://www.w3.org/1998/Math/MathML"> m a k e _ i m m u t a b l e \mathit {make\_immutable} </math>make_immutable 函数,将合约升级时必须的 <math xmlns="http://www.w3.org/1998/Math/MathML"> U P G R A D E _ C A P \mathit {UPGRADE\_CAP} </math>UPGRADE_CAP 传入:
sui client call --package 0x2 --module package --function make_immutable --args $UPGRADE_CAP --gas-budget 100000000

此时,再尝试迭代升级,会报错:

Could not find upgrade capability at <address>

这是因为这个函数将 <math xmlns="http://www.w3.org/1998/Math/MathML"> U P G R A D E _ C A P \mathit {UPGRADE\_CAP} </math>UPGRADE_CAP 销毁了,升级合约的必需品无了,自然也就无法再更新了。

类似的,如果你想要切换成其它权限,也可以用类似的命令,具体的函数名以及参数请移步至此 <math xmlns="http://www.w3.org/1998/Math/MathML"> p a c k a g e . m o v e \mathit {package.move} </math>package.move

注意:上述四个权限定制是依次收紧且单向的,也就是说,你可以从 <math xmlns="http://www.w3.org/1998/Math/MathML"> C o m p a t i b l e \mathit {Compatible} </math>Compatible 收紧成 <math xmlns="http://www.w3.org/1998/Math/MathML"> I m m u t a b l e \mathit {Immutable} </math>Immutable,但就再也无法松开了,哪怕是 <math xmlns="http://www.w3.org/1998/Math/MathML"> D e p e n d e n c y \mathit {Dependency} </math>Dependency- <math xmlns="http://www.w3.org/1998/Math/MathML"> o n l y \mathit {only} </math>only 也不行。

六:加入组织,共同进步!

  • Sui 中文开发群(TG)
  • <math xmlns="http://www.w3.org/1998/Math/MathML"> M o v e \mathit{Move} </math>Move 语言学习交流群: 79489587
相关推荐
BSV区块链2 天前
关于BSV区块链覆盖网络的常见问题解答(上篇)
网络·区块链
荔家大少2 天前
区块链媒体推广:15个数字解读未来-华媒舍
大数据·区块链·媒体
TheFirst0082 天前
The First项目报告:解读跨链互操作性平台Wormhole
web3
0x派大星3 天前
Solidity 存储和内存管理:深入理解与高效优化
web3·区块链·智能合约·solidity
唐天下文化3 天前
INTO:Web3世界的“价值引力场”
web3
0x派大星3 天前
Solidity智能合约中的事件和日志
web3·区块链·智能合约·solidity
_.Switch4 天前
Python Web WebAssembly 与 Python 的协同工作
前端·python·安全·架构·区块链·智能合约·wasm
CertiK4 天前
CertiK《Hack3d:2024年第三季度安全报告》(附报告全文链接)
安全·web3·区块链
Sui_Network4 天前
Sui主网升级至V1.34.2
运维·服务器·物联网·架构·区块链
杏酸雪菲期权5 天前
期权卖方怎么选择权利金高的品种,期货VIX高低对行情有什么影响
区块链