Manifiesto del Informe
| Elemento | Descripción |
|---|---|
| Cliente | Oinfinance |
| Objetivo | NearOinDao |
Historial de Versiones
| Versión | Fecha | Descripción |
|---|---|---|
| 1.0 | 04 Dic, 2021 | Primera Versión |
1. Introducción
1.1 Acerca de los Contratos Objetivo
Los contratos objetivo contienen un módulo de moneda estable. A su alrededor, también implementa otros módulos, incluyendo Staking y Farming. Estos módulos crean un ciclo de retroalimentación positivo para la estabilización de la moneda estable, es decir, USDO.
| Información | Descripción |
|---|---|
| Tipo | Contrato Inteligente |
| Lenguaje | Rust |
| Enfoque | Verificación semi-automática y manual |
Los repositorios que han sido auditados incluyen NearOinDao ^1
El proceso de auditoría es iterativo. Específicamente, auditaremos más a fondo los commits que corrigen los problemas encontrados. Si hay nuevos problemas, continuaremos con este proceso. Por lo tanto, hay múltiples valores de SHA de commit referenciados en este informe. Los valores de SHA de commit antes y después de la auditoría se muestran a continuación.
Antes y durante la auditoría

Después
| Proyecto | SHA del Commit |
|---|---|
| NearOinDao | 3bd117606c753d3c2f66b6dcddd1ae18ea47a20a |
1.2 Modelo de Seguridad
Para evaluar el riesgo, seguimos los estándares o sugerencias adoptados ampliamente tanto por la industria como por la academia, incluyendo la Metodología de Calificación de Riesgos de OWASP ^2 y la Enumeración de Debilidades Comunes ^3. En consecuencia, la gravedad medida en este informe se clasifica en cuatro categorías: Alta, Media, Baja e Indeterminada.
2. Hallazgos
En total, encontramos 22 problemas potenciales en el contrato inteligente. También tenemos 12 recomendaciones, como se indica a continuación:
-
Riesgo Alto: 19
-
Riesgo Medio: 2
-
Riesgo Bajo: 1
-
Recomendaciones: 12
Los detalles se proporcionan en las siguientes secciones.
| ID | Gravedad | Descripción | Categoría | Estado |
|---|---|---|---|---|
| 1 | Alta | Error de lógica cuando se modifica self.liquidation_line | Seguridad de Software | Confirmado y corregido |
| 2 | Alta | La función de liquidación puede no funcionar | Seguridad de Software | Confirmado y corregido |
| 3 | Alta | Error de lógica al establecer la marca de tiempo para abrir el contrato | Seguridad de Software | Confirmado y corregido |
| 4 | Alta | El estado del contrato no se revierte si la transacción entre contratos falla | Seguridad de Software | Confirmado y corregido |
| 5 | Alta | Cualquiera puede añadir el saldo de recompensa | Seguridad DeFi | Confirmado y corregido |
| 6 | Alta | Cualquiera puede añadir el saldo de recompensa del pool estable | Seguridad DeFi | Confirmado y corregido |
| 7 | Alta | Cualquiera puede quemar las monedas de otros usuarios | Seguridad DeFi | Confirmado y corregido |
| 8 | Alta | Cualquiera puede añadir el saldo de su cuenta | Seguridad DeFi | Confirmado y corregido |
| 9 | Alta | El oráculo no verifica el intervalo de tiempo | Seguridad DeFi | Confirmado y corregido |
| 10 | Alta | El intervalo de tiempo del oráculo es demasiado largo | Seguridad DeFi | Confirmado y corregido |
| 11 | Alta | No hay oráculo para el precio de Oin | Seguridad DeFi | Confirmado y corregido |
| 12 | Alta | Los usuarios pueden obtener recompensas adicionales | Seguridad DeFi | Confirmado y corregido |
| 13 | Alta | Los usuarios pueden pagar menos comisión estable | Seguridad DeFi | Confirmado y corregido |
| 14 | Media | La solicitud multi-firma puede confirmarse con una tasa de confirmación relativamente baja | Seguridad DeFi | Confirmado y corregido |
| 15 | Media | El número de bloques por año es inexacto | Seguridad DeFi | Confirmado y corregido |
| 16 | Alta | Las monedas acuñadas disponibles no son correctas | Seguridad DeFi | Confirmado y corregido |
| 17 | Alta | El pago de la comisión estable puede resultar en la pérdida de los tokens depositados por el usuario | Seguridad DeFi | Confirmado y corregido |
| 18 | Alta | Ratio de staking incorrecto | Seguridad DeFi | Confirmado y corregido |
| 19 | Baja | Las monedas de recompensa pueden superar el límite | Seguridad DeFi | Confirmado y corregido |
| 20 | Alta | Misma lista blanca para usuarios con diferentes privilegios | Seguridad DeFi | Confirmado y corregido |
| 21 | Alta | Sin verificación de la dirección de la comisión estable | Seguridad DeFi | Confirmado y corregido |
| 22 | Alta | El total_reward de la moneda de recompensa puede ser modificado por gestores multi-firma | Seguridad DeFi | Confirmado y corregido |
| 23 | - | Aserción redundante | Recomendación | Confirmado y corregido |
| 24 | - | Consideración repetida de la línea de liquidación | Recomendación | Confirmado y corregido |
| 25 | - | Verificación redundante de lista blanca | Recomendación | Confirmado y corregido |
| 26 | - | Función no utilizada | Recomendación | Confirmado y corregido |
| 27 | - | Código redundante | Recomendación | Confirmado y corregido |
| 28 | - | El nombre de la función y la implementación están en conflicto | Recomendación | Confirmado y corregido |
| 29 | - | Código redundante | Recomendación | Confirmado y corregido |
| 30 | - | La precisión del cálculo puede mejorarse | Recomendación | Confirmado y corregido |
| 31 | - | El sistema puede no registrar el precio consultado previamente | Recomendación | Confirmado y corregido |
| 32 | - | Distribución discontinua del token de garantía en la liquidación | Recomendación | Confirmado y corregido |
| 33 | - | La optimización de la precisión del cálculo no es necesaria | Recomendación | Confirmado y corregido |
| 34 | - | El riesgo del diseño centralizado | Recomendación | Reconocido |
2.1 Seguridad de Software
2.1.1 Problema Potencial 1: Dos atributos diferentes para el mismo uso
| Elemento | Descripción |
|---|---|
| Estado | Confirmado y corregido |
Descripción Este problema se introduce en o antes del Commit-1. Dos atributos (es decir, self.cost y self.liquidation_line) representan el mismo estado del contrato, que es la línea de liquidación del usuario. Se utilizan en diferentes funciones del contrato (Listado 2.1 y Listado 2.2). Sin embargo, self.liquidation_line puede ser modificado con la función set_liquidation_line mientras que self.cost no puede cambiar. En este caso, si self.liquidation_line es modificado, self.cost mantiene el valor original. Esto puede influir en la lógica de la función assert_user_ratio (Listado 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.");
}
}
Listado 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");
...
Listado 2.2: internal_can_mint_amount:lib.rs
Impacto La línea de liquidación de los usuarios no es consistente en las diferentes funciones del contrato, lo que influye en la lógica de todo el contrato.
Sugerencia I Podemos unificar los usos de estos dos atributos al calcular el ratio de staking del usuario y compararlo con la línea de liquidación del sistema.
2.1.2 Problema Potencial 2: Distribución inválida de la recompensa de liquidación
| Elemento | Descripción |
|---|---|
| Estado | Confirmado y corregido |
Descripción Este problema se introduce en o antes del Commit-4. La cuenta del remitente de liquidación y la cuenta del propietario del contrato pueden no estar registradas (Línea 193 y 206 del Listado 2.3). En este caso, cuando el remitente intenta realizar una acción de liquidación, la transacción no puede ejecutarse con éxito debido a la excepción generada de que las cuentas no están 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);
}
}
Listado 2.3: personal_liquidation_token:reward.rs
Impacto La función de liquidación no puede ejecutarse con éxito debido a la excepción generada de que las cuentas no están registradas.
Sugerencia I Verificar la existencia de la cuenta del remitente de liquidación y la cuenta del propietario del contrato al inicio de la función de liquidación.
2.1.3 Problema Potencial 3: Block_timestamp se guarda en closed_time al abrir el sistema
| Elemento | Descripción |
|---|---|
| Estado | Confirmado y corregido |
Descripción Este problema se introduce en o antes del Commit-3. env::block_time_stamp() no debería guardarse en self.closed_time al invocar la función 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
);
}
Listado 2.4: internal_open:esm.rs
Impacto El tiempo de apertura y el tiempo de cierre del contrato son completamente incorrectos. Las actualizaciones posteriores que dependan de la información de tiempo pueden tener errores de lógica.
Sugerencia I Sugerimos crear un nuevo estado de contrato llamado self.opening_time y asignar env::block_timestamp() a este valor al invocar la apertura del contrato.
2.1.4 Problema Potencial 4: El estado del contrato no se revierte cuando fallan las llamadas entre funciones
| Elemento | Descripción |
|---|---|
| Estado | Confirmado y corregido |
Descripción Este problema se introduce en o antes del Commit-3. El proceso de storage_deposit y ft_transfer puede fallar durante las llamadas entre contratos. No podemos garantizar que la transferencia siempre se realice correctamente. La función de callback no revierte el estado del contrato si la llamada falla.
#[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");
}
}
}
Listado 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");
}
}
}
Listado 2.6: liquidation_transfer_callback:ft.rs
Impacto Los usuarios pueden perder sus activos cuando las transacciones fallan ya que la función de callback no revierte el estado del contrato.
Sugerencia I Es necesario revertir el estado del contrato (cuando la transferencia falla) en la función de callback de las llamadas entre contratos.
2.2 Seguridad DeFi
2.2.1 Problema Potencial 5: inject_reward carece de control de acceso
| Elemento | Descripción |
|---|---|
| Estado | Confirmado y corregido |
Descripción Este problema se introduce en o antes del Commit-1. La función inject_reward es pública. Cualquiera puede invocar esta función para añadir el saldo de la recompensa en el 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"
);
}
...
}
Listado 2.7: inject_reward:pool.rs
Impacto Cualquiera puede añadir saldo arbitrario en la recompensa del contrato.
Sugerencia I Esta función debería cambiarse a privada ya que se llama internamente después de recibir la recompensa transferida.
2.2.2 Problema Potencial 6: inject_sp_reward carece de control de acceso
| Elemento | Descripción |
|---|---|
| Estado | Confirmado y corregido |
Descripción Este problema se introduce en o antes del Commit-1. La función inject_sp_reward es pública. Cualquiera puede invocar esta función para añadir el saldo de la recompensa del pool estable en el 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
);
}
Listado 2.8: inject_sp_reward:stablepool.rs
Impacto Cualquiera puede añadir saldo arbitrario en la recompensa del pool estable del contrato.
Sugerencia I Esta función debería cambiarse a privada ya que se llama internamente después de recibir la recompensa del pool estable transferida.
2.2.3 Problema Potencial 7: burn_coin carece de control de acceso
| Elemento | Descripción |
|---|---|
| Estado | Confirmado y corregido |
Descripción Este problema se introduce en o antes del Commit-1. La función burn_coin es pública. Cualquiera puede invocar esta función para quemar las monedas de cualquier persona.
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());
...
}
Listado 2.9: burn_coin:lib.rs
Impacto Cualquiera puede usar esta función para quemar las monedas de cualquier persona, resultando en la pérdida de activos de los usuarios.
Sugerencia I Esta función debería cambiarse a privada ya que se llama internamente después de recibir la comisión estable transferida para quemar monedas.
2.2.4 Problema Potencial 8: deposit_token carece de control de acceso
| Elemento | Descripción |
|---|---|
| Estado | Confirmado y corregido |
Descripción Este problema se introduce en o antes del Commit-1. La función deposit_token es pública. Cualquiera puede invocar esta función para añadir el saldo de su cuenta.
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);
. . .
}
Listado 2.10: deposit_token:lib.rs
Impacto Los atacantes pueden invocar esta función para añadir el saldo de su cuenta.
Sugerencia I Esta función debería cambiarse a privada ya que se llama internamente después de recibir los tokens depositados.
2.2.5 Problema Potencial 9: El oráculo carece de verificación de tiempo
| Elemento | Descripción |
|---|---|
| Estado | Confirmado y corregido |
Descripción Este problema se introduce en o antes del Commit-1. La función assert_is_poked en oracle.rs solo verifica si el valor del precio del token es cero. Esto no tiene sentido ya que el precio del token cambia constantemente.
pub(crate) fn assert_is_poked(&self) {
assert!(self.token_price != 0, "Oracle price isn't poked.");
}
Listado 2.11: assert_is_poked:oracle.rs
Impacto Este problema afecta a los oráculos de precios. Si el precio del token no ha sido consultado durante mucho tiempo, la aserción aún puede pasar y la transacción relacionada puede ejecutarse con un precio desactualizado.
Sugerencia I El contrato debería establecer un período de tiempo válido para el precio consultado.
2.2.6 Problema Potencial 10: Intervalo de tiempo de consulta del oráculo inapropiado
| Elemento | Descripción |
|---|---|
| Estado | Confirmado y corregido |
Descripción Este problema se introduce en o antes del Commit-3. La constante POKE_INTERVAL_TIME definida en types.rs significa 1000 días ahora. Y este intervalo de tiempo parece demasiado largo. Se requiere un valor razonable.
pub const POKE_INTERVAL_TIME: u64 = 86_400_000_000_000_000;
Listado 2.12: types.rs
Impacto El intervalo de tiempo para el precio consultado es inapropiado.
Sugerencia I Restablecer el intervalo de tiempo para el precio consultado con un valor razonable.
2.2.7 Problema Potencial 11: Falta de aserción para Oin_Price
| Elemento | Descripción |
|---|---|
| Estado | Confirmado y corregido |
Descripción Este problema se introduce en o antes del Commit-1. Esta función no verifica si el valor del precio del token oin ha sido consultado, ya que la comisión estable del usuario se calcula mediante 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)
}
Listado 2.13: internal_user_stable:lib.rs
Impacto El precio OIN desactualizado puede llevar a la manipulación de precios sin verificar la frescura del precio consultado por el oráculo.
Sugerencia I Agregar una aserción self.assert_is_poked(); antes del cálculo de la comisión estable del usuario.
2.2.8 Problema Potencial 12: Los usuarios pueden obtener más recompensa de minería con tokens en staking
| Elemento | Descripción |
|---|---|
| Estado | Confirmado y corregido |
Descripción Este problema se introduce en o antes del Commit-1. La recompensa reclamada no se calcula con precisión. La función internal_get_saved_reward se llama para calcular la recompensa de minería específica del usuario desde
t0 hasta t1 con la siguiente fórmula:

Nótese que account_allot.token es la recompensa de garantía añadida por la liquidación de otro usuario. Sin embargo, la liquidación puede ocurrir en cualquier momento desde t0 hasta t1. Por ejemplo, un usuario depositó 100 tokens el día 0. El día 999, se activa la liquidación para el otro usuario, por lo que account_allot.token puede aumentar a 1000.
Cuando el usuario reclama su recompensa el día 1000, los 1000 tokens resultantes de la liquidación del día 999 solo deberían contabilizarse para la minería durante un día. Sin embargo, el contrato realmente calcula la recompensa de minería para la recompensa de garantía desde el día 0 hasta el día 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
}
}
Listado 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)
}
}
Listado 2.15: staker_debt_of:views.rs
Impacto Los usuarios pueden obtener recompensas adicionales.
Sugerencia I Eliminar la partición de la garantía recién asignada al calcular la recompensa de minería. Podemos hacer que la recompensa de minería solo esté relacionada con la cantidad de tokens depositados por el usuario.
2.2.9 Problema Potencial 13: Los usuarios pueden pagar menos comisión estable
| Elemento | Descripción |
|---|---|
| Estado | Confirmado y corregido |
Descripción Este problema se introduce en o antes del Commit-1. Supongamos que un usuario acuña 1000 USDOs el día 0, y el stable_fee_rate en ese momento es 0.01oin/coin/día. Si el usuario devuelve los 1000 USDOs el día 100 y la tasa de comisión estable no cambia durante los últimos 100 días, la comisión estable que debe pagar es 0.01 Oin/coin/día * 1000 Coin * 100 Día = 1000 Oin. Sin embargo, si el propietario establece stable_fee_rate = 0.005 oin/coin/día el día 99, en este momento, el usuario solo necesita pagar 0.005 Oin/Coin/Día * 1000 Coin * 100 Día = 500 Oin. De hecho, la comisión exacta debería ser: (0.01 Oin/Coin/Día * 1000 Coin * 99 Día) + (0.005 Oin/Coin/Día * 1000 Coin * 1 Día) = 990 Oin + 5 Oin = 995 Oin.
En este caso, los 495 Oin no son requeridos para ser pagados por los usuarios.
// 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);
}
Listado 2.16: set_stable_fee_rate:dparam.rs
pub fn update_stable_index(&mut self) {
}
Listado 2.17: update_stable_index:stablefee.rs
Impacto Los usuarios del contrato pueden ser cobrados menos por la comisión estable.
Sugerencia I Implementar el índice del sistema de comisión estable como el cálculo de reward_coin en este contrato. Y asegurarse de que el índice del sistema de comisión estable se actualice siempre que set_stable_fee_rate, liquidation y update_stable_fee sean llamados por los usuarios del contrato.
2.2.10 Problema Potencial 14: Tasa de confirmación de solicitud multi-firma irrazonable
| Elemento | Descripción |
|---|---|
| Estado | Confirmado y corregido |
Descripción Este problema se introduce en o antes del Commit-1. La tasa de confirmación de la solicitud multi-firma se calcula por el número de gestores de multi-firma cuando se creó la solicitud. Pero el número de gestores de multi-firma puede cambiar más adelante. En este caso, si el número de gestores aumenta, la solicitud puede confirmarse con una tasa de confirmación baja.
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
}
Listado 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);
...
}
Listado 2.19: add_request_only:multisign.rs
Impacto Las solicitudes multi-firma pueden confirmarse con una tasa de confirmación baja ya que el contrato solo considera el número de gestores cuando se crea la solicitud.
Sugerencia I Considerar usar el número de usuarios multi-firma en el estado actual del contrato para calcular la tasa de confirmación de la solicitud multi-firma.
2.2.11 Problema Potencial 15: Número de bloques por año incorrecto
| Elemento | Descripción |
|---|---|
| Estado | Confirmado y corregido |
Descripción Este problema se introduce en o antes del Commit-1. Dado que un bloque se genera cada segundo en la red principal del protocolo NEAR, el número de bloques generados por año debería ser 31536000 (365 días) en lugar de 31104000 (360 días).
pub const BLOCK_PER_YEAR: u128 = 31104000;
Listado 2.20: types.rs
Impacto La constante inexacta para BLOCK_PER_YEAR hará que los resultados de los cálculos que usan la constante sean inconsistentes con la realidad.
Sugerencia I Cambiar BLOCK_PER_YEAR a 31536000.
2.2.12 Problema Potencial 16: Cálculo incorrecto del máximo de usdo que se puede acuñar
| Elemento | Descripción |
|---|---|
| Estado | Confirmado y corregido |
Descripción Este problema se introduce en o antes del Commit-1. allot_token.0 representa la deuda asignada. Al calcular el monto disponible de acuñación para USDO, la deuda asignada no debería contarse. De lo contrario, un usuario con deuda muy alta puede acuñar una enorme cantidad 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();
...
}
Listado 2.21: internal_can_mint_amount:lib.rs
Impacto Los usuarios pueden acuñar USDOs adicionales al invocar la función mint_coin.
Sugerencia I El allot_token.0, que representa la deuda asignada, no debería contarse como los USDOs disponibles para acuñar.
2.2.13 Problema Potencial 17: Manejo incorrecto de la comisión estable del usuario
| Elemento | Descripción |
|---|---|
| Estado | Confirmado y corregido (La lógica relacionada ha sido eliminada ahora) |
Descripción Este problema se introduce en o antes del Commit-1. Cuando los usuarios invocan la función burn_coin, la comisión estable se paga con el token 'OIN' en lugar de 'ST_NEAR'. Sin embargo, el contrato reducirá el saldo del token de staking del usuario, lo cual no es correcto.
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),
);
...
}
Listado 2.22: burn_coin:lib.rs
Impacto El token de staking de los usuarios puede reducirse debido al manejo incorrecto de la comisión estable del usuario.
Sugerencia I Usar el token correcto para pagar las comisiones estables.
2.2.14 Problema Potencial 18: Ratio del sistema incorrecto
| Elemento | Descripción |
|---|---|
| Estado | Confirmado y corregido |
Descripción Este problema se introduce en o antes del Commit-1. Si total_coin = 0, el ratio debería ser +∞. Establecerlo en 0 es incorrecto.
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()
}
}
Listado 2.23: internal_sys_ratio:lib.rs
Impacto Es probable que el sistema se apague debido al ratio incorrecto.
Sugerencia I Cambiar la condición if total_coin = 0 a token_usd = 0.
2.2.15 Problema Potencial 19: El número de monedas de recompensa puede ser mayor que el límite superior
| Elemento | Descripción |
|---|---|
| Estado | Confirmado y corregido |
Descripción Este problema se introduce en o antes del Commit-1. Cuando hay 20 monedas de recompensa ahora, la aserción en la línea 131 del Listado 2.24 puede pasar. En este caso, se puede añadir una moneda de recompensa más y el número total de monedas de recompensa puede ser mayor que 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
)
}
Listado 2.24: internal_add_reward_coin:pool.rs
Impacto El número disponible de monedas de recompensa añadidas está en conflicto con el diseño del sistema.
Sugerencia I Cambiar la aserción a self.reward_coins.len() < REWARD_UPPER_BOUND.
2.2.16 Problema Potencial 20: Los usuarios con diferentes privilegios usan la misma lista blanca
| Elemento | Descripción |
|---|---|
| Estado | Confirmado y corregido |
Descripción Este problema se introduce en o antes del Commit-1. Las funciones assert_param_white, assert_white, assert_esm_white, assert_oracle_white se usan para diferentes privilegios. Sin embargo, comparten la misma lista blanca.
pub(crate) fn assert_esm_white(&self) {
self.assert_white()
}
Listado 2.25: assert_esm_white:esm.rs
pub(crate) fn assert_param_white(&self) {
self.assert_white();
}
Listado 2.26: assert_param_white:dparam.rs
pub(crate) fn assert_oracle_white(&self) {
self.assert_white();
}
Listado 2.27: assert_oracle_white:oracle.rs
Impacto Los usuarios con diferentes privilegios comparten la misma lista blanca.
Sugerencia I Implementar diferentes listas blancas para usuarios con diferentes privilegios.
2.2.17 Problema Potencial 21: burn_coin no verifica el tipo de token
| Elemento | Descripción |
|---|---|
| Estado | Confirmado y corregido |
Descripción Este problema se introduce en o antes del Commit-1. La función burn_coin no verifica el tipo de token. En este caso, los atacantes pueden transferir tokens arbitrarios con una cantidad especificada para pagar la comisión estable.
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);
Listado 2.28: assert_esm_white:esm.rs
Impacto Los usuarios no necesitan pagar el token Oin. En su lugar, pueden pagar la comisión estable transfiriendo cualquier token con la cantidad requerida.
Sugerencia I Verificar la dirección del token recibido.
2.2.18 Problema Potencial 22: El total_reward de la moneda de recompensa puede ser modificado por gestores multi-firma
| Elemento | Descripción |
|---|---|
| Estado | Confirmado y corregido |
Descripción Este problema se introduce en o antes del Commit-3. La función inject_reward está decorada con #[private]. Por lo tanto, los gestores multi-firma pueden invocar esta función a través de solicitudes multi-firma y añadir una cantidad arbitraria en la recompensa total sin inyectar 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.");
}
}
Listado 2.29: ainject_reward:pool.rs
Sugerencia I Eliminar el decorador #[private] y cambiar la visibilidad de la función inject_reward a privada.
2.3 Recomendaciones Adicionales
2.3.1 Aserción redundante
| Elemento | Descripción |
|---|---|
| Estado | Confirmado y corregido |
Descripción Este problema se introduce en o antes del Commit-2. La función inject_reward solo debería ser llamada por ft_on_transfer internamente. La dirección de la moneda de recompensa se verifica en ft_on_transfer. En este caso, no es necesario verificar el nombre de la moneda de recompensa al principio de la función 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"
);
}
...
}
Listado 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;
}
...
}
Listado 2.31: ft_on_transfer:lib.rs
Sugerencia I Eliminar la verificación del nombre de la moneda de recompensa en inject_reward.
2.3.2 Aserción repetida para el ratio de liquidación del usuario
| Elemento | Descripción |
|---|---|
| Estado | Confirmado y corregido |
Descripción Este problema se introduce en o antes del Commit-1. La línea de liquidación ya se tiene en cuenta en la función internal_avaliable_token, por lo que no es necesario verificar si el user_ratio alcanza la línea de liquidación 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");
}
}
Listado 2.32: withdraw_token:lib.rs
Sugerencia I Eliminar la aserción redundante en la Línea 559 del Listado 2.32.
2.3.3 Verificación redundante de lista blanca
| Elemento | Descripción |
|---|---|
| Estado | Confirmado y corregido |
Descripción Este problema se introduce en o antes del Commit-1. La función set_reward_speed invoca la función assert_param_white para verificar el privilegio. Mientras tanto, internal_set_reward_speed, que es llamado por set_reward_speed, invoca assert_white nuevamente. assert_white tiene la misma lista blanca 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);
}
Listado 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();
. . .
}
Listado 2.34: internal_set_reward_speed:pool.rs
Sugerencia I Eliminar assert_white dentro de la función internal_set_reward_speed.
2.3.4 Función no utilizada
| Elemento | Descripción |
|---|---|
| Estado | Confirmado y corregido |
Descripción Este problema se introduce en o antes del Commit-3. La función on_inject_reward no es utilizada por ninguna otra función. Por lo tanto, puede eliminarse.
#[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);
}
};
}
Listado 2.35: on_inject_reward:pool.rs
Sugerencia I Eliminar la función on_inject_reward.
2.3.5 Código redundante
| Elemento | Descripción |
|---|---|
| Estado | Confirmado y corregido |
Descripción Este problema se introduce en o antes del Commit-3. La función account_allot.get() se usa para obtener la recompensa y deuda asignadas. Dentro de la función set_account_allot, la invocación de esta función no es necesaria.
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));
}
Listado 2.36: set_account_allot:allot.rs
Sugerencia I Eliminar la invocación de account_allot.get() en la línea 42.
2.3.6 El nombre de la función y la implementación son opuestos
| Elemento | Descripción |
|---|---|
| Estado | Confirmado y corregido |
Descripción Este problema se introduce en o antes del Commit-3. Las funciones is_stake_paused, is_redeem_paused, is_claim_reward_paused, is_liquidation_paused, is_stable_paused están definidas para representar si la función está pausada o no. Sin embargo, cuando el atributo específico está activo, devuelve 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
}
Listado 2.37: is_{stake|redeem|claim_reward|liquidation|stable}_paused:esm.rs
Sugerencia I Cambiar el nombre de la función is_{stake|redeem|claim_reward|liquidation|stable}_paused a is_{stake|redeem|claim_reward|liquidation|stable}_live
2.3.7 Código redundante
| Elemento | Descripción |
|---|---|
| Estado | Confirmado y corregido |
Descripción Este problema se introduce en o antes del Commit-3. La función update_stable_fee se usa para actualizar las comisiones estables requeridas. La comisión estable no está relacionada con los tokens en staking. Por lo tanto, cambiar el saldo de tokens de los usuarios no necesita actualizar las comisiones estables.
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());
. . .
}
Listado 2.38: deposit_token:lib.rs
Sugerencia I Eliminar la invocación de update_stable_fee en la línea 344.
2.3.8 La precisión del cálculo puede mejorarse
| Elemento | Descripción |
|---|---|
| Estado | Confirmado y corregido |
Descripción Este problema se introduce en o antes del Commit-3. La función internal_user_stable tiene como objetivo calcular la comisión estable. La precisión del cálculo puede mejorarse realizando la multiplicación antes de la división.
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")
}
}
Listado 2.39: update_stable_fee:stablefee.rs
Sugerencia I Realizar la multiplicación antes de la división para el cálculo desde la línea 25 hasta la línea 30.
2.3.9 El sistema puede no registrar el precio consultado anteriormente
| Elemento | Descripción |
|---|---|
| Estado | Confirmado y corregido |
Descripción Este problema se introduce en o antes del Commit-1. La función no está implementada correctamente. El sistema puede no registrar el precio consultado ya que el número total de tokens depositados en el contrato es mayor que 0 en la mayoría de los 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
);
}
}
Listado 2.40: poke:oracle.rs
Sugerencia I El registro del comportamiento de consulta del precio del token no debería verse influenciado por el número de tokens depositados en el contrato.
2.3.10 Distribución discontinua del token de garantía en la liquidación
| Elemento | Descripción |
|---|---|
| Estado | Confirmado y corregido |
Descripción Este problema se introduce en o antes del Commit-4. Cuando el ratio de staking del usuario es mayor o igual al 108.5%, los usuarios deben pagar la liquidation_fee, que es el 2% del allot_debt. Sin embargo, si el ratio de staking del usuario es menor al 108.5%, no necesita pagar la comisión de liquidación. Esto resulta en que el usuario con mayor ratio de staking puede asignar menos token de staking al pool después de la liquidación.
#[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);
}
...
Listado 2.41: liquidation:lib.rs
Sugerencia I Para usuarios cuyo ratio de staking esté entre 108.5% y 110.5%, se sugiere que la comisión de liquidación sea (ratio de staking - 108.5%).
2.3.11 La optimización de la precisión del cálculo no es necesaria
| Elemento | Descripción |
|---|---|
| Estado | Confirmado y corregido |
Descripción Este problema se introduce en o antes del Commit-4. Agregar 1 en la línea 832 del listado 2.42 no puede aumentar la precisión del cálculo ya que self.gas_compensation_ratio es 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);
}
...
Listado 2.42: liquidation:lib.rs
Sugerencia I Eliminar el "1" añadido en la línea 831 del listado 2.42.
2.3.12 El Riesgo del Diseño Centralizado
Estado Reconocido
Descripción El proyecto tiene un diseño altamente centralizado. El propietario del contrato tiene un privilegio muy alto que puede añadir/eliminar los gestores de multi-firma y puede retirar la comisión de liquidación y la recompensa, etc. Tal mecanismo es absolutamente centralizado, con un control completo sobre todos los tokens. Recomendamos encarecidamente que el propietario del proyecto refuerce mecanismos de seguridad para proteger las claves privadas del propietario del contrato para gestionar los contratos.
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 considera, y no debe interpretarse como considerando o teniendo algún impacto sobre, la economía potencial de un token, venta de tokens o cualquier otro producto, servicio u otro activo. Ninguna entidad debe basarse en este informe de ninguna manera, incluyendo con el propósito de tomar cualquier decisión de compra o venta de cualquier token, producto, servicio u otro activo.
Este informe de auditoría no es 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 ofrece 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 más hallazgos de problemas de seguridad. Dado que una auditoría no puede considerarse exhaustiva, siempre recomendamos proceder con 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 Rust), la cadena de herramientas de compilación subyacente y la infraestructura informática están fuera del alcance.
3.2 Procedimiento de Auditoría
Realizamos la auditoría de acuerdo con el siguiente procedimiento.
-
Detección de Vulnerabilidades Primero escaneamos 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 más profunda sobre las posibles vulnerabilidades usando una herramienta de fuzzing automático (desarrollada por nuestro equipo de investigación). También analizamos manualmente posibles escenarios de ataque con auditores independientes para verificar los resultados.
-
Recomendación Proporcionamos algunos consejos útiles a los desarrolladores desde la perspectiva de buenas prácticas de programación, incluyendo optimización de gas, estilo de código, etc.
Mostramos los principales puntos de verificación concretos a continuación.
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 inadecuado 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 la cadena
3.2.4 Recomendaciones Adicionales
-
Optimización de gas
-
Calidad y estilo de código
Los puntos de verificación anteriores son los principales. Podemos usar más puntos de verificación durante el proceso de auditoría según la funcionalidad del proyecto.



