Bài học 1

Giới thiệu và Hợp đồng Multisig

Hợp đồng đa chữ ký (multisig), còn được gọi là hợp đồng "M-of-N", là một cơ chế quan trọng được sử dụng để tăng tính bảo mật và tính linh hoạt của các giao dịch trong môi trường blockchain. Các hợp đồng này thay đổi cách thức kiểm soát tài sản bằng cách yêu cầu sự chấp thuận của nhiều bên trước khi giao dịch có thể được thực hiện. Thuật ngữ "M-of-N" đề cập đến yêu cầu M trong tổng số N bên phải phê duyệt giao dịch để giao dịch đó có hiệu lực.

Lý thuyết về hợp đồng Multisig

Hợp đồng nhiều chữ ký cung cấp một phương tiện để tạo ra quyền kiểm soát chung đối với tài sản. Các trường hợp sử dụng điển hình liên quan đến dịch vụ ký quỹ, quản lý tài khoản công ty, đồng ký kết các thỏa thuận tài chính, v.v. Những hợp đồng này đặc biệt có lợi cho các tổ chức hoặc nhóm nơi việc ra quyết định tập thể là cần thiết.

Theo thiết kế, các hợp đồng đa chữ ký có khả năng chống giả mạo và ngăn chặn các điểm lỗi đơn lẻ. Ngay cả khi khóa của một bên bị xâm phạm, kẻ tấn công không thể thực hiện giao dịch mà không có sự chấp thuận của các bên khác. Điều này bổ sung thêm một lớp bảo mật.

Hợp đồng nhiều chữ ký có thể được coi là một dạng kỹ thuật số tương đương với hộp ký gửi an toàn yêu cầu nhiều chìa khóa để mở. Tổng số chìa khóa (N) và số chìa khóa tối thiểu cần thiết để mở hộp (M) được thỏa thuận khi tạo hợp đồng.

Hợp đồng Multisig có thể có nhiều cấu hình khác nhau tùy thuộc vào giá trị của M và N:

  • 1-of-N: Một bên duy nhất trong tổng số có thể phê duyệt giao dịch. Cấu hình này giống như một giao dịch thông thường không có multisig. Nó có thể được sử dụng khi có nhiều khóa để thuận tiện, nhưng bất kỳ khóa nào trong số đó đều có thể phê duyệt giao dịch.
  • N-of-N: Tất cả các bên phải phê duyệt giao dịch. Cấu hình này cung cấp mức độ bảo mật cao nhất nhưng có thể trở thành vấn đề nếu một bên mất chìa khóa hoặc từ chối phê duyệt giao dịch.
  • M-of-N (trong đó M < N): Một tập hợp con của tổng số các bên phải phê duyệt giao dịch. Cấu hình này thường được sử dụng trong thực tế vì nó cân bằng giữa tính bảo mật và tính linh hoạt.

Hợp đồng nhiều chữ ký trong Blockchain

Trong bối cảnh blockchain, hợp đồng nhiều chữ ký được sử dụng rộng rãi để tăng cường bảo mật giao dịch, hỗ trợ các cơ chế quản trị phức tạp hoặc duy trì quyền kiểm soát linh hoạt đối với tài sản blockchain. Dưới đây là một số ví dụ:

  • Ví: Ví Multisig được sử dụng để bảo đảm tài sản. Họ yêu cầu nhiều bên ký kết các giao dịch, do đó cung cấp bảo mật bổ sung chống lại hành vi trộm cắp, hack từ bên ngoài và các mối đe dọa nội bộ.
  • Các tổ chức tự trị phi tập trung (DAO): DAO thường sử dụng các hợp đồng nhiều chữ ký để thực thi các quy tắc quản trị của họ. Việc bỏ phiếu cho các đề xuất được thực hiện dưới dạng giao dịch nhiều chữ ký, với các thành viên của DAO đóng vai trò là người ký kết. Đề xuất chỉ được thực hiện nếu nhận được đủ phiếu bầu.
  • Hoạt động xuyên chuỗi: Trong các hoạt động xuyên chuỗi, các hợp đồng đa chữ ký có thể đóng vai trò là người giám sát tài sản. Khi tài sản được chuyển từ blockchain này sang blockchain khác, hợp đồng nhiều chữ ký trên chuỗi gốc có thể đảm bảo rằng tài sản được khóa an toàn cho đến khi hoạt động trên chuỗi khác được xác nhận.
    Mặc dù việc triển khai các hợp đồng nhiều chữ ký có thể khác nhau tùy theo từng blockchain, nhưng khái niệm cốt lõi vẫn giống nhau - nhu cầu nhiều bên phê duyệt một giao dịch trước khi nó được thực hiện. Lớp bảo mật bổ sung này làm cho các hợp đồng đa chữ ký trở thành một công cụ thiết yếu trong không gian blockchain và tiền điện tử.

Ví dụ về mã hóa: Viết và triển khai hợp đồng Multisig với SmartPy

Đối với các ví dụ về mã của chúng tôi, chúng tôi sẽ xem xét ba cách triển khai hợp đồng đa chữ ký khác nhau:

Hợp đồng Lambda

Nó khá linh hoạt và cho phép sử dụng nhiều loại. Nó yêu cầu nhiều chữ ký để thực thi các hàm lambda tùy ý.

Python 
 nhập smartpy dưới dạng sp 


 @sp.module 
 def main(): 
 Operation_lambda: type = sp.lambda_(sp.unit, sp.unit, with_Operations=True) 

 lớp MultisigLambda(sp.Contract): 
 """Nhiều thành viên bỏ phiếu để thực thi lambdas.

        Hợp đồng này có thể được bắt nguồn từ danh sách địa chỉ và số phiếu bầu bắt buộc là 
 . Bất kỳ thành viên nào cũng có thể gửi bao nhiêu lambda tùy thích và bỏ phiếu 
 cho các đề xuất đang hoạt động. Khi lambda đạt được số phiếu yêu cầu, mã của nó được gọi là 
 và các hoạt động đầu ra được thực thi. Điều này cho phép hợp đồng này 
 thực hiện bất kỳ điều gì mà hợp đồng có thể làm: chuyển mã thông báo, quản lý tài sản, 
 quản lý hợp đồng khác...

        Khi áp dụng lambda, tất cả các lambda đã gửi cho đến thời điểm hiện tại đều không được kích hoạt.
        Các thành viên vẫn có thể gửi lambdas mới.
        """ 

 def __init__(tự, thành viên, require_votes): 
 """Constructor 

 Args: 
 thành viên (sp.set of sp.address): những người có thể gửi và bỏ phiếu 
 cho lambda.
                bắt buộc_votes (sp.nat): số phiếu bầu cần thiết 
 """ 
 khẳng định bắt buộc_votes <= sp.len( 
 thành viên 
 ), "required_votes phải là <= len(thành viên)" 
 self.data.lambdas = sp.cast (
                sp.big_map(), sp.big_map[sp.nat, Operation_lambda] 
 ) 
 self.data.votes = sp.cast(
                sp.big_map(), sp.big_map[sp.nat, sp.set[sp.address]] 
 ) 
 self.data.nextId = 0 
 self.data.inactiveBefore = 0 
 self.data.members = sp.cast(thành viên, sp.set[sp.address])
            self.data.required_votes = sp.cast(required_votes, sp.nat) 

 @sp.entrypoint 
 def submit_lambda(self, lambda_): 
 """Gửi lambda mới để bỏ phiếu.

            Việc gửi một đề xuất không có nghĩa là bỏ phiếu ủng hộ đề xuất đó.

            Args: 
 lambda_(sp.lambda có phép toán): lambda đề xuất bỏ phiếu.
            Tăng: 
 `Bạn không phải là thành viên` 
 """ 
 khẳng định self.data.members.contains(sp.sender), "Bạn không phải là thành viên" 
 self.data.lambdas[self.data.nextId] = lambda_ 
 self.data.votes[self.data.nextId] = sp.set()
            self.data.nextId += 1 

 @sp.entrypoint 
 def vote_lambda(self, id): 
 """Bỏ phiếu cho lambda.

            Đối số: 
 id(sp.nat): id của lambda để bỏ phiếu.
            Tăng: 
 `Bạn không phải là thành viên`, `Lambda không hoạt động`, `Không tìm thấy Lambda` 

 Không có phiếu chống hoặc thông qua. Nếu ai đó không đồng ý với lambda 
 họ có thể tránh bỏ phiếu.
            """ 
 khẳng định self.data.members.contains(sp.sender), "Bạn không phải là thành viên" 
 khẳng định id >= self.data.inactiveBefore, "Lambda không hoạt động" 
 khẳng định self.data.lambdas.contains(id), "Không tìm thấy Lambda" 
 self.data.votes[id].add(sp.sender)
            if sp.len(self.data.votes[id]) >= self.data.required_votes:
                self.data.lambdas[id]()
                self.data.inactiveTrước = self.data.nextId 

 @sp.onchain_view() 
 def get_lambda(self, id): 
 """Trả về lambda tương ứng.

            Args: 
 id (sp.nat): id của lambda cần lấy.

            Trả về: 
 cặp lambda và boolean hiển thị xem lambda có hoạt động hay không.
            """ 
 trả về (self.data.lambdas[id], id >= self.data.inactiveBefore)


# if "templates" not in __name__: 


 @sp.module 
 def test(): 
 class Administrated(sp.Contract): 
 def __init__(self, admin): 
 self.data.admin = admin 
 self.data.value = sp.int(0)

        @sp.entrypoint 
 def set_value(self, value): 
 khẳng định sp.sender == self.data.admin 
 self.data.value = value 


 @sp.add_test(name="Kịch bản cơ bản MultisigLambda", is_default=True ) 
 def basic_scenario(): 
 """Sử dụng multisigLambda làm quản trị viên của một hợp đồng mẫu.

    Kiểm tra: 
 - Nguồn gốc 
 - Đệ trình Lambda 
 - Phiếu bầu Lambda 
 """ 
 sc = sp.test_scenario([main, test]) 
 sc.h1("Kịch bản cơ bản.")

    member1 = sp.test_account("member1")
    member2 = sp.test_account("member2")
    member3 = sp.test_account("member3")
    thành viên = sp.set([member1.address, thành viên2.address, member3.address])

    sc.h2("MultisigLambda: nguồn gốc") 
 c1 = main.MultisigLambda(thành viên, 2) 
 sc += c1 

 sc.h2("Được quản lý: nguồn gốc") 
 c2 = test.Administrated(c1.address)
    sc += c2 

 sc.h2("MultisigLambda: submit_lambda") 

 def set_42(params): 
 administrated = sp.contract(sp.TINt, c2.address, entrypoint="set_value") 
 sp.transfer(sp. int(42), sp.tez(0), administrated.open_some())

    lambda_ = sp.build_lambda(set_42, with_Operation=True) 
 c1.submit_lambda(lambda_).run(sender=member1) 

 sc.h2("MultisigLambda: vote_lambda") 
 c1.vote_lambda(0).run(sender=member1)
    c1.vote_lambda(0).run(sender=member2)

    # Chúng tôi có thể kiểm tra xem hợp đồng được quản lý đã nhận được chuyển khoản hay chưa.
    sc.verify(c2.data.value == 42)

Hợp đồng MultisigAction

Nó giới thiệu khái niệm bỏ phiếu cho các đề xuất. Trong hợp đồng này, người ký có thể bỏ phiếu cho một số hành động nhất định sẽ được thực hiện và nếu đạt đến số đại biểu cần thiết, các hành động được đề xuất sẽ được thực hiện.

Python 
 import smartpy as sp 


 @sp.module 
 def main(): 
 # Đặc tả loại hành động quản trị nội bộ 
 InternalAdminAction: type = sp.variant(
        addSigners=sp.list[sp.address],
        ChangeQuorum=sp.nat,
        RemoveSigners=sp.list[sp.address],
    ) 

 lớp MultisigAction(sp.Contract): 
 """Một hợp đồng có thể được nhiều người ký sử dụng để quản lý 
 hợp đồng khác. Các hợp đồng được quản trị triển khai 
 giao diện giúp có thể trình bày rõ ràng quy trình quản trị cho người dùng không phải là chuyên gia.

        Người ký bỏ phiếu cho các đề xuất. Đề xuất là danh sách mục tiêu với danh sách 
 hành động. Một hành động là một byte đơn giản nhưng nó được dự định là giá trị gói bằng 
 biến thể. Mẫu đơn giản này giúp bạn có thể xây dựng giao diện UX 
 hiển thị nội dung của một đề xuất hoặc xây dựng một đề xuất.
        """ 

 def __init__(tự, số đại biểu, người ký): 
 self.data.inactiveBefore = 0 
 self.data.nextId = 0 
 self.data.proposals = sp.cast(
                sp.big_map(), 
 sp.big_map[ 
 sp.nat, 
 sp.list[sp.record(target=sp.address, actions=sp.list[sp.bytes])],
                ], 
 ) 
 self.data.quorum = sp.cast(quorum, sp.nat) 
 self.data.signers = sp.cast(signers, sp.set[sp.address])
            self.data.votes = sp.cast(
                sp.big_map(), sp.big_map[sp.nat, sp.set[sp.address]] 
 ) 

 @sp.entrypoint 
 def send_proposal(self, đề xuất): 
 """Chỉ dành cho người ký. Gửi đề xuất để bỏ phiếu.

            Args: 
 đề xuất (sp.list of sp.record of target address and action): Danh sách\ 
 mục tiêu và các hành động quản trị liên quan.
            """ 
 khẳng định self.data.signers.contains(sp.sender), "Chỉ người ký mới có thể đề xuất" 
 self.data.proposals[self.data.nextId] = đề xuất 
 self.data.votes[self.data.nextId] = sp.set()
            self.data.nextId += 1 

 @sp.entrypoint 
 def vote(self, pId): 
 """Bỏ phiếu cho một hoặc nhiều đề xuất 

 Args: 
 pId (sp.nat): Id của đề xuất.
            """ 
 khẳng định self.data.signers.contains(sp.sender), "Chỉ người ký mới có thể bỏ phiếu" 
 khẳng định self.data.votes.contains(pId), "Đề xuất không xác định" 
 khẳng định pId >= self.data.inactiveBefore, "Đề xuất không hoạt động" 
 self.data.votes[pId].add(sp.sender)

            nếu sp.len(self.data.votes.get(pId, mặc định=sp.set())) >= self.data.quorum:
                self._onApproved(pId)

        @sp.private(with_storage="read-write", with_Operations=True) 
 def _onApproved(self, pId): 
 """Hàm nội tuyến. Logic được áp dụng khi đề xuất đã được phê duyệt."""
            đề xuất = self.data.proposals.get(pId, default=[]) 
 cho p_item trong đề xuất: 
 hợp đồng = sp.contract(sp.list[sp.bytes], p_item.target)
                sp.transfer( 
 p_item.actions,
                    sp.tez(0), 
 hợp đồng.unwrap_some(error="InvalidTarget"),
                ) 
 # Vô hiệu hóa tất cả các đề xuất đã được gửi.
            self.data.inactiveTrước = self.data.nextId 

 @sp.entrypoint 
 def administrate(self, actions): 
 """Chỉ tự gọi. Quản lý hợp đồng này.

            Điểm vào này phải được gọi thông qua hệ thống đề xuất.

            Args: 
 hành động (sp.list of sp.bytes): Danh sách biến thể đóng gói của \ 
 `InternalAdminAction` (`addSigners`, `changeQuorum`, `removeSigners`).
            """ 
 khẳng định ( 
 sp.sender == sp.self_address() 
 ), "Điểm vào này phải được gọi thông qua hệ thống đề xuất." 

 cho pack_actions trong hành động: 
 action = sp.unpack(packed_actions, InternalAdminAction).unwrap_some( 
 error="Định dạng hành động xấu" 
 ) 
 với sp.match(action): 
 với sp.case.changeQuorum với số đại biểu: 
 self.data.quorum = số đại biểu 
 với sp.case.addSigners được thêm vào: 
 cho người ký đã thêm: 
 self.data.signers.add(signer)
                    với sp.case.removeSigners như đã xóa: 
 cho địa chỉ đã xóa: 
 self.data.signers.remove(address)
                # Đảm bảo rằng hợp đồng không bao giờ yêu cầu số đại biểu nhiều hơn tổng số người ký.
                khẳng định self.data.quorum <= sp.len( 
 self.data.signers 
 ), "Số đại biểu nhiều hơn người ký."


nếu "mẫu" không có trong __name__: 

 @sp.add_test(name="Basic script", is_default=True) 
 def test(): 
 signer1 = sp.test_account("signer1")
        người ký2 = sp.test_account("signer2")
        người ký3 = sp.test_account("signer3")

        s = sp.test_scenario(chính)
        s.h1("Kịch bản cơ bản") 

 s.h2("Nguồn gốc") 
 c1 = main.MultisigAction( 
 quorum=2, 
 signers=sp.set([signer1.address, signer2.address]), 
 ) 
 s += c1 

 s.h2("Đề xuất thêm người ký mới") 
 target = sp.to_address(
            sp.contract(sp.TList(sp.TBytes), c1.address, "administrate").open_some() 
 ) 
 action = sp.pack(
            sp.set_type_expr( 
 sp.variant("addSigners", [signer3.address]), main.InternalAdminAction 
 ) 
 ) 
 c1.send_proposal([sp.record(target=target, actions=[action])]).run( 
 sender=signer1 
 ) 

 s.h2("Người ký 1 phiếu cho đề xuất") 
 c1.vote(0).run(sender=signer1)
        s.h2("Người ký 2 phiếu cho đề xuất") 
 c1.vote(0).run(sender=signer2)

        s.verify(c1.data.signers.contains(signer3.address))

Hợp đồng MultisigView

Nó cũng sử dụng một cơ chế bỏ phiếu. Hợp đồng này cho phép các thành viên gửi và bỏ phiếu cho các byte tùy ý. Khi một đề xuất đạt được số phiếu bầu cần thiết, trạng thái của nó có thể được xác nhận thông qua chế độ xem.

Python 
 import smartpy as sp 


 @sp.module 
 def main(): 
 class MultisigView(sp.Contract): 
 """Nhiều thành viên bỏ phiếu cho các byte tùy ý.

        Hợp đồng này có thể được bắt nguồn từ danh sách địa chỉ và số phiếu bầu bắt buộc là 
 . Bất kỳ thành viên nào cũng có thể gửi bao nhiêu byte tùy thích và bỏ phiếu 
 cho các đề xuất đang hoạt động.

        Bất kỳ byte nào đạt được số phiếu yêu cầu đều có thể được xác nhận thông qua chế độ xem.
        """ 

 def __init__(tự, thành viên, bắt buộc_vote): 
 """Constructor 

 Args: 
 thành viên (sp.set of sp.address): những người có thể gửi và bỏ phiếu cho 
 lambda.
                bắt buộc_votes (sp.nat): số phiếu bầu cần thiết 
 """ 
 khẳng định bắt buộc_votes <= sp.len( 
 thành viên 
 ), "required_votes phải là <= len(thành viên)" 
 self.data.proposals = sp.cast (sp.big_map(), sp.big_map[sp.byte, sp.bool]) 
 self.data.votes = sp.cast(
                sp.big_map(), sp.big_map[sp.byte, sp.set[sp.address]] 
 ) 
 self.data.members = sp.cast(thành viên, sp.set[sp.address])
            self.data.required_votes = sp.cast(required_votes, sp.nat) 

 @sp.entrypoint 
 def submit_proposal(self, bytes): 
 """Gửi đề xuất mới để bỏ phiếu.

            Việc gửi một đề xuất không có nghĩa là bỏ phiếu ủng hộ đề xuất đó.

            Đối số: 
 byte(sp.byte): byte được đề xuất bỏ phiếu.
            Tăng: 
 `Bạn không phải là thành viên` 
 """ 
 khẳng định self.data.members.contains(sp.sender), "Bạn không phải là thành viên" 
 self.data.proposals[bytes] = Sai 
 self.data.votes[bytes] = sp.set()

        @sp.entrypoint 
 def vote_proposal(self, byte): 
 """Bỏ phiếu cho một đề xuất.

            Không có phiếu chống hoặc thông qua. Nếu một người không đồng ý với một đề xuất, 
 có thể tránh bỏ phiếu. Cảnh báo: những đề xuất cũ chưa được bình chọn không bao giờ trở nên 
 thời.

            Đối số: 
 id(sp.byte): byte của đề xuất.
            Tăng: 
 `Bạn không phải là thành viên`, `Không tìm thấy đề xuất` 
 """ 
 khẳng định self.data.members.contains(sp.sender), "Bạn không phải là thành viên" 
 khẳng định self.data.proposals.contains(byte), "Không tìm thấy đề xuất" 
 self.data.votes[bytes].add(sp.sender)
            if sp.len(self.data.votes[bytes]) >= self.data.required_votes:
                self.data.proposals[byte] = Đúng 

 @sp.onchain_view() 
 def is_voted(self, id): 
 """Trả về một boolean cho biết đề xuất đã được bỏ phiếu hay chưa.

            Args: 
 id (sp.bytes): byte của đề xuất 
 Return: 
 (sp.bool): Đúng nếu đề xuất đã được biểu quyết, nếu không thì sai.
            """ 
 trả về self.data.proposals.get(id, error="Không tìm thấy đề xuất") 


 nếu "mẫu" không có trong __name__: 

 @sp.add_test(name="MultisigView basic script", is_default=True) 
 def basic_scenario(): 
 """Một kịch bản có một cuộc bỏ phiếu cho hợp đồng multisigView.

        Kiểm tra: 
 - Khởi tạo 
 - Nộp đề xuất 
 - Phiếu đề xuất 
 """ 
 sc = sp.test_scenario(main)
        sc.h1("Kịch bản cơ bản.")

        member1 = sp.test_account("member1")
        member2 = sp.test_account("member2")
        member3 = sp.test_account("member3")
        thành viên = sp.set([member1.address, thành viên2.address, member3.address])

        sc.h2("Gốc") 
 c1 = main.MultisigView(thành viên, 2) 
 sc += c1 

 sc.h2("submit_proposal") 
 c1.submit_proposal(sp.bytes("0x42")).run( sender=member1) 

 sc.h2("vote_proposal") 
 c1.vote_proposal(sp.bytes("0x42")).run(sender=member1) 
 c1.vote_proposal(sp.bytes("0x42")).run (sender=member2) 

 # Chúng tôi có thể kiểm tra xem đề xuất đã được xác thực chưa.
        sc.verify(c1.is_voted(sp.bytes("0x42")))

Mỗi hợp đồng cung cấp một cơ chế khác nhau để đạt được khả năng kiểm soát đa chữ ký, mang lại sự linh hoạt tùy thuộc vào nhu cầu cụ thể trong trường hợp sử dụng blockchain của bạn.

Hướng dẫn từng bước để dùng thử Hợp đồng Multisig trên SmartPy Online

Để thử các hợp đồng nhiều chữ ký mà chúng tôi đã viết trong SmartPy, bạn có thể làm theo các bước sau:

  1. Truy cập SmartPy IDE tại https://smartpy.io/ide.

  2. Dán mã hợp đồng vào trình chỉnh sửa. Bạn có thể thay thế mã hiện có.

  3. Để thực hiện hợp đồng, hãy nhấp vào nút “Chạy” nằm ở bảng trên cùng.

  4. Sau khi chạy hợp đồng, bạn có thể xem việc thực hiện kịch bản trong bảng “Đầu ra” ở bên phải. Tại đây, bạn có thể xem chi tiết về từng hành động, bao gồm đề xuất, phiếu bầu và phê duyệt.

  5. Để triển khai hợp đồng của bạn trên mạng Tezos, trước tiên bạn cần biên dịch nó. Nhấp vào nút "Biên dịch" trên bảng trên cùng.

  6. Sau khi biên dịch, bạn có thể triển khai hợp đồng trên mạng thử nghiệm bằng cách nhấp vào “Triển khai Hợp đồng Michelson”. Bạn sẽ cần cung cấp Khóa bí mật cho tài khoản Tezos với đủ tiền để thanh toán chi phí gas khi triển khai.

  7. Sau khi hợp đồng được triển khai, bạn sẽ được cung cấp địa chỉ của hợp đồng trên blockchain. Bạn có thể sử dụng địa chỉ này để tương tác với hợp đồng thông qua các giao dịch.

  8. Để gửi đề xuất hoặc bỏ phiếu trong hợp đồng, bạn có thể sử dụng các điểm nhập được xác định trong mã hợp đồng, chẳng hạn như submit_proposal hoặc vote_proposal. Chúng có thể được gọi trực tiếp từ các giao dịch mà bạn tạo.

Hãy nhớ rằng, mặc dù SmartPy IDE cho phép bạn kiểm tra hợp đồng của mình trên chuỗi khối mô phỏng, nhưng việc triển khai hợp đồng trên mạng Tezos thực tế sẽ phát sinh chi phí gas, khoản phí này phải được thanh toán bằng XTZ, tiền điện tử gốc của mạng Tezos.

Tuyên bố từ chối trách nhiệm
* Đầu tư tiền điện tử liên quan đến rủi ro đáng kể. Hãy tiến hành một cách thận trọng. Khóa học không nhằm mục đích tư vấn đầu tư.
* Khóa học được tạo bởi tác giả đã tham gia Gate Learn. Mọi ý kiến chia sẻ của tác giả không đại diện cho Gate Learn.
Danh mục
Bài học 1

Giới thiệu và Hợp đồng Multisig

Hợp đồng đa chữ ký (multisig), còn được gọi là hợp đồng "M-of-N", là một cơ chế quan trọng được sử dụng để tăng tính bảo mật và tính linh hoạt của các giao dịch trong môi trường blockchain. Các hợp đồng này thay đổi cách thức kiểm soát tài sản bằng cách yêu cầu sự chấp thuận của nhiều bên trước khi giao dịch có thể được thực hiện. Thuật ngữ "M-of-N" đề cập đến yêu cầu M trong tổng số N bên phải phê duyệt giao dịch để giao dịch đó có hiệu lực.

Lý thuyết về hợp đồng Multisig

Hợp đồng nhiều chữ ký cung cấp một phương tiện để tạo ra quyền kiểm soát chung đối với tài sản. Các trường hợp sử dụng điển hình liên quan đến dịch vụ ký quỹ, quản lý tài khoản công ty, đồng ký kết các thỏa thuận tài chính, v.v. Những hợp đồng này đặc biệt có lợi cho các tổ chức hoặc nhóm nơi việc ra quyết định tập thể là cần thiết.

Theo thiết kế, các hợp đồng đa chữ ký có khả năng chống giả mạo và ngăn chặn các điểm lỗi đơn lẻ. Ngay cả khi khóa của một bên bị xâm phạm, kẻ tấn công không thể thực hiện giao dịch mà không có sự chấp thuận của các bên khác. Điều này bổ sung thêm một lớp bảo mật.

Hợp đồng nhiều chữ ký có thể được coi là một dạng kỹ thuật số tương đương với hộp ký gửi an toàn yêu cầu nhiều chìa khóa để mở. Tổng số chìa khóa (N) và số chìa khóa tối thiểu cần thiết để mở hộp (M) được thỏa thuận khi tạo hợp đồng.

Hợp đồng Multisig có thể có nhiều cấu hình khác nhau tùy thuộc vào giá trị của M và N:

  • 1-of-N: Một bên duy nhất trong tổng số có thể phê duyệt giao dịch. Cấu hình này giống như một giao dịch thông thường không có multisig. Nó có thể được sử dụng khi có nhiều khóa để thuận tiện, nhưng bất kỳ khóa nào trong số đó đều có thể phê duyệt giao dịch.
  • N-of-N: Tất cả các bên phải phê duyệt giao dịch. Cấu hình này cung cấp mức độ bảo mật cao nhất nhưng có thể trở thành vấn đề nếu một bên mất chìa khóa hoặc từ chối phê duyệt giao dịch.
  • M-of-N (trong đó M < N): Một tập hợp con của tổng số các bên phải phê duyệt giao dịch. Cấu hình này thường được sử dụng trong thực tế vì nó cân bằng giữa tính bảo mật và tính linh hoạt.

Hợp đồng nhiều chữ ký trong Blockchain

Trong bối cảnh blockchain, hợp đồng nhiều chữ ký được sử dụng rộng rãi để tăng cường bảo mật giao dịch, hỗ trợ các cơ chế quản trị phức tạp hoặc duy trì quyền kiểm soát linh hoạt đối với tài sản blockchain. Dưới đây là một số ví dụ:

  • Ví: Ví Multisig được sử dụng để bảo đảm tài sản. Họ yêu cầu nhiều bên ký kết các giao dịch, do đó cung cấp bảo mật bổ sung chống lại hành vi trộm cắp, hack từ bên ngoài và các mối đe dọa nội bộ.
  • Các tổ chức tự trị phi tập trung (DAO): DAO thường sử dụng các hợp đồng nhiều chữ ký để thực thi các quy tắc quản trị của họ. Việc bỏ phiếu cho các đề xuất được thực hiện dưới dạng giao dịch nhiều chữ ký, với các thành viên của DAO đóng vai trò là người ký kết. Đề xuất chỉ được thực hiện nếu nhận được đủ phiếu bầu.
  • Hoạt động xuyên chuỗi: Trong các hoạt động xuyên chuỗi, các hợp đồng đa chữ ký có thể đóng vai trò là người giám sát tài sản. Khi tài sản được chuyển từ blockchain này sang blockchain khác, hợp đồng nhiều chữ ký trên chuỗi gốc có thể đảm bảo rằng tài sản được khóa an toàn cho đến khi hoạt động trên chuỗi khác được xác nhận.
    Mặc dù việc triển khai các hợp đồng nhiều chữ ký có thể khác nhau tùy theo từng blockchain, nhưng khái niệm cốt lõi vẫn giống nhau - nhu cầu nhiều bên phê duyệt một giao dịch trước khi nó được thực hiện. Lớp bảo mật bổ sung này làm cho các hợp đồng đa chữ ký trở thành một công cụ thiết yếu trong không gian blockchain và tiền điện tử.

Ví dụ về mã hóa: Viết và triển khai hợp đồng Multisig với SmartPy

Đối với các ví dụ về mã của chúng tôi, chúng tôi sẽ xem xét ba cách triển khai hợp đồng đa chữ ký khác nhau:

Hợp đồng Lambda

Nó khá linh hoạt và cho phép sử dụng nhiều loại. Nó yêu cầu nhiều chữ ký để thực thi các hàm lambda tùy ý.

Python 
 nhập smartpy dưới dạng sp 


 @sp.module 
 def main(): 
 Operation_lambda: type = sp.lambda_(sp.unit, sp.unit, with_Operations=True) 

 lớp MultisigLambda(sp.Contract): 
 """Nhiều thành viên bỏ phiếu để thực thi lambdas.

        Hợp đồng này có thể được bắt nguồn từ danh sách địa chỉ và số phiếu bầu bắt buộc là 
 . Bất kỳ thành viên nào cũng có thể gửi bao nhiêu lambda tùy thích và bỏ phiếu 
 cho các đề xuất đang hoạt động. Khi lambda đạt được số phiếu yêu cầu, mã của nó được gọi là 
 và các hoạt động đầu ra được thực thi. Điều này cho phép hợp đồng này 
 thực hiện bất kỳ điều gì mà hợp đồng có thể làm: chuyển mã thông báo, quản lý tài sản, 
 quản lý hợp đồng khác...

        Khi áp dụng lambda, tất cả các lambda đã gửi cho đến thời điểm hiện tại đều không được kích hoạt.
        Các thành viên vẫn có thể gửi lambdas mới.
        """ 

 def __init__(tự, thành viên, require_votes): 
 """Constructor 

 Args: 
 thành viên (sp.set of sp.address): những người có thể gửi và bỏ phiếu 
 cho lambda.
                bắt buộc_votes (sp.nat): số phiếu bầu cần thiết 
 """ 
 khẳng định bắt buộc_votes <= sp.len( 
 thành viên 
 ), "required_votes phải là <= len(thành viên)" 
 self.data.lambdas = sp.cast (
                sp.big_map(), sp.big_map[sp.nat, Operation_lambda] 
 ) 
 self.data.votes = sp.cast(
                sp.big_map(), sp.big_map[sp.nat, sp.set[sp.address]] 
 ) 
 self.data.nextId = 0 
 self.data.inactiveBefore = 0 
 self.data.members = sp.cast(thành viên, sp.set[sp.address])
            self.data.required_votes = sp.cast(required_votes, sp.nat) 

 @sp.entrypoint 
 def submit_lambda(self, lambda_): 
 """Gửi lambda mới để bỏ phiếu.

            Việc gửi một đề xuất không có nghĩa là bỏ phiếu ủng hộ đề xuất đó.

            Args: 
 lambda_(sp.lambda có phép toán): lambda đề xuất bỏ phiếu.
            Tăng: 
 `Bạn không phải là thành viên` 
 """ 
 khẳng định self.data.members.contains(sp.sender), "Bạn không phải là thành viên" 
 self.data.lambdas[self.data.nextId] = lambda_ 
 self.data.votes[self.data.nextId] = sp.set()
            self.data.nextId += 1 

 @sp.entrypoint 
 def vote_lambda(self, id): 
 """Bỏ phiếu cho lambda.

            Đối số: 
 id(sp.nat): id của lambda để bỏ phiếu.
            Tăng: 
 `Bạn không phải là thành viên`, `Lambda không hoạt động`, `Không tìm thấy Lambda` 

 Không có phiếu chống hoặc thông qua. Nếu ai đó không đồng ý với lambda 
 họ có thể tránh bỏ phiếu.
            """ 
 khẳng định self.data.members.contains(sp.sender), "Bạn không phải là thành viên" 
 khẳng định id >= self.data.inactiveBefore, "Lambda không hoạt động" 
 khẳng định self.data.lambdas.contains(id), "Không tìm thấy Lambda" 
 self.data.votes[id].add(sp.sender)
            if sp.len(self.data.votes[id]) >= self.data.required_votes:
                self.data.lambdas[id]()
                self.data.inactiveTrước = self.data.nextId 

 @sp.onchain_view() 
 def get_lambda(self, id): 
 """Trả về lambda tương ứng.

            Args: 
 id (sp.nat): id của lambda cần lấy.

            Trả về: 
 cặp lambda và boolean hiển thị xem lambda có hoạt động hay không.
            """ 
 trả về (self.data.lambdas[id], id >= self.data.inactiveBefore)


# if "templates" not in __name__: 


 @sp.module 
 def test(): 
 class Administrated(sp.Contract): 
 def __init__(self, admin): 
 self.data.admin = admin 
 self.data.value = sp.int(0)

        @sp.entrypoint 
 def set_value(self, value): 
 khẳng định sp.sender == self.data.admin 
 self.data.value = value 


 @sp.add_test(name="Kịch bản cơ bản MultisigLambda", is_default=True ) 
 def basic_scenario(): 
 """Sử dụng multisigLambda làm quản trị viên của một hợp đồng mẫu.

    Kiểm tra: 
 - Nguồn gốc 
 - Đệ trình Lambda 
 - Phiếu bầu Lambda 
 """ 
 sc = sp.test_scenario([main, test]) 
 sc.h1("Kịch bản cơ bản.")

    member1 = sp.test_account("member1")
    member2 = sp.test_account("member2")
    member3 = sp.test_account("member3")
    thành viên = sp.set([member1.address, thành viên2.address, member3.address])

    sc.h2("MultisigLambda: nguồn gốc") 
 c1 = main.MultisigLambda(thành viên, 2) 
 sc += c1 

 sc.h2("Được quản lý: nguồn gốc") 
 c2 = test.Administrated(c1.address)
    sc += c2 

 sc.h2("MultisigLambda: submit_lambda") 

 def set_42(params): 
 administrated = sp.contract(sp.TINt, c2.address, entrypoint="set_value") 
 sp.transfer(sp. int(42), sp.tez(0), administrated.open_some())

    lambda_ = sp.build_lambda(set_42, with_Operation=True) 
 c1.submit_lambda(lambda_).run(sender=member1) 

 sc.h2("MultisigLambda: vote_lambda") 
 c1.vote_lambda(0).run(sender=member1)
    c1.vote_lambda(0).run(sender=member2)

    # Chúng tôi có thể kiểm tra xem hợp đồng được quản lý đã nhận được chuyển khoản hay chưa.
    sc.verify(c2.data.value == 42)

Hợp đồng MultisigAction

Nó giới thiệu khái niệm bỏ phiếu cho các đề xuất. Trong hợp đồng này, người ký có thể bỏ phiếu cho một số hành động nhất định sẽ được thực hiện và nếu đạt đến số đại biểu cần thiết, các hành động được đề xuất sẽ được thực hiện.

Python 
 import smartpy as sp 


 @sp.module 
 def main(): 
 # Đặc tả loại hành động quản trị nội bộ 
 InternalAdminAction: type = sp.variant(
        addSigners=sp.list[sp.address],
        ChangeQuorum=sp.nat,
        RemoveSigners=sp.list[sp.address],
    ) 

 lớp MultisigAction(sp.Contract): 
 """Một hợp đồng có thể được nhiều người ký sử dụng để quản lý 
 hợp đồng khác. Các hợp đồng được quản trị triển khai 
 giao diện giúp có thể trình bày rõ ràng quy trình quản trị cho người dùng không phải là chuyên gia.

        Người ký bỏ phiếu cho các đề xuất. Đề xuất là danh sách mục tiêu với danh sách 
 hành động. Một hành động là một byte đơn giản nhưng nó được dự định là giá trị gói bằng 
 biến thể. Mẫu đơn giản này giúp bạn có thể xây dựng giao diện UX 
 hiển thị nội dung của một đề xuất hoặc xây dựng một đề xuất.
        """ 

 def __init__(tự, số đại biểu, người ký): 
 self.data.inactiveBefore = 0 
 self.data.nextId = 0 
 self.data.proposals = sp.cast(
                sp.big_map(), 
 sp.big_map[ 
 sp.nat, 
 sp.list[sp.record(target=sp.address, actions=sp.list[sp.bytes])],
                ], 
 ) 
 self.data.quorum = sp.cast(quorum, sp.nat) 
 self.data.signers = sp.cast(signers, sp.set[sp.address])
            self.data.votes = sp.cast(
                sp.big_map(), sp.big_map[sp.nat, sp.set[sp.address]] 
 ) 

 @sp.entrypoint 
 def send_proposal(self, đề xuất): 
 """Chỉ dành cho người ký. Gửi đề xuất để bỏ phiếu.

            Args: 
 đề xuất (sp.list of sp.record of target address and action): Danh sách\ 
 mục tiêu và các hành động quản trị liên quan.
            """ 
 khẳng định self.data.signers.contains(sp.sender), "Chỉ người ký mới có thể đề xuất" 
 self.data.proposals[self.data.nextId] = đề xuất 
 self.data.votes[self.data.nextId] = sp.set()
            self.data.nextId += 1 

 @sp.entrypoint 
 def vote(self, pId): 
 """Bỏ phiếu cho một hoặc nhiều đề xuất 

 Args: 
 pId (sp.nat): Id của đề xuất.
            """ 
 khẳng định self.data.signers.contains(sp.sender), "Chỉ người ký mới có thể bỏ phiếu" 
 khẳng định self.data.votes.contains(pId), "Đề xuất không xác định" 
 khẳng định pId >= self.data.inactiveBefore, "Đề xuất không hoạt động" 
 self.data.votes[pId].add(sp.sender)

            nếu sp.len(self.data.votes.get(pId, mặc định=sp.set())) >= self.data.quorum:
                self._onApproved(pId)

        @sp.private(with_storage="read-write", with_Operations=True) 
 def _onApproved(self, pId): 
 """Hàm nội tuyến. Logic được áp dụng khi đề xuất đã được phê duyệt."""
            đề xuất = self.data.proposals.get(pId, default=[]) 
 cho p_item trong đề xuất: 
 hợp đồng = sp.contract(sp.list[sp.bytes], p_item.target)
                sp.transfer( 
 p_item.actions,
                    sp.tez(0), 
 hợp đồng.unwrap_some(error="InvalidTarget"),
                ) 
 # Vô hiệu hóa tất cả các đề xuất đã được gửi.
            self.data.inactiveTrước = self.data.nextId 

 @sp.entrypoint 
 def administrate(self, actions): 
 """Chỉ tự gọi. Quản lý hợp đồng này.

            Điểm vào này phải được gọi thông qua hệ thống đề xuất.

            Args: 
 hành động (sp.list of sp.bytes): Danh sách biến thể đóng gói của \ 
 `InternalAdminAction` (`addSigners`, `changeQuorum`, `removeSigners`).
            """ 
 khẳng định ( 
 sp.sender == sp.self_address() 
 ), "Điểm vào này phải được gọi thông qua hệ thống đề xuất." 

 cho pack_actions trong hành động: 
 action = sp.unpack(packed_actions, InternalAdminAction).unwrap_some( 
 error="Định dạng hành động xấu" 
 ) 
 với sp.match(action): 
 với sp.case.changeQuorum với số đại biểu: 
 self.data.quorum = số đại biểu 
 với sp.case.addSigners được thêm vào: 
 cho người ký đã thêm: 
 self.data.signers.add(signer)
                    với sp.case.removeSigners như đã xóa: 
 cho địa chỉ đã xóa: 
 self.data.signers.remove(address)
                # Đảm bảo rằng hợp đồng không bao giờ yêu cầu số đại biểu nhiều hơn tổng số người ký.
                khẳng định self.data.quorum <= sp.len( 
 self.data.signers 
 ), "Số đại biểu nhiều hơn người ký."


nếu "mẫu" không có trong __name__: 

 @sp.add_test(name="Basic script", is_default=True) 
 def test(): 
 signer1 = sp.test_account("signer1")
        người ký2 = sp.test_account("signer2")
        người ký3 = sp.test_account("signer3")

        s = sp.test_scenario(chính)
        s.h1("Kịch bản cơ bản") 

 s.h2("Nguồn gốc") 
 c1 = main.MultisigAction( 
 quorum=2, 
 signers=sp.set([signer1.address, signer2.address]), 
 ) 
 s += c1 

 s.h2("Đề xuất thêm người ký mới") 
 target = sp.to_address(
            sp.contract(sp.TList(sp.TBytes), c1.address, "administrate").open_some() 
 ) 
 action = sp.pack(
            sp.set_type_expr( 
 sp.variant("addSigners", [signer3.address]), main.InternalAdminAction 
 ) 
 ) 
 c1.send_proposal([sp.record(target=target, actions=[action])]).run( 
 sender=signer1 
 ) 

 s.h2("Người ký 1 phiếu cho đề xuất") 
 c1.vote(0).run(sender=signer1)
        s.h2("Người ký 2 phiếu cho đề xuất") 
 c1.vote(0).run(sender=signer2)

        s.verify(c1.data.signers.contains(signer3.address))

Hợp đồng MultisigView

Nó cũng sử dụng một cơ chế bỏ phiếu. Hợp đồng này cho phép các thành viên gửi và bỏ phiếu cho các byte tùy ý. Khi một đề xuất đạt được số phiếu bầu cần thiết, trạng thái của nó có thể được xác nhận thông qua chế độ xem.

Python 
 import smartpy as sp 


 @sp.module 
 def main(): 
 class MultisigView(sp.Contract): 
 """Nhiều thành viên bỏ phiếu cho các byte tùy ý.

        Hợp đồng này có thể được bắt nguồn từ danh sách địa chỉ và số phiếu bầu bắt buộc là 
 . Bất kỳ thành viên nào cũng có thể gửi bao nhiêu byte tùy thích và bỏ phiếu 
 cho các đề xuất đang hoạt động.

        Bất kỳ byte nào đạt được số phiếu yêu cầu đều có thể được xác nhận thông qua chế độ xem.
        """ 

 def __init__(tự, thành viên, bắt buộc_vote): 
 """Constructor 

 Args: 
 thành viên (sp.set of sp.address): những người có thể gửi và bỏ phiếu cho 
 lambda.
                bắt buộc_votes (sp.nat): số phiếu bầu cần thiết 
 """ 
 khẳng định bắt buộc_votes <= sp.len( 
 thành viên 
 ), "required_votes phải là <= len(thành viên)" 
 self.data.proposals = sp.cast (sp.big_map(), sp.big_map[sp.byte, sp.bool]) 
 self.data.votes = sp.cast(
                sp.big_map(), sp.big_map[sp.byte, sp.set[sp.address]] 
 ) 
 self.data.members = sp.cast(thành viên, sp.set[sp.address])
            self.data.required_votes = sp.cast(required_votes, sp.nat) 

 @sp.entrypoint 
 def submit_proposal(self, bytes): 
 """Gửi đề xuất mới để bỏ phiếu.

            Việc gửi một đề xuất không có nghĩa là bỏ phiếu ủng hộ đề xuất đó.

            Đối số: 
 byte(sp.byte): byte được đề xuất bỏ phiếu.
            Tăng: 
 `Bạn không phải là thành viên` 
 """ 
 khẳng định self.data.members.contains(sp.sender), "Bạn không phải là thành viên" 
 self.data.proposals[bytes] = Sai 
 self.data.votes[bytes] = sp.set()

        @sp.entrypoint 
 def vote_proposal(self, byte): 
 """Bỏ phiếu cho một đề xuất.

            Không có phiếu chống hoặc thông qua. Nếu một người không đồng ý với một đề xuất, 
 có thể tránh bỏ phiếu. Cảnh báo: những đề xuất cũ chưa được bình chọn không bao giờ trở nên 
 thời.

            Đối số: 
 id(sp.byte): byte của đề xuất.
            Tăng: 
 `Bạn không phải là thành viên`, `Không tìm thấy đề xuất` 
 """ 
 khẳng định self.data.members.contains(sp.sender), "Bạn không phải là thành viên" 
 khẳng định self.data.proposals.contains(byte), "Không tìm thấy đề xuất" 
 self.data.votes[bytes].add(sp.sender)
            if sp.len(self.data.votes[bytes]) >= self.data.required_votes:
                self.data.proposals[byte] = Đúng 

 @sp.onchain_view() 
 def is_voted(self, id): 
 """Trả về một boolean cho biết đề xuất đã được bỏ phiếu hay chưa.

            Args: 
 id (sp.bytes): byte của đề xuất 
 Return: 
 (sp.bool): Đúng nếu đề xuất đã được biểu quyết, nếu không thì sai.
            """ 
 trả về self.data.proposals.get(id, error="Không tìm thấy đề xuất") 


 nếu "mẫu" không có trong __name__: 

 @sp.add_test(name="MultisigView basic script", is_default=True) 
 def basic_scenario(): 
 """Một kịch bản có một cuộc bỏ phiếu cho hợp đồng multisigView.

        Kiểm tra: 
 - Khởi tạo 
 - Nộp đề xuất 
 - Phiếu đề xuất 
 """ 
 sc = sp.test_scenario(main)
        sc.h1("Kịch bản cơ bản.")

        member1 = sp.test_account("member1")
        member2 = sp.test_account("member2")
        member3 = sp.test_account("member3")
        thành viên = sp.set([member1.address, thành viên2.address, member3.address])

        sc.h2("Gốc") 
 c1 = main.MultisigView(thành viên, 2) 
 sc += c1 

 sc.h2("submit_proposal") 
 c1.submit_proposal(sp.bytes("0x42")).run( sender=member1) 

 sc.h2("vote_proposal") 
 c1.vote_proposal(sp.bytes("0x42")).run(sender=member1) 
 c1.vote_proposal(sp.bytes("0x42")).run (sender=member2) 

 # Chúng tôi có thể kiểm tra xem đề xuất đã được xác thực chưa.
        sc.verify(c1.is_voted(sp.bytes("0x42")))

Mỗi hợp đồng cung cấp một cơ chế khác nhau để đạt được khả năng kiểm soát đa chữ ký, mang lại sự linh hoạt tùy thuộc vào nhu cầu cụ thể trong trường hợp sử dụng blockchain của bạn.

Hướng dẫn từng bước để dùng thử Hợp đồng Multisig trên SmartPy Online

Để thử các hợp đồng nhiều chữ ký mà chúng tôi đã viết trong SmartPy, bạn có thể làm theo các bước sau:

  1. Truy cập SmartPy IDE tại https://smartpy.io/ide.

  2. Dán mã hợp đồng vào trình chỉnh sửa. Bạn có thể thay thế mã hiện có.

  3. Để thực hiện hợp đồng, hãy nhấp vào nút “Chạy” nằm ở bảng trên cùng.

  4. Sau khi chạy hợp đồng, bạn có thể xem việc thực hiện kịch bản trong bảng “Đầu ra” ở bên phải. Tại đây, bạn có thể xem chi tiết về từng hành động, bao gồm đề xuất, phiếu bầu và phê duyệt.

  5. Để triển khai hợp đồng của bạn trên mạng Tezos, trước tiên bạn cần biên dịch nó. Nhấp vào nút "Biên dịch" trên bảng trên cùng.

  6. Sau khi biên dịch, bạn có thể triển khai hợp đồng trên mạng thử nghiệm bằng cách nhấp vào “Triển khai Hợp đồng Michelson”. Bạn sẽ cần cung cấp Khóa bí mật cho tài khoản Tezos với đủ tiền để thanh toán chi phí gas khi triển khai.

  7. Sau khi hợp đồng được triển khai, bạn sẽ được cung cấp địa chỉ của hợp đồng trên blockchain. Bạn có thể sử dụng địa chỉ này để tương tác với hợp đồng thông qua các giao dịch.

  8. Để gửi đề xuất hoặc bỏ phiếu trong hợp đồng, bạn có thể sử dụng các điểm nhập được xác định trong mã hợp đồng, chẳng hạn như submit_proposal hoặc vote_proposal. Chúng có thể được gọi trực tiếp từ các giao dịch mà bạn tạo.

Hãy nhớ rằng, mặc dù SmartPy IDE cho phép bạn kiểm tra hợp đồng của mình trên chuỗi khối mô phỏng, nhưng việc triển khai hợp đồng trên mạng Tezos thực tế sẽ phát sinh chi phí gas, khoản phí này phải được thanh toán bằng XTZ, tiền điện tử gốc của mạng Tezos.

Tuyên bố từ chối trách nhiệm
* Đầu tư tiền điện tử liên quan đến rủi ro đáng kể. Hãy tiến hành một cách thận trọng. Khóa học không nhằm mục đích tư vấn đầu tư.
* Khóa học được tạo bởi tác giả đã tham gia Gate Learn. Mọi ý kiến chia sẻ của tác giả không đại diện cho Gate Learn.