Problemas de precisão no cálculo de inteiros em contratos inteligentes Rust e soluções.

Diário de Desenvolvimento de Contratos Inteligentes Rust (7): Problema de Precisão em Cálculos Inteiros

Revisão de edições anteriores:

  • Diário de desenvolvimento de contratos inteligentes Rust (1) Definição de dados de estado do contrato e implementação de métodos
  • Diário de desenvolvimento de contratos inteligentes Rust (2) Escrevendo testes unitários para contratos inteligentes Rust
  • Diário de desenvolvimento de contratos inteligentes Rust (3) Implementação de contratos inteligentes Rust, chamadas de funções e uso do Explorer
  • Diário de desenvolvimento de contratos inteligentes Rust (4) Rust contratos inteligentes estouro de inteiro
  • Rust contratos inteligentes养成日记(5)重入攻击
  • Diário de desenvolvimento de contratos inteligentes Rust (6) ataque de negação de serviço

1. Problema de precisão em operações de ponto flutuante

Ao contrário do Solidity, Rust suporta nativamente operações com números de ponto flutuante. No entanto, as operações com ponto flutuante têm problemas de precisão inevitáveis, portanto, não é recomendado o seu uso em contratos inteligentes, especialmente ao lidar com taxas ou juros que envolvem decisões econômicas/financeiras importantes.

Rust segue o padrão IEEE 754 para representar números de ponto flutuante. O tipo de ponto flutuante de dupla precisão f64 é representado internamente em notação científica binária no computador.

Alguns números decimais podem ser representados com precisão em binário com um número finito de dígitos, como 0.8125 que pode ser representado como 0.1101. Mas decimais como 0.7 geram uma representação binária que se repete infinitamente, não podendo ser representados com precisão por números de ponto flutuante de tamanho finito, resultando em um problema de "arredondamento".

No exemplo de distribuição de 0,7 tokens NEAR para 10 usuários na blockchain NEAR:

ferrugem #[test] fn precision_test_float() { let amount: f64 = 0.7;
let divisor: f64 = 10.0;
let result_0 = amount / divisor;
println!("O valor da quantia: {:.20}", quantia); assert_eq!(result_0, 0.07); }

O resultado da execução mostra que o valor real de amount é 0.69999999999999995559, e result_0 é 0.06999999999999999, que não é igual ao esperado de 0.07.

Para resolver este problema, pode-se usar números de ponto fixo. No NEAR, normalmente é usado 10^24 yoctoNEAR para representar 1 token NEAR. Código modificado:

ferrugem
#[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); }

Assim, pode-se obter o resultado exato: 0,7 NEAR / 10 = 0,07 NEAR.

2. Problemas de precisão nos cálculos inteiros em Rust

Embora as operações inteiras possam resolver problemas de precisão de números de ponto flutuante em certos cenários, os cálculos inteiros também apresentam problemas de precisão.

2.1 Ordem das operações

A ordem das operações de multiplicação e divisão de mesmo nível pode afetar o resultado:

ferrugem #[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).unwrap().checked_div(b).unwrap();
let result_1 = a.checked_div(b).unwrap().checked_mul(c).unwrap();

assert_eq!(result_0,result_1);

}

Os resultados mostram result_0 = 2, result_1 = 0.

A razão é que a divisão inteira descarta a precisão que é menor que o divisor. Ao calcular result_1, (a / b) perde a precisão e se torna 0; enquanto result_0 primeiro calcula (a * c), evitando a perda de precisão.

2.2 magnitude muito pequeno

ferrugem #[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).unwrap().checked_mul(c).unwrap();

let result_1 = a.checked_mul(decimal).unwrap()
                .checked_div(b).unwrap()
                .checked_mul(c).unwrap()
                .checked_div(decimal).unwrap();

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

}

Os resultados mostram que result_0 = 12, result_1 = 13, sendo este último mais próximo do valor real 13.3333.

3. Como escrever contratos inteligentes de cálculo numérico em Rust

Para melhorar a precisão, podem ser adotadas as seguintes medidas:

3.1 Ajustar a ordem das operações

Fazer com que a multiplicação de inteiros tenha prioridade sobre a divisão.

3.2 aumentar a ordem de grandeza dos inteiros

Usar uma magnitude maior, criar moléculas maiores. Por exemplo, definir 1 NEAR = 10^24 yoctoNEAR.

3.3 perda de precisão nos cálculos acumulados

Registar e acumular perdas de precisão, compensar nas operações subsequentes:

ferrugem const USER_NUM: u128 = 3;

u128 { let token_to_distribute = offset + amount; let per_user_share = token_to_distribute / USER_NUM; let recorded_offset = token_to_distribute - per_user_share * USER_NUM; recorded_offset }

#( fn record_offset_test)[test] { let mut offset: u128 = 0; for i in 1..7 { offset = distribute(10_000_000_000_000_000_000_000_000, offset); } }

( 3.4 Usando a biblioteca Rust Crate rust-decimal

Esta biblioteca é adequada para cálculos financeiros de decimais que exigem alta precisão e sem erros de arredondamento.

) 3.5 considerar o mecanismo de arredondamento

Na concepção de contratos inteligentes, o arredondamento geralmente segue o princípio de "a meu favor": se arredondar para baixo for favorável, arredonda-se para baixo; se arredondar para cima for favorável, arredonda-se para cima; raramente se utiliza o arredondamento convencional.

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

EQ1.09%
Ver original
Esta página pode conter conteúdos de terceiros, que são fornecidos apenas para fins informativos (sem representações/garantias) e não devem ser considerados como uma aprovação dos seus pontos de vista pela Gate, nem como aconselhamento financeiro ou profissional. Consulte a Declaração de exoneração de responsabilidade para obter mais informações.
  • Recompensa
  • 8
  • Partilhar
Comentar
0/400
LostBetweenChainsvip
· 07-25 17:53
Não consegue lidar com cálculos inteiros, que contratos está a brincar?
Ver originalResponder0
WagmiWarriorvip
· 07-25 11:45
Já vi o 7º artigo, é tão detalhado.
Ver originalResponder0
MetaMisfitvip
· 07-25 01:13
Rust tem muitos buracos em contratos inteligentes, não tem?
Ver originalResponder0
DuckFluffvip
· 07-22 22:44
Bons tempos, os números de ponto flutuante estão causando problemas novamente~
Ver originalResponder0
MaticHoleFillervip
· 07-22 22:44
Aqueles que já caíram nas armadilhas da precisão vêm compartilhar suas experiências!
Ver originalResponder0
MetadataExplorervip
· 07-22 22:44
A parte dos inteiros é muitas vezes ignorada, mas é bastante crucial.
Ver originalResponder0
MEVHuntervip
· 07-22 22:29
a precisão é um pote de mel mev... mantenha os seus floats apertados ou será rekt ser
Ver originalResponder0
TrustlessMaximalistvip
· 07-22 22:16
Esta operação de ponto flutuante deve ser evitada.
Ver originalResponder0
  • Pino
Negocie cripto em qualquer lugar e a qualquer hora
qrCode
Digitalizar para transferir a aplicação Gate
Novidades
Português (Portugal)
  • 简体中文
  • English
  • Tiếng Việt
  • 繁體中文
  • Español
  • Русский
  • Français (Afrique)
  • Português (Portugal)
  • Bahasa Indonesia
  • 日本語
  • بالعربية
  • Українська
  • Português (Brasil)