Kỹ thuật và lưu ý về tính toán số trong hợp đồng thông minh Rust

Nhật ký phát triển hợp đồng thông minh Rust (7): Tính toán số lượng

Bài viết này sẽ khám phá vấn đề tính toán số học trong lập trình hợp đồng thông minh Rust, chủ yếu bao gồm các vấn đề về độ chính xác của phép toán số thực, độ chính xác của phép toán số nguyên, cũng như cách viết hợp đồng thông minh Rust cho tính toán số học.

1. Vấn đề độ chính xác trong phép toán số thực

Ngôn ngữ Rust hỗ trợ tính toán số thực một cách tự nhiên, nhưng việc tính toán số thực gặp phải vấn đề về độ chính xác không thể tránh khỏi. Khi viết hợp đồng thông minh, không nên sử dụng phép toán số thực, đặc biệt là khi xử lý các tỷ lệ hoặc lãi suất liên quan đến quyết định kinh tế/tài chính quan trọng.

Loại số thực độ chính xác gấp đôi f64 trong ngôn ngữ Rust tuân theo tiêu chuẩn IEEE 754, sử dụng hệ số khoa học với cơ số 2 để biểu diễn. Tuy nhiên, một số số thập phân như ( chẳng hạn như 0.7) không thể được biểu diễn chính xác bằng số thực có độ dài hữu hạn, sẽ có hiện tượng "làm tròn".

Lấy ví dụ phân phối 0.7 NEAR token cho mười người dùng trên chuỗi công khai NEAR, kết quả tính toán thực tế sẽ xuất hiện tình huống không chính xác:

gỉ #[test] fn precision_test_float() { let amount: f64 = 0.7;
let divisor: f64 = 10.0;
let result_0 = amount / divisor;
println!("Giá trị của số tiền: {:.20}", amount); assert_eq!(result_0, 0.07, ""); }

Kết quả thực thi cho thấy giá trị của amount không phải là 0.7 chính xác, mà là một giá trị rất gần gũi là 0.69999999999999995559. Kết quả của phép chia tiếp theo cũng trở nên không chính xác là 0.06999999999999999, chứ không phải là 0.07 như mong đợi.

Để giải quyết vấn đề này, có thể xem xét việc sử dụng số cố định. Trong NEAR Protocol, thường sử dụng 10^24 làm mẫu số, tức là 10^24 yoctoNEAR tương đương với 1 token NEAR.

Mã kiểm tra đã được sửa đổi như sau:

gỉ #[test] fn precision_test_integer() { let N: u128 = 1_000_000_000_000_000_000_000_000;
let amount: u128 = 700_000_000_000_000_000_000_000; let divisor: u128 = 10;
let result_0 = amount / divisor; assert_eq!(result_0, 70_000_000_000_000_000_000_000, ""); }

Cách này có thể đạt được kết quả tính toán chính xác: 0.7 NEAR / 10 = 0.07 NEAR.

2. Vấn đề độ chính xác trong tính toán số nguyên Rust

Mặc dù việc sử dụng phép toán số nguyên có thể giải quyết vấn đề mất độ chính xác của số thực trong một số kịch bản, nhưng kết quả tính toán số nguyên cũng không hoàn toàn chính xác và đáng tin cậy. Một số lý do ảnh hưởng đến độ chính xác của phép toán số nguyên bao gồm:

2.1 Thứ tự thực hiện

Thứ tự trước và sau của phép nhân và phép chia cùng một độ ưu tiên toán học có thể ảnh hưởng trực tiếp đến kết quả tính toán. Ví dụ:

gỉ #[test] fn precision_test_div_before_mul() { let a: u128 = 1_0000; let b: u128 = 10_0000; let c: u128 = 20;

let result_0 = a.checked_mul(c).expect("ERR_MUL")
                .checked_div(b).expect("ERR_DIV");

let result_1 = a.checked_div(b).expect("ERR_DIV")
                .checked_mul(c).expect("ERR_MUL");

assert_eq!(result_0,result_1,"");

}

Kết quả kiểm tra cho thấy, kết quả tính toán của result_0 và result_1 khác nhau. Điều này là do đối với phép chia số nguyên, độ chính xác nhỏ hơn mẫu số sẽ bị loại bỏ. Khi tính toán result_1, (a / b) sẽ mất độ chính xác tính toán trước tiên, trở thành 0; trong khi khi tính toán result_0, việc tính a * c trước có thể tránh mất độ chính xác.

2.2 quy mô quá nhỏ

Khi liên quan đến các phép toán ở quy mô nhỏ hơn, cũng có thể xuất hiện vấn đề về độ chính xác:

gỉ #[test] fn precision_test_decimals() { let a: u128 = 10; let b: u128 = 3; let c: u128 = 4; let decimal: u128 = 100_0000;

let result_0 = a.checked_div(b).expect("ERR_DIV")
                .checked_mul(c).expect("ERR_MUL");

let result_1 = a.checked_mul(decimal).expect("ERR_MUL")
                .checked_div(b).expect("ERR_DIV")
                .checked_mul(c).expect("ERR_MUL")
                .checked_div(decimal).expect("ERR_DIV");

println!("{}:{}", result_0, result_1);
assert_eq!(result_0, result_1, "");

}

Kết quả kiểm tra cho thấy, kết quả của result_0 và result_1 khác nhau, và result_1 = 13 gần hơn với giá trị tính toán thực tế dự kiến là 13.3333....

3. Cách viết hợp đồng thông minh Rust cho tính toán số

Để nâng cao độ chính xác, có thể thực hiện các biện pháp bảo vệ sau:

3.1 Điều chỉnh thứ tự thao tác tính toán

Đặt phép nhân của số nguyên ưu tiên hơn phép chia của số nguyên.

3.2 Tăng cường độ lớn của số nguyên

Sử dụng đại lượng lớn hơn, tạo ra phân số lớn hơn. Ví dụ, có thể biểu thị 5.123 NEAR là 5.123 * 10^10 = 51_230_000_000.

3.3 Tổn thất độ chính xác của phép toán tích lũy

Đối với vấn đề độ chính xác của phép toán số nguyên không thể tránh khỏi, có thể xem xét ghi lại tổn thất độ chính xác tích lũy của phép toán. Ví dụ:

gỉ const USER_NUM: u128 = 3;

u128 { let token_to_distribute = offset + amount; let per_user_share = token_to_distribute / USER_NUM; println!("per_user_share {}", per_user_share); let recorded_offset = token_to_distribute - per_user_share * USER_NUM; recorded_offset }

#( fn record_offset_test)[test] { let mut offset: u128 = 0; cho i trong 1..7 { println!("Round {}", i); offset = distribute(to_yocto)"10"(, offset(; println!)"Offset {}\n", offset); } }

Phương pháp này có thể tích lũy và phân phối lại các token chưa được phân phát do mất độ chính xác.

( 3.4 Sử dụng thư viện Rust Crate rust-decimal

Thư viện này phù hợp cho các phép toán tài chính với số thập phân cần tính toán chính xác mà không có sai số làm tròn.

) 3.5 Xem xét cơ chế làm tròn

Khi thiết kế hợp đồng thông minh, vấn đề làm tròn thường tuân theo nguyên tắc "Tôi muốn hưởng lợi, người khác không được lợi dụng tôi". Theo nguyên tắc này, nếu việc làm tròn xuống có lợi cho hợp đồng thì sẽ làm tròn xuống; nếu việc làm tròn lên có lợi cho hợp đồng thì sẽ làm tròn lên; làm tròn theo quy tắc chung ít khi được áp dụng vì không xác định được ai sẽ được lợi.

Bằng cách áp dụng những phương pháp này, có thể thực hiện tính toán số học chính xác hơn trong hợp đồng thông minh Rust, nâng cao độ tin cậy và tính công bằng của hợp đồng.

![]###https://img-cdn.gateio.im/webp-social/moments-6e8b4081214a69423fc7ae022d05c728.webp###

Xem bản gốc
Trang này có thể chứa nội dung của bên thứ ba, được cung cấp chỉ nhằm mục đích thông tin (không phải là tuyên bố/bảo đảm) và không được coi là sự chứng thực cho quan điểm của Gate hoặc là lời khuyên về tài chính hoặc chuyên môn. Xem Tuyên bố từ chối trách nhiệm để biết chi tiết.
  • Phần thưởng
  • 8
  • Chia sẻ
Bình luận
0/400
ZeroRushCaptainvip
· 07-17 09:17
À lại bắt đầu lăn lộn với cái này rồi, lần trước chính là vấn đề độ chính xác bị Vị thế bị khóa hợp đồng phản giết, lỗ đến mức ăn đất.
Xem bản gốcTrả lời0
BlockchainFriesvip
· 07-16 10:52
Cái này thật sự quá tệ, ai dùng thì người đó xui xẻo.
Xem bản gốcTrả lời0
MoonBoi42vip
· 07-15 08:48
Độ chính xác của số dấu phẩy động không bằng số cố định thoải mái~
Xem bản gốcTrả lời0
OnchainDetectivevip
· 07-14 09:54
Rõ ràng, sai số độ chính xác chính là điểm yếu bị một số cầu nối Cross-chain khai thác bởi Hacker, thật đáng sợ.
Xem bản gốcTrả lời0
HashRateHermitvip
· 07-14 09:54
Vấn đề độ chính xác thật sự làm tôi chết khiếp.
Xem bản gốcTrả lời0
GateUser-e87b21eevip
· 07-14 09:47
Lại làm chuyện về dấu phẩy động, đau đầu quá.
Xem bản gốcTrả lời0
BearMarketSurvivorvip
· 07-14 09:45
Độ chính xác cái hố này ai cũng không thoát khỏi, người này hiểu.
Xem bản gốcTrả lời0
AirdropLickervip
· 07-14 09:35
Việc làm tròn số thực thì thật là sâu sắc, tôi đã từng bị rơi vào đó.
Xem bản gốcTrả lời0
Giao dịch tiền điện tử mọi lúc mọi nơi
qrCode
Quét để tải xuống ứng dụng Gate
Cộng đồng
Tiếng Việt
  • 简体中文
  • English
  • Tiếng Việt
  • 繁體中文
  • Español
  • Русский
  • Français (Afrique)
  • Português (Portugal)
  • Bahasa Indonesia
  • 日本語
  • بالعربية
  • Українська
  • Português (Brasil)