paint-brush
Cách để mất 13 đô la tiền của người dùng (với tư cách là nhà phát triển chuỗi khối)từ tác giả@msokola
579 lượt đọc
579 lượt đọc

Cách để mất 13 đô la tiền của người dùng (với tư cách là nhà phát triển chuỗi khối)

từ tác giả Matéush7m2022/12/20
Read on Terminal Reader

dài quá đọc không nổi

Các loại dữ liệu rất quan trọng và bỏ bê chúng sẽ dẫn đến hậu quả nghiêm trọng. Các ngôn ngữ hiện đại như JavaScript và Python đang sử dụng "gõ vịt" để xác định loại biến. Trong hầu hết các trường hợp, trình thông dịch ngôn ngữ của bạn sẽ xử lý các loại theo đúng cách. Vấn đề bắt đầu khi chương trình của bạn cần chạy các phương trình (thậm chí đơn giản) với số lượng lớn.
featured image - Cách để mất 13 đô la tiền của người dùng (với tư cách là nhà phát triển chuỗi khối)
Matéush HackerNoon profile picture
0-item

Chính phủ nói rằng chúng ta không bị suy thoái, nhưng đồng thời, chúng ta cũng nghe nói về lạm phát tăng vọt, lãi suất tăng và tình trạng sa thải nhân công ở hầu hết mọi lĩnh vực của nền kinh tế.


Mặc dù tiền điện tử và TradFi bị ảnh hưởng nặng nề nhất, nhưng nhiều công ty vẫn đang xây dựng mã thông báo, giao thức và sản phẩm DeFi của họ. bạn có phải là một trong số họ không?


Hôm nay, tôi sẽ nói về các loại dữ liệu và chờ đã. Tôi có một điều rất quan trọng để nói. Bạn có thể hình dung tôi là một giáo sư hơn 60 tuổi từ MIT tra tấn sinh viên bằng những bài giảng về những chủ đề không còn quan trọng nữa. Nhưng điều đó không đúng.


Các kiểu dữ liệu vẫn rất quan trọng và việc bỏ qua chúng sẽ dẫn đến những hậu quả nghiêm trọng. Tôi sẽ cố gắng lướt qua tất cả các vấn đề tiềm ẩn và giải quyết chúng một cách ngắn gọn để bạn không thấy phí 8 phút dành cho việc đọc bài viết này.


Các ngôn ngữ hiện đại như JavaScript và Python đang sử dụng "gõ vịt" để xác định loại biến. Nếu chúng ta gán loại công thức a = 2 + 2 này cho một biến, trình thông dịch ngôn ngữ sẽ biết nó đang xử lý các con số và nó sẽ thực hiện các phép toán trên chữ này.


Việc gõ vịt có thể được giải thích bằng câu này: “Nếu nó đi như một con vịt và nó kêu như một con vịt, thì nó phải là một con vịt”. Khi bạn xem xét kỹ hơn ý nghĩa của nó - nó có ý nghĩa hoàn hảo. Nếu chữ có chữ cái và số, thì nó phải là một chuỗi, và điều đó rõ ràng. Nhưng nếu nó có số thì sao?


Nó có phải là boolean , integer , decimal , float hay date . Trong hầu hết các trường hợp, trình thông dịch ngôn ngữ của bạn sẽ xử lý các loại theo đúng cách. Vấn đề bắt đầu khi chương trình của bạn cần chạy các phương trình (thậm chí đơn giản) với số lượng lớn.


“Nếu nó đi như một con vịt và nó kêu như một con vịt, thì nó phải là một con vịt”, phải không? Thực ra không phải vậy.


Tóm tắt mệnh giá Ethereum

Trong các đoạn tiếp theo, tôi đang đề cập đến các mệnh giá chung của Ethereum - weigwei . Hãy để tôi giới thiệu ngắn gọn với bạn để chúng tôi nói ngôn ngữ chung.


Mệnh giá nhỏ nhất là 1 wei1 ether bằng 1.000.000.000.000.000.000 Wei (18 số không) . Tôi nhắc lại - 18 số không. Thật khó để nghĩ về những con số lớn như vậy nhưng chúng tạo ra sự khác biệt và quan trọng rất nhiều.


Mệnh giá phổ biến tiếp theo là 1 gwei . 1 ether bằng 1.000.000.000 gwei (9 số không) . Gwei dễ chịu hơn đối với con người - cuối cùng thì ai cũng muốn trở thành triệu phú, phải không? (nháy mắt)


Hãy tổng hợp lại - 1 ether bằng:

  • 1.000.000.000 gwei (9 số không)
  • 1.000.000.000.000.000.000 wei (18 số không)


Lưu ý kỹ thuật: Ethereum có hai lớp - lớp thực thi và lớp đồng thuận. Lớp thực thi đang sử dụng wei để biểu thị các giá trị ether và lớp đồng thuận đang sử dụng gwei. Nếu bạn là nhà phát triển blockchain, bạn cần học cách tương tác với cả hai.

Ví dụ trong thế giới thực: Mẹo của Stakefish và Nhóm MEV

Tôi là một kỹ sư phần mềm tại stakefish . Tôi chịu trách nhiệm xây dựng bảng sản phẩm DeFi của chúng tôi và một trong những bảng gần đây nhất là mẹo và nhóm MEV của chúng tôi cho Ethereum.


Bắt đầu từ ngày 15 tháng 9 năm 2022, tất cả những người xác thực đều đủ điều kiện nhận mẹo giao dịch và có thể tham gia MEV để kiếm thêm phần thưởng. Mẹo giao dịch và MEV kiếm được khi trình xác thực đề xuất một khối mới.


Chúng tôi đã quyết định xây dựng một hợp đồng thông minh thu thập tất cả phần thưởng vào một kho tiền chung và cho phép người dùng yêu cầu phần của họ từ đó. Tôi không cố gắng quảng cáo sản phẩm của mình, nhưng tôi cần đặt bối cảnh cho bài viết này.


Nếu quan tâm hơn đến sản phẩm này, bạn có thể đọc thêm tại đây . Tôi không bán cho bạn bất cứ thứ gì ngoài kinh nghiệm của tôi.


Như tôi đã đề cập, chúng tôi có một hợp đồng thông minh nhận mẹo giao dịch và phần thưởng MEV do người xác thực kiếm được. Nó có nghĩa là hợp đồng thông minh của chúng tôi có số dư khá lớn. Hiện tại là hơn 963 ether ($1,1 triệu) và chúng tôi có 8671 trình xác thực đóng góp cho nó.


Phần quan trọng chịu trách nhiệm đồng bộ hóa giữa thực thi Ethereum và lớp đồng thuận là Oracle . Đây là một hệ thống rất quan trọng cho phép chúng tôi xác định những trình xác thực nào đang đóng góp cho nhóm.


Lời tiên tri được viết bằng Python, nhưng nó có thể được viết bằng JavaScript - vấn đề vẫn không thay đổi và tôi sẽ sớm chứng minh điều đó.


Hãy đi sâu vào mã!

Tại sao các kiểu dữ liệu lại quan trọng

Số dư của hợp đồng thông minh hiện tại là 963.135.554.442.603.402.422 wei (963 ether). Con số này không chỉ khó hiểu đối với con người mà còn đối với máy tính (chính xác là trình thông dịch ngôn ngữ). Hãy kiểm tra JavaScript:


 const vault_balance = parseInt("963135554442603402422") console.log(vault_balance) // 963135554442603400000 (lost 2422 wei in total)


Tôi chỉ chuyển số dư từ string sang int và tôi đã thiếu 2422 wei . Chúng tôi chưa chạy bất kỳ phương trình nào.


Số dư hợp đồng thông minh cao như vậy là nhờ có nhiều người xác thực đóng góp vào đó. Bây giờ, hãy tính toán phần chia sẻ trung bình của trình xác thực trong số dư hợp đồng hiện tại:


 const vault_balance = parseInt("963135554442603402422") const validator_count = 8671 const avg_validator_contribution = vault_balance / validator_count // 111075487768723730 (lost 7 wei per validator)


Chia sẻ trung bình là 0,111 ether. Nhưng số tiền này không chính xác - chúng tôi thực sự thiếu 7 wei. Tổng cộng là 60,697 wei (7 wei nhân với 8671 trình xác nhận). Tôi sẽ hiển thị số chính xác sau này.


Tiếp tục đi xuống hố sâu thua lỗ - hãy tính tổng số phần thưởng cho mỗi trình xác thực nhất định. Hãy nhớ rằng người dùng cần ký gửi 32 ether để bắt đầu trình xác thực, vì vậy tôi sẽ khấu trừ số tiền đó từ số dư của trình xác thực.


Và tôi sẽ lấy ví dụ về một trình xác thực ngẫu nhiên đóng góp vào hợp đồng thông minh có số dư là 32.779 ether.


 const vault_balance = parseInt("963135554442603402422") // (lost 2422 wei) const validator_count = 8671 const avg_validator_contribution = vault_balance / validator_count // (lost 7 wei) const initial_deposit = parseInt("32000000000000000000") const validator_balance = parseInt("32779333896000000000") const total_validator_rewards = validator_balance - initial_deposit + avg_validator_contribution // 890409383768723700 (lost 23 wei per validator)


Tổng phần thưởng mà trình xác nhận này kiếm được bằng 0,8904 ether, nhưng giá trị này cũng không chính xác. Tại thời điểm này, chúng tôi đã tính sai tổng cộng 199,443 wei (23 wei nhân với 8671 trình xác nhận). Như bạn có thể thấy, cách tính số này không bền vững.

Điều gì đi sai?

Có hai vấn đề với đoạn mã trên:


  • Trong JavaScript, giá trị an toàn tối đa cho số nguyên chỉ bằng 2^53 - 1 . Điều đó có nghĩa là nó có thể xử lý tới 9007199254740991 wei (0,009 ether)


  • Về mặt kỹ thuật, chúng tôi có thể sử dụng BigInt nhưng chúng tôi sẽ gặp vấn đề với phép chia. Chúng tôi sẽ kết thúc với các giá trị "thả nổi". Số tiền thả nổi là gốc rễ của mọi tội lỗi trong lĩnh vực tài chính vì chúng chỉ mang tính gần đúng. Nó có nghĩa là họ mất độ chính xác. Chúng ta cần sử dụng số thập phân. (Sự khác biệt chính giữa số thập phân và số float là số thập phân lưu trữ giá trị chính xác và số float gần đúng.)


Nếu bạn đã từng thực hiện bất kỳ mã hóa nào liên quan đến Ethereum bằng JavaScript, thì bạn hẳn đã nghe nói về ethers.js . Thư viện này chứa tất cả các tiện ích cần thiết để tương tác với chuỗi khối. Để khắc phục sự cố trên, chúng tôi sẽ sử dụng một trong những công cụ có tên là BigNumber hỗ trợ các số cực lớn và xử lý các số thập phân theo đúng cách.


Hãy làm nó!


 const vault_balance = BigNumber.from("963135554442603402422") // no loss const validator_count = BigNumber.from(8671) const avg_validator_contribution = vault_balance.div(validator_count) // no loss // 111075487768723723 const initial_deposit = BigNumber.from("32000000000000000000") const validator_balance = BigNumber.from("32779333896000000000") const total_validator_rewards = validator_balance.sub(initial_deposit).add(avg_validator_contribution) // 890409383768723723


Như bạn có thể thấy, bây giờ chúng tôi đã kết thúc với con số chính xác. Làm thế nào để tôi biết đây thực sự là con số đúng? Tôi sẽ lặp lại bài tập tương tự trong Python để chứng minh rằng tôi đúng.

Hãy dùng thử bằng Python

Python hỗ trợ các số nguyên dài, vì vậy các giá trị sẽ không bị cắt đột ngột như chúng ta đã thấy trong JavaScript. Thật không may, nó vẫn xác định tất cả các số thực là float theo mặc định:


 vault_balance = int("963135554442603402422") # no loss validator_count = 8671 avg_validator_contribution = vault_balance / validator_count # 111075487768723728 (5 wei too much) initial_deposit = int("32000000000000000000") validator_balance = int("32779333896000000000") total_validator_rewards = validator_balance - initial_deposit + avg_validator_contribution # 890409383768723712 (lost 11 wei)


Tự hỏi chính xác nó mất độ chính xác ở đâu? Bộ phận avg_validator_contribution thành float thay vì decimal . Đoạn mã chính xác sẽ trông như thế này:


 vault_balance = Decimal("963135554442603402422") validator_count = Decimal(8671) avg_validator_contribution = vault_balance / validator_count # 111075487768723723 initial_deposit = Decimal("32000000000000000000") validator_balance = Decimal("32779333896000000000") total_validator_rewards = validator_balance - initial_deposit + avg_validator_contribution # 890409383768723723


Bây giờ, các giá trị được trả về bởi Python và JavaScript là chính xác. Kiểm tra của riêng bạn!


Những loại tổn thất này là nhỏ và có thể dễ dàng bỏ qua. Thông thường, chúng tôi phát hiện ra chúng khi chúng gộp lại theo thời gian và phát triển thành những con số đáng kể.


Những tình huống như vậy luôn làm đau đầu không chỉ các nhà phát triển mà còn các bộ phận khác như tài chính hoặc pháp lý. Bạn phải luôn kiểm tra các công thức của mình và không bao giờ sử dụng các số tròn đẹp để làm như vậy!


Nó sẽ có nghĩa là cả thế giới đối với tôi nếu bạn theo dõi tôi trên Twitter . Tôi đang tập trung hoạt động của mình vào công nghệ phần mềm và chuỗi khối. Tôi đang mã nguồn mở hầu hết công việc của mình, vì vậy bạn có thể muốn kiểm tra GitHub của tôi .