Sui.

Bài viết

Chia sẻ kiến thức của bạn.

article banner.
harry phan.
Apr 24, 2025
Bài Viết

Logic ràng buộc linh hồn và mô hình nhận hàng trả

#** Logic ràng buộc linh hồn và mô hình nhận trả lời**

Bây giờ là một mẫu thiết kế đặc biệt sử dụng cơ chế cha mẹ-con:** các đối tượng liên kết với linh hồn**. Một đối tượng bị ràng buộc với linh hồn dự định được gắn với một chủ sở hữu cụ thể và không thể chuyển nhượng vĩnh viễn. Tuy nhiên, bạn có thể muốn tạm thời cấp cho ai đó quyền truy cập vào nó trong một giao dịch duy nhất - ví dụ, để thực hiện một số thao tác trên nó - nhưng đảm bảo nó trở lại cho bạn vào cuối giao dịch đó. Điều này đạt được với cái mà chúng tôi gọi là mô hình ReturnReceipt, một biến thể của mẫu “khoai tây nóng”.

###Mẫu khoai tây nóng ở Sui

Mẫu “khoai tây nóng” được đặt tên theo trò chơi dành cho trẻ em - nếu bạn được trao một củ khoai tây nóng 🌡️🥔, bạn không thể giữ nó; bạn phải truyền nó nhanh chóng. Trong thuật ngữ Move, hot potato thường là một đối tượng hoặc tài nguyên mà thể được tiêu thụ hoặc chuyển đi trong cùng một giao dịch, nếu không giao dịch sẽ thất bại. Điều này thường được thực hiện bằng cách cung cấp cho đối tượng khả năng no drop (vì vậy bạn không thể bỏ qua nó hoặc thả nó) và thiết kế logic sao cho cách duy nhất để loại bỏ nó là thực hiện hành động cần thiết (như trả lại khoản vay).

Một trường hợp sử dụng phổ biến làcho vay flash: bạn mượn một số coin trong một giao dịch (nhận một đối tượng Cho vay, cộng với mã thông báo “nợ”). Nếu bạn không hoàn trả trước khi kết thúc giao dịch, bạn không thể hoàn tất vì bạn vẫn giữ mã thông báo nợ mà bạn không được phép loại bỏ - buộc bạn phải trả lại khoản vay hoặc hủy bỏ.

###Ví dụ về đối tượng bị ràng buộc linh hồn

Giả sử chúng ta có một đối tượng SoulBound mà chúng ta luôn muốn giữ nguyên với địa chỉ chủ sở hữu ban đầu của nó. Chúng ta có thể thực thi rằng nếu ai đó “mượn” nó (như một đối tượng con dưới một số phụ huynh), họ phải trả lại nó trong cùng một giao dịch. Làm thế nào? Bằng cách sử dụng ReturnReceipt.

Dưới đây là một phiên bản đơn giản lấy cảm hứng từ ví dụ tài liệu của Sui về các đối tượng bị ràng buộc linh hồn

module demo::soul_bound {
    use sui::transfer::{Self, Receiving};
    use sui::object::UID;
    use sui::transfer;

    const EWrongObject: u64 = 0;

    /// A soul-bound object that cannot be permanently transferred.
    /// It has only `key` ability (no `store`), to tighten transfer/receive rules [oai_citation_attribution:40‡docs.sui.io](https://docs.sui.io/concepts/transfers/transfer-to-object#:~:text=%2F%2F%2F%20This%20object%20has%20,id%3A%20UID%2C).
    public struct SoulBound has key {
        id: UID,
        data: u64
    }

    /// A receipt that proves a SoulBound object must be returned.
    /// No abilities: cannot be copied, dropped, or stored (implicitly).
    public struct ReturnReceipt {
        /// The object ID of the soul-bound object that must be returned.
        object_id: UID,
        /// The address (or object ID) it must be returned to (the original owner).
        return_to: address
    }

    /// Allows the owner of `parent` to retrieve their SoulBound child.
    /// Returns the SoulBound object *and* a ReturnReceipt that compels return.
    public fun take_soul_bound(parent: &mut UID, sb_ticket: Receiving<SoulBound>): (SoulBound, ReturnReceipt) {
        let sb = transfer::receive(parent, sb_ticket);  // receive SoulBound (only this module can do it, since SoulBound has no store) [oai_citation_attribution:41‡docs.sui.io](https://docs.sui.io/concepts/transfers/transfer-to-object#:~:text=%2F%2F%2F%20,to_address%28%29%2C%20object_id%3A%20object%3A%3Aid%28%26soul_bound%29%2C) 
        let receipt = ReturnReceipt {
            object_id: sb.id,
            return_to: parent.to_address()
        };
        (sb, receipt)
    }

    /// Return a SoulBound object using the ReturnReceipt.
    /// This must be called in the same transaction after take_soul_bound.
    public fun return_soul_bound(sb: SoulBound, receipt: ReturnReceipt) {
        // Verify the receipt matches this SoulBound object
        assert!(sb.id == receipt.object_id, EWrongObject);
        // Send the SoulBound object back to the original owner address
        transfer::transfer(sb, receipt.return_to);
        // (ReturnReceipt is consumed/destroyed here as function param)
    }
}

Trong thiết kế này:

  • SoulBound là một đối tượng chỉ có khóa. Bằng cách không cho nó lưu trữ, chúng tôi ngăn chặn việc sử dụng public_transfer hoặc public_receiver trên nó, nghĩa là cách duy nhất để chuyển hoặc nhận nó là thông qua các chức năng của mô-đun xác định của nó (đảm bảo logic tùy chỉnh của chúng tôi được sử dụng).
  • take_soul_bound (tương tự như “get_object” trong tài liệu) là một hàm mà chủ sở hữu sẽ gọi để lấy đối tượng bị ràng buộc linh hồn của họ ra khỏi một số cha mẹ. Nó gọi transfer: :receiver để lấy đối tượng SoulBound. Bởi vì SoulBound không có cửa hàng, cuộc gọi này chỉ được phép ở đây trong mô-đun của nó (không sao cả). Sau đó, nó tạo một cấu trúc returnReceipt chứa ID của đối tượng bị ràng buộc linh hồn và địa chỉ để quay lại (địa chỉ của cha mẹ). Chúng tôi trả lạicả thâuđối tượng và biên lai.
  • ReturnReceipt không có khả năng thả, lưu trữ hoặc sao chép (chúng tôi không khai báo bất kỳ khả năng nào, vì vậy nó được coi là tài nguyên không thể bị loại bỏ hoặc sao chép). Điều này có nghĩa là giao dịch** phải làm gì đó với nó; nếu không, nó sẽ là tài nguyên còn sót lại và VM sẽ không để giao dịch kết thúc thành công. Về cơ bản, ReturnReceipt là mã thông báo khoai tây nóng của chúng tôi 🔥🥔.
  • Điều hợp lệ duy nhất cần làm với returnReceipt là gọi return_soul_bound (hoặc một hàm được chỉ định tương tự) trong cùng một giao dịch. Trong return_soul_bound, chúng tôi xác minh rằng ID của đối tượng SoulBound khớp với bản ghi của biên nhận (để ngăn ai đó cố gắng trả lại một đối tượng khác với biên nhận sai). Sau đó, chúng tôi gọi transfer: :transfer (sb, receipt.return_to), gửi đối tượng SoulBound trở lại địa chỉ trong biên nhận (chủ sở hữu ban đầu). Điều này có hiệu quả khôi phục đối tượng bị ràng buộc với linh hồn về vị trí xứng đáng của nó. ReturnReceipt được sử dụng như một đối số (do đó bị phá hủy).
  • Nếu người dùng cố gắng hoàn thành giao dịch mà không gọi return_soul_bound, họ vẫn sẽ giữ returnReceipt (từ đầu ra take_soul_bound). Vì ReturnReceipt không bị rơi, Move VM sẽ từ chối hoàn tất giao dịch (trong Move, bạn không thể đơn giản loại bỏ một tài nguyên; nó phải được sử dụng hoặc lưu trữ ở đâu đó). Giao dịch sẽ bị hủy hoặc bị coi là không hợp lệ, có nghĩa là toàn bộ hoạt động sẽ quay trở lại.Kết quả: bạn không thể chạy trốn với đối tượng bị ràng buộc với linh hồn; nếu bạn không trả lại nó, tx sẽ thất bại và nó ở lại với đối tượng cha mẹ.

Từ thời điểm bạn gọi take_soul_bound đến thời điểm bạn gọi return_soul_bound trong giao dịch, bạn có sẵn giá trị đối tượng SoulBound - có lẽ là để thực hiện một số thao tác được phép trên nó (có thể đọc nó hoặc sử dụng nó khi cần). Nhưng bạn** phải trả lại nó trước khi giao dịch kết thúc, nhờ vào việc thực thi biên lai.

Mô hình này thường được mô tả là “bị ràng buộc linh hồn = không thể rời khỏi chủ sở hữu của nó” nhưng chính xác hơn, nó có thể rời khỏi trong một giao dịch duy nhất như một đối tượng con, với sự đảm bảo rằng nó quay trở lại. Nó giống như kiểm tra một cuốn sách trong thư viện - bạn phải trả lại nó trước khi thư viện đóng cửa trong ngày 😅.

Tại sao không bao giờ chuyển nó cả? Có những tình huống mà việc chuyển tạm thời một đối tượng sang một đối tượng khác (hoặc ngữ cảnh) là hữu ích. Một ví dụ là ngữ cảnhđối tượng được chia sẻ: có lẽ mục SoulBound được giữ dưới một đối tượng được chia sẻ trong một số hoạt động và cần được chủ sở hữu ban đầu lấy ra. Một cách khác là cho phép bố cục có kiểm soát - bạn có thể muốn để một số mô-đun hoạt động trên đối tượng của bạn bằng cách đưa nó cho đối tượng của mô-đun đó và sau đó lấy lại nó.

Bằng cách làm cho SoulBound chỉ có khóa, chúng tôi đảm bảo không có public_receiver bên ngoài nào có thể lấy nó mà không có sự tham gia của mô-đun của chúng tôi. Bằng cách sử dụng biên lai, chúng tôi buộc phải tuân thủ chính sách hoàn trả. Nhận xét trong mã ví dụ của Sui thậm chí còn lưu ý rằng ReturnReceipt ngăn chặn việc hoán đổi - nếu hai đối tượng bị ràng buộc linh hồn được lấy ra trong một giao dịch, mỗi biên nhận sẽ mang một ID đối tượng cụ thể nên bạn không thể trộn lẫn chúng và trả lại sai đối tượng để gian lận.

Khái quát hóa ý tưởng: Mẫu ReturnReceipt có thể được sử dụng bất cứ khi nào bạn muốn thực thi rằng một đối tượng được trả lại. Các khoản vay flash sử dụng một khái niệm tương tự (token cho vay và mã thông báo nợ phải được hoàn trả). Bất cứ khi nào bạn có một bất biến “đối tượng X phải kết thúc trở lại địa chỉ Y ở cuối tx”, bạn có thể tạo tài nguyên biên lai để duy trì nó.

###Tóm tắt nhanh:

-** Các đối tượng liên kết với linh hồn**: chỉ có khóa, chỉ được truy xuất bởi logic của mô-đun của chúng. -ReturnReceipt: tài nguyên giả được trả về cùng với đối tượng để đảm bảo nó được trả về. Nó không thả, vì vậy người dùng phải gọi hàm return nếu không thất bại. -Khoai tây nóng: Nếu bạn giữ tờ ReturnReceipt, bạn nên x��� lý nó (trả lại đối tượng) trước khi “nhạc dừng lại” (giao dịch kết thúc).

  • Nếu đối tượng không được trả về, giao dịch sẽ không thực hiện thành công - các thay đổi của bạn sẽ hoàn lại, thực thi hiệu quả quy tắc ràng buộc linh hồn.

Và với điều đó, chúng tôi đã đề cập đến các khái niệm chính về mối quan hệ cha mẹ và con cái trong Sui Move!

##5. Cấu trúc dự án & Chiến lược thử nghiệm (sẵn sàng cho GitHub)

Để củng cố các khái niệm này, bạn có thể muốn thiết lập một dự án Sui Move và chơi với các ví dụ. Dưới đây là bố cục dự án được đề xuất bao gồm các mô-đun cho các ví dụ trên. Bạn có thể biên dịch và chạy các bài kiểm tra đơn vị hoặc sử dụng Sui CLI để tương tác với chúng.

parent_child_demo/
├── Move.toml
├── sources/
│   ├── toy_box.move        (Parent & child in same module example)
│   ├── parcel.move         (Child module for Parcel)
│   ├── warehouse.move      (Parent module for Warehouse, uses public_receive)
│   └── soul_bound.move     (SoulBound and ReturnReceipt module)
└── tests/
    └── parent_child_test.move   (Integration tests for the modules)

Move.toml: Đảm bảo bao gồm khung Sui và bất kỳ địa chỉ nào cho gói của bạn. Ví dụ:

[package]
name = "parent_child_demo"
version = "0.0.1"
dependencies = [ 
    { name = "Sui", git = "https://github.com/MystenLabs/sui.git", subdir = "crates/sui-framework", rev = "devnet" }
]

[addresses]
demo = "0x0"
  • (Sử dụng địa chỉ thích hợp thay vì 0x0 nếu Sui yêu cầu, hoặc địa chỉ có tên mà bạn sẽ gán khi xuất bản. ) *

Trong thư mục nguồn:

-toy_box.movecó thể chứa mô-đun ví dụ: :toy_box từ Phần 2, với các định nghĩa Toy và Box và hàm take_toy. -parcel.movewarehouse.movesẽ chứa các mô-đun từ Phần 3. -soul_bound.movesẽ chứa mô-đun từ Phần 4.

Mỗi mô-đun nên sử dụng sui::... khi cần thiết cho chuyển, object, tx_context, v.v., như thể hiện trong các ví dụ. Các tests/parent_child_test.move sau đó có thể nhập các mô-đun này và mô phỏng các kịch bản:

module demo::parent_child_test {
    use 0xYourAddr::toy_box;
    use 0xYourAddr::warehouse;
    use 0xYourAddr::parcel;
    use 0xYourAddr::soul_bound;
    use sui::tx_context::TxContext;

    #[test]
    fun test_toy_withdraw() {
        let ctx = TxContext::new(); // pseudo, context is usually provided
        let my_box = toy_box::Box { id: object::new(&mut ctx) };
        let my_toy = toy_box::Toy { id: object::new(&mut ctx), name: b"Ball".to_vec() };
        // Transfer toy into the box
        transfer::transfer(my_toy, my_box.id); 
        // Now simulate receiving it back
        let ticket = transfer::Receiving<toy_box::Toy>{ /*...*/ }; // In real test, use transfer::make_receiver or a transaction call
        let returned_toy = toy_box::take_toy(&mut my_box, ticket);
        assert!(returned_toy.name == b"Ball".to_vec());
    }

    // Additional tests for warehouse/parcel and soul_bound can be written similarly.
}

Trên đây là một minh họa khái niệm. Trong thực tế, ngữ cảnh thử nghiệm của Sui có thể khác nhau — bạn có thể cần sử dụng transfer: :public_transfer trong các bài kiểm tra khi chuyển sang đối tượng (nếu bên ngoài mô-đun) và sử dụng bối cảnh Nhận từ giao dịch. Sui cung cấp một transfer: :make_receiver (hàm chỉ thử nghiệm) có thể tạo ra một Receiving cho một ID và phiên bản, rất hữu ích trong các bài kiểm tra đơn vị để mô phỏng đầu vào đối tượng con.

Chạy thử nghiệm: Bạn có thể chạy thử nghiệm sui move sẽ thực thi các hàm # [test]. Hoặc triển khai gói vào mạng Sui cục bộ và gọi các chức năng nhập thông qua CLI hoặc SDK để quan sát hành vi:

  • Tạo một Bưu kiện, tạo một Kho, gọi chuyển: :public_transfer qua CLI để đưa bưu kiện vào kho, sau đó gọi _parcel.
  • Tạo một đối tượng SoulBound (có lẽ bằng cách tạo một mục nhập thú vị để tạo ra một đối tượng), chuyển nó sang một đối tượng cha (hoặc đối tượng được chia sẻ), sau đó trong một giao dịch duy nhất gọi take_soul_bound và bỏ qua return_soul_bound để thấy giao dịch thất bại (mong đợi), so với bao gồm lệnh return để thấy thành công.

Mỗi phần của dự án này đề cập đến một trong các chủ đề:

  • toy_box.move: phụ huynh-con cơ bản và nhận.
  • parcel.move & warehouse.move: con chênh lệch mô-đun và public_receiver.
  • soul_bound.move: bị ràng buộc linh hồn với ReturnReceipt.

Bằng cách thử nghiệm với những thứ này, bạn sẽ hiểu sâu hơn về mô hình đối tượng của Sui. Mã được cấu trúc để mang tính giáo dục và không nên được sử dụng như hiện tại để sản xuất mà không có đánh giá bảo mật, nhưng nó cung cấp một điểm khởi đầu vững chắc.


Kết luận: Chức năng đối tượng cha mẹ-con của Sui Move rất mạnh mẽ. Nó cho phép bạn tạo cấu trúc dữ liệu phức tạp trên chuỗi (như hàng tồn kho, ví, bộ sưu tập) với kiểm soát truy cập chi tiết. Sự kết hợp giữa transfer: :receive/public_receiver và hệ thống kiểu Move đảm bảo rằng chỉ mã được ủy quyền mới có thể truy xuất các đối tượng con và các mẫu như ReturnReceipt cho phép thực thi các quy tắc sở hữu tạm thời (ràng buộc linh hồn, vay flash, v.v.). Chúng tôi đã rắc một số phép loại tương tự và biểu tượng cảm xúc thú vị, nhưng cuối cùng, đây là những công cụ mạnh mẽ cho các nhà xây dựng. Bây giờ hãy đi và xây dựng một số phép thuật đối tượng lồng nhau! 🚀🔥

Khi một đối tượng được chuyển sang một đối tượng khác thay vì địa chỉ tài khoản, chúng tạo thành mối quan hệ cha mẹ-con tục**. Bạn có thể nghĩ về nó như thế này:

-Parent= đối tượng container -** Trẻ em**= thứ được đặt bên trong

Move không quan tâm nếu bạn đang chuyển sang tài khoản hoặc ID đối tượng. Nó chỉ * di chuyển*.

public struct Parent has key {
    id: UID,
    name: String,
}

public struct Child has key {
    id: UID,
    description: String,
}

##** Tạo đối tượng phụ huynh và chân**

Cha mẹ phải có thể thay đổi và phải tồn tại. Bạn không thể nhét mọi thứ vào một chiếc hộp bất biến!

  • Sui
  • Architecture
4
Chia sẻ
Bình luận
.

Sui is a Layer 1 protocol blockchain designed as the first internet-scale programmable blockchain platform.

610Bài viết1335Câu trả lời
Sui.X.Peera.

Kiếm phần của bạn từ 1000 Sui

Tích lũy điểm danh tiếng và nhận phần thưởng khi giúp cộng đồng Sui phát triển.

Chiến dịch phần thưởngTháng Bảy