Manifesto do Relatório
| Item | Descrição |
|---|---|
| Cliente | LiNEAR Protocol |
| Alvo | LiNEAR |
Histórico de Versões
| Versão | Data | Descrição |
|---|---|---|
| 1.0 | 1º de Abr, 2022 | Primeira Versão |
1. Introdução
1.1 Sobre os Contratos Alvo
| Informação | Descrição |
|---|---|
| Tipo | Contrato Inteligente |
| Linguagem | Rust |
| Abordagem | Verificação semiautomática e manual |
O repositório que foi auditado inclui LiNEAR ^1.
O processo de auditoria é iterativo. Especificamente, auditaremos os commits que corrigem os problemas descobertos. Se houver novos problemas, continuaremos esse processo. Os valores de SHA dos commits durante a auditoria estão apresentados a seguir. Nosso relatório de auditoria é responsável pela versão inicial (ou seja, Versão 1), bem como pelos novos códigos (nas versões seguintes) para corrigir problemas no relatório de auditoria.

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. A severidade geral do risco é determinada pela probabilidade e pelo impacto. Especificamente, a probabilidade é usada para estimar a chance de uma determinada vulnerabilidade ser descoberta e explorada por um atacante, enquanto o impacto é usado para medir as consequências de uma exploração bem-sucedida.
Neste relatório, tanto a probabilidade quanto o impacto são categorizados em duas classificações, ou seja, alta e baixa, respectivamente, e suas combinações são apresentadas na Tabela 1.1.

Consequentemente, a severidade medida neste relatório é classificada em três categorias: Alta, Média, Baixa. Em prol da completude, Indeterminada também é usada para cobrir circunstâncias em que o risco não pode ser bem determinado.
Além disso, o status de um problema descoberto se enquadrará em uma das seguintes quatro categorias:
-
Indeterminado Sem resposta ainda.
-
Reconhecido O problema foi recebido pelo cliente, mas ainda não confirmado.
-
Confirmado O problema foi reconhecido pelo cliente, mas ainda não corrigido.
-
Corrigido O problema foi confirmado e corrigido pelo cliente.
2. Descobertas
No total, encontramos 4 problemas potenciais no contrato inteligente. Também temos 4 recomendações, como segue:
-
Risco Alto: 0
-
Risco Médio: 2
-
Risco Baixo: 2
-
Recomendações: 4
| ID | Severidade | Descrição | Categoria | Status |
|---|---|---|---|---|
| 1 | Média | Perda de precisão | Segurança de Software | Corrigido |
| 2 | Baixa | O saldo disponível do usuário pode ser bloqueado temporariamente | Segurança DeFi | Confirmado |
| 3 | Média | Distribuição ilimitada de recompensas aos beneficiários | Segurança DeFi | Corrigido |
| 4 | Baixa | As solicitações de unstaking dos usuários podem não ser atendidas a tempo | Segurança DeFi | Corrigido |
| 7 | - | A função epoch_update_rewards pode não funcionar devido a beneficiários ilimitados | Recomendação | Corrigido |
| 8 | - | Código redundante | Recomendação | Confirmado |
| 6 | - | Verificação ausente do prepaid_gas na função ft_transfer_call | Recomendação | Corrigido |
| 9 | - | O risco do design centralizado | Recomendação | Confirmado |
Os detalhes são fornecidos nas seções a seguir.
2.1 Segurança de Software
2.1.1 Problema Potencial 1: Perda de precisão
| Informação | Descrição |
|---|---|
| Status | Corrigido na versão 2 |
| Introduzido pela | versão 1 |
Descrição Na linha 125 da função internal_calculate_distribution, a divisão é realizada antes da multiplicação ao calcular a variável reward_per_session.
fn internal_calculate_distribution(
&self,
farm: &Farm,
total_staked: Balance,
) -> Option<RewardDistribution> {
if farm.start_date > env::block_timestamp() {
// Farm hasn't started.
return None;
}
let mut distribution = farm.last_distribution.clone();
if distribution.undistributed == 0 {
// Farm has ended.
return Some(distribution);
}
distribution.reward_round = (env::block_timestamp() - farm.start_date) / SESSION_INTERVAL;
let reward_per_session =
farm.amount / (farm.end_date - farm.start_date) as u128 * SESSION_INTERVAL as u128;
Listagem 2.1: contracts/linear/src/farm.rs
Impacto A divisão trunca inteiros na linguagem Rust. Nesse caso, a divisão antes da multiplicação para inteiros pode resultar em perda de precisão.
Sugestão I Modificar este cálculo para realizar a multiplicação antes da divisão.
Sugestão II Pré-calcular o valor de reward_per_session quando uma farm é adicionada. Isso ocorre porque farm.amount, farm.end_date e farm.start_date não mudam durante o processo de farming, a menos que o proprietário o interrompa.
2.2 Segurança DeFi
2.2.1 Problema Potencial 2: O saldo disponível do usuário pode ser bloqueado temporariamente
| Informação | Descrição |
|---|---|
| Status | Confirmado |
| Introduzido pela | versão 1 |
Descrição Os NEARs depositados pelo usuário serão adicionados diretamente ao saldo de unstaked do usuário. Portanto, se o usuário invocar as funções unstake/unstake_all, essa quantidade de NEARs disponíveis ficará bloqueada nos próximos 0 a 8 epochs.
pub(crate) fn internal_deposit(&mut self, amount: Balance) {
let account_id = env::predecessor_account_id();
let mut account = self.internal_get_account(&account_id);
account.unstaked += amount;
self.internal_save_account(&account_id, &account);
Event::Deposit {
account_id: &account_id,
amount: &U128(amount),
new_unstaked_balance: &U128(account.unstaked),
}
.emit();
}
Listagem 2.2: contracts/linear/src/internal.rs
Impacto Se um usuário não estiver ciente do fluxo de trabalho deste contrato e interagir diretamente com ele, o saldo disponível do usuário pode ser bloqueado temporariamente.
Sugestão I Adicionar outro atributo (por exemplo, available_amount) à struct Account para manter os NEARs disponíveis.
Feedback do Projeto Isso é por design, seguindo basicamente a interface e o design originais do pool de staking. Para resolver o problema potencial, podemos fazer melhorias adicionando outro campo 'unstaking' para distinguir de 'unstaked', e mover 'unstaking' para 'unstaked' antes de iniciarmos o próximo processo de unstaking para esta conta. Mas por ora, preferimos não fazer a mudança no momento, para manter o fluxo de trabalho consistente com o pool de staking. Como alternativa, quando os usuários estiverem fazendo unstaking pelo frontend, a interface irá lembrar os usuários de sacar primeiro caso tenham valor 'unstaked' em sua conta.
2.2.2 Problema Potencial 3: Distribuição ilimitada de recompensas aos beneficiários
| Informação | Descrição |
|---|---|
| Status | Corrigido na versão 2 |
| Introduzido pela | versão 1 |
Descrição Este contrato não verifica o peso total de todos os beneficiários na função assert_valid ao configurar um novo beneficiário.
pub fn set_beneficiary(&mut self, account_id: AccountId, fraction: Fraction) {
self.assert_owner();
fraction.assert_valid();
self.beneficiaries.insert(&account_id, &fraction);
}
Listagem 2.3: contracts/linear/src/owner.rs
pub fn assert_valid(&self) {
require!(self.denominator != 0, ERR_FRACTION_BAD_DENOMINATOR);
require!(
self.numerator <= self.denominator,
ERR_FRACTION_BAD_NUMERATOR
);
}
Listagem 2.4: contracts/linear/src/utils.rs
Impacto Uma vez que o peso total dos beneficiários ultrapasse 100%, os LiNEARs cunhados para os beneficiários podem diminuir o preço do LiNEAR após a execução da ação epoch_update_rewards.
Sugestão I Introduzir um limite razoável para restringir o total de recompensas distribuídas aos beneficiários.
2.2.3 Problema Potencial 4: As solicitações de unstaking dos usuários podem não ser atendidas a tempo
| Informação | Descrição |
|---|---|
| Status | Corrigido na versão 2 |
| Introduzido pela | versão 1 |
Descrição O número de epochs retornado pela função get_num_epoch_to_unstake deve ser dobrado em alguns casos extremos. Por exemplo, se o total de NEARs em staking nos pools de validadores, que não estão em status pendente, não for suficiente, os usuários não poderão sacar todos os NEARs de unstaking solicitados após 4 epochs.
pub fn get_num_epoch_to_unstake(&self, _amount: u128) -> EpochHeight {
// the num of epoches can be doubled or trippled if not enough stake is available
NUM_EPOCHS_TO_UNLOCK
}
Listagem 2.5: contracts/linear/src/validator_pool.rs
Impacto As solicitações de unstaking dos usuários podem não ser sempre atendidas a tempo.
Sugestão I Implementar uma estratégia para prever o tempo de espera de unstaking do usuário com base no status dos pools de staking dos validadores.
2.3 Recomendação Adicional
2.3.1 A função epoch_update_rewards pode não funcionar devido a beneficiários ilimitados
| Informação | Descrição |
|---|---|
| Status | Corrigido na versão 2 |
| Introduzido pela | versão 1 |
Descrição O número de beneficiários é ilimitado. Nesse caso, a função epoch_update_rewards que invoca a função internal_distribute_staking_rewards pode ficar sem gas.
pub fn epoch_update_rewards(&mut self, validator_id: AccountId) {
let min_gas = GAS_EPOCH_UPDATE_REWARDS + GAS_EXT_GET_BALANCE + GAS_CB_VALIDATOR_GET_BALANCE;
require!(
env::prepaid_gas() >= min_gas,
format!("{}. require at least {:?}", ERR_NO_ENOUGH_GAS, min_gas)
);
let validator = self
.validator_pool
.get_validator(&validator_id)
.expect(ERR_VALIDATOR_NOT_EXIST);
if validator.staked_amount == 0 && validator.unstaked_amount == 0 {
return;
}
validator
.refresh_total_balance()
.then(ext_self_action_cb::validator_get_balance_callback(
validator.account_id,
env::current_account_id(),
NO_DEPOSIT,
GAS_CB_VALIDATOR_GET_BALANCE,
));
}
Listagem 2.6: contracts/linear/src/epoch_actions.rs
/// When there are rewards, a part of them will be
/// given to executor, manager or treasury by minting new LiNEAR tokens.
pub(crate) fn internal_distribute_staking_rewards(&mut self, rewards: Balance) {
let hashmap: HashMap<AccountId, Fraction> = self.internal_get_beneficiaries();
for (account_id, fraction) in hashmap.iter() {
let reward_near_amount: Balance = fraction.multiply(rewards);
// mint extra LiNEAR for him
self.internal_mint_beneficiary_rewards(&account_id, reward_near_amount);
}
}
Listagem 2.7: contract/src/internal.rs
Impacto A função epoch_update_rewards pode não funcionar devido ao gas limitado quando há beneficiários demais.
Sugestão I Recomenda-se adicionar um limite razoável para restringir o número de beneficiários.
2.3.2 Código redundante
| Informação | Descrição |
|---|---|
| Status | Confirmado |
| Introduzido pela | versão 1 |
Descrição O parâmetro registration_only é redundante, pois a função storage_deposit não implementa nenhuma lógica para este parâmetro.
fn storage_deposit(
&mut self,
account_id: Option<AccountId>,
registration_only: Option<bool>,
) -> StorageBalance {
let amount: Balance = env::attached_deposit();
let account_id = account_id.unwrap_or_else(env::predecessor_account_id);
if let Some(account) = self.accounts.get(&account_id) {
log!("The account is already registered, refunding the deposit");
if amount > 0 {
Promise::new(env::predecessor_account_id()).transfer(amount);
}
} else {
let min_balance = self.storage_balance_bounds().min.0;
if amount < min_balance {
env::panic_str("The attached deposit is less than the minimum storage balance");
}
self.internal_register_account(&account_id);
let refund = amount - min_balance;
if refund > 0 {
Promise::new(env::predecessor_account_id()).transfer(refund);
}
}
self.internal_storage_balance_of(&account_id).unwrap()
}
Listagem 2.8: contracts/linear/src/fungible_token/storage.rs
Sugestão I Recomenda-se remover este parâmetro não utilizado da função storage_deposit.
Feedback do Projeto É verdade. O mesmo ocorre na implementação padrão de FT no crate nearcontract-standards. Manteremos o parâmetro registration_only não utilizado para manter consistência com a interface padrão storage_deposit(account_id, registration_only).
2.3.3 Verificação ausente do prepaid_gas na função ft_transfer_call
| Informação | Descrição |
|---|---|
| Status | Corrigido na versão 2 |
| Introduzido pela | versão 1 |
Descrição O prepaid_gas deve ser verificado para garantir que seja suficiente para as funções alvo, incluindo ft_on_transfer e ft_resolve_transfer.
#[payable]
fn ft_transfer_call(
&mut self,
receiver_id: AccountId,
amount: U128,
memo: Option<String>,
msg: String,
) -> PromiseOrValue<U128> {
assert_one_yocto();
let sender_id = env::predecessor_account_id();
let amount = amount.into();
self.internal_ft_transfer(&sender_id, &receiver_id, amount, memo);
// Initiating receiver's call and the callback
ext_fungible_token_receiver::ft_on_transfer(
sender_id.clone(),
amount.into(),
msg,
receiver_id.clone(),
NO_DEPOSIT,
env::prepaid_gas() - GAS_FOR_FT_TRANSFER_CALL,
)
.then(ext_ft_self::ft_resolve_transfer(
sender_id,
receiver_id,
amount.into(),
env::current_account_id(),
NO_DEPOSIT,
GAS_FOR_RESOLVE_TRANSFER,
))
.into()
}
Listagem 2.9: contracts/linear/src/fungible_token/core.rs
Sugestão I Verificar o prepaid_gas.
2.3.4 O risco do design centralizado
| Informação | Descrição |
|---|---|
| Status | Confirmado |
| Introduzido pela | versão 1 |
Descrição Este projeto tem potenciais problemas de centralização.
Sugestão I Recomenda-se introduzir um design descentralizado no contrato, como múltiplas assinaturas ou DAO.
Feedback do Projeto I Sim. Isso está planejado conforme mencionado em github.com/linear-protocol/LiNEAR/issues/60
Sugestão II O proprietário do projeto precisa garantir a segurança da chave privada do OWNER_ROLE/MANAGERS_ROLE e usar um esquema de múltiplas assinaturas para reduzir o risco de falha em ponto único.
Feedback do Projeto II Sim. Temos trabalhado em políticas de segurança para reduzir os riscos de ponto único de falha.
3. Avisos e Observações
3.1 Isenção de Responsabilidade
Este relatório de auditoria não constitui aconselhamento de investimento nem uma recomendação pessoal. Ele não considera, e não deve ser interpretado como considerando ou tendo qualquer relação com, a economia potencial 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 em particular, e o relatório não garante a segurança de nenhum projeto específico. Esta auditoria não oferece nenhuma garantia 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, sempre recomendamos 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 Solidity), o conjunto de ferramentas de compilação subjacente e a 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 relatados por eles.
-
Análise Semântica Estudamos a lógica de negócio dos contratos inteligentes e conduzimos investigações adicionais sobre as possíveis vulnerabilidades usando uma ferramenta automática de fuzzing (desenvolvida por nossa equipe de pesquisa). Também analisamos manualmente possíveis cenários de ataque com auditores independentes para verificação cruzada dos 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 impróprio 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 de permissões e lista de bloqueios
-
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ção Adicional
-
Otimização de gas
-
Qualidade e estilo de código
::: Note Os pontos de verificação anteriores são os principais. Podemos utilizar mais pontos de verificação durante o processo de auditoria de acordo com a funcionalidade do projeto. :::



