Back to Blog

Relatório de Auditoria de Segurança para NearOinDao

Code Auditing
December 10, 2021
34 min read

Manifesto do Relatório

Item Descrição
Cliente Oinfinance
Alvo NearOinDao

Histórico de Versões

Versão Data Descrição
1.0 04 de dez, 2021 Primeira Versão

1. Introdução

1.1 Sobre os Contratos Alvo

Os contratos alvo contêm um módulo de moeda estável. Em torno dele, também implementa outros módulos, incluindo Staking e Farming. Esses módulos criam um ciclo de retroalimentação positivo para a estabilização da moeda estável, ou seja, USDO.

Informação Descrição
Tipo Contrato Inteligente
Linguagem Rust
Abordagem Verificação semiautomática e manual

Os repositórios que foram auditados incluem NearOinDao ^1

O processo de auditoria é iterativo. Especificamente, auditaremos ainda mais os commits que corrigem os problemas encontrados. Se houver novos problemas, continuaremos esse processo. Portanto, há múltiplos valores de SHA de commit referenciados neste relatório. Os valores de SHA de commit antes e após a auditoria são mostrados a seguir.

Antes e durante a auditoria

Após

Projeto SHA do Commit
NearOinDao 3bd117606c753d3c2f66b6dcddd1ae18ea47a20a

1.2 Modelo de Segurança

Para avaliar o risco, seguimos os padrões ou sugestões amplamente adotados tanto pela indústria quanto pela academia, incluindo a Metodologia de Avaliação de Risco OWASP ^2 e a Enumeração de Fraquezas Comuns ^3. Assim, a severidade medida neste relatório é classificada em quatro categorias: Alto, Médio, Baixo e Indeterminado.

2. Descobertas

No total, encontramos 22 potenciais problemas no contrato inteligente. Também temos 12 recomendações, conforme segue:

  • Risco Alto: 19

  • Risco Médio: 2

  • Risco Baixo: 1

  • Recomendações: 12

Os detalhes são fornecidos nas seções a seguir.

ID Severidade Descrição Categoria Status
1 Alto Erro de lógica enquanto self.liquidation_line é modificado Segurança de Software Confirmado e corrigido
2 Alto A função liquidation pode não funcionar Segurança de Software Confirmado e corrigido
3 Alto Erro de lógica ao definir o carimbo de tempo para abertura do contrato Segurança de Software Confirmado e corrigido
4 Alto O estado do contrato não é revertido se a transação de contrato cruzado falhar Segurança de Software Confirmado e corrigido
5 Alto Qualquer pessoa pode adicionar o saldo da recompensa Segurança DeFi Confirmado e corrigido
6 Alto Qualquer pessoa pode adicionar o saldo da recompensa do pool estável Segurança DeFi Confirmado e corrigido
7 Alto Qualquer pessoa pode queimar as moedas de outros usuários Segurança DeFi Confirmado e corrigido
8 Alto Qualquer pessoa pode adicionar o saldo de sua conta Segurança DeFi Confirmado e corrigido
9 Alto O oráculo não verifica o intervalo de tempo Segurança DeFi Confirmado e corrigido
10 Alto O intervalo de tempo do oráculo é muito longo Segurança DeFi Confirmado e corrigido
11 Alto Sem oráculo para o preço do Oin Segurança DeFi Confirmado e corrigido
12 Alto Usuários podem ganhar recompensa extra Segurança DeFi Confirmado e corrigido
13 Alto Usuários podem pagar menos taxa estável Segurança DeFi Confirmado e corrigido
14 Médio A solicitação de múltiplas assinaturas pode ser confirmada com uma taxa de confirmação relativamente baixa Segurança DeFi Confirmado e corrigido
15 Médio O número de blocos por ano está impreciso Segurança DeFi Confirmado e corrigido
16 Alto O número de moedas cunhadas disponíveis não está correto Segurança DeFi Confirmado e corrigido
17 Alto O pagamento da taxa estável pode resultar na perda dos tokens depositados pelo usuário Segurança DeFi Confirmado e corrigido
18 Alto Razão de staking incorreta Segurança DeFi Confirmado e corrigido
19 Baixo Moedas de recompensa podem ultrapassar o limite Segurança DeFi Confirmado e corrigido
20 Alto Mesma lista branca para usuários em diferentes privilégios Segurança DeFi Confirmado e corrigido
21 Alto Sem verificação no endereço da taxa estável Segurança DeFi Confirmado e corrigido
22 Alto O total_reward da moeda de recompensa pode ser modificado por gerentes de múltiplas assinaturas Segurança DeFi Confirmado e corrigido
23 - Asserção redundante Recomendação Confirmado e corrigido
24 - Consideração repetida da linha de liquidação Recomendação Confirmado e corrigido
25 - Verificação de lista branca redundante Recomendação Confirmado e corrigido
26 - Função não utilizada Recomendação Confirmado e corrigido
27 - Código redundante Recomendação Confirmado e corrigido
28 - O nome da função e a implementação estão em conflito Recomendação Confirmado e corrigido
29 - Código redundante Recomendação Confirmado e corrigido
30 - A precisão do cálculo pode ser melhorada Recomendação Confirmado e corrigido
31 - O sistema pode não registrar o preço consultado anteriormente Recomendação Confirmado e corrigido
32 - Distribuição descontínua do token de garantia na liquidação Recomendação Confirmado e corrigido
33 - A otimização da precisão de cálculo não é necessária Recomendação Confirmado e corrigido
34 - O risco do design centralizado Recomendação Reconhecido

2.1 Segurança de Software

2.1.1 Problema Potencial 1: Dois atributos diferentes para o mesmo uso

Item Descrição
Status Confirmado e corrigido

Descrição Este problema foi introduzido em ou antes do Commit-1. Dois atributos (ou seja, self.cost e self.liquidation_line) representam o mesmo estado do contrato, que é a linha de liquidação do usuário. Eles são utilizados em diferentes funções do contrato (Listagem 2.1 e Listagem 2.2). No entanto, self.liquidation_line pode ser modificado com a função set_liquidation_line enquanto self.cost não pode ser alterado. Nesse caso, se o self.liquidation_line for modificado, self.cost mantém o valor original. Isso pode influenciar a lógica da função assert_user_ratio (Listagem 2.1).

pub(crate) fn assert_user_ratio(&self) {
        let user_ratio = self.internal_user_ratio(env::predecessor_account_id());
        if user_ratio != 0 {
            assert!(user_ratio >= self.cost, "User ratio less than standard.");
        }
    }

Listagem 2.1: assert_user_ratio:lib.rs

// TODO liquidation
    #[payable]
    pub fn liquidation(&mut self, account: AccountId) {
        assert!(self.is_liquidation_paused(), "{}", SYSTEM_PAUSE);
        let ratio = self.internal_user_ratio(account.clone());
        assert!(ratio > 0, "No current pledge");
        assert!(ratio <= self.liquidation_line, "Not at the clearing line");
        ...

Listagem 2.2: internal_can_mint_amount:lib.rs

Impacto A linha de liquidação dos usuários não é consistente nas diferentes funções do contrato, o que influencia a lógica do contrato como um todo.

Sugestão I Podemos unificar os usos desses dois atributos ao calcular a razão de staking do usuário e compará-la à linha de liquidação do sistema.

2.1.2 Problema Potencial 2: Distribuição inválida da recompensa de liquidação

Item Descrição
Status Confirmado e corrigido

Descrição Este problema foi introduzido em ou antes do Commit-4. A conta do remetente da liquidação e a conta do proprietário do contrato podem não estar registradas (Linha 193 e 206 da Lista 2.3). Nesse caso, quando o remetente tem como objetivo conduzir uma ação de liquidação, a transação não pode ser executada com sucesso devido à exceção levantada de que as contas não estão registradas.

pub(crate) fn personal_liquidation_token(&mut self, send_id: AccountId, account_id: AccountId, liquidation_gas: Balance, surplus_token: Balance, liquidation_fee: Balance) {
        //self.owner_id
        let coin_id = ST_NEAR.to_string();
        let mut sys_reward_coin = self.internal_get_reward_coin(coin_id.clone());
        
        let account_reward_key_o = self.get_staker_reward_key(send_id.clone(), coin_id.clone());
        let user_reward_coin_o = self.internal_get_account_reward(send_id.clone(), coin_id.clone());
        
        self.account_reward.insert(
            &account_reward_key_o,
            &UserReward {
                index:  user_reward_coin_o.index,
                reward: user_reward_coin_o.reward.checked_add(liquidation_gas).expect(ERR_ADD),
            },
        );
        
        let account_reward_key_t = self.get_staker_reward_key(account_id.clone(), coin_id.clone());
        let user_reward_coin_t = self.internal_get_account_reward(account_id.clone(), coin_id.clone());

        if surplus_token > 0 {
            self.account_reward.insert(
                &account_reward_key_t,
                &UserReward {
                    index:  user_reward_coin_t.index,
                    reward: user_reward_coin_t.reward.checked_add(surplus_token).expect(ERR_ADD),
                },
            );
        }

        let account_reward_key_s = self.get_staker_reward_key(self.owner_id.clone(), coin_id.clone());
        let user_reward_coin_s = self.internal_get_account_reward(self.owner_id.clone(), coin_id.clone());

        self.account_reward.insert(
            &account_reward_key_s,
            &UserReward {
                index:  user_reward_coin_s.index,
                reward: user_reward_coin_s.reward.checked_add(liquidation_fee).expect(ERR_ADD),
            },
        );
       
        sys_reward_coin.total_reward = sys_reward_coin
            .total_reward
            .checked_add(liquidation_gas).expect(ERR_ADD)
            .checked_add(liquidation_fee).expect(ERR_ADD)
            .checked_add(surplus_token).expect(ERR_ADD);

        self.reward_coins.insert(&coin_id, &sys_reward_coin);
    }

}

Listagem 2.3: personal_liquidation_token:reward.rs

Impacto A função de liquidação não pode ser executada com sucesso devido à exceção levantada de que as contas não estão registradas.

Sugestão I Verifique a existência da conta do remetente da liquidação e da conta do proprietário do contrato no início da função de liquidação.

2.1.3 Problema Potencial 3: Block_timestamp é salvo em closed_time ao abrir o sistema

Item Descrição
Status Confirmado e corrigido

Descrição Este problema foi introduzido em ou antes do Commit-3. env::block_time_stamp() não deve ser salvo em self.closed_time ao invocar a função internal_open.

#[private]
    pub fn internal_open(&mut self) {
        self.closed_time = env::block_timestamp();
        self.open_stake();
        self.open_redeem();
        self.open_claim_reward();
        self.open_liquidation();
        self.open_stable();
        log!(
            "{} open sys in {}",
            env::predecessor_account_id(),
            self.closed_time
        );
    }

Listagem 2.4: internal_open:esm.rs

Impacto O horário de abertura e o horário de fechamento do contrato estão completamente incorretos. Atualizações posteriores que dependem das informações de tempo podem ter erro de lógica.

Sugestão I Sugerimos criar um novo estado de contrato chamado self.opening_time e atribuir env::block_timestamp() a este valor ao invocar a abertura do contrato.

2.1.4 Problema Potencial 4: O estado do contrato não é revertido quando as chamadas de função cruzada falham

Item Descrição
Status Confirmado e corrigido

Descrição Este problema foi introduzido em ou antes do Commit-3. O processo de storage_deposit e ft_transfer pode falhar durante as chamadas de função cruzada de contrato. Não podemos garantir que a transferência será sempre executada corretamente. A função de callback não reverte o estado do contrato se a chamada falhar.

#[private]
    pub fn storage_deposit_callback(&mut self) {
        match env::promise_result(0) {
            PromiseResult::NotReady => unreachable!(),
            PromiseResult::Successful(_) => {
                log!("Transfer success");
            }
            PromiseResult::Failed => {
                log!("Transfer failed");
            }
        }
    }

Listagem 2.5: storage_deposit_callback:ft.rs

#[private]
    pub fn liquidation_transfer_callback(&mut self) {
        match env::promise_result(0) {
            PromiseResult::NotReady => unreachable!(),
            PromiseResult::Successful(_) => {
                log!("Transfer success");
            }
            PromiseResult::Failed => {
                log!("Transfer failed");
            }
        }
    }

Listagem 2.6: liquidation_transfer_callback:ft.rs

Impacto Os usuários podem perder seus ativos quando as transações falham, pois a função de callback não reverte o estado do contrato.

Sugestão I É necessário reverter o estado do contrato (quando a transferência falha) na função de callback das chamadas de função cruzada de contrato.

2.2 Segurança DeFi

2.2.1 Problema Potencial 5: inject_reward não possui controle de acesso

Item Descrição
Status Confirmado e corrigido

Descrição Este problema foi introduzido em ou antes do Commit-1. A função inject_reward é pública. Qualquer pessoa pode invocar essa função para adicionar o saldo da recompensa no contrato.

pub fn inject_reward(&mut self, amount: U128, reward_coin: AccountId) {
        // self.assert_owner();

        if reward_coin == String::from("NEAR") {
            assert!(
                amount.0 == env::attached_deposit(),
                "Amount not equal transfer_amount"
            );
        }
        ...
    }

Listagem 2.7: inject_reward:pool.rs

Impacto Qualquer pessoa pode adicionar saldo arbitrário na recompensa do contrato.

Sugestão I Esta função deve ser alterada para privada, pois é chamada internamente após receber a recompensa transferida.

2.2.2 Problema Potencial 6: inject_sp_reward não possui controle de acesso

Item Descrição
Status Confirmado e corrigido

Descrição Este problema foi introduzido em ou antes do Commit-1. A função inject_sp_reward é pública. Qualquer pessoa pode invocar essa função para adicionar o saldo da recompensa do pool estável no contrato.

pub fn inject_sp_reward(&mut self, _amount: U128, sender_id: ValidAccountId) {
        self.reward_sp = self.reward_sp + u128::from(_amount);

        log!(
            "{} add sp_reward  {} cur amount{}",
            sender_id,
            u128::from(_amount),
            self.reward_sp
        );
    }

Listagem 2.8: inject_sp_reward:stablepool.rs

Impacto Qualquer pessoa pode adicionar saldo arbitrário na recompensa do pool estável do contrato.

Sugestão I Esta função deve ser alterada para privada, pois é chamada internamente após receber a recompensa do pool estável transferida.

2.2.3 Problema Potencial 7: burn_coin não possui controle de acesso

Item Descrição
Status Confirmado e corrigido

Descrição Este problema foi introduzido em ou antes do Commit-1. A função burn_coin é pública. Qualquer pessoa pode invocar essa função para queimar a moeda de qualquer pessoa.

pub fn burn_coin(&mut self, amount: U128, fee: Balance, sender_id: ValidAccountId) -> Balance{
        assert!(self.is_redeem_paused(), "{}", SYSTEM_PAUSE);
        let sender_id = AccountId::from(sender_id);
        self.assert_is_poked();
        self.accured_token(sender_id.clone());
        ...
    }

Listagem 2.9: burn_coin:lib.rs

Impacto Qualquer pessoa pode usar essa função para queimar a moeda de qualquer pessoa, resultando na perda dos ativos dos usuários.

Sugestão I Esta função deve ser alterada para privada, pois é chamada internamente após receber a taxa estável transferida para queima de moedas.

2.2.4 Problema Potencial 8: deposit_token não possui controle de acesso

Item Descrição
Status Confirmado e corrigido

Descrição Este problema foi introduzido em ou antes do Commit-1. A função deposit_token é pública. Qualquer pessoa pode invocar essa função para adicionar o saldo de sua conta.

pub fn deposit_token(&mut self, amount: u128, _sender_id: ValidAccountId) {
        self.assert_is_poked();
        assert!(self.is_stake_paused(), "{}", SYSTEM_PAUSE);
        let _amount = u128::from(amount);
        let sender_id = AccountId::from(_sender_id);
        . . .
    }

Listagem 2.10: deposit_token:lib.rs

Impacto Atacantes podem invocar essa função para adicionar o saldo de sua conta.

Sugestão I Esta função deve ser alterada para privada, pois é chamada internamente após receber os tokens depositados.

2.2.5 Problema Potencial 9: O oráculo não possui verificação de tempo

Item Descrição
Status Confirmado e corrigido

Descrição Este problema foi introduzido em ou antes do Commit-1. A função assert_is_poked em oracle.rs apenas verifica se o valor do preço do token é zero. Isso não faz sentido, pois o preço do token está em constante mudança.

pub(crate) fn assert_is_poked(&self) {
        assert!(self.token_price != 0, "Oracle price isn't poked.");
    }

Listagem 2.11: assert_is_poked:oracle.rs

Impacto Este problema afeta os oráculos de preço. Se o preço do token não tiver sido consultado por um longo período de tempo, a asserção ainda pode ser aprovada e a transação relacionada pode ser executada com um preço desatualizado.

Sugestão I O contrato deve definir um período de tempo válido para o preço consultado.

2.2.6 Problema Potencial 10: Intervalo de tempo de consulta do oráculo inadequado

Item Descrição
Status Confirmado e corrigido

Descrição Este problema foi introduzido em ou antes do Commit-3. A constante POKE_INTERVAL_TIME definida em types.rs representa atualmente 1000 dias. E esse intervalo de tempo parece muito longo. É necessário um valor razoável.

pub const POKE_INTERVAL_TIME: u64 = 86_400_000_000_000_000;

Listagem 2.12: types.rs

Impacto O intervalo de tempo para o preço consultado é inadequado.

Sugestão I Redefinir o intervalo de tempo para o preço consultado com um valor razoável.

2.2.7 Problema Potencial 11: Asserção ausente para Oin_Price

Item Descrição
Status Confirmado e corrigido

Descrição Este problema foi introduzido em ou antes do Commit-1. Esta função não verifica se o valor do preço do oin_token foi consultado, pois a taxa estável do usuário é calculada por self.oin_price.

pub fn internal_user_stable(&self, account: AccountId) -> u128 {
        let user_stable = self.account_stable.get(&account).expect("error");
        let allot = self.get_account_allot(account.clone()); 
        let coin = self
            .account_coin
            .get(&account)
            .expect("error")
            .checked_add(allot.0)
            .expect(ERR_ADD);
        let current_block_number = env::block_timestamp().checked_div(INIT_BLOCK_TIME).expect(ERR_DIV);
        user_stable
            .saved_stable
            .checked_add(
                self.stable_fee_rate//16
                    .checked_div(BLOCK_PER_YEAR)
                    .expect(ERR_DIV)
                    .checked_mul(current_block_number as u128 - user_stable.block)
                    .expect(ERR_MUL)
                    .checked_mul(coin)//8
                    .expect(ERR_MUL)
                    .checked_div(self.oin_price)//8
                    .expect(ERR_DIV)
                    .checked_div(ONE_COIN)//8
                    .expect(ERR_DIV),
            )
            .expect(ERR_ADD)
    }

Listagem 2.13: internal_user_stable:lib.rs

Impacto O preço OIN desatualizado pode levar à manipulação de preços sem verificar a atualidade do preço consultado pelo oráculo.

Sugestão I Adicionar uma asserção self.assert_is_poked(); antes do cálculo da taxa estável do usuário.

2.2.8 Problema Potencial 12: Usuários podem ganhar mais recompensa de mineração com token em staking

Item Descrição
Status Confirmado e corrigido

Descrição Este problema foi introduzido em ou antes do Commit-1. A recompensa reivindicada não é calculada com precisão. A função internal_get_saved_reward é chamada para calcular a recompensa de mineração específica do usuário de t0 a t1 com a seguinte fórmula:

Observe que account_allot.token é a recompensa de garantia adicionada pela liquidação de outros usuários. No entanto, a liquidação pode ocorrer a qualquer momento de t0 a t1. Por exemplo, um usuário depositou 100 tokens no dia 0. No dia 999, a liquidação de outro usuário é acionada, fazendo com que account_allot.token possa aumentar para 1000.

Quando o usuário reivindica sua recompensa no dia 1000, os 1000 tokens resultantes da liquidação no dia 999 devem ser contados para mineração apenas por um dia. No entanto, o contrato na verdade calcula a recompensa de mineração para a recompensa de garantia do dia 0 ao dia 1000.

// TODO[OK] Calculation of reward
    pub(crate) fn internal_get_saved_reward(
        &self,
        staker: AccountId,      
        reward_coin: AccountId, 
    ) -> u128 {
        let reward_coin_ins = self.internal_get_reward_coin(reward_coin.clone());
        let (stake_token_num, _) = self.staker_debt_of(staker.clone());

        if let Some(user_reward) = self
            .account_reward
            .get(&self.get_staker_reward_key(staker.clone(), reward_coin.clone()))
        {
            user_reward
                .reward
                .checked_add(
                    U256::from(
                        reward_coin_ins
                            .index
                            .checked_sub(user_reward.index)
                            .expect(ERR_SUB),
                    )
                    .checked_mul(U256::from(stake_token_num))
                    .expect(ERR_MUL)
                    .checked_div(U256::from(reward_coin_ins.double_scale))
                    .expect(ERR_DIV)
                    .as_u128(),
                )
                .expect(ERR_ADD)
        } else {
            0
        }
    }

Listagem 2.14: internal_get_saved_reward:views.rs

pub fn staker_debt_of(&self, staker: AccountId) -> (u128, u128) {
        if let Some(token) = self.account_token.get(&staker) {
            let coin = self.account_coin.get(&staker).expect(ERR_NOT_REGISTER);
            let allot = self.get_account_allot(staker.clone());
            (token + allot.1, coin + allot.0)
        } else {
            (0, 0)
        }
    }

Listagem 2.15: staker_debt_of:views.rs

Impacto Os usuários podem ganhar recompensas extras.

Sugestão I Remover a parcela de garantia recém-alocada ao calcular a recompensa de mineração. Podemos fazer a recompensa de mineração relacionada apenas à quantidade de tokens depositados pelo usuário.

2.2.9 Problema Potencial 13: Usuários podem pagar menos taxa estável

Item Descrição
Status Confirmado e corrigido

Descrição Este problema foi introduzido em ou antes do Commit-1. Suponha que um usuário cunhe 1000 USDOs no dia 0, e o stable_fee_rate naquele momento seja 0,01oin/moeda/dia. Se o usuário devolver os 1000 USDOs no dia 100 e a taxa de taxa estável não mudar durante os últimos 100 dias, a taxa estável que ele precisa pagar é 0,01 Oin/moeda/dia * 1000 Moedas * 100 Dias = 1000 Oin. No entanto, se o proprietário definir o stable_fee_rate = 0,005 oin/moeda/dia no dia 99. Nesse caso, o usuário só precisa pagar 0,005 Oin/Moeda/Dia * 1000 Moedas * 100 Dias = 500 Oin. Na verdade, a taxa precisa deveria ser: (0,01 Oin/Moeda/Dia * 1000 Moedas * 99 Dias) + (0,005 Oin/Moeda/Dia * 1000 Moedas * 1 Dia) = 990 Oin + 5 Oin = 995 Oin.

Nesse caso, os 495 Oin não precisam ser pagos pelos usuários.

// TODO [OK]
    pub fn set_stable_fee_rate(&mut self, fee_rate: U128) {
        self.assert_param_white();
        self.update_stable_index();
        assert!(fee_rate.0 <= INIT_MAX_STABLE_FEE_RATE, "Exceeding the maximum setting");
        self.stable_fee_rate = fee_rate.into();
        log!("Set stable fee rate {}", fee_rate.0);
    }

Listagem 2.16: set_stable_fee_rate:dparam.rs

pub fn update_stable_index(&mut self) {
    }

Listagem 2.17: update_stable_index:stablefee.rs

Impacto Os usuários do contrato podem ser cobrados menos pela taxa estável.

Sugestão I Implementar o índice de sistema da taxa estável como o cálculo de reward_coin neste contrato. E garantir que o índice de sistema da taxa estável seja atualizado sempre que set_stable_fee_rate, liquidation e update_stable_fee forem chamados pelos usuários do contrato.

2.2.10 Problema Potencial 14: Taxa de confirmação de solicitação de múltiplas assinaturas inadequada

Item Descrição
Status Confirmado e corrigido

Descrição Este problema foi introduzido em ou antes do Commit-1. A taxa de confirmação da solicitação de múltiplas assinaturas é calculada pelo número de gerentes de múltiplas assinaturas quando a solicitação foi criada. Mas o número de gerentes de múltiplas assinaturas pode mudar posteriormente. Nesse caso, se o número de gerentes aumentar, a solicitação pode ser confirmada com uma baixa taxa de confirmação.

pub(crate) fn is_num_enough(&self, request_id: RequestId) -> bool {
        let request = self.requests.get(&request_id).unwrap();
        let confirmations = self.confirmations.get(&request_id).unwrap();

        let num_confirmrations = request.num_confirm_ratio * (request.mul_white_num);
        log!(
            "confim num is {} num needed is {} ",
            confirmations.len() as u32 * 100,
            num_confirmrations
        );

        (confirmations.len() as u64) * 100 >= num_confirmrations
    }

Listagem 2.18: is_num_enough:multisign.rs

pub fn add_request_only(&mut self, request: MultiSigRequest) -> RequestId {
        self.assert_mul_white();
        ...

        let request_added = MultiSigRequestWithSigner {
            signer_pk: env::signer_account_pk(),
            added_timestamp: env::block_timestamp(),
            confirmed_timestamp: 0,
            request: request,
            is_executed: false,
            cool_down: self.request_cooldown,
            mul_white_num: self.mul_white_num(),
            num_confirm_ratio: self.num_confirm_ratio,
        };

        self.requests.insert(&self.request_nonce, &request_added);
        ...
    }

Listagem 2.19: add_request_only:multisign.rs

Impacto Solicitações de múltiplas assinaturas podem ser confirmadas com uma baixa taxa de confirmação, pois o contrato considera apenas o número de gerentes quando a solicitação é criada.

Sugestão I Considerar o uso do número de usuários de múltiplas assinaturas no estado atual do contrato para calcular a taxa de confirmação da solicitação de múltiplas assinaturas.

2.2.11 Problema Potencial 15: Número de blocos por ano incorreto

Item Descrição
Status Confirmado e corrigido

Descrição Este problema foi introduzido em ou antes do Commit-1. Dado que um bloco é gerado a cada segundo na rede principal do protocolo NEAR, o número de blocos gerados por ano deveria ser 31536000 (365 dias) e não 31104000 (360 dias).

pub const BLOCK_PER_YEAR: u128 = 31104000;

Listagem 2.20: types.rs

Impacto A constante imprecisa para BLOCK_PER_YEAR tornará os resultados dos cálculos que utilizam a constante inconsistentes com a realidade.

Sugestão I Alterar o BLOCK_PER_YEAR para 31536000.

2.2.12 Problema Potencial 16: Cálculo incorreto do máximo de usdo que pode ser cunhado

Item Descrição
Status Confirmado e corrigido

Descrição Este problema foi introduzido em ou antes do Commit-1. allot_token.0 representa a dívida alocada. Ao calcular o valor disponível para cunhagem de USDO, a dívida alocada não deve ser contada. Caso contrário, um usuário com dívida muito alta pode cunhar um número enorme de USDOs.

pub(crate) fn internal_can_mint_amount(&self, account: AccountId) -> u128 {
        self.assert_is_poked();
        let token = self.account_token.get(&account).expect(ERR_NOT_REGISTER);
        let guarantee = self.guarantee.get(&account).expect(ERR_NOT_REGISTER);
        let allot_token = self.get_account_allot(account.clone());

        let max_usdo = (U256::from(token)
            .checked_add(U256::from(allot_token.1))
            .expect(ERR_ADD))
        .checked_mul(U256::from(self.token_price))
        .expect(ERR_MUL)
        .checked_div(U256::from(self.liquidation_line))
        .expect(ERR_DIV)
        .checked_div(U256::from(INIT_STABLE_INDEX))
        .expect(ERR_DIV)
        .checked_add(U256::from(allot_token.0))
        .expect(ERR_ADD)
        .checked_sub(U256::from(guarantee))
        .unwrap_or(U256::from(0))
        .as_u128();
        
        ...
    }

Listagem 2.21: internal_can_mint_amount:lib.rs

Impacto Os usuários podem cunhar USDOs adicionais ao invocar a função mint_coin.

Sugestão I O allot_token.0, que representa a dívida alocada, não deve ser contado como os USDOs disponíveis para cunhagem.

2.2.13 Problema Potencial 17: Tratamento incorreto da taxa estável do usuário

Item Descrição
Status Confirmado e corrigido (A lógica relacionada foi removida agora)

Descrição Este problema foi introduzido em ou antes do Commit-1. Quando os usuários invocam a função burn_coin, a taxa estável é paga com o token 'OIN' e não com 'ST_NEAR'. No entanto, o contrato reduzirá o saldo do token de staking do usuário, o que não está correto.

pub(crate) fn burn_coin(&mut self, amount: U128, fee: Balance, sender_id: ValidAccountId) -> Balance{
        ...
            assert!(usdo >= amount.into(), "Insufficient amount");
            let token = self.account_token.get(&sender_id.clone()).expect(ERR_NOT_REGISTER);
            self.internal_burn(sender_id.clone(), amount.into());
   
            self.total_token = self.total_token.checked_sub(unpaid_fee.into()).expect(ERR_SUB);
            self.account_token.insert(
                &sender_id.clone(),
                &token.checked_sub(unpaid_fee.into()).expect(ERR_SUB),
            );
        ...
        
    }

Listagem 2.22: burn_coin:lib.rs

Impacto O token de staking dos usuários pode ser reduzido devido ao tratamento incorreto da taxa estável do usuário.

Sugestão I Usar o token correto para o pagamento das taxas estáveis.

2.2.14 Problema Potencial 18: Razão do sistema incorreta

Item Descrição
Status Confirmado e corrigido

Descrição Este problema foi introduzido em ou antes do Commit-1. Se total_coin = 0, a razão deve ser +∞. Definí-la como 0 está incorreto.

pub(crate) fn internal_sys_ratio(&self) -> u128 {
        self.assert_is_poked();
        let token_usd = U256::from(self.total_token)
            .checked_mul(U256::from(self.token_price))
            .expect(ERR_MUL); /* 32 */
        let total_coin = self.total_coin + self.total_guarantee;
        if total_coin == 0 {
            0
        } else {
            token_usd
                .checked_div(U256::from(STAKE_RATIO_BASE))
                .expect(ERR_DIV)
                .checked_div(U256::from(total_coin))
                .expect(ERR_DIV)
                .as_u128()
        }
    }

Listagem 2.23: internal_sys_ratio:lib.rs

Impacto É provável que o sistema seja desligado devido à razão incorreta.

Sugestão I Alterar a condição if total_coin = 0 para token_usd = 0.

2.2.15 Problema Potencial 19: O número de moedas de recompensa pode ser maior que o limite superior

Item Descrição
Status Confirmado e corrigido

Descrição Este problema foi introduzido em ou antes do Commit-1. Quando há 20 moedas de recompensa agora, a asserção na linha 131 da Listagem 2.24 pode ser aprovada. Nesse caso, mais uma moeda de recompensa pode ser adicionada e o número total de moedas de recompensa pode ser maior que o REWARD_UPPER_BOUND.

pub(crate) fn internal_add_reward_coin(&mut self, coin: RewardCoin) {
        assert!(
            self.reward_coins.len() <= REWARD_UPPER_BOUND,
            "The currency slot has been used up, please modify other currency information as appropriate",
        );

        match self.reward_coins.get(&coin.token) {
            Some(_) => {
                env::panic(b"The current currency has been added, please add a new currency.");
            }
            None => {}
        }
        self.reward_coins.insert(&coin.token, &coin);

        log!(
            "{} add the RewardCoin=> {:?}",
            env::predecessor_account_id(),
            coin
        )
    }

Listagem 2.24: internal_add_reward_coin:pool.rs

Impacto O número disponível de moedas de recompensa adicionadas está em conflito com o design do sistema.

Sugestão I Alterar a asserção para self.reward_coins.len() < REWARD_UPPER_BOUND.

2.2.16 Problema Potencial 20: Usuários em diferentes privilégios usam a mesma lista branca

Item Descrição
Status Confirmado e corrigido

Descrição Este problema foi introduzido em ou antes do Commit-1. As funções assert_param_white, assert_white, assert_esm_white, assert_oracle_white são usadas para diferentes privilégios. No entanto, elas compartilham a mesma lista branca.

pub(crate) fn assert_esm_white(&self) {
        self.assert_white()
    }

Listagem 2.25: assert_esm_white:esm.rs

pub(crate) fn assert_param_white(&self) {
        self.assert_white();
    }

Listagem 2.26: assert_param_white:dparam.rs

pub(crate) fn assert_oracle_white(&self) {
        self.assert_white();
    }

Listagem 2.27: assert_oracle_white:oracle.rs

Impacto Usuários em diferentes privilégios compartilham a mesma lista branca.

Sugestão I Implementar listas brancas diferentes para usuários com diferentes privilégios.

2.2.17 Problema Potencial 21: burn_coin não verifica o tipo de token

Item Descrição
Status Confirmado e corrigido

Descrição Este problema foi introduzido em ou antes do Commit-1. A função burn_coin não verifica o tipo de token. Nesse caso, atacantes podem transferir tokens arbitrários com um valor especificado para pagar a taxa estável.

pub fn burn_coin(&mut self, amount: U128, fee: Balance, sender_id: ValidAccountId) -> Balance{
        assert!(self.is_redeem_paused(), "{}", SYSTEM_PAUSE);
        let sender_id = AccountId::from(sender_id);

Listagem 2.28: assert_esm_white:esm.rs

Impacto Os usuários não precisam pagar o token Oin. Em vez disso, podem pagar a taxa estável transferindo tokens arbitrários com o valor exigido.

Sugestão I Verificar o endereço do token recebido.

2.2.18 Problema Potencial 22: O total_reward da moeda de recompensa pode ser modificado por gerentes de múltiplas assinaturas

Item Descrição
Status Confirmado e corrigido

Descrição Este problema foi introduzido em ou antes do Commit-3. A função inject_reward é decorada com #[private]. Portanto, gerentes de múltiplas assinaturas podem invocar essa função por meio de solicitações de múltiplas assinaturas e adicionar quantidade arbitrária na recompensa total sem injetar recompensa.

#[payable]
    #[private]
    pub fn inject_reward(&mut self, amount: U128, reward_coin: AccountId) {
        // self.assert_owner();

        if reward_coin == String::from("NEAR") {
            assert!(
                amount.0 == env::attached_deposit(),
                "Amount not equal transfer_amount"
            );
        }

        if let Some(reward_coin_ins) = self.get_reward_coin(reward_coin.clone()) {
            let mut reward_coin_ins = reward_coin_ins;
            reward_coin_ins.total_reward = reward_coin_ins
                .total_reward
                .checked_add(amount.into())
                .expect(ERR_SUB);
            self.reward_coins.insert(&reward_coin, &reward_coin_ins);

            if reward_coin == String::from("NEAR") {
            
            } else {
                log!("Transfer is not required for post-processing");
            }
        } else {
            env::panic(b"No the reward coin.");
        }
    }

Listagem 2.29: ainject_reward:pool.rs

Sugestão I Remover o decorator #[private] e alterar a visibilidade da função inject_reward para privada.

2.3 Recomendações Adicionais

2.3.1 Asserção redundante

Item Descrição
Status Confirmado e corrigido

Descrição Este problema foi introduzido em ou antes do Commit-2. A função inject_reward deve ser chamada apenas internamente por ft_on_transfer. O endereço da moeda de recompensa é verificado em ft_on_transfer. Nesse caso, não precisamos verificar o nome da moeda de recompensa no início da função inject_reward.

#[payable]
    #[private]
    pub  fn inject_reward(&mut self, amount: U128, reward_coin: AccountId) {
        // self.assert_owner();

        if reward_coin == String::from("NEAR") {
            assert!(
                amount.0 == env::attached_deposit(),
                "Amount not equal transfer_amount"
            );
        }

    ...
    }

Listagem 2.30: inject_reward:pool.rs


    pub fn ft_on_transfer(
        &mut self,
        sender_id: ValidAccountId,
        amount: U128,
        msg: String, /* token */
    ) -> PromiseOrValue<U128> {
    ...
            FtOnTransferArgs::InjectReward => {
                assert_eq!(sender_id.to_string(), self.owner_id, "ERR_NOT_ALLOWED");

                assert!(
                    self.reward_coins.get(&token_account_id).is_some(),
                    "Invalid reward coin"
                );

                self.inject_reward(amount, token_account_id);
                amount_return = 0;
            }
    ...
    }

Listagem 2.31: ft_on_transfer:lib.rs

Sugestão I Remover a verificação do nome da moeda de recompensa em inject_reward.

2.3.2 Asserção repetida para a razão de liquidação do usuário

Item Descrição
Status Confirmado e corrigido

Descrição Este problema foi introduzido em ou antes do Commit-1. A linha de liquidação já é levada em consideração na função internal_avaliable_token, portanto não há necessidade de verificar se a user_ratio atinge a linha de liquidação posteriormente.

#[payable]
    pub fn withdraw_token(&mut self, amount: U128) {
        assert!(self.is_stake_paused(), "{}", SYSTEM_PAUSE);
        let mut amount = amount.0;

        let token = self.internal_avaliable_token(env::predecessor_account_id());
        let debt = self.get_dept(env::predecessor_account_id());

        log!("token :{} amount: {}", token, amount);
        assert!(token >= amount, "Insufficient avaliable token.");
        if debt.0 - debt.2 == 0 {
            if token - amount < self._min_amount_token() {
                amount = token;
            }
        } else {
            self.assert_user_ratio();
            if token - amount < self._min_amount_token() {
                env::panic(b"Please return all coins first");
            }
        }

Listagem 2.32: withdraw_token:lib.rs

Sugestão I Remover a asserção redundante na Linha 559 da Listagem 2.32.

2.3.3 Verificação de lista branca redundante

Item Descrição
Status Confirmado e corrigido

Descrição Este problema foi introduzido em ou antes do Commit-1. A função set_reward_speed invoca a função assert_param_white para verificar o privilégio. Enquanto isso, internal_set_reward_speed, que é chamada por set_reward_speed, invoca assert_white novamente. assert_white tem a mesma lista branca que assert_param_white.

pub fn set_reward_speed(&mut self, reward_coin: AccountId, speed: U128) {
        self.assert_param_white();
        self.internal_set_reward_speed(reward_coin, speed);
    }

Listagem 2.33: set_reward_speed:dparam.rs

pub(crate) fn internal_set_reward_speed(&mut self, reward_coin: AccountId, speed: U128) {
        self.assert_white();
        self.update_index();
        . . .
    }

Listagem 2.34: internal_set_reward_speed:pool.rs

Sugestão I Remover assert_white dentro da função internal_set_reward_speed.

2.3.4 Função não utilizada

Item Descrição
Status Confirmado e corrigido

Descrição Este problema foi introduzido em ou antes do Commit-3. A função on_inject_reward não é usada por nenhuma outra função. Portanto, pode ser removida.

#[private]
    pub fn on_inject_reward(&mut self, reward_coin: AccountId, amount: U128) {
        match env::promise_result(0) {
            PromiseResult::NotReady => unreachable!(),
            PromiseResult::Successful(_) => {}
            PromiseResult::Failed => {
                let mut reward_coin_ins = self.internal_get_reward_coin(reward_coin.clone());
                reward_coin_ins.total_reward = reward_coin_ins
                    .total_reward
                    .checked_sub(amount.into())
                    .expect(ERR_ADD);
                self.reward_coins.insert(&reward_coin, &reward_coin_ins);
            }
        };
    }

Listagem 2.35: on_inject_reward:pool.rs

Sugestão I Remover a função on_inject_reward.

2.3.5 Código redundante

Item Descrição
Status Confirmado e corrigido

Descrição Este problema foi introduzido em ou antes do Commit-3. A função account_allot.get() é usada para obter a recompensa e a dívida alocadas. Dentro da função set_account_allot, a invocação desta função não é necessária.

pub(crate) fn set_account_allot(&mut self,account_id: AccountId){
        //Update [personally assigned debt, personally assigned pledge] to system value
        let (allot_debt, allot_token) = self.get_account_allot(account_id.clone());
        let token = self.account_token.get(&account_id).expect(ERR_NOT_REGISTER);
        let coin = self.account_coin.get(&account_id).expect(ERR_NOT_REGISTER);

        self.account_allot.get(&account_id);

        self.account_allot.insert(
            &account_id, 
            &AccountAllot{
                account_allot_debt: self.sys_allot_debt,
                account_allot_token: self.sys_allot_token,
            }
        );
        self.account_coin.insert(&account_id, &coin.checked_add(allot_debt).expect(ERR_ADD));
        self.account_token.insert(&account_id, &token.checked_add(allot_token).expect(ERR_ADD));       
    }

Listagem 2.36: set_account_allot:allot.rs

Sugestão I Remover a invocação de account_allot.get() na linha 42.

2.3.6 O nome da função e a implementação são opostos

Item Descrição
Status Confirmado e corrigido

Descrição Este problema foi introduzido em ou antes do Commit-3. As funções is_stake_paused, is_redeem_paused, is_claim_reward_paused, is_liquidation_paused, is_stable_paused são definidas para representar se a função está pausada ou não. No entanto, quando o atributo específico está ativo, ele retorna True.

// TODO [OK]
    pub(crate) fn is_stake_paused(&self) -> bool {
        self.stake_live == 1
    }

    // TODO [OK]
    pub(crate) fn is_redeem_paused(&self) -> bool {
        self.redeem_live == 1
    }

    // TODO [OK]
    pub(crate) fn is_claim_reward_paused(&self) -> bool {
        self.claim_live == 1
    }

    // TODO [OK]
    pub(crate) fn is_liquidation_paused(&self) -> bool {
        self.liquidation_live == 1
    }

    // TODO [OK]
    pub(crate) fn is_stable_paused(&self) -> bool {
        self.stable_live == 1
    }

Listagem 2.37: is_{stake|redeem|claim_reward|liquidation|stable}_paused:esm.rs

Sugestão I Alterar o nome da função is_{stake|redeem|claim_reward|liquidation|stable}_paused para is_{stake|redeem|claim_reward|liquidation|stable}_live

2.3.7 Código redundante

Item Descrição
Status Confirmado e corrigido

Descrição Este problema foi introduzido em ou antes do Commit-3. A função update_stable_fee é usada para atualizar as taxas estáveis necessárias. A taxa estável não está relacionada aos tokens em staking. Portanto, alterar o saldo de tokens para os usuários não precisa atualizar as taxas estáveis.

pub(crate) fn deposit_token(&mut self, _amount: u128, _sender_id: ValidAccountId) {
        self.assert_is_poked();
        assert!(self.is_stake_paused(), "{}", SYSTEM_PAUSE);
        let sender_id = AccountId::from(_sender_id);
        assert!(_amount > 0, "Deposit token amount must greater than zero.");

        if let Some(0) = self.guarantee.get(&sender_id) {
            assert!(
                _amount >= self._min_amount_token(),
                "Deposit token amount must greater the minimum deposit token."
            );
        }
        self.update_personal_token(sender_id.clone());
        self.update_stable_fee(sender_id.clone());
        self.set_account_allot(sender_id.clone());
        . . .
    }

Listagem 2.38: deposit_token:lib.rs

Sugestão I Remover a invocação de update_stable_fee na linha 344.

2.3.8 A precisão do cálculo pode ser melhorada

Item Descrição
Status Confirmado e corrigido

Descrição Este problema foi introduzido em ou antes do Commit-3. A função internal_user_stable tem como objetivo calcular a taxa estável. A precisão do cálculo pode ser melhorada realizando multiplicação antes da divisão.

pub(crate) fn update_stable_fee(&mut self, account: AccountId) {
        if let Some(mut user_stable) = self.account_stable.get(&account) {
            let allot = self.get_account_allot(account.clone());
            let debt = allot.0;
            let current_block_number = self.to_nano( env::block_timestamp()) as u128;

            let coin = self.account_coin.get(&account).expect(ERR_NOT_REGISTER).checked_add(debt).expect(ERR_ADD);
            let delta_block = current_block_number.checked_sub(user_stable.block).expect(ERR_SUB);
            if delta_block > 0 && coin > 0 {
                let fee = self.stable_fee_rate//16
                        .checked_mul(delta_block).expect(ERR_MUL)
                        .checked_mul(coin).expect(ERR_MUL)//8
                        .checked_div(BLOCK_PER_YEAR).expect(ERR_DIV)
                        .checked_div(self.oin_price).expect(ERR_DIV)//8
                        .checked_div(ONE_COIN).expect(ERR_DIV);//8
                        
                self.saved_stable = self.saved_stable
                        .checked_add(fee).expect(ERR_ADD);

                user_stable.saved_stable = user_stable.saved_stable
                        .checked_add(fee).expect(ERR_ADD); 
            }
            
            user_stable.block = current_block_number;
            self.account_stable.insert(&account, &user_stable);    
            log!("Current stabilization fee: {:?}",self.account_stable.get(&account));
        } else {
            env::panic(b"Not register")
        }
    }

Listagem 2.39: update_stable_fee:stablefee.rs

Sugestão I Realizar a multiplicação antes da divisão no cálculo das linhas 25 a 30.

2.3.9 O sistema pode não registrar o preço consultado anteriormente

Item Descrição
Status Confirmado e corrigido

Descrição Este problema foi introduzido em ou antes do Commit-1. A função não está implementada corretamente. O sistema pode não registrar o preço consultado pois o número total de tokens depositados no contrato é maior que 0 na maioria dos casos.

pub fn poke(&mut self, token_price: U128) {
    ...
       if self.total_token > 0 {
           if self.internal_sys_ratio() <= INIT_MIN_RATIO_LINE {
                self.internal_shutdown();
           }
       }else {
            log!(
                "{} poke price {} successfully.",
                env::predecessor_account_id(),
                token_price.0
            );
        }
    }

Listagem 2.40: poke:oracle.rs

Sugestão I O registro do comportamento de consulta do preço do token não deve ser influenciado pelo número de tokens depositados no contrato.

2.3.10 Distribuição descontínua do token de garantia na liquidação

Item Descrição
Status Confirmado e corrigido

Descrição Este problema foi introduzido em ou antes do Commit-4. Quando a razão de staking do usuário é maior ou igual a 108,5%, os usuários devem pagar a liquidation_fee, que representa 2% do allot_debt. No entanto, se a razão de staking do usuário for menor que 108,5%, ele/ela não precisa pagar a taxa de liquidação. Isso resulta no fato de que usuários com razão de staking maior podem alocar menos token de staking ao pool após a liquidação.

#[payable]
    pub fn liquidation(&mut self, account: AccountId) {
        ...
        if ratio >= INIT_NO_LIQUIDATION_FEE_RATE {
            liquidation_fee = _allot_debt
                            .checked_mul(self.liquidation_fee_ratio).expect(ERR_MUL)
                            .checked_mul(STAKE_RATIO_BASE).expect(ERR_MUL)//16
                            .checked_div(self.token_price).expect(ERR_DIV);
        }else{
            allot_ratio = ratio
                .checked_sub(self.gas_compensation_ratio).expect(ERR_SUB)
                .checked_add(1).expect(ERR_ADD);
        }
        ...

Listagem 2.41: liquidation:lib.rs

Sugestão I Para usuários cuja razão de staking está entre 108,5% e 110,5%, a taxa de liquidação sugerida é (razão de staking - 108,5%).

2.3.11 A otimização da precisão de cálculo não é necessária

Item Descrição
Status Confirmado e corrigido

Descrição Este problema foi introduzido em ou antes do Commit-4. Adicionar 1 na linha 832 da listagem 2.42 não pode aumentar a precisão do cálculo, pois self.gas_compensation_ratio é bastante grande.

#[payable]
    pub fn liquidation(&mut self, account: AccountId) {
        ...
        if ratio >= INIT_NO_LIQUIDATION_FEE_RATE {
            liquidation_fee = _allot_debt
                            .checked_mul(self.liquidation_fee_ratio).expect(ERR_MUL)
                            .checked_mul(STAKE_RATIO_BASE).expect(ERR_MUL)//16
                            .checked_div(self.token_price).expect(ERR_DIV);
        }else{
            allot_ratio = ratio
                .checked_sub(self.gas_compensation_ratio).expect(ERR_SUB)
                .checked_add(1).expect(ERR_ADD);
        }
        ...

Listagem 2.42: liquidation:lib.rs

Sugestão I Remover o "1" adicionado na linha 831 da listagem 2.42.

2.3.12 O Risco do Design Centralizado

Status Reconhecido

Descrição O projeto possui um design altamente centralizado. O proprietário do contrato tem privilégios muito elevados que podem adicionar/remover os gerentes de múltiplas assinaturas e podem retirar a taxa de liquidação e recompensas, etc. Tal mecanismo é absolutamente centralizado, tendo controle completo sobre todos os tokens. Recomendamos fortemente que o proprietário do projeto aplique mecanismos de segurança para proteger as chaves privadas do proprietário do contrato para gerenciar os contratos.

3. Avisos e Observações

3.1 Isenção de Responsabilidade

Este relatório de auditoria não constitui conselho de investimento ou uma recomendação pessoal. Ele não considera, e não deve ser interpretado como considerando ou tendo qualquer influência sobre, a potencial economia de um token, venda de token ou qualquer outro produto, serviço ou outro ativo. Nenhuma entidade deve se basear neste relatório de qualquer forma, inclusive para fins de tomar decisões de compra ou venda de qualquer token, produto, serviço ou outro ativo.

Este relatório de auditoria não é um endosso de nenhum projeto ou equipe específica, e o relatório não garante a segurança de nenhum projeto específico. Esta auditoria não oferece garantias sobre a descoberta de todos os problemas de segurança dos contratos inteligentes, ou seja, o resultado da avaliação não garante a inexistência de quaisquer outras descobertas de problemas de segurança. Como uma auditoria não pode ser considerada abrangente, recomendamos sempre prosseguir com auditorias independentes e um programa público de recompensas por bugs para garantir a segurança dos contratos inteligentes.

O escopo desta auditoria está limitado ao código mencionado na Seção 1.1. Salvo especificação explícita, a segurança da própria linguagem (por exemplo, a linguagem Rust), da cadeia de ferramentas de compilação subjacente e da infraestrutura computacional estão fora do escopo.

3.2 Procedimento de Auditoria

Realizamos a auditoria de acordo com o seguinte procedimento.

  • Detecção de Vulnerabilidades Primeiro verificamos os contratos inteligentes com analisadores de código automáticos e, em seguida, verificamos manualmente (rejeitamos ou confirmamos) os problemas reportados por eles.

  • Análise Semântica Estudamos a lógica de negócio dos contratos inteligentes e conduzimos investigação adicional sobre as possíveis vulnerabilidades usando uma ferramenta automática de fuzzing (desenvolvida pela nossa equipe de pesquisa). Também analisamos manualmente possíveis cenários de ataque com auditores independentes para verificar cruzadamente os resultados.

  • Recomendação Fornecemos alguns conselhos úteis aos desenvolvedores sob a perspectiva de boas práticas de programação, incluindo otimização de gas, estilo de código, entre outros.

Apresentamos os principais pontos de verificação concretos a seguir.

3.2.1 Segurança de Software

  • Reentrância

  • DoS

  • Controle de acesso

  • Manipulação e fluxo de dados

  • Tratamento de exceções

  • Chamada externa não confiável e fluxo de controle

  • Consistência de inicialização

  • Operações de eventos

  • Aleatoriedade propensa a erros

  • Uso inadequado do sistema de proxy

3.2.2 Segurança DeFi

  • Consistência semântica

  • Consistência de funcionalidade

  • Controle de acesso

  • Lógica de negócio

  • Operação de tokens

  • Mecanismo de emergência

  • Segurança de oráculos

  • Lista branca e lista negra

  • Impacto econômico

  • Transferência em lote

3.2.3 Segurança de NFT

  • Item duplicado

  • Verificação do receptor do token

  • Segurança de metadados off-chain

3.2.4 Recomendações Adicionais

  • Otimização de gas

  • Qualidade e estilo de código

Os pontos de verificação anteriores são os principais. Podemos usar mais pontos de verificação durante o processo de auditoria de acordo com a funcionalidade do projeto.

Best Security Auditor for Web3

Validate design, code, and business logic before launch. Aligned with the highest industry security standards.

BlockSec Audit