สัญญา Multisig มอบวิธีการสร้างการควบคุมสินทรัพย์ร่วมกัน กรณีการใช้งานทั่วไปเกี่ยวข้องกับบริการเอสโครว์ การจัดการบัญชีบริษัท การลงนามข้อตกลงทางการเงินร่วมกัน และอื่นๆ สัญญาเหล่านี้มีประโยชน์อย่างยิ่งสำหรับองค์กรหรือกลุ่มที่จำเป็นต้องมีการตัดสินใจร่วมกัน
จากการออกแบบ สัญญา Multisig ทนต่อการงัดแงะและป้องกันความล้มเหลวจุดเดียว แม้ว่าคีย์ของฝ่ายหนึ่งจะถูกบุกรุก ผู้โจมตีก็ไม่สามารถดำเนินธุรกรรมโดยไม่ได้รับอนุมัติจากอีกฝ่าย นี่เป็นการเพิ่มการรักษาความปลอดภัยอีกชั้นหนึ่ง
สัญญา Multisig ถือได้ว่าเป็นดิจิทัลที่เทียบเท่ากับตู้เซฟที่ต้องใช้กุญแจหลายดอกในการเปิด จำนวนกุญแจทั้งหมด (N) และจำนวนกุญแจขั้นต่ำที่ต้องใช้ในการเปิดกล่อง (M) ได้รับการตกลงกันเมื่อมีการสร้างสัญญา
สัญญา Multisig สามารถมีการกำหนดค่าที่แตกต่างกันได้มากมาย ขึ้นอยู่กับค่าของ M และ N:
ในบริบทของบล็อกเชน สัญญา Multisig ถูกนำมาใช้อย่างกว้างขวางเพื่อเพิ่มความปลอดภัยของธุรกรรม สนับสนุนกลไกการกำกับดูแลที่ซับซ้อน หรือรักษาการควบคุมสินทรัพย์บล็อกเชนที่ยืดหยุ่น นี่คือตัวอย่างบางส่วน:
สำหรับตัวอย่างโค้ดของเรา เราจะดูการใช้งานสัญญาแบบหลายลายเซ็นที่แตกต่างกันสามแบบ:
มันค่อนข้างหลากหลายและสามารถใช้งานได้หลากหลาย ต้องใช้ลายเซ็นหลายรายการเพื่อเรียกใช้ฟังก์ชันแลมบ์ดาตามอำเภอใจ
Python
นำเข้า smartpy เป็น sp
@sp.module
def main():
operation_lambda: type = sp.lambda_(sp.unit, sp.unit, with_operations=True)
คลาส MultisigLambda(sp.Contract):
"""สมาชิกหลายคนโหวตให้ดำเนินการ lambdas
สัญญานี้สามารถจัดทำขึ้นโดยมีรายชื่อที่อยู่และการลงคะแนนเสียงที่ต้องการจำนวน
เสียง สมาชิกคนใดก็ตามสามารถส่ง lambdas ได้มากเท่าที่ต้องการและโหวต
สำหรับข้อเสนอที่ยังดำเนินการอยู่ เมื่อแลมบ์ดาถึงคะแนนที่ต้องการ รหัสของมันจะถูกเรียก
และการดำเนินการเอาต์พุตจะถูกดำเนินการ สิ่งนี้ทำให้สัญญานี้สามารถ
ทำทุกสิ่งที่สัญญาทำได้: การโอนโทเค็น การจัดการสินทรัพย์
การบริหารสัญญาอื่น...
เมื่อใช้ lambda แลมบ์ดาที่ส่งมาทั้งหมดจนถึงขณะนี้จะถูกปิดใช้งาน
สมาชิกยังสามารถส่งแลมบ์ดาใหม่ได้
"""
def __init__(ตนเอง, สมาชิก, required_votes):
"""Constructor
Args: สมาชิก
คน (sp.set of sp.address): ผู้ที่สามารถส่งและโหวต
สำหรับ lambda
required_votes (sp.nat): จำนวนโหวตที่ต้องการ
"""
ยืนยัน required_votes <= sp.len(
สมาชิก
), "required_votes ต้องเป็น <= len(members)"
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 (สมาชิก sp.set[sp.address])
self.data.required_votes = sp.cast(required_votes, sp.nat)
@sp.entrypoint
def send_lambda(self, lambda_):
"""ส่ง lambda ใหม่เพื่อโหวต
การส่งข้อเสนอไม่ได้หมายความถึงการลงคะแนนเสียงเห็นชอบด้วย
อาร์กิวเมนต์:
lambda_(sp.lambda พร้อมการดำเนินการ): lambda เสนอให้ลงคะแนน
เพิ่ม:
`คุณไม่ใช่สมาชิก`
"""
ยืนยัน self.data.members.contains(sp.sender) "คุณไม่ได้เป็นสมาชิก"
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):
"""โหวตให้แลมบ์ดา
อาร์กิวเมนต์:
id(sp.nat): รหัสแลมบ์ดาที่จะลงคะแนนให้
เพิ่ม:
`คุณไม่ใช่สมาชิก`, `แลมบ์ดาไม่ได้ใช้งาน`, `ไม่พบแลมบ์ดา'
ไม่มีการลงคะแนนคัดค้านหรือผ่าน หากมีใครไม่เห็นด้วยกับ lambda
ก็สามารถหลีกเลี่ยงการลงคะแนนได้
"""
ยืนยัน self.data.members.contains (sp.sender) "คุณไม่ใช่สมาชิก"
ยืนยัน id >= self.data.inactiveBefore, "แลมบ์ดาไม่ได้ใช้งาน"
ยืนยัน self.data.lambdas.contains(id) "ไม่พบแลมบ์ดา"
self.data.votes[id].add(sp.sender)
ถ้า sp.len (self.data.votes [id]) >= self.data.required_votes:
self.data.lambdas[id]()
self.data.inactiveBefore = self.data.nextId
@sp.onchain_view()
def get_lambda(self, id):
"""ส่งคืนแลมบ์ดาที่เกี่ยวข้อง
Args:
id (sp.nat): id ของแลมบ์ดาที่จะได้รับ
กลับ: แลมบ์ดา
คู่และบูลีนแสดงว่าแลมบ์ดาทำงานอยู่หรือไม่
"""
คืน (self.data.lambdas[id], id >= self.data.inactiveBefore)
# ถ้า "เทมเพลต" ไม่ได้อยู่ใน __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 (ตนเอง, ค่า):
ยืนยัน sp.sender == self.data.admin
self.data.value = ค่า
@sp.add_test (name = "สถานการณ์พื้นฐาน MultisigLambda", is_default = True )
def basic_scenario():
"""ใช้ multisigLambda เป็นผู้ดูแลระบบของสัญญาตัวอย่าง
การทดสอบ:
- กำเนิด
- การส่งแลมบ์ดา
- โหวตแลมบ์ดา
"""
sc = sp.test_scenario([main, ทดสอบ])
sc.h1("สถานการณ์พื้นฐาน")
member1 = sp.test_account("member1")
member2 = sp.test_account("member2")
member3 = sp.test_account("member3")
สมาชิก = sp.set ([member1.address, สมาชิก2.ที่อยู่ สมาชิก3.ที่อยู่])
sc.h2("MultisigLambda: origination")
c1 = main.MultisigLambda (สมาชิก 2)
sc += c1
sc.h2("Administrated: origination")
c2 = test.Administrated(c1.address)
sc += c2
sc.h2("MultisigLambda: send_lambda")
def set_42(params):
ผู้ดูแลระบบ = sp.contract(sp.TInt, c2.address, entrypoint="set_value")
sp.transfer(sp. อินท์(42) sp.tez(0), ผู้ดูแลระบบ.open_some())
แลมบ์ดา_ = sp.build_lambda(set_42, with_operations=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)
# เราสามารถตรวจสอบได้ว่าสัญญาที่บริหารได้รับการโอนแล้ว
sc.verify(c2.data.value == 42)
โดยจะแนะนำแนวคิดของการลงคะแนนเสียงสำหรับข้อเสนอ ในสัญญานี้ ผู้ลงนามสามารถลงคะแนนสำหรับการดำเนินการบางอย่างที่จะดำเนินการ และหากถึงองค์ประชุม การดำเนินการที่เสนอไว้ก็จะดำเนินการ
Python
นำเข้า smartpy เป็น sp
@sp.module
def main():
# ข้อกำหนดประเภทการดำเนินการดูแลระบบภายใน
InternalAdminAction: type = sp.variant(
addSigners=sp.list[sp.address],
changeQuorum=sp.nat,
RemoveSigners=sp.list[sp.address],
)
คลาส MultisigAction(sp.Contract):
"""สัญญาที่ผู้ลงนามหลายคนสามารถใช้ได้เพื่อจัดการสัญญาอีก
สัญญา สัญญาที่ได้รับการดูแลใช้อินเทอร์เฟซที่ทำให้สามารถ
กระบวนการดูแลระบบแก่ผู้ใช้ที่ไม่ใช่ผู้เชี่ยวชาญได้
ผู้ลงนามลงคะแนนเสียงสำหรับข้อเสนอ ข้อเสนอคือรายการเป้าหมายที่มีรายการการดำเนินการ
รายการ การดำเนินการเป็นไบต์ธรรมดา แต่ตั้งใจให้เป็นค่าแพ็ก
ตัวแปร รูปแบบที่เรียบง่ายนี้ทำให้สามารถสร้างอินเทอร์เฟซ UX
ที่แสดงเนื้อหาของข้อเสนอหรือสร้างข้อเสนอได้
"""
def __init__(ตนเอง, โควรัม, ผู้ลงนาม):
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, การกระทำ=sp.list[sp.bytes])],
],
)
self.data.quorum = sp.cast(องค์ประชุม, sp.nat)
self.data.signers = sp.cast (ผู้ลงนาม 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, ข้อเสนอ):
"""ผู้ลงนามเท่านั้น ส่งข้อเสนอเพื่อลงคะแนนเสียง
Args:
ข้อเสนอ (sp.list of sp.record ของที่อยู่เป้าหมายและการดำเนินการ): รายการ\
ของเป้าหมายและการดำเนินการด้านการบริหารที่เกี่ยวข้อง
"""
ยืนยัน self.data.signers.contains (sp.sender) "มีเพียงผู้ลงนามเท่านั้นที่สามารถเสนอได้"
self.data.proposals[self.data.nextId] = ข้อเสนอ
self.data.votes[self.data.nextId] = sp.set()
self.data.nextId += 1
@sp.entrypoint
def vote(self, pId):
"""Vote for one or more ข้อเสนอที่
Args:
pId (sp.nat): รหัสข้อเสนอ
"""
ยืนยัน self.data.signers.contains (sp.sender) "มีเพียงผู้ลงนามเท่านั้นที่สามารถลงคะแนนได้"
ยืนยัน self.data.votes.contains(pId) "ไม่ทราบข้อเสนอ"
ยืนยัน pId >= self.data.inactiveBefore "ข้อเสนอไม่ทำงาน"
self.data.votes[pId].add(sp.sender)
ถ้า sp.len(self.data.votes.get(pId, ค่าเริ่มต้น=sp.set())) >= self.data.quorum:
self._onApproved(pId)
@ sp.private(with_storage="read-write", with_operations=True)
def _onApproved(self, pId):
"""ฟังก์ชันอินไลน์ ตรรกะที่ใช้เมื่อข้อเสนอได้รับการอนุมัติแล้ว"""
ข้อเสนอ = self.data.proposals.get (pId, default=[])
สำหรับ p_item ในข้อเสนอ:
สัญญา = sp.contract(sp.list[sp.bytes], p_item.target)
sp.transfer (
p_item.actions,
sp.tez(0),
สัญญา unwrap_some (ข้อผิดพลาด = "InvalidTarget"),
)
# ยกเลิกข้อเสนอทั้งหมดที่ถูกส่งไปแล้ว
self.data.inactiveBefore = self.data.nextId
@sp.entrypoint
def ผู้ดูแลระบบ (ตนเอง, การดำเนินการ):
"""โทรด้วยตนเองเท่านั้น จัดการสัญญานี้
จุดเริ่มต้นนี้จะต้องถูกเรียกผ่านระบบข้อเสนอ
Args:
การดำเนินการ (sp.list ของ sp.bytes): รายการตัวแปรที่อัดแน่นของ \
`InternalAdminAction` (`addSigners`, `changeQuorum`, `removeSigners`)
"""
assert (
sp.sender == sp.self_address()
), "จุดเริ่มต้นนี้ต้องถูกเรียกผ่านระบบข้อเสนอ"
สำหรับ pack_actions ในการดำเนินการ:
action = sp.unpack(packed_actions, InternalAdminAction).unwrap_some(
error="รูปแบบการกระทำที่ไม่ถูกต้อง"
)
ด้วย sp.match(action):
พร้อม sp.case.changeQuorum เป็นองค์ประชุม:
self.data.quorum = องค์ประชุม
พร้อมด้วย sp.case.addSigners ตามที่เพิ่ม:
สำหรับผู้ลงนามที่เพิ่ม:
self.data.signers.add(ผู้ลงนาม)
ด้วย sp.case.removeSigners เมื่อถูกลบออก:
สำหรับที่อยู่ในการลบออก:
self.data.signers.remove (ที่อยู่)
# ตรวจสอบให้แน่ใจว่าสัญญาไม่จำเป็นต้องมีองค์ประชุมมากกว่าจำนวนผู้ลงนามทั้งหมด
assert self.data.quorum <= sp.len(
self.data.signers
), "มีองค์ประชุมมากกว่าผู้ลงนาม"
หาก "เทมเพลต" ไม่ได้อยู่ใน __name__:
@sp.add_test(name="Basic allowance", is_default=True)
def test():
signer1 = sp.test_account("signer1")
ผู้ลงนาม2 = sp.test_account("ผู้ลงนาม2")
ผู้ลงนาม3 = sp.test_account("ผู้ลงนาม3")
s = sp.test_scenario (หลัก)
s.h1("สถานการณ์พื้นฐาน")
s.h2("จุดเริ่มต้น")
c1 = main.MultisigAction(
quorum=2,
signers=sp.set([signer1.address, signer2.address]),
)
s += c1
s.h2("ข้อเสนอสำหรับการเพิ่มผู้ลงนามใหม่")
target = sp.to_address(
sp.contract(sp.TList(sp.TBytes), c1.address, "administrate").open_some()
)
การกระทำ = sp.pack(
sp.set_type_expr(
sp.variant("addSigners", [signer3.address]), main.InternalAdminAction
)
)
c1.send_proposal([sp.record(target=target, actions=[action])]).run(
ผู้ส่ง=signer1
)
s.h2("ผู้ลงนาม 1 โหวตสำหรับข้อเสนอ")
c1.vote(0).run(sender=signer1)
s.h2("ผู้ลงนาม 2 โหวตสำหรับข้อเสนอ")
c1.vote(0).run(sender=signer2)
s.verify(c1.data.signers.contains(signer3.address))
นอกจากนี้ยังใช้กลไกการลงคะแนนเสียง สัญญานี้อนุญาตให้สมาชิกส่งและลงคะแนนให้กับไบต์ตามอำเภอใจ เมื่อข้อเสนอได้รับคะแนนโหวตตามจำนวนที่กำหนด สถานะจะได้รับการยืนยันผ่านการดู
Python
นำเข้า smartpy เป็น sp
@sp.module
def main():
คลาส MultisigView(sp.Contract):
"""สมาชิกหลายคนโหวตให้กับไบต์ที่กำหนดเอง
สัญญานี้สามารถจัดทำขึ้นโดยมีรายชื่อที่อยู่และการลงคะแนนเสียงที่ต้องการจำนวน
เสียง สมาชิกคนใดก็ตามสามารถส่งไบต์ได้มากเท่าที่ต้องการและโหวต
สำหรับข้อเสนอที่ใช้งานอยู่
ไบต์ใดๆ ที่ได้รับการโหวตตามที่กำหนดสามารถยืนยันได้ผ่านมุมมอง
"""
def __init__(ตนเอง, สมาชิก, required_votes):
"""Constructor
Args: สมาชิก
คน (sp.set of sp.address): ผู้คนที่สามารถส่งและโหวตให้
lambda
required_votes (sp.nat): จำนวนโหวตที่ต้องการ
"""
ยืนยัน required_votes <= sp.len(
สมาชิก
), "required_votes ต้องเป็น <= len(members)"
self.data.proposals = sp.cast (sp.big_map(), sp.big_map[sp.ไบต์, sp.bool])
self.data.votes = sp.cast(
sp.big_map(), sp.big_map[sp.bytes, sp.set[sp.address]]
)
self.data.members = sp.cast(สมาชิก, sp.set[sp.address])
self.data.required_votes = sp.cast(required_votes, sp.nat)
@sp.entrypoint
def send_proposal(self, bytes):
"""ส่งข้อเสนอใหม่เพื่อโหวต
การส่งข้อเสนอไม่ได้หมายความถึงการลงคะแนนเสียงเห็นชอบ
อาร์กิวเมนต์:
ไบต์ (sp.bytes): ไบต์ที่เสนอให้ลงคะแนนเสียง
เพิ่ม:
`คุณไม่ใช่สมาชิก`
"""
ยืนยัน self.data.members.contains(sp.sender) "คุณไม่ได้เป็นสมาชิก"
self.data.proposals[bytes] = เท็จ
self.data.votes[ไบต์] = sp.set()
@ sp.entrypoint
def vote_proposal(self, bytes):
"""โหวตข้อเสนอ
ไม่มีการลงคะแนนเสียงคัดค้านหรือผ่าน หากใครไม่เห็นด้วยกับข้อเสนอ
สามารถหลีกเลี่ยงการลงคะแนนเสียงได้ คำเตือน: ข้อเสนอที่เก่าที่ไม่ได้ลงคะแนนจะไม่กลายเป็น
ล้าสมัย
อาร์กิวเมนต์:
id (sp.bytes): จำนวนไบต์ของข้อเสนอ
เพิ่ม:
`คุณไม่ใช่สมาชิก`, `ไม่พบข้อเสนอ`
"""
ยืนยัน self.data.members.contains(sp.sender) "คุณไม่ใช่สมาชิก"
ยืนยัน self.data.proposals.contains(bytes) "ไม่พบข้อเสนอ"
self.data.votes [bytes] .add (sp.sender)
ถ้า sp.len (self.data.votes [ไบต์]) >= self.data.required_votes:
self.data.proposals [ไบต์] = True
@sp.onchain_view()
def is_voted(self, id):
"""ส่งคืนบูลีนเพื่อระบุว่าข้อเสนอได้รับการโหวตแล้วหรือ
Args:
id (sp.bytes): ไบต์ของข้อเสนอ
ส่งคืน:
(sp.bool): เป็นจริงหากข้อเสนอได้รับการโหวต มิฉะนั้นจะเป็นเท็จ
"""
คืน self.data.proposals.get (id, error="ไม่พบข้อเสนอ")
หาก "เทมเพลต" ไม่ได้อยู่ใน __name__:
@sp.add_test(name="MultisigView basic allowance", is_default=True)
def basic_scenario():
"""A สถานการณ์ด้วย การลงคะแนนในสัญญา multisigView
การทดสอบ:
- การกำเนิด
- การส่งข้อเสนอ
- การโหวตข้อเสนอ
"""
sc = sp.test_scenario(main)
sc.h1("สถานการณ์พื้นฐาน")
member1 = sp.test_account("member1")
member2 = sp.test_account("member2")
member3 = sp.test_account("member3")
สมาชิก = sp.set ([member1.address, สมาชิก2.ที่อยู่ สมาชิก3.ที่อยู่])
sc.h2("การกำเนิด")
c1 = main.MultisigView(สมาชิก, 2)
sc += c1
sc.h2("submit_proposal")
c1.submit_proposal(sp.bytes("0x42")).run( ผู้ส่ง=member1)
sc.h2("vote_proposal")
c1.vote_proposal(sp.bytes("0x42")).run(sender=member1)
c1.vote_proposal(sp.bytes("0x42")).run (sender=member2)
# เราสามารถตรวจสอบได้ว่าข้อเสนอได้รับการตรวจสอบแล้ว
sc.verify(c1.is_voted(sp.bytes("0x42")))
แต่ละสัญญามีกลไกที่แตกต่างกันเพื่อให้บรรลุการควบคุมหลายลายเซ็น โดยให้ความยืดหยุ่นโดยขึ้นอยู่กับความต้องการเฉพาะของกรณีการใช้งานบล็อกเชนของคุณ
หากต้องการลองใช้สัญญา Multisig ที่เราเขียนใน SmartPy คุณสามารถทำตามขั้นตอนเหล่านี้:
ไปที่ SmartPy IDE ที่ https://smartpy.io/ide
วางรหัสสัญญาลงในตัวแก้ไข คุณสามารถแทนที่รหัสที่มีอยู่ได้
ในการดำเนินการตามสัญญา ให้คลิกที่ปุ่ม "เรียกใช้" ที่แผงด้านบน
หลังจากดำเนินการตามสัญญา คุณสามารถดูการดำเนินการตามสถานการณ์ได้ในแผง "ผลลัพธ์" ทางด้านขวา ที่นี่ คุณสามารถดูรายละเอียดของการดำเนินการแต่ละอย่าง รวมถึงข้อเสนอ การลงคะแนนเสียง และการอนุมัติ
หากต้องการปรับใช้สัญญาของคุณบนเครือข่าย Tezos คุณต้องคอมไพล์ก่อน คลิกปุ่ม "คอมไพล์" ที่แผงด้านบน
หลังจากคอมไพล์แล้ว คุณสามารถปรับใช้สัญญาบนเทสเน็ตได้โดยคลิก “ปรับใช้สัญญา Michelson” คุณจะต้องจัดเตรียมรหัสลับสำหรับบัญชี Tezos ที่มีเงินทุนเพียงพอสำหรับชำระค่าน้ำมันในการใช้งาน
เมื่อปรับใช้สัญญาแล้ว คุณจะได้รับที่อยู่ของสัญญาบนบล็อกเชน คุณสามารถใช้ที่อยู่นี้เพื่อโต้ตอบกับสัญญาผ่านธุรกรรม
หากต้องการส่งข้อเสนอหรือลงคะแนนในสัญญา คุณสามารถใช้จุดเข้าใช้งานที่กำหนดไว้ในรหัสสัญญา เช่น submit_proposal
หรือ vote_proposal
สิ่งเหล่านี้สามารถเรียกได้โดยตรงจากธุรกรรมที่คุณสร้าง
โปรดจำไว้ว่า แม้ว่า SmartPy IDE จะช่วยให้คุณสามารถทดสอบสัญญาของคุณบนบล็อกเชนจำลองได้ แต่การปรับใช้สัญญาบนเครือข่าย Tezos จริงจะต้องเสียค่าน้ำมัน ซึ่งจะต้องชำระเป็น XTZ ซึ่งเป็นสกุลเงินดิจิทัลดั้งเดิมของเครือข่าย Tezos
สัญญา Multisig มอบวิธีการสร้างการควบคุมสินทรัพย์ร่วมกัน กรณีการใช้งานทั่วไปเกี่ยวข้องกับบริการเอสโครว์ การจัดการบัญชีบริษัท การลงนามข้อตกลงทางการเงินร่วมกัน และอื่นๆ สัญญาเหล่านี้มีประโยชน์อย่างยิ่งสำหรับองค์กรหรือกลุ่มที่จำเป็นต้องมีการตัดสินใจร่วมกัน
จากการออกแบบ สัญญา Multisig ทนต่อการงัดแงะและป้องกันความล้มเหลวจุดเดียว แม้ว่าคีย์ของฝ่ายหนึ่งจะถูกบุกรุก ผู้โจมตีก็ไม่สามารถดำเนินธุรกรรมโดยไม่ได้รับอนุมัติจากอีกฝ่าย นี่เป็นการเพิ่มการรักษาความปลอดภัยอีกชั้นหนึ่ง
สัญญา Multisig ถือได้ว่าเป็นดิจิทัลที่เทียบเท่ากับตู้เซฟที่ต้องใช้กุญแจหลายดอกในการเปิด จำนวนกุญแจทั้งหมด (N) และจำนวนกุญแจขั้นต่ำที่ต้องใช้ในการเปิดกล่อง (M) ได้รับการตกลงกันเมื่อมีการสร้างสัญญา
สัญญา Multisig สามารถมีการกำหนดค่าที่แตกต่างกันได้มากมาย ขึ้นอยู่กับค่าของ M และ N:
ในบริบทของบล็อกเชน สัญญา Multisig ถูกนำมาใช้อย่างกว้างขวางเพื่อเพิ่มความปลอดภัยของธุรกรรม สนับสนุนกลไกการกำกับดูแลที่ซับซ้อน หรือรักษาการควบคุมสินทรัพย์บล็อกเชนที่ยืดหยุ่น นี่คือตัวอย่างบางส่วน:
สำหรับตัวอย่างโค้ดของเรา เราจะดูการใช้งานสัญญาแบบหลายลายเซ็นที่แตกต่างกันสามแบบ:
มันค่อนข้างหลากหลายและสามารถใช้งานได้หลากหลาย ต้องใช้ลายเซ็นหลายรายการเพื่อเรียกใช้ฟังก์ชันแลมบ์ดาตามอำเภอใจ
Python
นำเข้า smartpy เป็น sp
@sp.module
def main():
operation_lambda: type = sp.lambda_(sp.unit, sp.unit, with_operations=True)
คลาส MultisigLambda(sp.Contract):
"""สมาชิกหลายคนโหวตให้ดำเนินการ lambdas
สัญญานี้สามารถจัดทำขึ้นโดยมีรายชื่อที่อยู่และการลงคะแนนเสียงที่ต้องการจำนวน
เสียง สมาชิกคนใดก็ตามสามารถส่ง lambdas ได้มากเท่าที่ต้องการและโหวต
สำหรับข้อเสนอที่ยังดำเนินการอยู่ เมื่อแลมบ์ดาถึงคะแนนที่ต้องการ รหัสของมันจะถูกเรียก
และการดำเนินการเอาต์พุตจะถูกดำเนินการ สิ่งนี้ทำให้สัญญานี้สามารถ
ทำทุกสิ่งที่สัญญาทำได้: การโอนโทเค็น การจัดการสินทรัพย์
การบริหารสัญญาอื่น...
เมื่อใช้ lambda แลมบ์ดาที่ส่งมาทั้งหมดจนถึงขณะนี้จะถูกปิดใช้งาน
สมาชิกยังสามารถส่งแลมบ์ดาใหม่ได้
"""
def __init__(ตนเอง, สมาชิก, required_votes):
"""Constructor
Args: สมาชิก
คน (sp.set of sp.address): ผู้ที่สามารถส่งและโหวต
สำหรับ lambda
required_votes (sp.nat): จำนวนโหวตที่ต้องการ
"""
ยืนยัน required_votes <= sp.len(
สมาชิก
), "required_votes ต้องเป็น <= len(members)"
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 (สมาชิก sp.set[sp.address])
self.data.required_votes = sp.cast(required_votes, sp.nat)
@sp.entrypoint
def send_lambda(self, lambda_):
"""ส่ง lambda ใหม่เพื่อโหวต
การส่งข้อเสนอไม่ได้หมายความถึงการลงคะแนนเสียงเห็นชอบด้วย
อาร์กิวเมนต์:
lambda_(sp.lambda พร้อมการดำเนินการ): lambda เสนอให้ลงคะแนน
เพิ่ม:
`คุณไม่ใช่สมาชิก`
"""
ยืนยัน self.data.members.contains(sp.sender) "คุณไม่ได้เป็นสมาชิก"
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):
"""โหวตให้แลมบ์ดา
อาร์กิวเมนต์:
id(sp.nat): รหัสแลมบ์ดาที่จะลงคะแนนให้
เพิ่ม:
`คุณไม่ใช่สมาชิก`, `แลมบ์ดาไม่ได้ใช้งาน`, `ไม่พบแลมบ์ดา'
ไม่มีการลงคะแนนคัดค้านหรือผ่าน หากมีใครไม่เห็นด้วยกับ lambda
ก็สามารถหลีกเลี่ยงการลงคะแนนได้
"""
ยืนยัน self.data.members.contains (sp.sender) "คุณไม่ใช่สมาชิก"
ยืนยัน id >= self.data.inactiveBefore, "แลมบ์ดาไม่ได้ใช้งาน"
ยืนยัน self.data.lambdas.contains(id) "ไม่พบแลมบ์ดา"
self.data.votes[id].add(sp.sender)
ถ้า sp.len (self.data.votes [id]) >= self.data.required_votes:
self.data.lambdas[id]()
self.data.inactiveBefore = self.data.nextId
@sp.onchain_view()
def get_lambda(self, id):
"""ส่งคืนแลมบ์ดาที่เกี่ยวข้อง
Args:
id (sp.nat): id ของแลมบ์ดาที่จะได้รับ
กลับ: แลมบ์ดา
คู่และบูลีนแสดงว่าแลมบ์ดาทำงานอยู่หรือไม่
"""
คืน (self.data.lambdas[id], id >= self.data.inactiveBefore)
# ถ้า "เทมเพลต" ไม่ได้อยู่ใน __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 (ตนเอง, ค่า):
ยืนยัน sp.sender == self.data.admin
self.data.value = ค่า
@sp.add_test (name = "สถานการณ์พื้นฐาน MultisigLambda", is_default = True )
def basic_scenario():
"""ใช้ multisigLambda เป็นผู้ดูแลระบบของสัญญาตัวอย่าง
การทดสอบ:
- กำเนิด
- การส่งแลมบ์ดา
- โหวตแลมบ์ดา
"""
sc = sp.test_scenario([main, ทดสอบ])
sc.h1("สถานการณ์พื้นฐาน")
member1 = sp.test_account("member1")
member2 = sp.test_account("member2")
member3 = sp.test_account("member3")
สมาชิก = sp.set ([member1.address, สมาชิก2.ที่อยู่ สมาชิก3.ที่อยู่])
sc.h2("MultisigLambda: origination")
c1 = main.MultisigLambda (สมาชิก 2)
sc += c1
sc.h2("Administrated: origination")
c2 = test.Administrated(c1.address)
sc += c2
sc.h2("MultisigLambda: send_lambda")
def set_42(params):
ผู้ดูแลระบบ = sp.contract(sp.TInt, c2.address, entrypoint="set_value")
sp.transfer(sp. อินท์(42) sp.tez(0), ผู้ดูแลระบบ.open_some())
แลมบ์ดา_ = sp.build_lambda(set_42, with_operations=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)
# เราสามารถตรวจสอบได้ว่าสัญญาที่บริหารได้รับการโอนแล้ว
sc.verify(c2.data.value == 42)
โดยจะแนะนำแนวคิดของการลงคะแนนเสียงสำหรับข้อเสนอ ในสัญญานี้ ผู้ลงนามสามารถลงคะแนนสำหรับการดำเนินการบางอย่างที่จะดำเนินการ และหากถึงองค์ประชุม การดำเนินการที่เสนอไว้ก็จะดำเนินการ
Python
นำเข้า smartpy เป็น sp
@sp.module
def main():
# ข้อกำหนดประเภทการดำเนินการดูแลระบบภายใน
InternalAdminAction: type = sp.variant(
addSigners=sp.list[sp.address],
changeQuorum=sp.nat,
RemoveSigners=sp.list[sp.address],
)
คลาส MultisigAction(sp.Contract):
"""สัญญาที่ผู้ลงนามหลายคนสามารถใช้ได้เพื่อจัดการสัญญาอีก
สัญญา สัญญาที่ได้รับการดูแลใช้อินเทอร์เฟซที่ทำให้สามารถ
กระบวนการดูแลระบบแก่ผู้ใช้ที่ไม่ใช่ผู้เชี่ยวชาญได้
ผู้ลงนามลงคะแนนเสียงสำหรับข้อเสนอ ข้อเสนอคือรายการเป้าหมายที่มีรายการการดำเนินการ
รายการ การดำเนินการเป็นไบต์ธรรมดา แต่ตั้งใจให้เป็นค่าแพ็ก
ตัวแปร รูปแบบที่เรียบง่ายนี้ทำให้สามารถสร้างอินเทอร์เฟซ UX
ที่แสดงเนื้อหาของข้อเสนอหรือสร้างข้อเสนอได้
"""
def __init__(ตนเอง, โควรัม, ผู้ลงนาม):
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, การกระทำ=sp.list[sp.bytes])],
],
)
self.data.quorum = sp.cast(องค์ประชุม, sp.nat)
self.data.signers = sp.cast (ผู้ลงนาม 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, ข้อเสนอ):
"""ผู้ลงนามเท่านั้น ส่งข้อเสนอเพื่อลงคะแนนเสียง
Args:
ข้อเสนอ (sp.list of sp.record ของที่อยู่เป้าหมายและการดำเนินการ): รายการ\
ของเป้าหมายและการดำเนินการด้านการบริหารที่เกี่ยวข้อง
"""
ยืนยัน self.data.signers.contains (sp.sender) "มีเพียงผู้ลงนามเท่านั้นที่สามารถเสนอได้"
self.data.proposals[self.data.nextId] = ข้อเสนอ
self.data.votes[self.data.nextId] = sp.set()
self.data.nextId += 1
@sp.entrypoint
def vote(self, pId):
"""Vote for one or more ข้อเสนอที่
Args:
pId (sp.nat): รหัสข้อเสนอ
"""
ยืนยัน self.data.signers.contains (sp.sender) "มีเพียงผู้ลงนามเท่านั้นที่สามารถลงคะแนนได้"
ยืนยัน self.data.votes.contains(pId) "ไม่ทราบข้อเสนอ"
ยืนยัน pId >= self.data.inactiveBefore "ข้อเสนอไม่ทำงาน"
self.data.votes[pId].add(sp.sender)
ถ้า sp.len(self.data.votes.get(pId, ค่าเริ่มต้น=sp.set())) >= self.data.quorum:
self._onApproved(pId)
@ sp.private(with_storage="read-write", with_operations=True)
def _onApproved(self, pId):
"""ฟังก์ชันอินไลน์ ตรรกะที่ใช้เมื่อข้อเสนอได้รับการอนุมัติแล้ว"""
ข้อเสนอ = self.data.proposals.get (pId, default=[])
สำหรับ p_item ในข้อเสนอ:
สัญญา = sp.contract(sp.list[sp.bytes], p_item.target)
sp.transfer (
p_item.actions,
sp.tez(0),
สัญญา unwrap_some (ข้อผิดพลาด = "InvalidTarget"),
)
# ยกเลิกข้อเสนอทั้งหมดที่ถูกส่งไปแล้ว
self.data.inactiveBefore = self.data.nextId
@sp.entrypoint
def ผู้ดูแลระบบ (ตนเอง, การดำเนินการ):
"""โทรด้วยตนเองเท่านั้น จัดการสัญญานี้
จุดเริ่มต้นนี้จะต้องถูกเรียกผ่านระบบข้อเสนอ
Args:
การดำเนินการ (sp.list ของ sp.bytes): รายการตัวแปรที่อัดแน่นของ \
`InternalAdminAction` (`addSigners`, `changeQuorum`, `removeSigners`)
"""
assert (
sp.sender == sp.self_address()
), "จุดเริ่มต้นนี้ต้องถูกเรียกผ่านระบบข้อเสนอ"
สำหรับ pack_actions ในการดำเนินการ:
action = sp.unpack(packed_actions, InternalAdminAction).unwrap_some(
error="รูปแบบการกระทำที่ไม่ถูกต้อง"
)
ด้วย sp.match(action):
พร้อม sp.case.changeQuorum เป็นองค์ประชุม:
self.data.quorum = องค์ประชุม
พร้อมด้วย sp.case.addSigners ตามที่เพิ่ม:
สำหรับผู้ลงนามที่เพิ่ม:
self.data.signers.add(ผู้ลงนาม)
ด้วย sp.case.removeSigners เมื่อถูกลบออก:
สำหรับที่อยู่ในการลบออก:
self.data.signers.remove (ที่อยู่)
# ตรวจสอบให้แน่ใจว่าสัญญาไม่จำเป็นต้องมีองค์ประชุมมากกว่าจำนวนผู้ลงนามทั้งหมด
assert self.data.quorum <= sp.len(
self.data.signers
), "มีองค์ประชุมมากกว่าผู้ลงนาม"
หาก "เทมเพลต" ไม่ได้อยู่ใน __name__:
@sp.add_test(name="Basic allowance", is_default=True)
def test():
signer1 = sp.test_account("signer1")
ผู้ลงนาม2 = sp.test_account("ผู้ลงนาม2")
ผู้ลงนาม3 = sp.test_account("ผู้ลงนาม3")
s = sp.test_scenario (หลัก)
s.h1("สถานการณ์พื้นฐาน")
s.h2("จุดเริ่มต้น")
c1 = main.MultisigAction(
quorum=2,
signers=sp.set([signer1.address, signer2.address]),
)
s += c1
s.h2("ข้อเสนอสำหรับการเพิ่มผู้ลงนามใหม่")
target = sp.to_address(
sp.contract(sp.TList(sp.TBytes), c1.address, "administrate").open_some()
)
การกระทำ = sp.pack(
sp.set_type_expr(
sp.variant("addSigners", [signer3.address]), main.InternalAdminAction
)
)
c1.send_proposal([sp.record(target=target, actions=[action])]).run(
ผู้ส่ง=signer1
)
s.h2("ผู้ลงนาม 1 โหวตสำหรับข้อเสนอ")
c1.vote(0).run(sender=signer1)
s.h2("ผู้ลงนาม 2 โหวตสำหรับข้อเสนอ")
c1.vote(0).run(sender=signer2)
s.verify(c1.data.signers.contains(signer3.address))
นอกจากนี้ยังใช้กลไกการลงคะแนนเสียง สัญญานี้อนุญาตให้สมาชิกส่งและลงคะแนนให้กับไบต์ตามอำเภอใจ เมื่อข้อเสนอได้รับคะแนนโหวตตามจำนวนที่กำหนด สถานะจะได้รับการยืนยันผ่านการดู
Python
นำเข้า smartpy เป็น sp
@sp.module
def main():
คลาส MultisigView(sp.Contract):
"""สมาชิกหลายคนโหวตให้กับไบต์ที่กำหนดเอง
สัญญานี้สามารถจัดทำขึ้นโดยมีรายชื่อที่อยู่และการลงคะแนนเสียงที่ต้องการจำนวน
เสียง สมาชิกคนใดก็ตามสามารถส่งไบต์ได้มากเท่าที่ต้องการและโหวต
สำหรับข้อเสนอที่ใช้งานอยู่
ไบต์ใดๆ ที่ได้รับการโหวตตามที่กำหนดสามารถยืนยันได้ผ่านมุมมอง
"""
def __init__(ตนเอง, สมาชิก, required_votes):
"""Constructor
Args: สมาชิก
คน (sp.set of sp.address): ผู้คนที่สามารถส่งและโหวตให้
lambda
required_votes (sp.nat): จำนวนโหวตที่ต้องการ
"""
ยืนยัน required_votes <= sp.len(
สมาชิก
), "required_votes ต้องเป็น <= len(members)"
self.data.proposals = sp.cast (sp.big_map(), sp.big_map[sp.ไบต์, sp.bool])
self.data.votes = sp.cast(
sp.big_map(), sp.big_map[sp.bytes, sp.set[sp.address]]
)
self.data.members = sp.cast(สมาชิก, sp.set[sp.address])
self.data.required_votes = sp.cast(required_votes, sp.nat)
@sp.entrypoint
def send_proposal(self, bytes):
"""ส่งข้อเสนอใหม่เพื่อโหวต
การส่งข้อเสนอไม่ได้หมายความถึงการลงคะแนนเสียงเห็นชอบ
อาร์กิวเมนต์:
ไบต์ (sp.bytes): ไบต์ที่เสนอให้ลงคะแนนเสียง
เพิ่ม:
`คุณไม่ใช่สมาชิก`
"""
ยืนยัน self.data.members.contains(sp.sender) "คุณไม่ได้เป็นสมาชิก"
self.data.proposals[bytes] = เท็จ
self.data.votes[ไบต์] = sp.set()
@ sp.entrypoint
def vote_proposal(self, bytes):
"""โหวตข้อเสนอ
ไม่มีการลงคะแนนเสียงคัดค้านหรือผ่าน หากใครไม่เห็นด้วยกับข้อเสนอ
สามารถหลีกเลี่ยงการลงคะแนนเสียงได้ คำเตือน: ข้อเสนอที่เก่าที่ไม่ได้ลงคะแนนจะไม่กลายเป็น
ล้าสมัย
อาร์กิวเมนต์:
id (sp.bytes): จำนวนไบต์ของข้อเสนอ
เพิ่ม:
`คุณไม่ใช่สมาชิก`, `ไม่พบข้อเสนอ`
"""
ยืนยัน self.data.members.contains(sp.sender) "คุณไม่ใช่สมาชิก"
ยืนยัน self.data.proposals.contains(bytes) "ไม่พบข้อเสนอ"
self.data.votes [bytes] .add (sp.sender)
ถ้า sp.len (self.data.votes [ไบต์]) >= self.data.required_votes:
self.data.proposals [ไบต์] = True
@sp.onchain_view()
def is_voted(self, id):
"""ส่งคืนบูลีนเพื่อระบุว่าข้อเสนอได้รับการโหวตแล้วหรือ
Args:
id (sp.bytes): ไบต์ของข้อเสนอ
ส่งคืน:
(sp.bool): เป็นจริงหากข้อเสนอได้รับการโหวต มิฉะนั้นจะเป็นเท็จ
"""
คืน self.data.proposals.get (id, error="ไม่พบข้อเสนอ")
หาก "เทมเพลต" ไม่ได้อยู่ใน __name__:
@sp.add_test(name="MultisigView basic allowance", is_default=True)
def basic_scenario():
"""A สถานการณ์ด้วย การลงคะแนนในสัญญา multisigView
การทดสอบ:
- การกำเนิด
- การส่งข้อเสนอ
- การโหวตข้อเสนอ
"""
sc = sp.test_scenario(main)
sc.h1("สถานการณ์พื้นฐาน")
member1 = sp.test_account("member1")
member2 = sp.test_account("member2")
member3 = sp.test_account("member3")
สมาชิก = sp.set ([member1.address, สมาชิก2.ที่อยู่ สมาชิก3.ที่อยู่])
sc.h2("การกำเนิด")
c1 = main.MultisigView(สมาชิก, 2)
sc += c1
sc.h2("submit_proposal")
c1.submit_proposal(sp.bytes("0x42")).run( ผู้ส่ง=member1)
sc.h2("vote_proposal")
c1.vote_proposal(sp.bytes("0x42")).run(sender=member1)
c1.vote_proposal(sp.bytes("0x42")).run (sender=member2)
# เราสามารถตรวจสอบได้ว่าข้อเสนอได้รับการตรวจสอบแล้ว
sc.verify(c1.is_voted(sp.bytes("0x42")))
แต่ละสัญญามีกลไกที่แตกต่างกันเพื่อให้บรรลุการควบคุมหลายลายเซ็น โดยให้ความยืดหยุ่นโดยขึ้นอยู่กับความต้องการเฉพาะของกรณีการใช้งานบล็อกเชนของคุณ
หากต้องการลองใช้สัญญา Multisig ที่เราเขียนใน SmartPy คุณสามารถทำตามขั้นตอนเหล่านี้:
ไปที่ SmartPy IDE ที่ https://smartpy.io/ide
วางรหัสสัญญาลงในตัวแก้ไข คุณสามารถแทนที่รหัสที่มีอยู่ได้
ในการดำเนินการตามสัญญา ให้คลิกที่ปุ่ม "เรียกใช้" ที่แผงด้านบน
หลังจากดำเนินการตามสัญญา คุณสามารถดูการดำเนินการตามสถานการณ์ได้ในแผง "ผลลัพธ์" ทางด้านขวา ที่นี่ คุณสามารถดูรายละเอียดของการดำเนินการแต่ละอย่าง รวมถึงข้อเสนอ การลงคะแนนเสียง และการอนุมัติ
หากต้องการปรับใช้สัญญาของคุณบนเครือข่าย Tezos คุณต้องคอมไพล์ก่อน คลิกปุ่ม "คอมไพล์" ที่แผงด้านบน
หลังจากคอมไพล์แล้ว คุณสามารถปรับใช้สัญญาบนเทสเน็ตได้โดยคลิก “ปรับใช้สัญญา Michelson” คุณจะต้องจัดเตรียมรหัสลับสำหรับบัญชี Tezos ที่มีเงินทุนเพียงพอสำหรับชำระค่าน้ำมันในการใช้งาน
เมื่อปรับใช้สัญญาแล้ว คุณจะได้รับที่อยู่ของสัญญาบนบล็อกเชน คุณสามารถใช้ที่อยู่นี้เพื่อโต้ตอบกับสัญญาผ่านธุรกรรม
หากต้องการส่งข้อเสนอหรือลงคะแนนในสัญญา คุณสามารถใช้จุดเข้าใช้งานที่กำหนดไว้ในรหัสสัญญา เช่น submit_proposal
หรือ vote_proposal
สิ่งเหล่านี้สามารถเรียกได้โดยตรงจากธุรกรรมที่คุณสร้าง
โปรดจำไว้ว่า แม้ว่า SmartPy IDE จะช่วยให้คุณสามารถทดสอบสัญญาของคุณบนบล็อกเชนจำลองได้ แต่การปรับใช้สัญญาบนเครือข่าย Tezos จริงจะต้องเสียค่าน้ำมัน ซึ่งจะต้องชำระเป็น XTZ ซึ่งเป็นสกุลเงินดิจิทัลดั้งเดิมของเครือข่าย Tezos