Nghiên cứu so sánh giữa Move và Rust trong phát triển hợp đồng thông minh
Tiêu đề gốc: 《Phát triển Hợp đồng Thông minh---Move vs.Rust》
Tác giả: Krešimir Klas, người sáng lập ngôn ngữ Move
Biên dịch:郭倩雯, ChainCatcher
1. Giới thiệu
Gần đây, các cuộc thảo luận về Aptos và Sui đang diễn ra sôi nổi, cả hai đều là chuỗi công khai L1 hiệu suất cao mới nổi, ngôn ngữ lập trình hợp đồng thông minh Move là một phần không thể thiếu của những chuỗi mới này. Một số nhà phát triển đang tích cực chuyển sang Move, tuyên bố rằng nó là tương lai của phát triển hợp đồng thông minh. Những người khác thì thận trọng hơn, cho rằng Move không thể cung cấp nhiều điều mới mẻ so với các ngôn ngữ lập trình hiện có.
Các nhà đầu tư tiền điện tử cũng đang tò mò về những điểm độc đáo của các chuỗi công khai L1 này, làm thế nào chúng có thể cạnh tranh với Solana, hiện là người chơi chính trong lĩnh vực L1 hiệu suất cao, nổi tiếng với việc sử dụng Rust làm ngôn ngữ lập trình hợp đồng thông minh.
Nhưng hiện tại, những cuộc thảo luận mà chúng ta thấy không đạt đến độ sâu nhất định, không thể thực sự thấu hiểu tác động của những công nghệ mới này đối với chúng ta. Điều này áp dụng cho cả hai cực của cuộc thảo luận------ những người hoài nghi Move đã hạ thấp Move đến mức không còn giá trị, không thể đánh giá được những khía cạnh tinh tế (nhưng rất quan trọng) của nó, nhưng những người ủng hộ Move, quá phấn khích về Move, cũng không thể nhìn thấu điều gì làm cho nó vĩ đại. Điều này tạo ra một vùng trung gian khổng lồ và mơ hồ, khiến cho những người quan sát bên ngoài, các nhà phát triển tiền điện tử, nhà đầu tư, quan tâm đến chủ đề này, nhưng không thể tự tin về quan điểm của mình.
Trong bài viết này, tôi sẽ đi sâu vào Move, mô hình lập trình mới của nó, chuỗi khối Sui và cách nó tận dụng các tính năng của Move, cũng như so sánh nó với Solana và mô hình lập trình của nó. Để làm nổi bật các đặc điểm của Move, tôi sẽ so sánh Solana/Rust với Sui/Move. Bởi vì khi bạn so sánh một thứ với một thứ khác mà bạn đã quen thuộc, việc hiểu sẽ dễ dàng hơn.
Move còn có các biến thể khác, như Aptos Move, chúng có một số khác biệt nhỏ. Mục tiêu của bài viết này không phải là thảo luận về những khác biệt tinh tế giữa các biến thể khác nhau của Move, mà là để trình bày những lợi thế chung của Move, cũng như so sánh nó với mô hình lập trình của Solana. Do đó, để đơn giản, tôi chỉ sử dụng một biến thể (Sui Move) trong bài viết này. Vì vậy, một số khái niệm Move mà tôi giới thiệu trong bài viết này (tức là đối tượng và các chức năng liên quan) chỉ áp dụng cho biến thể Sui của Move, không áp dụng cho các biến thể khác. Mặc dù các biến thể khác của Move không nhất thiết có những khái niệm này, nhưng chúng sử dụng các cơ chế khác (ví dụ như lưu trữ toàn cầu) để đạt được chức năng tương tự. Nhưng ngay cả như vậy, tất cả những lợi ích chính của Move mà bài viết này thảo luận đều áp dụng cho tất cả các tích hợp Move (hỗ trợ mã byte Move theo cài đặt gốc), bao gồm cả Aptos. Tôi chọn Sui chỉ vì tôi quen thuộc hơn với nó và tôi cảm thấy nó trực quan hơn, dễ dàng hơn để trình bày dưới dạng bài viết.
2. Mô hình lập trình Solana
Trên Solana, chương trình (hợp đồng thông minh) là không trạng thái, chúng không thể tự truy cập (đọc hoặc ghi) bất kỳ trạng thái nào tồn tại trong toàn bộ giao dịch. Để truy cập hoặc duy trì trạng thái, chương trình cần sử dụng tài khoản. Mỗi tài khoản có một địa chỉ duy nhất (khóa công khai của cặp khóa Ed25519), có thể lưu trữ bất kỳ dữ liệu nào.
Chúng ta có thể coi không gian tài khoản của Solana như một kho lưu trữ khóa-giá trị toàn cầu, trong đó khóa là địa chỉ tài khoản (pubkey), giá trị là dữ liệu tài khoản. Chương trình thực hiện thao tác trên kho lưu trữ khóa-giá trị này bằng cách đọc và sửa đổi giá trị của nó.
Tài khoản có khái niệm quyền sở hữu. Mỗi tài khoản được sở hữu bởi một (và chỉ một) chương trình. Khi một tài khoản được một chương trình sở hữu, chương trình đó được phép sửa đổi dữ liệu của nó. Chương trình không thể sửa đổi tài khoản mà nó không sở hữu (nhưng được phép đọc những tài khoản đó). Trong quá trình thực thi, bằng cách so sánh trạng thái tài khoản trước và sau khi thực thi chương trình, có thể thực hiện kiểm tra động này, nếu có sự thay đổi trái phép, giao dịch sẽ thất bại.
Mỗi tài khoản cũng có một khóa riêng liên quan (khóa công khai tương ứng là địa chỉ của nó), người dùng có thể truy cập khóa riêng này có thể sử dụng nó để ký giao dịch. Thông qua cơ chế này, chúng tôi đã thực hiện chức năng quyền hạn và quyền sở hữu trong hợp đồng thông minh Solana--ví dụ, để nhận một số tiền, hợp đồng thông minh có thể yêu cầu người dùng cung cấp chữ ký cần thiết.
Khi thực hiện gọi chương trình khác, khách hàng cần chỉ định chương trình này sẽ truy cập những tài khoản nào khi gọi. Điều này cho phép thời gian xử lý giao dịch có thể sắp xếp các giao dịch không chồng chéo để thực thi song song, đồng thời đảm bảo tính nhất quán dữ liệu. Đây là một trong những đặc điểm thiết kế của Solana, giúp nó có khả năng thông lượng cao.
Chương trình có thể gọi các chương trình khác thông qua CPI. Cách thức hoạt động của những cuộc gọi này tương tự như các cuộc gọi từ khách hàng------ chương trình gọi cần chỉ định tài khoản mà chương trình được gọi sẽ truy cập, chương trình được gọi sẽ thực hiện kiểm tra đầu vào, giống như từ cuộc gọi của khách hàng (bởi vì nó không tin tưởng chương trình gọi).
Tài khoản PDA là một loại tài khoản đặc biệt, cho phép chương trình cung cấp chữ ký tài khoản mà không cần sở hữu hoặc lưu trữ khóa riêng. PDA đảm bảo rằng chỉ chương trình tạo ra PDA mới có thể tạo chữ ký cho nó (các người dùng và chương trình khác thì không). Khi một chương trình cần tương tác với một chương trình khác thông qua CPI và cung cấp quyền hạn, điều này rất hữu ích (ví dụ, thực hiện một kho bạc). PDA đảm bảo rằng không ai ngoài chương trình có thể truy cập trực tiếp vào tài nguyên của chương trình. PDA cũng có thể được sử dụng để tạo tài khoản tại một địa chỉ xác định.
Đây là những thành phần cơ bản của lập trình hợp đồng thông minh an toàn trên Solana. Ở một mức độ nào đó, bạn có thể coi chương trình Solana như là chương trình trong hệ điều hành, trong khi tài khoản là tệp, bất kỳ ai cũng có thể tự do thực thi bất kỳ chương trình nào, thậm chí triển khai chương trình của riêng mình. Khi chương trình (hợp đồng thông minh) chạy, chúng sẽ đọc và ghi vào tệp (tài khoản). Tất cả các tệp đều có thể được tất cả các chương trình đọc, nhưng chỉ có chương trình có quyền sở hữu tệp mới có thể sửa đổi nó. Chương trình cũng có thể thực thi các chương trình khác, nhưng chúng không có bất kỳ sự tin tưởng nào với nhau------ bất kỳ ai thực thi chương trình đều cần giả định rằng đầu vào có thể là độc hại. Bởi vì hệ điều hành này có thể được bất kỳ ai truy cập trên toàn cầu, nên đã thêm hỗ trợ xác thực chữ ký gốc vào chương trình để thực hiện chức năng quyền hạn và quyền sở hữu cho người dùng…… đây không phải là một phép ẩn dụ hoàn hảo, nhưng vẫn khá thú vị.
3. Mô hình lập trình Move
Trong Move, hợp đồng thông minh được phát hành dưới dạng mô-đun. Mô-đun bao gồm các hàm và kiểu tùy chỉnh (cấu trúc/struct). Cấu trúc bao gồm các trường, có thể là kiểu nguyên thủy (u8, u64, bool…) hoặc các cấu trúc khác. Hàm có thể gọi các hàm khác------ có thể là trong cùng một mô-đun hoặc trong các mô-đun công khai khác.
Trong Solana, điều này tương đương với việc tất cả các hợp đồng thông minh đều được phát hành dưới dạng mô-đun trong một chương trình. Điều này có nghĩa là tất cả các hợp đồng thông minh (mô-đun) đều nằm trong cùng một hệ thống, có thể gọi trực tiếp lẫn nhau mà không cần thông qua API hoặc giao diện trung gian. Điều này rất quan trọng, ảnh hưởng của nó sẽ được thảo luận kỹ lưỡng trong bài viết này.
3.1. Đối tượng
Cần lưu ý rằng khái niệm đối tượng dưới đây áp dụng cho biến thể Sui của Move. Trong các tích hợp Move khác (ví dụ như Aptos hoặc Diem/core Move), tình hình có thể hơi khác. Tuy nhiên, trong các biến thể Move khác cũng có các giải pháp tương tự để đạt được những điều tương tự (tính bền vững của trạng thái), những giải pháp này không có sự khác biệt lớn.
Lý do chính để giới thiệu biến thể Sui là vì các mẫu mã trong phần sau của bài viết đều dựa trên biến thể Sui của Move, đồng thời các đối tượng của nó, chẳng hạn như cơ chế lưu trữ toàn cầu trong core Move, dễ hiểu hơn một chút. Quan trọng là, tất cả các lợi ích chính của Move mà bài viết này thảo luận đều áp dụng cho tất cả các tích hợp Move (hỗ trợ gốc mã byte Move), bao gồm cả Aptos.
Đối tượng là các phiên bản cấu trúc được lưu trữ trong thời gian chạy (struct instance), và duy trì trạng thái trong giao dịch.
Có ba loại đối tượng khác nhau (trong Sui):
- Đối tượng sở hữu (owned objects)
- Đối tượng chia sẻ (shared objects)
- Đối tượng không thay đổi (immutable objects)
Đối tượng sở hữu là đối tượng thuộc về người dùng. Chỉ có người dùng sở hữu đối tượng đó mới có thể sử dụng nó trong giao dịch. Siêu dữ liệu quyền sở hữu hoàn toàn minh bạch, được xử lý bởi thời gian chạy. Nó sử dụng công nghệ mã hóa khóa công khai------ mỗi đối tượng sở hữu đều liên kết với một khóa công khai (được lưu trữ trong siêu dữ liệu đối tượng trong thời gian chạy), bất cứ khi nào bạn muốn sử dụng đối tượng trong giao dịch, bạn cần cung cấp chữ ký tương ứng (hiện tại hỗ trợ Ed25519, sắp tới sẽ hỗ trợ ECDSA và chữ ký nhiều chữ ký K-of-N).
Đối tượng chia sẻ tương tự như đối tượng sở hữu, nhưng chúng không có một chủ sở hữu liên quan. Do đó, bạn không cần sở hữu bất kỳ khóa riêng nào để sử dụng chúng trong giao dịch (bất kỳ ai cũng có thể sử dụng chúng). Bất kỳ đối tượng sở hữu nào cũng có thể được chia sẻ (bởi chủ sở hữu của nó), một khi một đối tượng đã được chia sẻ, nó sẽ luôn luôn giữ trạng thái chia sẻ------ không bao giờ có thể được chuyển nhượng hoặc trở thành đối tượng sở hữu một lần nữa.
Đối tượng không thay đổi là đối tượng không thể bị thay đổi. Một khi một đối tượng được đánh dấu là không thay đổi, các trường của nó không thể bị sửa đổi nữa. Tương tự như đối tượng chia sẻ, những đối tượng này không có chủ sở hữu và có thể được bất kỳ ai sử dụng.
Mô hình lập trình Move rất trực quan và đơn giản. Mỗi hợp đồng thông minh là một mô-đun, được định nghĩa bởi các hàm và cấu trúc. Cấu trúc được khởi tạo trong hàm và có thể được truyền đến các mô-đun khác thông qua các cuộc gọi hàm. Để một cấu trúc có thể duy trì tính bền vững qua các giao dịch, chúng tôi biến nó thành một đối tượng có thể được sở hữu, chia sẻ hoặc không thay đổi (chỉ áp dụng cho Sui, có thể khác một chút trong các biến thể Move khác).
4. An toàn của Move
Chúng ta đã thấy rằng, trong Move:
- Bạn có thể truyền bất kỳ đối tượng nào mà bạn sở hữu (hoặc chia sẻ) cho bất kỳ hàm nào trong bất kỳ mô-đun nào
- Bất kỳ ai cũng có thể phát hành một mô-đun (có thể là thù địch)
- Không có khái niệm mô-đun sở hữu cấu trúc, điều này sẽ khiến mô-đun sở hữu có quyền duy nhất để thay đổi cấu trúc, giống như trường hợp tài khoản Solana------ cấu trúc có thể chảy vào các mô-đun khác hoặc có thể được nhúng trong các cấu trúc khác.
Vấn đề là, tại sao cách làm này lại an toàn? Điều gì ngăn cản mọi người phát hành các mô-đun độc hại, lấy các đối tượng chia sẻ (như hồ bơi AMM), và gửi chúng vào các mô-đun độc hại, sau đó tiếp tục tiêu tán tài sản của chúng?
Trong Solana, có một khái niệm quyền sở hữu tài khoản, có nghĩa là chỉ có chương trình sở hữu tài khoản mới được phép thay đổi nó. Nhưng trong Move, không có khái niệm mô-đun sở hữu đối tượng, bạn có thể gửi đối tượng đến bất kỳ mô-đun nào------ không chỉ có thể tham chiếu đối tượng, toàn bộ đối tượng, mà còn có thể tham chiếu giá trị của nó. Hơn nữa, thời gian chạy cũng không có kiểm tra cụ thể để đảm bảo rằng đối tượng này không bị thay đổi trái phép khi đi qua mô-đun không đáng tin cậy. Vậy, điều gì đang bảo vệ sự an toàn của đối tượng này? Làm thế nào để đảm bảo rằng đối tượng này không bị mã không đáng tin cậy lạm dụng?
Đây chính là điều mới mẻ của Move…… hãy để chúng ta nói về tài nguyên.
4.1. Cấu trúc
Định nghĩa một loại cấu trúc (struct) gần giống như bạn mong đợi:

Cho đến nay vẫn ổn------ đây cũng là cách bạn định nghĩa một cấu trúc trong Rust. Nhưng trong Move, cấu trúc có những điểm độc đáo so với các ngôn ngữ lập trình truyền thống, mô-đun Move có nhiều không gian hơn trong cách sử dụng loại. Trong đoạn mã trên, cấu trúc được định nghĩa sẽ chịu các giới hạn sau:
- Nó chỉ có thể được khởi tạo ("đóng gói") và hủy bỏ ("mở gói") trong mô-đun định nghĩa cấu trúc đó------ có nghĩa là, bạn không thể khởi tạo hoặc hủy bỏ một phiên bản cấu trúc từ bất kỳ hàm nào trong mô-đun khác.
- Các trường của phiên bản cấu trúc chỉ có thể được truy cập (do đó cũng có thể được sửa đổi) trong mô-đun của nó.
- Không thể sao chép hoặc nhân bản phiên bản cấu trúc bên ngoài mô-đun của nó
- Không thể lưu trữ một phiên bản cấu trúc trong các trường của các phiên bản cấu trúc khác
Điều này có nghĩa là, nếu xử lý phiên bản cấu trúc này trong hàm của các mô-đun khác, chúng tôi sẽ không thể sửa đổi các trường của nó, nhân bản nó, lưu trữ nó trong các trường của các cấu trúc khác, hoặc vứt bỏ nó (phải truyền nó đến nơi khác thông qua cuộc gọi hàm). Tình hình là như vậy: mô-đun của cấu trúc thực hiện các hàm có thể được gọi từ mô-đun của chúng tôi để hoàn thành những điều này. Nhưng ngoài điều đó, chúng tôi không thể trực tiếp làm những điều này cho các loại bên ngoài. Điều này cho phép mô-đun hoàn toàn kiểm soát cách sử dụng và không sử dụng loại của nó.
Vì những giới hạn này, chúng ta dường như mất đi nhiều tính linh hoạt. Điều này cũng là sự thật------ trong lập trình truyền thống, xử lý các cấu trúc như vậy sẽ rất rắc rối, nhưng thực tế, đó chính là điều mà chúng ta muốn trong hợp đồng thông minh. Phát triển hợp đồng thông minh cuối cùng là về lập trình tài sản số (tài nguyên). Nếu bạn nhìn vào cấu trúc được mô tả ở trên, đó chính là bản chất của nó------ nó là một loại tài nguyên. Nó không thể được tạo ra tùy ý, không thể bị sao chép, và không thể bị phá hủy một cách tình cờ. Do đó, chúng tôi thực sự mất đi một số tính linh hoạt ở đây, nhưng tính linh hoạt mà chúng tôi mất đi chính là điều mà chúng tôi mong muốn, vì điều này làm cho việc thao tác tài nguyên trở nên trực quan và an toàn.
Hơn nữa, Move cho phép chúng tôi nới lỏng một số giới hạn này bằng cách thêm khả năng (capability) vào cấu trúc. Có bốn loại khả năng: khóa, lưu trữ, sao chép và xóa. Bạn có thể thêm bất kỳ tổ hợp nào của những khả năng này vào một cấu trúc.

Dưới đây là tác dụng của chúng:
- Khóa - cho phép một cấu trúc trở thành một đối tượng (chỉ dành riêng cho Sui, trường hợp core Move có chút khác biệt). Như đã đề cập, đối tượng là bền vững, nếu là đối tượng sở hữu, cần chữ ký của người dùng để sử dụng trong cuộc gọi hợp đồng thông minh. Khi sử dụng khả năng khóa, trường đầu tiên của cấu trúc phải là ID đối tượng có kiểu UID. Điều này sẽ cho nó một ID toàn cầu duy nhất, có thể được tham chiếu bằng nó.
- Lưu trữ - cho phép cấu trúc này được nhúng như một trường trong một cấu trúc khác
- Sao chép - cho phép sao chép/nhân bản cấu trúc này từ bất kỳ đâu
- Xóa - cho phép xóa cấu trúc này từ bất kỳ đâu
Về bản chất, mỗi cấu trúc trong Move đều là tài nguyên mặc định. Khả năng cho chúng tôi quyền lực, có thể tinh chỉnh để nới lỏng những giới hạn này, khiến nó hoạt động giống như các cấu trúc truyền thống hơn.
4.2. Đồng tiền (Coin)
Đồng tiền trong Sui thực hiện chức năng tương tự như token ERC20/SPL, là một phần của Thư viện Sui Move. Định nghĩa của nó như sau:

Bạn có thể tìm thấy triển khai mô-đun đầy đủ trong kho mã Sui (liên kết).
Loại đồng tiền có khả năng khóa và lưu trữ. Khả năng khóa có nghĩa là nó có thể được sử dụng như một đối tượng. Điều này cho phép người dùng sở hữu đồng tiền trực tiếp (như một đối tượng cấp cao). Khi bạn sở hữu một đồng tiền, không ai khác ngoài bạn thậm chí không thể tham chiếu nó trong giao dịch (đừng nói đến việc sử dụng nó). Khả năng lưu trữ có nghĩa là, đồng tiền có thể được nhúng như một trường trong một cấu trúc khác, điều này rất hữu ích cho khả năng kết hợp.
Do không có khả năng vứt bỏ, đồng tiền không thể bị vứt bỏ một cách tình cờ trong hàm (bị xóa). Đây là một đặc điểm rất tốt------ nó có nghĩa là bạn sẽ không vô tình mất một đồng tiền. Nếu bạn đang triển khai một hàm nhận đồng tiền làm tham số, vào cuối hàm, bạn cần phải làm một điều gì đó rõ ràng với nó------ chuyển nó cho người dùng, nhúng nó vào một đối tượng khác, hoặc gửi nó vào một hàm khác thông qua cuộc gọi (cũng cần làm một điều gì đó với nó). Tất nhiên, việc gọi hàm coin::burn trong mô-đun đồng tiền để xóa một đồng tiền là có thể, nhưng bạn cần phải làm điều đó một cách có chủ đích (bạn sẽ không thực hiện thao tác đó một cách tình cờ).
Thiếu khả năng sao chép có nghĩa là không ai có thể sao chép đồng tiền, do đó không thể tạo ra nguồn cung mới từ không khí. Việc tạo ra nguồn cung mới có thể được thực hiện thông qua hàm coin::mint, và chỉ có thể được gọi bởi chủ sở hữu của đối tượng khả năng kho bạc (treasury capability) của đồng tiền đó.
Ngoài ra, do sự tồn tại của kiểu tổng quát (generics), mỗi đồng tiền khác nhau đều là loại độc nhất. Vì hai đồng tiền chỉ có thể được cộng lại thông qua hàm coin::join (chứ không phải truy cập trực tiếp vào các trường của chúng), điều này có nghĩa là không thể cộng giá trị của các đồng tiền khác nhau (đồng tiền A + đồng tiền B)------ vì không có hàm nào có chữ ký như vậy. Hệ thống kiểu có thể bảo vệ chúng ta khỏi những khoản nợ xấu.
Trong Move, sự an toàn của tài nguyên được xác định bởi kiểu của nó. Với việc Move có hệ thống kiểu toàn cầu, điều này làm cho mô hình lập trình trở nên tự nhiên và an toàn hơn, tài nguyên có thể được truyền vào và ra khỏi mã không đáng tin cậy.
4.3. Xác minh mã byte
Như đã đề cập trước đó, hợp đồng thông minh Move được phát hành dưới dạng mô-đun. Bất kỳ ai cũng được phép tạo và tải lên bất kỳ mô-đun tùy ý nào lên chuỗi khối, được thực thi bởi bất kỳ ai. Chúng ta cũng đã thấy rằng Move có một số quy tắc về cách sử dụng cấu trúc.
Vậy, điều gì đảm bảo rằng những quy tắc này được tuân thủ bởi bất kỳ mô-đun nào? Điều gì ngăn cản mọi người tải lên các mô-đun có mã byte được chế tạo đặc biệt, chẳng hạn như nhận một đối tượng đồng tiền, sau đó thay đổi trực tiếp các trường bên trong của nó để vượt qua những quy tắc này? Bằng cách làm như vậy, có thể tăng số lượng đồng tiền một cách trái phép. Chỉ riêng cú pháp của mã byte chắc chắn cho phép điều này.
Xác minh mã byte có thể ngăn chặn loại lạm dụng này. Trình xác minh Move là một công cụ phân tích tĩnh, nó phân tích mã byte Move và xác định xem nó có tuân thủ các quy tắc an toàn về kiểu, bộ nhớ và tài nguyên hay không. Tất cả mã được tải lên chuỗi đều cần phải qua trình xác minh. Khi bạn cố gắng tải lên một mô-đun Move lên chuỗi, nút và trình xác minh sẽ chạy thông qua trình xác minh trước, sau đó mới cho phép gửi. Nếu bất kỳ mô-đun nào cố gắng vượt qua các quy tắc an toàn của Move, nó sẽ bị trình xác minh từ chối và sẽ không được phát hành.
Mã byte Move và trình xác minh là những đổi mới cốt lõi của Move. Nó thực hiện một mô hình lập trình trực quan tập trung vào tài nguyên, điều này không thể đạt được ở nơi khác. Quan trọng nhất, nó cho phép các kiểu có cấu trúc vượt qua các ranh giới tin cậy mà không mất đi tính toàn vẹn của chúng.
Trên Solana, hợp đồng thông minh là chương trình, trong khi trong Move, chúng là mô-đun. Điều này có vẻ chỉ là một sự khác biệt ngữ nghĩa, nhưng thực tế không phải vậy, có ý nghĩa lớn. Sự khác biệt nằm ở chỗ, trên Solana, không có tính an toàn kiểu khi vượt qua ranh giới chương trình------ mỗi chương trình tải các phiên bản bằng cách giải mã dữ liệu tài khoản thô một cách thủ công, điều này cần phải thực hiện các kiểm tra an toàn quan trọng một cách thủ công, và không có an toàn tài nguyên cục bộ. Ngược lại, an toàn tài nguyên phải được thực hiện riêng biệt bởi mỗi hợp đồng thông minh. Điều này thực sự có thể đạt được đủ khả năng lập trình, nhưng so với mô hình của Move, nó phần lớn cản trở khả năng kết hợp và tính thân thiện với người dùng, vì mô hình của Move có hỗ trợ gốc cho tài nguyên, chúng có thể an toàn chảy vào và ra khỏi mã không đáng tin cậy.
Trong Move, kiểu thực sự tồn tại trong các mô-đun khác nhau------ hệ thống kiểu là toàn cầu. Điều này có nghĩa là không cần gọi CPI, mã hóa/giải mã tài khoản, kiểm tra quyền sở hữu tài khoản, v.v.------ bạn chỉ cần gọi trực tiếp hàm trong mô-đun khác với các tham số. Tính an toàn kiểu và tài nguyên của toàn bộ hợp đồng thông minh được đảm bảo bởi việc xác minh mã byte trong quá trình biên dịch/phát hành, không cần phải thực hiện ở cấp độ hợp đồng thông minh như Solana, sau đó kiểm tra tại thời gian chạy.
5. So sánh Solana và Move
Bây giờ chúng ta đã thấy cách thức hoạt động của lập trình Move, lý do an toàn cơ bản. Vậy hãy cùng đi sâu vào tác động của điều này đối với lập trình hợp đồng thông minh từ góc độ khả năng kết hợp, tính thân thiện với người dùng và an toàn. Ở đây, tôi sẽ so sánh phát triển Move/Sui với EVM và Rust/Solana/Anchor để giúp hiểu rõ hơn về những lợi ích mà mô hình lập trình Move mang lại.
5.1. Cho vay chớp nhoáng
Cho vay chớp nhoáng là một loại khoản vay trong DeFi, số tiền vay phải được hoàn trả trong cùng một giao dịch mà nó được vay. Lợi ích chính của việc này là, vì giao dịch là nguyên tử, khoản vay có thể hoàn toàn không cần thế chấp. Điều này có thể được sử dụng để thực hiện giao dịch chênh lệch giá giữa các tài sản mà không cần có vốn.
Khó khăn chính trong việc đạt được điều này là------ bạn làm thế nào để đảm bảo rằng số tiền vay từ hợp đồng cho vay chớp nhoáng sẽ được hoàn trả trong cùng một giao dịch? Để khoản vay không cần thế chấp, giao dịch cần phải là nguyên tử------ có nghĩa là, nếu số tiền vay không được hoàn trả trong cùng một giao dịch, toàn bộ giao dịch cần phải thất bại.
EVM có lập lịch động, vì vậy có thể sử dụng tính tái nhập (reentrancy) để thực hiện điều này, như sau:
- Người dùng cho vay chớp nhoáng tạo và tải lên hợp đồng thông minh tùy chỉnh, khi gọi hợp đồng đó, quyền kiểm soát sẽ được chuyển đến hợp đồng cho vay chớp nhoáng thông qua cuộc gọi
- Sau đó, hợp đồng cho vay chớp nhoáng sẽ gửi số tiền vay yêu cầu đến hợp đồng thông minh tùy chỉnh và gọi hàm executeOperation() trong hợp đồng thông minh tùy chỉnh.
- Sau đó, hợp đồng thông minh tùy chỉnh sẽ sử dụng số tiền vay nhận được để thực hiện các thao tác mà nó cần (như giao dịch chênh lệch giá).
- Khi hợp đồng thông minh tùy chỉnh hoàn thành thao tác của mình, nó cần phải trả lại số tiền đã vay cho hợp đồng cho vay chớp nhoáng.
- Như vậy, hàm executionOperation() của hợp đồng thông minh tùy chỉnh đã hoàn thành, quyền kiểm soát sẽ trở lại hợp đồng cho vay chớp nhoáng, nó sẽ kiểm tra xem số tiền vay đã được trả lại đúng hay chưa.
- Nếu hợp đồng thông minh tùy chỉnh không trả lại đúng số tiền vay, toàn bộ giao dịch sẽ thất bại.
Điều này thực hiện tốt chức năng cần thiết, nhưng vấn đề là, nó phụ thuộc vào tính tái nhập, mà chúng tôi rất mong không xuất hiện trong lập trình hợp đồng thông minh. Bởi vì tính tái nhập về bản chất rất nguy hiểm, là nguyên nhân gốc rễ của nhiều lỗ hổng, bao gồm cả cuộc tấn công nổi tiếng DAO.
Solana làm tốt hơn trong vấn đề này, vì nó không cho phép tái nhập. Tuy nhiên, nếu không có tính tái nhập, nếu hợp đồng cho vay chớp nhoáng không thể gọi lại hợp đồng thông minh tùy chỉnh, thì làm thế nào để thực hiện cho vay chớp nhoáng trên Solana? Nhờ vào sự tự kiểm tra lệnh (instruction introspection). Trên Solana, mỗi giao dịch bao gồm nhiều lệnh (cuộc gọi hợp đồng thông minh), từ bất kỳ lệnh nào bạn có thể kiểm tra các lệnh khác tồn tại trong cùng một giao dịch (ID chương trình của chúng, dữ liệu lệnh và tài khoản). Điều này làm cho việc thực hiện cho vay chớp nhoáng trở nên khả thi, cụ thể như sau:
- Hợp đồng cho vay chớp nhoáng thực hiện các lệnh vay (borrow) và hoàn trả (repay)
- Người dùng tạo một giao dịch cho vay chớp nhoáng bằng cách xếp chồng các lệnh vay và hoàn trả trong cùng một giao dịch. Lệnh vay khi thực hiện sẽ sử dụng tự kiểm tra lệnh để kiểm tra xem lệnh hoàn trả có được sắp xếp trong giai đoạn sau của cùng một giao dịch hay không. Nếu lệnh hoàn trả không tồn tại hoặc không hợp lệ, giao dịch sẽ thất bại ở giai đoạn này.
- Giữa các cuộc gọi vay và hoàn trả, số tiền đã vay có thể được sử dụng bởi bất kỳ lệnh nào khác nằm giữa hai lệnh đó.
- Khi giao dịch kết thúc, lệnh hoàn trả sẽ trả số tiền lại cho hợp đồng cho vay chớp nhoáng (sự tồn tại của lệnh này sẽ được kiểm tra trong sự tự kiểm tra của lệnh vay)
Giải pháp này đủ tốt, nhưng vẫn không lý tưởng. Tự kiểm tra lệnh ở một mức độ nào đó là một trường hợp đặc biệt, không thường được sử dụng trên Solana, việc sử dụng nó yêu cầu nhà phát triển phải nắm vững nhiều khái niệm, và việc thực hiện nó cũng có yêu cầu kỹ thuật lớn, vì có một số chi tiết cần được xem xét đúng cách. Còn một giới hạn kỹ thuật------- lệnh hoàn trả cần phải tồn tại tĩnh trong giao dịch, vì vậy không thể gọi lệnh hoàn trả một cách động thông qua CPI trong quá trình thực hiện giao dịch. Điều này không phải là vấn đề lớn, nhưng khi tích hợp với các hợp đồng thông minh khác, nó phần nào hạn chế tính linh hoạt của mã, và đẩy nhiều phức tạp về phía khách hàng.
Move cũng cấm lập lịch động và tái nhập, nhưng khác với Solana, nó có một giải pháp cho vay chớp nhoáng rất đơn giản và tự nhiên. Hệ thống kiểu tuyến tính của Move cho phép tạo ra các cấu trúc, đảm bảo rằng trong quá trình thực hiện giao dịch, chúng sẽ được tiêu thụ đúng một lần. Đây được gọi là mô hình "khoai tây nóng" (Hot Potato)------ một cấu trúc không có khả năng khóa, lưu trữ, xóa hoặc sao chép. Các mô-đun thực hiện mô hình này thường sẽ có một hàm để khởi tạo cấu trúc và một hàm để hủy cấu trúc. Bởi vì cấu trúc "khoai tây nóng" không có khả năng vứt bỏ, khóa hoặc lưu trữ, nên có thể đảm bảo rằng hàm hủy (destroy) của nó sẽ được gọi để tiêu thụ nó. Mặc dù chúng tôi có thể truyền nó đến bất kỳ hàm nào khác trong bất kỳ mô-đun nào, nhưng cuối cùng nó vẫn cần được xử lý trong hàm hủy. Bởi vì không có cách nào khác để xử lý nó, và trình xác minh yêu cầu phải xử lý nó vào cuối giao dịch (nó không thể bị vứt bỏ tùy ý, vì không có khả năng vứt bỏ).
Hãy xem cách tận dụng điều này để thực hiện cho vay chớp nhoáng.
- Hợp đồng cho vay chớp nhoáng thực hiện một cấu trúc biên nhận "khoai tây nóng" (Receipt)
- Khi thực hiện cho vay thông qua hàm cho vay, nó sẽ gửi cho người gọi hai đối tượng------ số tiền yêu cầu (một đồng tiền) và một biên nhận, là bản ghi cần hoàn trả số tiền vay.
- Sau đó, người vay có thể sử dụng số tiền đã nhận để thực hiện các thao tác mà họ cần (như giao dịch chênh lệch giá).
- Khi người vay hoàn thành thao tác dự kiến của mình, họ cần gọi hàm hoàn trả, hàm này sẽ nhận số tiền vay và biên nhận làm tham số. Hàm này được đảm bảo sẽ được gọi trong cùng một giao dịch, vì người gọi không có cách nào khác để thoát khỏi phiên bản biên nhận (nó không cho phép bị vứt bỏ hoặc nhúng vào một đối tượng khác, điều này là yêu cầu của trình xác minh).
- Hàm hoàn trả kiểm tra xem số tiền đã trả có đúng hay không bằng cách đọc thông tin vay được nhúng trong biên nhận.
Các đặc tính an toàn tài nguyên của Move làm cho cho vay chớp nhoáng trong Move trở nên khả thi mà không cần sử dụng tái nhập hoặc tự kiểm tra. Chúng đảm bảo rằng biên nhận không thể bị mã không đáng tin cậy sửa đổi, và nó cần phải được trả lại cho hàm hoàn trả vào cuối giao dịch. Như vậy, chúng tôi có thể đảm bảo rằng số tiền đúng sẽ được trả lại trong cùng một giao dịch.
Chức năng này hoàn toàn được thực hiện bằng các nguyên thủy ngôn ngữ cơ bản, việc thực hiện của Move sẽ không bị ảnh hưởng bởi các vấn đề tích hợp như trong Solana, vì cái sau cần giao dịch được thiết lập cẩn thận. Không có bất kỳ phức tạp nào được đẩy về phía khách hàng.
Cho vay chớp nhoáng rất tốt để minh họa cách mà hệ thống kiểu tuyến tính và bảo đảm an toàn tài nguyên của Move cho phép chúng tôi biểu đạt chức năng theo cách mà các ngôn ngữ lập trình khác không thể thực hiện.
5.2. Khóa quyền mint (Mint Authority Lock)
"Hợp đồng thông minh khóa quyền mint" mở rộng chức năng mint token, cho phép nhiều bên trong danh sách trắng (authority) mint token. Các chức năng cần thiết của hợp đồng thông minh này như sau (cũng áp dụng cho cả triển khai trên Solana và Sui):
- Bên có quyền mint token gốc tạo một "khóa mint", điều này sẽ cho phép hợp đồng thông minh của chúng tôi giám sát việc mint. Người gọi trở thành quản trị viên của khóa mint này.
- Quản trị viên có thể tạo thêm quyền mint cho khóa này, có thể cấp quyền cho các bên khác, và cho phép họ sử dụng khóa này để mint token bất cứ lúc nào.
- Mỗi quyền mint có giới hạn số lượng token có thể mint mỗi ngày.
- Quản trị viên có thể cấm (và gỡ bỏ cấm) bất kỳ bên nào có quyền mint nào vào bất kỳ lúc nào.
- Quyền của quản trị viên có thể được chuyển nhượng cho bên khác.
Hợp đồng thông minh này có thể được sử dụng, ví dụ, để chuyển quyền mint token cho các người dùng hoặc hợp đồng thông minh khác, trong khi bên có quyền mint gốc (quản trị viên) vẫn giữ quyền kiểm soát đối với việc mint. Nếu không, chúng tôi sẽ phải giao toàn bộ quyền kiểm soát việc mint cho bên khác, điều này không lý tưởng, vì chúng tôi chỉ có thể tin tưởng rằng nó sẽ không lạm dụng quyền lực đó. Hơn nữa, việc cung cấp quyền cho nhiều bên cũng không khả thi.
Triển khai hoàn chỉnh của các hợp đồng thông minh này có thể được tìm thấy ở đây (Solana) và ở đây (Sui).
Lưu ý: Vui lòng không sử dụng đoạn mã này trong sản xuất! Đây là mã mẫu, chỉ dành cho mục đích giáo dục. Mặc dù tôi đã kiểm tra chức năng của nó, nhưng tôi chưa thực hiện kiểm toán hoặc kiểm tra an toàn toàn diện.
Bây giờ hãy cùng xem mã này, xem cách triển khai có gì khác biệt. Dưới đây là ảnh chụp màn hình mã hoàn chỉnh của hợp đồng thông minh này trên Solana và Sui.

Có thể nhận thấy rằng, đối với cùng một chức năng, quy mô của triển khai Solana gấp hơn hai lần Sui (230 LOC so với 104). Đây là một vấn đề lớn, vì mã ít hơn thường có nghĩa là ít lỗi hơn và thời gian phát triển ngắn hơn.
Vậy, những dòng mã bổ sung này đến từ đâu trong Solana? Nếu chúng ta xem xét kỹ mã của Solana, chúng ta có thể chia nó thành hai phần------ thực hiện lệnh (logic hợp đồng thông minh) và kiểm tra tài khoản. Thực hiện lệnh gần giống với trường hợp của chúng tôi trên Sui------- Solana 136 dòng, Sui 104 dòng. Số dòng bổ sung đến từ việc tham chiếu hai cuộc gọi CPI (mỗi cuộc gọi khoảng 10 LOC). Sự khác biệt quan trọng nhất nằm ở kiểm tra tài khoản (được đánh dấu bằng màu đỏ trong ảnh chụp màn hình trên), điều này là cần thiết (trên thực tế là rất quan trọng) trong Solana, nhưng không phải trong Move. Kiểm tra tài khoản chiếm khoảng 40% của hợp đồng thông minh này (91 LOC).
Move không cần kiểm tra tài khoản. Việc giảm LOC có thể mang lại lợi ích, nhưng việc loại bỏ kiểm tra tài khoản cũng rất cần thiết. Bởi vì thực tế chứng minh rằng việc thực hiện đúng những kiểm tra này là rất khó khăn, và nếu bạn mắc phải một lỗi nhỏ, thường sẽ dẫn đến lỗ hổng nghiêm trọng và mất mát tài sản của người dùng. Thực tế, một số lỗ hổng lớn nhất (về mất mát tài sản của người dùng) trong hợp đồng thông minh Solana là do các cuộc tấn công thay thế tài khoản gây ra bởi việc kiểm tra tài khoản không đúng cách.
-Wormhole (336 triệu USD) - https://rekt.news/wormhole-rekt/
-Cashio (48 triệu USD) - https://rekt.news/cashio-rekt/
-Crema Finance (8.8 triệu USD) - https://rekt.news/crema-finance-rekt/
Vậy, Move làm thế nào để không có những kiểm tra này mà vẫn an toàn? Hãy cùng xem xét kỹ lưỡng vai trò thực tế của những kiểm tra này. Đây là kiểm tra tài khoản cần thiết cho lệnh mint_to (người có quyền mint gọi lệnh này để mint token):

Có 6 kiểm tra (được đánh dấu bằng màu đỏ):
Kiểm tra xem tài khoản khóa được cung cấp có thuộc về hợp đồng thông minh này và có kiểu MintLock hay không. Cần truyền vào khóa vì nó sẽ được sử dụng cho cuộc gọi CPI đến chương trình token để thực hiện mint (nó lưu trữ quyền hạn).
Kiểm tra xem tài khoản quyền mint được cung cấp có thuộc về khóa được cung cấp hay không. Tài khoản quyền mint giữ trạng thái quyền (khóa công khai của nó, nó có bị cấm hay không, v.v.).
Kiểm tra xem người gọi lệnh có sở hữu khóa cần thiết hay không (quyền hạn cần thiết đã ký giao dịch này).
Cần truyền vào tài khoản đích của token, vì chương trình token sẽ thay đổi nó trong cuộc gọi CPI (tăng số dư). Kiểm tra mint ở đây không thực sự cần thiết, vì nếu tài khoản sai được truyền vào, cuộc gọi CPI sẽ thất bại, nhưng kiểm tra này vẫn là một thực hành tốt.
Tương tự như 4.
Kiểm tra xem tài khoản chương trình token có được truyền vào đúng cách hay không.
- Chúng ta có thể thấy rằng, kiểm tra tài khoản (trong ví dụ này) được chia thành năm loại:
- Kiểm tra quyền sở hữu tài khoản (1, 2, 4, 5)
- Kiểm tra kiểu tài khoản (1, 2, 4, 5)
- Kiểm tra phiên bản tài khoản (kiểm tra xem tài khoản có kiểu đúng hay không) (2, 5)
- Kiểm tra chữ ký tài khoản (3)
- Kiểm tra địa chỉ tài khoản chương trình (6)
Nhưng trong Move, không có kiểm tra tài khoản hoặc thứ gì tương tự, chỉ có chữ ký chức năng:

Hàm mint_balance chỉ cần bốn tham số. Trong bốn tham số này, chỉ có lock và cap đại diện cho đối tượng (có chút tương tự như tài khoản).
Trong Solana, chúng tôi cần khai báo 6 tài khoản và thực hiện các kiểm tra khác nhau một cách thủ công, trong khi trong Move, chúng tôi chỉ cần truyền vào 2 đối tượng và không cần kiểm tra rõ ràng, điều này được thực hiện như thế nào?
Trong Move, một số kiểm tra được thực hiện một cách minh bạch bởi thời gian chạy, một số được thực hiện một cách tĩnh bởi trình xác minh trong quá trình biên dịch, và một số thì không cần thiết về mặt cấu trúc.
Kiểm tra quyền sở hữu tài khoản------ Move có hệ thống kiểu, vì vậy thiết kế này là không cần thiết. Một cấu trúc Move chỉ có thể được sửa đổi thông qua các hàm được định nghĩa trong mô-đun của nó, không thể sửa đổi trực tiếp. Xác minh mã byte đảm bảo rằng các phiên bản cấu trúc có thể tự do chảy vào mã không đáng tin cậy (các mô-đun khác) mà không bị thay đổi trái phép.
Kiểm tra kiểu tài khoản------ không cần thiết, vì kiểu Move tồn tại trong toàn bộ hợp đồng thông minh. Định nghĩa kiểu được nhúng vào tệp nhị phân của mô-đun (được phát hành trên chuỗi khối và thực thi bởi máy ảo). Trình xác minh sẽ kiểm tra, trong quá trình biên dịch/phát hành, khi hàm của chúng tôi được gọi, liệu các kiểu đúng có được truyền vào hay không.
Kiểm tra phiên bản tài khoản------ trong Move (đôi khi cũng như trong Solana), bạn sẽ làm điều này trong thân hàm. Trong trường hợp đặc biệt này, điều này là không cần thiết, vì các tham số kiểu tổng quát T của các tham số lock và cap buộc yêu cầu đối với cap (quyền mint).











