Bài 10: Các vấn đề tiềm ẩn với hợp đồng
Ngôn ngữ Marlowe được thiết kế để có ít cạm bẫy và khó hiểu nhất có thể, để các hợp đồng có thể được viết một cách trực quan, tránh bất kỳ sự bất ngờ nào. Tuy nhiên, theo thiết kế, không thể loại trừ tất cả các hợp đồng không nên được viết, mà không làm cho Marlowe khó sử dụng hơn nhiều. Hơn nữa, ngay cả khi một hợp đồng được viết tốt, người dùng của nó vẫn có thể tương tác với nó theo những cách không hợp lệ, bằng cách đưa ra các giao dịch vô hiệu.
Trong mọi trường hợp, khi những tác động không mong muốn này xảy ra, Marlowe được thiết kế để hành xử theo cách trực quan và thận trọng nhất có thể. Tuy nhiên, cần lưu ý những vấn đề tiềm ẩn này và xem lại cách Marlowe ứng xử trong những tình huống này. Đó là chủ đề của hướng dẫn này.
Cảnh báo
Cảnh báo của Marlowe là dấu hiệu cho thấy hợp đồng bị viết sai. Một hợp đồng được viết tốt sẽ không bao giờ đưa ra cảnh báo, bất kể người dùng tương tác với nó như thế nào. Lý tưởng nhất là chúng ta muốn cấm các hợp đồng có thể đưa ra cảnh báo chưa từng được viết, nhưng điều đó sẽ yêu cầu các hợp đồng Marlowe phải được gõ phụ thuộc và việc viết các biểu thức được gõ phụ thuộc sẽ rườm rà hơn nhiều.
Thay vào đó, Marlowe cho phép viết các hợp đồng đưa ra cảnh báo và chúng ta cung cấp các công cụ phân tích tĩnh cho phép các nhà phát triển hợp đồng kiểm tra xem một hợp đồng cụ thể có thể đưa ra cảnh báo hay không. Ngoài ra, chúng ta cung cấp các hành vi dự phòng khi hợp đồng đưa ra cảnh báo, bất chấp lời khuyên của chúng ta. chúng ta cung cấp các hành vi dự phòng vì chúng ta thừa nhận rằng việc phân tích các hợp đồng lớn có thể rất tốn kém về mặt tính toán và vì có thể mắc sai lầm. chúng ta muốn những hợp đồng được viết dở sẽ thất bại theo cách vô hại nhất có thể, đó là một cách thận trọng.
Thanh toán âm (<0)
Khi một hợp đồng được cho là thanh toán một số tiền nhỏ hơn một đơn vị tiền tệ hoặc token, nó sẽ đưa ra NonPositivePay
cảnh báo và nó sẽ không chuyển bất kỳ khoản tiền nào.
Thanh toán âm nên được thực hiện dưới dạng tiền gửi dương (khi thanh toán cho một người tham gia) hoặc thanh toán dương theo chiều ngược lại (khi thanh toán giữa các tài khoản).
Tiền gửi âm (<0)
Khi một hợp đồng được cho là mong đợi một số tiền nhỏ hơn một đơn vị tiền tệ token, nó sẽ vẫn chờ một IDeposit
giao dịch, nhưng giao dịch đó không cần chuyển bất kỳ khoản tiền nào vào hợp đồng và không có tiền nào được chuyển đến người tham gia phát hành giao dịch. Khi việc đặt cược 'giả' này thành công, hợp đồng sẽ đưa ra NonPositiveDeposit
cảnh báo.
Các khoản tiền gửi âm phải luôn được thực hiện như các khoản thanh toán dương.
Thanh toán từng phần
Khi một hợp đồng được cho là phải trả một số tiền lớn hơn số tiền có trong tài khoản nguồn, nó sẽ chỉ chuyển bất cứ thứ gì có sẵn trong tài khoản đó, ngay cả khi có đủ tiền trong tất cả các tài khoản của hợp đồng, và nó sẽ đưa ra một PartialPay
cảnh báo.
Nên tránh thanh toán từng phần vì hợp đồng không bao giờ tạo ra thanh toán từng phần là một hợp đồng rõ ràng. Các hợp đồng rõ ràng trấn an người dùng của họ rằng chúng sẽ có thể thực thi và bất cứ nơi nào trong hợp đồng có ghi rằng một khoản thanh toán sẽ xảy ra thì điều đó thực sự sẽ xảy ra.
Let
ghi đè
Không được phát hiện khi phân tích Hợp đồng
Khi hợp đồng gặp cấu trúc Let
xác định lại giá trị bằng số nhận dạng đã được xác định bởi bên ngoài Let
, hợp đồng sẽ đưa ra Shadowing
cảnh báo và nó sẽ ghi đè định nghĩa trước đó.
Shadowing
là một thực hành lập trình không tốt vì nó dẫn đến sự nhầm lẫn. Việc sử dụng cùng một số nhận dạng cho nhiều thứ có thể đánh lừa các nhà phát triển hoặc người dùng nghĩ rằng một lần sử dụng Use
sẽ được đánh giá bằng một số tiền trong khi thực sự nó sẽ được đánh giá thành một số lượng khác.
Chủ ý xấu
Có một số 'chủ ý' khác cho thấy rằng một hợp đồng có thể đã được thiết kế kém.
Những hợp đồng này có hiệu lực, theo nghĩa là chúng sẽ không nhất thiết gây ra bất kỳ cảnh báo nào và chúng làm những gì họ nói là làm, nhưng chúng có những đặc điểm cho thấy rằng hoặc nhà phát triển hợp đồng đã không nhận thức đầy đủ về hậu quả của hợp đồng, hoặc rằng nhà phát triển đã cố ý viết hợp đồng theo cách gây khó hiểu cho người đọc.
Cấu trúc Use
(một cảnh báo)
Khi một cấu trúc Use
sử dụng một số nhận dạng chưa được xác định, nó sẽ đánh giá giá trị mặc định là 0
. Sẽ không có cảnh báo nào được đưa ra, nhưng một lần nữa, đây là một thực hành không tốt vì nó có thể gây hiểu lầm. (Constant 0)
nên được sử dụng thay thế vì nó làm rõ số tiền được đề cập.
Các phần không thể truy cập của hợp đồng
Đây là "chủ ý xấu" chính trong các hợp đồng của Marlowe. Nếu một phần của hợp đồng không thể truy cập được, tại sao nó đã được đưa vào ngay từ đầu?
"Chủ ý xấu" này có một số hình dạng.
Sub-Contract
không thể truy cập được
Ví dụ:
If FalseObs contract1 contract2
Hợp đồng trước tương đương với contract2
. Nói chung, bạn không bao giờ nên sử dụng FalseObs
, và bạn chỉ nên sử dụng TrueObs
làm quan sát gốc của một Case
cấu trúc.
Observation
luôn luôn đi tắt
Ví dụ:
OrObs TrueObs observation1
Các quan sát trước đó là tương đương với observation1
. Một lần nữa, bạn chỉ nên sử dụng TrueObs
làm quan sát gốc của một Case
cấu trúc.
Rẽ nhanh của When
không thể gọi được
Ví dụ:
When [ Case (Notify TrueObs) contract1
, Case (Notify TrueObs) contract2 ]
10
contract3
contract2
là không thể truy cập, toàn bộ Casecó thể bị xóa khỏi hợp đồng và hành vi sẽ giống nhau.
Thời gian chờ lồng nhau không tăng
Ví dụ:
When []
10
When [ Case (Notify TrueObs)
contract1 ]
10
contract2
contract1
không thể truy cập được: sau khi chặn 10
, hợp đồng sẽ trực tiếp phát triển thành contract2
. Bên trong When
không tạo ra bất kỳ sự khác biệt nào đối với hợp đồng.
Các vấn đề về tính khả dụng
Ngay cả khi hợp đồng tránh được các cảnh báo và không có mã không thể truy cập được, nó vẫn có thể cho phép người dùng độc hại ép người dùng khác vào các tình huống không mong muốn mà không phải do nhà phát triển hợp đồng dự định ban đầu.
Thời gian sai của các cấu trúc When
Hãy xem xét hợp đồng sau:
When [Case (Choice (ChoiceId "choice1" (Role "alice")) [Bound 0 10])
(When [Case (Choice (ChoiceId "choice2" (Role "bob")) [Bound 0 10])
Close
]
10
(Pay (Role "bob") (Party (Role "alice"))
ada
(Constant 10)
Close
)
)
]
10
Close
Không có gì sai về nguyên tắc với hợp đồng này, nhưng nếu (Role "alice")
cô ấy đưa ra lựa chọn bị chặn 9
, sẽ hầu như không thể đưa ra bob
lựa chọn của anh ấy đúng thời hạn và nhận được hoàn lại số tiền trong tài khoản của anh ấy (Role "bob")
. Trừ khi, đây là một phần của trò chơi và đó là một hiệu ứng dự kiến, đây có thể là một hợp đồng không công bằng cho (Role "bob")
Nói chung, đó là một thực tiễn tốt để đảm bảo rằng các cấu trúc When
có thời gian chờ ngày càng tăng và việc tăng thời gian chờ là hợp lý để các bên khác nhau phát hành và làm cho giao dịch của họ được chấp nhận bởi blockchain. Có nhiều lý do tại sao sự tham gia của một bên có thể bị trì hoãn: sự cố cung cấp năng lượng, số lượng giao dịch đang chờ xử lý trong blockchain đột ngột đạt đỉnh, các cuộc tấn công mạng, v.v. Vì vậy, điều quan trọng là phải có nhiều thời gian và hào phóng với thời gian chờ và tăng thời gian chờ.
Các Lỗi
Cuối cùng, ngay cả khi một hợp đồng được viết hoàn hảo. Người dùng có thể sử dụng nó không đúng cách và chúng ta gọi đó là những lỗi sử dụng không chính xác.
Trong mọi trường hợp, bất cứ khi nào một giao dịch gây ra lỗi, giao dịch đó sẽ không ảnh hưởng đến Contract
hoặc đối với nó State
. Trên thực tế, ví của người dùng sẽ biết trước liệu một giao dịch có tạo ra lỗi hay không, bởi vì các giao dịch là xác định, vì vậy người dùng không bao giờ cần phải gửi một giao dịch có lỗi đến blockchain.
Khoảng thời gian không rõ ràng
Khi một giao dịch đạt đến thời gian chờ, khoảng thời gian của nó phải rõ ràng về việc thời gian chờ đã qua hay chưa. Ví dụ: nếu phần lớn nhất When
của hợp đồng có thời gian chờ 10
và giao dịch có khoảng thời gian [6, 14]
được phát hành, giao dịch sẽ gây ra lỗi, vì AmbiguousSlotIntervalError
không thể biết liệu thời gian chờ đã qua hay chưa chỉ bằng cách nhìn vào giao dịch. Để tránh điều này, giao dịch phải được chia thành hai giao dịch riêng biệt:
- Một với khoảng thời gian slot
[6, 9]
- Một cái khác với khoảng thời gian slot
[10, 14]
Áp dụng thuộc tính Không phù hợp
Nếu một giao dịch không cung cấp các đầu vào như mong đợi Contract
, thì hợp đồng sẽ phát sinh NoMatchError
lỗi và toàn bộ giao dịch sẽ bị hủy.
Giao dịch vô nghĩa
Nếu một giao dịch không có bất kỳ ảnh hưởng nào đến Contrac
thoặc State
, nó sẽ dẫn đến UselessTransaction
lỗi và toàn bộ giao dịch sẽ bị hủy. Lý do tại sao chúng ta loại bỏ các giao dịch vô nghĩa là chúng mở ra cánh cửa cho các cuộc tấn công Từ chối Dịch vụ (DoS), bởi vì kẻ tấn công tiềm năng có thể làm tràn ngập hợp đồng với các giao dịch không cần thiết và ngăn chặn các giao dịch cần thiết để đưa nó vào blockchain.