Bài 08 - Marlowe nhúng trong JavaScript
Marlowe được viết dưới dạng kiểu dữ liệu Haskell, và do đó, việc mô tả các hợp đồng thông minh của Marlowe bằng Haskell rất dễ hiểu. Nhưng vì hợp đồng Marlowe “chỉ là” một dạng dữ liệu, chúng ta có thể trình bày chúng bằng các ngôn ngữ khác một cách tốt như nhau.
Ở đây chúng ta mô tả một thư viện được viết bằng TypeScript có thể được sử dụng để tạo các hợp đồng thông minh Marlowe từ TypeScript hoặc JavaScript theo cách tương tự như cách sử dụng Haskell. Nếu bạn không quen thuộc với TypeScript, bạn cũng có thể sử dụng API như thể nó được viết bằng JavaScript vì TypeScript là một tập hợp siêu của JavaScript.
Bạn có thể thử thư viện trực tuyến trong Marlowe Playground bằng cách chọn Bắt đầu bằng JavaScript trên trang chủ hoặc bằng cách mở một trong các ví dụ JavaScript.
chúng ta bắt đầu phần này bằng cách giải thích cách nhúng, sau đó giải thích một số điểm cụ thể về cách nhúng trong JavaScript và cuối cùng trình bày một ví dụ về hợp đồng đầy đủ được mô tả bằng cách sử dụng phương pháp nhúng JS.
Sử dụng Trình chỉnh sửa JS trong Marlowe Playground
Bản thân việc triển khai thư viện rất đơn giản và bạn có thể tìm thấy tất cả mã nguồn của nó tại đây. Nó dựa trên nguyên tắc là đối với mỗi kiểu Haskell thì có một kiểu TypeScript tương ứng, và tương ứng với mỗi hàm tạo sẽ có một định nghĩa không đổi.
Thư viện JavaScript / TypeScript cung cấp các định nghĩa không đổi cho các cấu trúc Marlowe không có đối số, như trường hợp của SlotIntervalEnd:
const SlotIntervalStart: Value
hoặc Close
hợp đồng:
const Close: Contract
Các cấu trúc có đối số được biểu diễn dưới dạng hàm, như trong trường hợp AvailableMoney
:
const AvailableMoney: (token: Token, accountId: Party) => Value
Bạn có thể xem các khai báo kiểu cho từng cấu trúc và kiểu bằng cách di chuột qua số nhận dạng trong khai báo nhập ở đầu tệp xuất hiện trong trình chỉnh sửa của tab Trình chỉnh sửa JS. Cả khai báo nhập và khai báo hàm đều được tô xám để báo hiệu rằng chúng không được sửa đổi, mã tạo hợp đồng phải được viết bên trong nội dung của hàm được cung cấp và hợp đồng kết quả phải được trả về do hàm (sử dụng return
hướng dẫn).
Bên trong, các hàm và hằng số của thư viện JavaScript / TypeScript trả về một biểu diễn JSON của các cấu trúc Marlowe. Ví dụ, hàm AvailableMoneyđược định nghĩa như sau:
const AvailableMoney =
function (token: Token, accountId: Party) => Value {
return { "amount_of_token": token,
"in_account": accountId };
};
Khi bạn nhấp vào node Biên dịch trong trình chỉnh sửa JS của Marlowe Playground, mã trong phần thân của tab sẽ được thực thi và đối tượng JSON được trả về bởi hàm trong quá trình thực thi được phân tích cú pháp thành một hợp đồng Marlowe thực tế. Sau khi thành công, có thể Gửi đến Trình mô phỏng ; cách thức hoạt động sẽ được mô tả trong phần tiếp theo.
Về nguyên tắc, bạn có thể viết mã JavaScript tạo ra biểu diễn JSON của Marlowe trực tiếp, nhưng bạn không phải lo lắng về JSON khi sử dụng thư viện JS.
Khi bạn sử dụng thư viện JS Marlowe và việc bạn sử dụng các hàm và hằng số của kiểm tra kiểu thư viện, thì kết quả mã của bạn sẽ tạo ra một bản trình bày JSON hợp lệ của hợp đồng Marlowe, vì vậy chúng ta đảm bảo an toàn cho việc tạo hợp đồng thông qua kiểu hệ thống của TypeScript.
Kiểu SomeNumber
Có một kiểu quan trọng không có trong định nghĩa Haskell của Marlowe, chúng ta đã gọi kiểu đó là SomeNumber và nó được định nghĩa như sau:
type SomeNumber = string | number | bigint
Lý do chúng ta có kiểu này là kiểu gốc cho các số trong JavaScript và TypeScript mất độ chính xác khi được sử dụng với các số nguyên lớn. Điều này là do việc triển khai nó dựa trên số dấu phẩy động.
Biểu thức sau đúng trong JavaScript:
9007199254740992 == 9007199254740993
Điều này có thể gây rắc rối cho các hợp đồng tài chính, vì cuối cùng nó có thể dẫn đến mất tiền.
Do đó, chúng ta khuyên bạn nên sử dụng bigintloại. Nhưng chúng ta hỗ trợ ba cách biểu diễn số để thuận tiện và khả năng tương thích ngược với các phiên bản cũ của JS:
- Số tự nhiên:
- Chúng rất dễ sử dụng
- Ký hiệu rất đơn giản và có thể được sử dụng với các toán tử tiêu chuẩn, ví dụ:32 + 57
- Chúng mất độ chính xác đối với số lượng lớn
- Biểu diễn chuỗi:
- Ký hiệu chỉ yêu cầu thêm dấu ngoặc kép xung quanh các số
- Bạn không thể sử dụng trực tiếp các toán tử tiêu chuẩn, ví dụ:
"32" + "57" = "3257"
- Chúng không mất độ chính xác
bigint
loại hình:- Chúng rất dễ sử dụng (chỉ cần thêm nvào sau các ký tự số)
- Ký hiệu rất đơn giản và có thể được sử dụng với các toán tử tiêu chuẩn, ví dụ:
32n + 57n
- Chúng không mất độ chính xác
Tất cả các biểu diễn này được chuyển đổi thành BigNumber
nội bộ, nhưng có thể xảy ra mất độ chính xác nếu sử dụng các số tự nhiên, như BigNumber
được xây dựng, trước khi chuyển đổi xảy ra và API không thể làm gì với nó.
Kiểu EValue
và kiểu boolean
Trong Haskell, các quan sát boolean không đổi được biểu thị bằng TrueObs
và FalseObs
, và các giá trị số nguyên không đổi được biểu thị bằng dấu, Constant
theo sau là dấu Integer
. Trong JavaScript và TypeScript, bạn cũng có thể sử dụng các hàm tạo này, nhưng bạn không cần phải làm như vậy, vì loại Quan sát được quá tải để chấp nhận các boolean JavaScript gốc và các hàm mà trong Haskell có một Value
, trong JavaScript chúng lấy một EValuethay
thế, và EValuelà
được định nghĩa như sau:
type EValue = SomeNumber | Value
Ví dụ: Viết hợp đồng Hoán đổi trong TypeScript
Cho dù chúng ta bắt đầu bằng cách sửa đổi một ví dụ hiện có hay bằng cách tạo một hợp đồng JavaScript mới, chúng ta sẽ tự động được cung cấp danh sách nhập và khai báo hàm. Chúng ta có thể bắt đầu bằng cách xóa mọi thứ không bị chuyển sang màu xám và bắt đầu viết bên trong dấu ngoặc nhọn của định nghĩa hàm đã cung cấp.
Giả sử chúng ta muốn viết một hợp đồng để Alice có thể trao đổi 1000 Ada với Bob lấy 100 đô la.
Trước tiên, hãy tính toán số tiền chúng ta muốn làm việc với mỗi đơn vị, chúng ta có thể xác định một số hằng số bằng cách sử dụng const:
const lovelacePerAda : SomeNumber = 1000000n;
const amountOfAda : SomeNumber = 1000n;
const amountOfLovelace : SomeNumber = lovelacePerAda \* amountOfAda;
const amountOfDollars : SomeNumber = 100n;
Số tiền trong hợp đồng phải được viết bằng Lovelace, là 0,000001 Ada. Vì vậy, chúng ta tính số lượng Lovelace bằng cách nhân 1.000 Ada với 1.000.000. Số lượng đô la là 100 trong ví dụ của chúng ta.
API đã cung cấp một phương thức khởi tạo cho tiền tệ ADA và hiện không có biểu tượng tiền tệ trong Cardano cho đô la, nhưng chúng ta hãy tưởng tượng có và hãy định nghĩa nó như sau:
const dollars : Token = Token("85bb65", "dollar")
Trong thực tế, chuỗi "85bb65"
sẽ tương ứng với ký hiệu tiền tệ, là một mã băm và phải được viết bằng cơ số 16 (biểu diễn hệ thập lục phân của một chuỗi byte). Và chuỗi "dollar"sẽ tương ứng với tên token.
Bây giờ chúng ta hãy xác định một loại đối tượng để chứa thông tin về các bên và những gì họ muốn trao đổi để thuận tiện:
type SwapParty = {
party: Party;
currency: Token;
amount: SomeNumber;
};
chúng ta sẽ lưu tên của bên trong trường bên, tên của đơn vị tiền tệ trong trường đơn vị tiền tệ và số lượng đơn vị tiền tệ mà bên đó muốn trao đổi trong trường số tiền:
const alice : SwapParty = {
party: Role("alice"),
currency: ada,
amount: amountOfLovelace
}
const bob : SwapParty = {
party: Role("bob"),
currency: dollars,
amount: amountOfDollars
}
Bây giờ chúng ta đã sẵn sàng để bắt đầu viết hợp đồng của mình. Trước tiên, hãy xác định các khoản tiền gửi. chúng ta lấy thông tin từ bên phải thực hiện việc gửi tiền, số slot mà chúng ta sẽ đợi khoản tiền gửi được thực hiện và hợp đồng tiếp tục sẽ được thực thi nếu khoản tiền gửi thành công.
const makeDeposit = function(src : SwapParty, timeout : SomeNumber,
continuation : Contract) : Contract
{
return When([Case(Deposit(src.party, src.party, src.currency, src.amount),
continuation)],
timeout,
Close);
}
chúng ta chỉ cần một cấu trúc When
với một đơn Case
đại diện cho một Deposit
bên src
vào tài khoản của chính họ, theo cách này nếu chúng ta hủy bỏ hợp đồng trước khi hoán đổi, mỗi bên sẽ lấy lại những gì họ đã đặt cược.
Tiếp theo, chúng ta xác định một trong hai khoản thanh toán của hoán đổi. chúng ta lấy nguồn và bên đích làm thông số, cũng như hợp đồng tiếp tục sẽ được thực thi sau khi thanh toán.
const makePayment = function(src : SwapParty, dest : SwapParty,
continuation : Contract) : Contract
{
return Pay(src.party, Party(dest.party), src.currency, src.amount,
continuation);
}
Đối với điều này, chúng ta chỉ cần sử dụng Pay
cấu trúc để thanh toán từ tài khoản mà bên nguồn đã thực hiện gửi tiền cho bên đích.
Cuối cùng, chúng ta có thể kết hợp tất cả các phần:
const contract : Contract = makeDeposit(alice, 10n,
makeDeposit(bob, 20n,
makePayment(alice, bob,
makePayment(bob, alice,
Close))));
return contract;
Hợp đồng có bốn bước:
- Alice có thể gửi tiền cho đến slot 10
- Bob có thể gửi tiền cho đến thời điểm 20 (nếu không Alice sẽ được hoàn lại tiền và hợp đồng bị hủy bỏ)
- Sau đó, chúng ta trả tiền đặt cược của Alice cho Bob
- chúng ta trả tiền đặt cược của Bob cho Alice.
Và đó là nó. Bạn có thể tìm thấy mã nguồn đầy đủ cho phiên bản mẫu của hợp đồng thông minh hoán đổi trong các ví dụ trong Marlowe Playground mà chúng ta sẽ xem xét tiếp theo.