Manifiesto del Informe
| Elemento | Descripción |
|---|---|
| Cliente | LiNEAR Protocol |
| Objetivo | LiNEAR |
Historial de Versiones
| Versión | Fecha | Descripción |
|---|---|---|
| 1.0 | 1 de abril de 2022 | Primera Versión |
1. Introducción
1.1 Acerca de los Contratos Objetivo
| Información | Descripción |
|---|---|
| Tipo | Contrato Inteligente |
| Lenguaje | Rust |
| Enfoque | Verificación semi-automática y manual |
El repositorio que ha sido auditado incluye LiNEAR ^1.
El proceso de auditoría es iterativo. Específicamente, auditaremos los commits que corrijan los problemas descubiertos. Si hay nuevos problemas, continuaremos este proceso. Los valores SHA de los commits durante la auditoría se muestran a continuación. Nuestro informe de auditoría es responsable de la versión inicial (es decir, la Versión 1), así como del nuevo código (en las versiones siguientes) para corregir los problemas del informe de auditoría.

1.2 Modelo de Seguridad
Para evaluar el riesgo, seguimos los estándares o sugerencias ampliamente adoptados tanto por la industria como por la academia, incluyendo la Metodología de Evaluación de Riesgos de OWASP ^2 y la Enumeración de Debilidades Comunes ^3. La gravedad general del riesgo está determinada por la probabilidad y el impacto. Específicamente, la probabilidad se utiliza para estimar cuán probable es que una vulnerabilidad particular pueda ser descubierta y explotada por un atacante, mientras que el impacto se utiliza para medir las consecuencias de una explotación exitosa.
En este informe, tanto la probabilidad como el impacto se clasifican en dos niveles, es decir, alto y bajo respectivamente, y sus combinaciones se muestran en la Tabla 1.1.

En consecuencia, la gravedad medida en este informe se clasifica en tres categorías: Alta, Media, Baja. En aras de la exhaustividad, Indeterminada también se utiliza para cubrir circunstancias en las que el riesgo no puede determinarse con precisión.
Además, el estado de un problema descubierto se encuadrará en una de las siguientes cuatro categorías:
-
Indeterminado Sin respuesta aún.
-
Reconocido El problema ha sido recibido por el cliente, pero aún no confirmado.
-
Confirmado El problema ha sido reconocido por el cliente, pero aún no corregido.
-
Corregido El problema ha sido confirmado y corregido por el cliente.
2. Hallazgos
En total, encontramos 4 posibles problemas en el contrato inteligente. También tenemos 4 recomendaciones, como se detalla a continuación:
-
Riesgo Alto: 0
-
Riesgo Medio: 2
-
Riesgo Bajo: 2
-
Recomendaciones: 4
| ID | Gravedad | Descripción | Categoría | Estado |
|---|---|---|---|---|
| 1 | Media | Pérdida de precisión | Seguridad de Software | Corregido |
| 2 | Baja | El saldo disponible del usuario puede quedar bloqueado temporalmente | Seguridad DeFi | Confirmado |
| 3 | Media | Distribución ilimitada de recompensas a beneficiarios | Seguridad DeFi | Corregido |
| 4 | Baja | Las solicitudes de retiro de staking de los usuarios pueden no ser atendidas a tiempo | Seguridad DeFi | Corregido |
| 7 | - | La función epoch_update_rewards puede no funcionar debido a beneficiarios ilimitados | Recomendación | Corregido |
| 8 | - | Código redundante | Recomendación | Confirmado |
| 6 | - | Verificación faltante del prepaid_gas en la función ft_transfer_call | Recomendación | Corregido |
| 9 | - | El riesgo del diseño centralizado | Recomendación | Confirmado |
Los detalles se proporcionan en las siguientes secciones.
2.1 Seguridad de Software
2.1.1 Problema Potencial 1: Pérdida de precisión
| Información | Descripción |
|---|---|
| Estado | Corregido en la versión 2 |
| Introducido en | versión 1 |
Descripción En la línea 125 de la función internal_calculate_distribution, la división se realiza antes de la multiplicación al calcular la variable 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;
Listado 2.1: contracts/linear/src/farm.rs
Impacto La división trunca los enteros en el lenguaje Rust. En este caso, la división antes de la multiplicación para enteros puede resultar en pérdida de precisión.
Sugerencia I Modificar este cálculo para realizar la multiplicación antes de la división.
Sugerencia II Pre-calcular el valor de reward_per_session cuando se agrega una farm. Esto se debe a que farm.amount, farm.end_date y farm.start_date no cambian durante el proceso de farming a menos que el propietario lo detenga.
2.2 Seguridad DeFi
2.2.1 Problema Potencial 2: El saldo disponible del usuario puede quedar bloqueado temporalmente
| Información | Descripción |
|---|---|
| Estado | Confirmado |
| Introducido en | versión 1 |
Descripción Los NEARs depositados por el usuario se agregarán directamente al saldo sin staking del usuario. Por lo tanto, si el usuario invocó las funciones unstake/unstake_all, esa cantidad de NEARs disponibles quedará bloqueada en los próximos 0 a 8 épocas.
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();
}
Listado 2.2: contracts/linear/src/internal.rs
Impacto Si un usuario desconoce el flujo de trabajo de este contrato e interactúa directamente con él, el saldo disponible del usuario puede quedar bloqueado temporalmente.
Sugerencia I Agregar otro atributo (por ejemplo, available_amount) a la estructura Account para mantener los NEARs disponibles.
Comentario del Proyecto Esto es por diseño, siguiendo básicamente la interfaz y el diseño originales del grupo de staking. Para resolver el problema potencial, podemos realizar mejoras para agregar otro campo 'unstaking' que se distinga de 'unstaked', y mover 'unstaking' a 'unstaked' antes de iniciar el siguiente proceso de retiro de staking para esta cuenta. Pero por ahora, preferimos no realizar el cambio en este momento, para mantener el flujo de trabajo consistente con el grupo de staking. Como solución alternativa, cuando los usuarios realizan el retiro de staking desde el frontend, la interfaz de usuario les recordará que primero retiren si tienen un monto 'unstaked' en su cuenta.
2.2.2 Problema Potencial 3: Distribución ilimitada de recompensas a beneficiarios
| Información | Descripción |
|---|---|
| Estado | Corregido en la versión 2 |
| Introducido en | versión 1 |
Descripción Este contrato no verifica el peso total de todos los beneficiarios en la función assert_valid al configurar un nuevo beneficiario.
pub fn set_beneficiary(&mut self, account_id: AccountId, fraction: Fraction) {
self.assert_owner();
fraction.assert_valid();
self.beneficiaries.insert(&account_id, &fraction);
}
Listado 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
);
}
Listado 2.4: contracts/linear/src/utils.rs
Impacto Una vez que el peso total de los beneficiarios supere el 100%, los LiNEARs acuñados para los beneficiarios pueden disminuir el precio de LiNEAR después de la ejecución de la acción epoch_update_rewards.
Sugerencia I Introducir un umbral razonable para limitar la recompensa total distribuida a los beneficiarios.
2.2.3 Problema Potencial 4: Las solicitudes de retiro de staking de los usuarios pueden no ser atendidas a tiempo
| Información | Descripción |
|---|---|
| Estado | Corregido en la versión 2 |
| Introducido en | versión 1 |
Descripción El número de épocas devuelto por la función get_num_epoch_to_unstake debería duplicarse en algunos casos límite. Por ejemplo, si el total de NEARs en staking en los grupos de validadores, que no están en estado pendiente, no es suficiente, los usuarios no pueden retirar todos los NEARs sin staking solicitados después de 4 épocas.
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
}
Listado 2.5: contracts/linear/src/validator_pool.rs
Impacto Las solicitudes de retiro de staking de los usuarios pueden no siempre ser atendidas a tiempo.
Sugerencia I Implementar una estrategia para predecir el tiempo de espera de retiro de staking del usuario basándose en el estado de los grupos de validadores.
2.3 Recomendación Adicional
2.3.1 La función epoch_update_rewards puede no funcionar debido a beneficiarios ilimitados
| Información | Descripción |
|---|---|
| Estado | Corregido en la versión 2 |
| Introducido en | versión 1 |
Descripción El número de beneficiarios es ilimitado. En este caso, la función epoch_update_rewards que invoca a la función internal_distribute_staking_rewards puede quedarse sin 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,
));
}
Listado 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);
}
}
Listado 2.7: contract/src/internal.rs
Impacto La función epoch_update_rewards puede no funcionar debido al gas limitado cuando hay demasiados beneficiarios.
Sugerencia I Se recomienda agregar un umbral razonable para limitar el número de beneficiarios.
2.3.2 Código redundante
| Información | Descripción |
|---|---|
| Estado | Confirmado |
| Introducido en | versión 1 |
Descripción El parámetro registration_only es redundante ya que la función storage_deposit no implementa ninguna 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()
}
Listado 2.8: contracts/linear/src/fungible_token/storage.rs
Sugerencia I Se recomienda eliminar este parámetro no utilizado en la función storage_deposit.
Comentario del Proyecto Es cierto. Lo mismo ocurre con la implementación estándar de FT en el crate nearcontract-standards. Mantendremos el parámetro no utilizado registration_only para mantener la consistencia con la interfaz estándar storage_deposit(account_id, registration_only).
2.3.3 Verificación faltante del prepaid_gas en la función ft_transfer_call
| Información | Descripción |
|---|---|
| Estado | Corregido en la versión 2 |
| Introducido en | versión 1 |
Descripción El prepaid_gas debe verificarse para asegurarse de que sea suficiente para las funciones objetivo, incluyendo ft_on_transfer y 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()
}
Listado 2.9: contracts/linear/src/fungible_token/core.rs
Sugerencia I Verificar el prepaid_gas.
2.3.4 El riesgo del diseño centralizado
| Información | Descripción |
|---|---|
| Estado | Confirmado |
| Introducido en | versión 1 |
Descripción Este proyecto tiene posibles problemas de centralización.
Sugerencia I Se recomienda introducir un diseño descentralizado en el contrato, como firma múltiple o DAO.
Comentario del Proyecto I Sí. Esto está planificado como se menciona en github.com/linear-protocol/LiNEAR/issues/60
Sugerencia II El propietario del proyecto debe garantizar la seguridad de la clave privada del OWNER_ROLE/MANAGERS_ROLE y utilizar un esquema de firma múltiple para reducir el riesgo de fallo en un único punto.
Comentario del Proyecto II Sí. Hemos estado trabajando en políticas de seguridad para reducir los riesgos de fallo en un único punto.
3. Avisos y Observaciones
3.1 Descargo de Responsabilidad
Este informe de auditoría no constituye asesoramiento de inversión ni una recomendación personal. No tiene en cuenta, y no debe interpretarse como que considera o tiene alguna incidencia en, la economía potencial de un token, venta de tokens o cualquier otro producto, servicio u otro activo. Ninguna entidad debe confiar en este informe de ninguna manera, incluyendo para el propósito de tomar decisiones de compra o venta de cualquier token, producto, servicio u otro activo.
Este informe de auditoría no constituye un respaldo de ningún proyecto o equipo en particular, y el informe no garantiza la seguridad de ningún proyecto en particular. Esta auditoría no otorga ninguna garantía sobre el descubrimiento de todos los problemas de seguridad de los contratos inteligentes, es decir, el resultado de la evaluación no garantiza la inexistencia de hallazgos adicionales de problemas de seguridad. Dado que una auditoría no puede considerarse exhaustiva, siempre recomendamos realizar auditorías independientes y un programa público de recompensas por errores para garantizar la seguridad de los contratos inteligentes.
El alcance de esta auditoría se limita al código mencionado en la Sección 1.1. A menos que se especifique explícitamente, la seguridad del lenguaje en sí (por ejemplo, el lenguaje Solidity), la cadena de herramientas de compilación subyacente y la infraestructura informática quedan fuera del alcance.
3.2 Procedimiento de Auditoría
Realizamos la auditoría de acuerdo con el siguiente procedimiento.
-
Detección de Vulnerabilidades Primero analizamos los contratos inteligentes con analizadores de código automáticos, y luego verificamos manualmente (rechazamos o confirmamos) los problemas reportados por ellos.
-
Análisis Semántico Estudiamos la lógica de negocio de los contratos inteligentes y realizamos una investigación adicional sobre las posibles vulnerabilidades utilizando una herramienta de fuzzing automática (desarrollada por nuestro equipo de investigación). También analizamos manualmente posibles escenarios de ataque con auditores independientes para contrastar los resultados.
-
Recomendación Proporcionamos algunos consejos útiles a los desarrolladores desde la perspectiva de las buenas prácticas de programación, incluyendo la optimización del gas, el estilo del código, entre otros.
A continuación se muestran los principales puntos de verificación concretos.
3.2.1 Seguridad de Software
-
Reentrada
-
DoS
-
Control de acceso
-
Manejo de datos y flujo de datos
-
Manejo de excepciones
-
Llamada externa no confiable y flujo de control
-
Consistencia de inicialización
-
Operaciones de eventos
-
Aleatoriedad propensa a errores
-
Uso indebido del sistema proxy
3.2.2 Seguridad DeFi
-
Consistencia semántica
-
Consistencia de funcionalidad
-
Control de acceso
-
Lógica de negocio
-
Operación de tokens
-
Mecanismo de emergencia
-
Seguridad del oráculo
-
Lista blanca y lista negra
-
Impacto económico
-
Transferencia por lotes
3.2.3 Seguridad NFT
-
Elemento duplicado
-
Verificación del receptor del token
-
Seguridad de metadatos fuera de cadena
3.2.4 Recomendación Adicional
-
Optimización del gas
-
Calidad y estilo del código
::: Note Los puntos de verificación anteriores son los principales. Podemos utilizar más puntos de verificación durante el proceso de auditoría según la funcionalidad del proyecto. :::



