
Hợp đồng đã xác minh, proxy và chiếc khóa nâng cấp không ai nhắc tới
Trên trình duyệt khối có một dấu tích xanh ghi Mã nguồn hợp đồng đã được xác minh, và vô số người đọc nó thành «hợp đồng này an toàn».
Nó không có nghĩa như vậy. Nó mang một nghĩa hẹp hơn nhiều và hữu ích hơn nhiều, và chính việc lẫn lộn hai điều ấy là cách những người cẩn thận rốt cuộc lại phê duyệt những thứ mà lẽ ra họ chẳng bao giờ đồng ý.
«Đã xác minh» thực sự chứng nhận điều gì
Hợp đồng được triển khai dưới dạng bytecode — chỉ thị máy, trên thực tế không đọc nổi. Xác minh là hành động tải lên mã nguồn gốc và để trình duyệt khối xác nhận rằng biên dịch nó, với đúng phiên bản và thiết lập trình biên dịch đã khai báo, sẽ cho ra chính xác đoạn bytecode vốn đang nằm ở địa chỉ đó.
Toàn bộ tuyên bố chỉ có vậy: mã nguồn bạn đang đọc đúng là mã nguồn đang chạy.
Đó là một bảo đảm có thật và đáng giá. Không có nó, bạn chẳng có cách nào biết đoạn mã trông đẹp đẽ trên GitHub của một dự án có liên quan gì đến thứ mà giao dịch của bạn sắp chạm vào hay không.
Nhưng hãy để ý mọi điều nó không nói. Xác minh không phải kiểm toán. Không ai đánh giá xem mã có đúng đắn không, có lỗi không, hay có làm bất cứ điều gì giống như tên các hàm gợi ý không. Một hợp đồng có hàm tên claimRewards chuyển toàn bộ số dư của bạn cho người triển khai vẫn có thể đã xác minh, và việc xác minh ấy hoàn toàn trung thực — nó chứng nhận đúng sự thật rằng vụ trộm chính là điều đoạn mã nói nó làm.
Đã xác minh nghĩa là đọc được, chứ không phải đáng tin. Nó chuyển câu hỏi từ «thứ này là gì?» sang «tôi có hiểu điều vừa đọc không?», đó là một bước tiến, nhưng nó không trả lời hộ bạn câu thứ hai.
Chiều ngược lại cũng quan trọng. Một hợp đồng chưa xác minh không phải bằng chứng của ác ý — rất nhiều lần triển khai chính đáng chẳng bao giờ kịp xác minh. Nhưng nó có nghĩa là không ai — không công cụ nào bạn dùng, không nhà nghiên cứu nào có thể đã cảnh báo bạn — đọc được thứ bạn sắp tương tác. Thận trọng với điều đó là hợp lý.
Rồi proxy làm mọi thứ tệ hơn
Đây là chỗ mặt đất dịch chuyển, và cũng là phần thường bị bỏ qua nhất.
Phần lớn những hợp đồng không tầm thường mà bạn tương tác không phải hợp đồng đơn lẻ. Chúng là proxy. Proxy là địa chỉ bạn nhìn thấy, và nó giữ toàn bộ dữ liệu — số dư, hạn mức, trạng thái. Nhưng nó hầu như không chứa logic. Thay vào đó, nó chuyển tiếp mọi lời gọi tới một hợp đồng triển khai riêng, rồi đơn giản thực thi những gì hợp đồng ấy nói.
Lý do là khả năng nâng cấp. Hợp đồng bất biến không thể vá, nên một lỗi là vĩnh viễn và một tính năng thiếu là thiếu mãi mãi. Trỏ proxy sang một triển khai mới cho phép đội ngũ sửa lỗi và ra cải tiến mà không phải yêu cầu từng người dùng di chuyển. Xét xem bao nhiêu giá trị đã mất vì những lỗi không sửa được, đây là một lựa chọn kỹ thuật có thể biện hộ.
Nó có một hệ quả dễ bỏ sót. Khi bạn tra một proxy trên trình duyệt khối và thấy nó đã xác minh, bạn đã xác minh logic chuyển tiếp, chứ không phải hành vi. Đoạn mã thật sự quyết định điều gì xảy ra với tiền của bạn nằm ở địa chỉ triển khai, nơi trình duyệt khối có thể hiển thị nổi bật hoặc không, và nơi ấy có thể bị thay thế.
Vậy nên câu hỏi thật sự chưa bao giờ là «hợp đồng này đã xác minh chưa?». Nó là:
Ai có thể thay đổi hành vi của hợp đồng này, và nhanh đến đâu?
Khóa quản trị mới là mô hình an toàn thật sự
Mọi hợp đồng nâng cấp được đều có ai đó thực hiện được việc nâng cấp. Quyền hạn ấy chính là mô hình an toàn, bất kể đoạn mã hiện thời tốt đến đâu.
Phổ trải từ đáng báo động tới hợp lý:
Một tài khoản do bên ngoài sở hữu, đơn lẻ. Một khóa riêng, trên máy của ai đó, có thể hoán đổi logic của hợp đồng đang giữ tiền bạn. Nếu nó bị lừa lấy, bị trộm, hay chủ nhân bị ép buộc, hợp đồng trở thành bất cứ thứ gì kẻ tấn công muốn. Cách sắp đặt này phổ biến hơn mức đáng có.
Một ví đa chữ ký. Nhiều người ký phải đồng thuận. Tốt hơn hẳn — nó loại bỏ điểm hỏng đơn lẻ — dù vẫn đáng biết có bao nhiêu người ký, và họ có thật sự là những người độc lập hay chỉ là bốn chiếc máy tính của cùng một đội.
Một ví đa chữ ký sau một khóa thời gian. Việc nâng cấp được công bố trên chuỗi và chỉ thực thi được sau một độ trễ cố định, thường là 24 đến 72 giờ. Đây mới là điều có ý nghĩa, vì nó thay đổi hoàn toàn vị thế của bạn: bạn được báo trước về thay đổi, và có một cửa sổ để rút ra trước khi nó có hiệu lực. Khóa thời gian không ngăn được một bản nâng cấp độc hại. Nó khiến bản ấy có thể sống sót qua.
Không có đường nâng cấp nào cả. Hợp đồng là bất biến. Tính dự đoán được cao nhất, không có lối cứu vãn nếu tìm thấy lỗi. Đó là lựa chọn chính đáng, thường do các giao thức đã được thử lửa đủ lâu để dám đặt cược.
Chẳng lựa chọn nào tự động đúng. Nhưng khác biệt giữa «một khóa» và «đa chữ ký cộng độ trễ hai ngày» là khác biệt giữa hy vọng không ai phạm sai lầm và có thời gian phản ứng khi ai đó phạm.
Vì sao điều này cắn đúng vào các phê duyệt
Đây là phần nối với một việc bạn thực sự làm thường xuyên.
Khi bạn cấp một phê duyệt token, bạn đang cho phép một địa chỉ di chuyển token của mình. Không phải một đoạn mã — một địa chỉ. Phê duyệt ấy nằm trong bộ nhớ của hợp đồng token và tồn tại vô thời hạn.
Nếu địa chỉ đó là một proxy nâng cấp được, thì logic có quyền di chuyển token của bạn có thể bị thay sau khi bạn đã phê duyệt, không cần bạn tham gia và không cần chữ ký mới nào từ bạn. Bạn đã soi xét một thứ và vẫn phơi mình trước một thứ khác.
Đó là lý do lời khuyên thường trực về việc thu hồi những phê duyệt bạn không còn dùng nặng ký hơn vẻ ngoài của nó. Không chỉ là chuyện một hợp đồng hóa ra có lỗi. Mà là một hạn mức không giới hạn cấp cho hợp đồng nâng cấp được chính là quyền trao cho bất cứ ai nắm khóa nâng cấp, suốt chừng nào bạn còn để ngỏ nó.
Đó cũng là giới hạn sắc nét nhất của việc mô phỏng giao dịch. Mô phỏng trước khi ký cho bạn biết một giao dịch làm gì với trạng thái hiện tại và bản triển khai hiện tại. Nó thực sự hữu ích, và nó không thể cho bạn biết chính phê duyệt ấy sẽ cho phép điều gì vào tháng sau.
SSP cho bạn thấy gì, và dừng lại ở đâu
Khi SSP Key trình bày một phê duyệt, nó giải mã dữ liệu lời gọi thay vì phô ra chuỗi thập lục phân thô. Nó nhận ra các bộ chọn ERC-20 chuẩn — approve, transfer, transferFrom, increaseAllowance, setApprovalForAll — và diễn đạt chúng bằng ngôn ngữ bình thường, kể cả gắn cờ các hạn mức không giới hạn, được định nghĩa trong mã là bất kỳ khoản nào bằng hoặc vượt 2^255.
Bộ giải mã cố ý dè dặt. Chính phần đầu của nó mô tả nó chỉ thuộc tầng trình bày: nó không bao giờ thay đổi thứ được ký, và nó hỏng theo hướng đóng. Một bộ chọn lạ, một độ dài sai, chuỗi thập lục phân méo mó, hay phần đệm địa chỉ phi chuẩn đều khiến nó không trả về gì và lùi về một hành động chung chung với chuỗi thô nằm sau mục «Nâng cao». Nó thà thừa nhận mình không biết còn hơn đoán chắc nịch rồi sai. Ký hiệu và số thập phân của token chỉ được gắn kèm khi biết chắc từ sổ đăng ký ngay trên thiết bị, không bao giờ suy đoán.
Giờ là ranh giới trung thực. Bộ giải mã không có khái niệm proxy. Trong đó chẳng có chỗ nào nhắc tới hợp đồng triển khai. Nó có thể nói với bạn, đúng và rõ ràng, «bạn đang cấp hạn mức USDC không giới hạn cho 0xABC…» — và nó không thể nói rằng 0xABC là một proxy, ai giữ khóa nâng cấp của nó, hay hành vi của nó có thể hoàn toàn khác vào tuần sau.
Đó không hẳn là một thiếu sót, mà là một ranh giới không bộ giải mã ví nào vượt qua được. Giải mã mô tả lời gọi đang ở trước mặt bạn. Việc đối tác có nâng cấp được hay không, và ai cai quản nó, là thuộc tính của thế giới rộng hơn, phải kiểm tra riêng. Chúng tôi thà nói rõ ranh giới còn hơn để một màn hình phê duyệt trông đầy tự tin ám chỉ một sự đầy đủ mà nó không có.
Một phép kiểm tra thực tế trước khi phê duyệt điều gì đáng kể
Nó đã xác minh chưa? Nếu chưa, hãy hiểu rằng bạn đang chỉ tin vào danh tiếng.
Nó có phải proxy không? Các trình duyệt khối có gắn nhãn — hãy tìm thông báo proxy, hoặc tùy chọn «Read as Proxy». Nếu có, bản triển khai mới là đoạn mã đáng kể.
Ai nâng cấp được nó, và có độ trễ không? Đây là câu hỏi giá trị nhất trong danh sách và cũng là câu hầu như không ai hỏi. Một dự án đã nghĩ tới điều này sẽ ghi lại. Một dự án chưa nghĩ, hoặc không chịu nói, thì đã nói với bạn điều gì đó rồi.
Phê duyệt có cần không giới hạn không? Thường là không. Phê duyệt đúng khoản bạn thực sự định tiêu sẽ giới hạn phơi nhiễm của bạn đúng bằng chừng ấy, bất kể sau này hợp đồng trở thành gì.
Thu hồi khi xong việc. Một hạn mức để ngỏ là một quyền thường trực, và nó sống lâu hơn sự chú ý của bạn.
Điều chỉnh trong đầu đáng làm thì nhỏ nhưng thay đổi rất nhiều. Xác minh cho bạn biết đoạn mã nào đang chạy ngay lúc này. Khả năng nâng cấp cho bạn biết ai quyết định đoạn mã nào sẽ chạy ngày mai. Gần như toàn bộ rủi ro nằm ở câu hỏi thứ hai, còn gần như toàn bộ sự trấn an được đưa ra chỉ đáp lại câu thứ nhất.


