16 de diciembre de 2021
Introducción
El 1inch equipo nos pidió que revisáramos y auditáramos su Protocolo de órdenes limitadas contratos inteligentes v2. Miramos el código y ahora publicamos nuestros resultados.
Lo que hacemos
Compromiso auditado 4d94eea25e4dac6271bfd703096a5c4a4d899b4a
de las 1inch/limit-order-protocol
repositorio. En el alcance estaban los siguientes contratos:
– OrderMixin.sol
– OrderRFQMixin.sol
– PredicateHelper.sol
– RevertReasonParser.sol
– Permitable.sol
– ChainlinkCalculator.sol
– ArgumentsDecoder.sol
– AmountCalculator.sol
– NonceManager.sol
– LimitOrderProtocol.sol
– ImmutableOwner.sol
– InteractiveNotificationReceiver.sol
– AggregatorInterface.sol
– IDaiLikePermit.sol
Todos los demás archivos y directorios de proyectos (incluidas las pruebas), junto con las dependencias y proyectos externos, la teoría de juegos y el diseño de incentivos, también se excluyeron del alcance de esta auditoría. Se asumió que las dependencias de contratos y códigos externos funcionaban según lo documentado y se asumió que los servicios de back-end proporcionados por 1 pulgada actuarían en el mejor interés del protocolo.
Salud en general
En general, encontramos que el código base del proyecto es legible y está bien organizado, aunque podría beneficiarse de una documentación más extensa, especialmente en torno a los bloques de código ensamblador, casos extremos del protocolo, activos/predicados/recursos externos que se utilizarán, responsabilidades/limitaciones del servicio back-end proporcionado e interacciones entre actores. El proyecto hace todo lo posible para que las acciones sean eficientes en términos de gas, ocasionalmente incluso a riesgo de hacer que el código sea más difícil de razonar; planteamos cuestiones relacionadas con eso a continuación. A lo largo de la auditoría, el equipo de 1 pulgada estuvo altamente disponible, receptivo y muy fácil de trabajar.
Descripción general del sistema
El Protocolo de Orden Limitada habilita la orden makers
para firmar pedidos fuera de la cadena para intercambios de tokens. El protocolo facilita entonces el llenado de pedidos previamente firmados por pedido. takers
. Los pedidos son altamente extensibles y pueden llamar estáticamente a contratos externos en varios puntos a lo largo del proceso de llenado del pedido. Esta extensibilidad dota al protocolo de utilidad, pero añade complejidad y una mayor superficie de ataque para las propias órdenes.
Es importante señalar que no hay almacenamiento en cadena de los detalles del pedido. El estado de cumplimiento o de cancelación de los pedidos solo se rastrea a través de hash de pedidos. Esto requiere que los pedidos se compartan entre pares o a través de una parte centralizada. En este caso, el equipo de 1 pulgada tiene la intención de actuar como esa parte centralizada, agregando órdenes firmadas y usándolas como fuente de liquidez para sus otros protocolos. Los pedidos se publicarán a través de su propia API para que los usuarios puedan interactuar con ellos.
Esta centralización le da al equipo de 1 pulgada un control extremo sobre qué órdenes se publican y finalmente se ejecutan. Esto también les da la capacidad de censurar pedidos, lo que podría ser útil en el caso de pedidos maliciosos o engañosos, pero también podría abusarse y permitirles adelantar a cualquier otro usuario en caso de un pedido favorable al no mostrarlo a través de la API.
Roles privilegiados
Aunque los contratos en los que se usa el rol estaban fuera del alcance, se identificó un rol privilegiado. Un immutableOwner
se establece para el creador de un contrato de proxy en el momento de la construcción, y se utiliza para limitar el acceso a los proxy external
funciones.
Dependencias externas y suposiciones de confianza
El diseño de este protocolo requiere componentes dentro y fuera de la cadena, y este modelo híbrido se puede usar para mitigar algunos vectores de ataque que identificamos en nuestro informe, pero el costo de esa capacidad es una mayor dependencia del equipo y la infraestructura de 1 pulgada.
Además, el Protocolo de orden limitada proporciona funciones destinadas a recuperar precios de los oráculos de Chainlink. Asumimos que esos oráculos eran honestos, accesibles y que funcionaban correctamente.
Además, debido a la flexibilidad de un pedido, existen varios puntos de contacto con contratos externos que no se validan. Esto significa que un usuario malintencionado podría abusar de dichas llamadas y hacerse pasar por predicados, activos u oráculos con contratos maliciosos para ejecutar acciones durante el cumplimiento de pedidos. Aunque el proyecto está protegido en algunas áreas contra la reentrada, dichos vectores podrían provocar ataques de denegación de servicio u órdenes de spam no detectadas. El equipo de 1 pulgada es consciente de que pueden surgir ciertos problemas cuando se utilizan contratos desconocidos para el protocolo y ha indicado su intención de que el proyecto solo admita los principales activos de primera clase. Sin embargo, debe tenerse en cuenta que incluso con los activos más populares, existen comportamientos intrínsecos de cada activo que pueden causar problemas en los protocolos que no los abordan correctamente, como tener una tarifa durante las transferencias con USDT o devolver un código de error en lugar de un éxito booleano con cTokens.
Hallazgos
Aquí presentamos nuestros hallazgos.
Severidad crítica
Ninguna.
Alta severidad
[H01] Datos incoherentes pasados a _makeCall
En OrderMixin
contrato, el _makeCall
función se utiliza para transferir activos del tomador al creador y luego del hacedor al tomador. En esta última transferencia, el _makeCall
la función se pasa incorrectamente la orden makerAsset
como último parámetro, cuando debería ser el de la orden makerAssetData
.
Como resultado, cualquier funcionalidad de proxy que se base en el makerAssetData
el argumento se romperá.
Para ser consistente con la llamada anterior a _makeCall
y para admitir completamente la funcionalidad de proxy, considere actualizar el order.makerAsset
parámetro para order.makerAssetData
.
Actualizar: Fijado en solicitud de extracción # 57.
[H02] Los pedidos privados completados parcialmente pueden ser completados por cualquier persona
El protocolo permite la creación de órdenes privadas y públicas. En pedidos privados, sólo el allowedSender
dirección, especificada por el fabricante durante la creación del pedido, puede completar el pedido.
Sin embargo, en el OrderMixin
contrato, validación para el allowedSender
dirección tiene un alcance incorrecto, lo que significa que solo se evalúa dentro de la lógica que maneja el primer llenado de una orden. Si una orden privada se completa parcialmente, entonces el cheque por la allowedSender
ya no se puede acceder a la dirección y cualquiera puede completar el pedido.
Para aclarar la intención de si un usuario debe poder completar pedidos privados parcialmente completados o no, considere documentar el motivo del comportamiento actual o validar el allowedSender
dirección fuera del alcance del primer llenado para garantizar que se validará cada vez que se intente un llenado.
Actualizar: Fijado en solicitud de extracción # 58.
[H03] El fabricante malicioso podría aprovechar los rellenos parciales para robar los activos del tomador
Órdenes de la OrderMixin
contrato tiene la capacidad de ser parcialmente cumplimentado. Para admitir rellenos parciales, el protocolo requiere una forma de calcular ambos lados de los intercambios. Ambas cosas getMakerAmount
y getTakerAmount
los campos son definidos por el fabricante de la orden para este propósito exacto.
Al completar un pedido, los compradores deben proporcionar el makingAmount
o de takingAmount
valores, así como un thresholdAmount
valor. Hay dos caminos de código diferentes que se pueden tomar, en función de si el makingAmount
o de takingAmount
fue dado.
La primera es cuando el makingAmount
se define el parámetro. Podria truncar las makingAmount
valor y también calcula el takingAmount
valor por ello. En esta situación, el thresholdAmount
asegura que el takingAmount
el valor tomado es no inesperadamente grande.
La segunda es cuando el takingAmount
se define el parámetro. En tal caso, será calcula el makingAmount
valor, con la posibilidad de truncándolo y recalculando el takingAmount
valor si eso sucede. En esta situación, el thresholdAmount
valor asegura que el makingAmount
el valor devuelto es no inesperadamente pequeño.
Existen dos métodos de explotación, cada uno exclusivo de una de las rutas de código mencionadas anteriormente. Estos métodos de explotación requieren getMakerAmount
y getTakerAmount
funciones Una simple implementación de estas funciones tendría un comportamiento idéntico a AmountCalculator
es getMakerAmount
y getTakerAmount
funciones, pero con un interruptor codificado que los obligará a devolver un valor controlado por el atacante cuando sea necesario.
El primer patrón de explotación menos grave involucra la primera ruta de código donde el makingAmount
se especifica el valor en una orden de llenado. Un fabricante malicioso esperaría una orden de llenado que especifique makingAmount
para aparecer en el mempool con el fin de ejecutarlo. Drenarían todo el valor excepto 1 del lado del fabricante y luego forzarían _callGetTakerAmount
para devolver la cantidad especificada en el usuario thresholdAmount
valor (o su asignación si es menor). Cuando la transacción del usuario finalmente se lleve a cabo, intercambiará su cuenta completa thresholdAmount
por valor de takerAsset
por una sola unidad de makerAsset
. Este exploit está limitado por la cantidad otorgada por el thresholdAmount
valor o la cantidad de la takerAsset
el usuario permitido en el LimitOrderProtocol
contrato.
El segundo patrón de explotación más grave involucra la segunda ruta de código donde el takingAmount
se especifica el valor. El fabricante malicioso esperaría de manera similar una orden de llenado que especificara un takingAmount
value para aparecer en el mempool. Adelantarían la transacción y obligarían a la makingAmount
valor devuelto por _callGetMakerAmount
función sea más alta que tanto la remainingMakerAmount
y del thresholdAmount
. También establecerían el takingAmount
valor devuelto por _callGetTakerAmount
ser la cantidad de takerAsset
activo permitido en el LimitOrderProtocol
por el tomador. Cuando la transacción del tomador se lleve a cabo, se truncar el makingAmount
valor y luego recalcular el takingAmount
valor. Sin embargo, no se garantiza que este recálculo sea menor y, en este caso, agotará al tomador de todos los takerAsset
que permitieron en el contrato. En esta ruta de código, el thresholdAmount
Valor es asegurando que el makingAmount
no es demasiado bajo, así que tomando todos los del tomador takerAsset
el activo está desmarcado. Los fondos perdidos están limitados por la cantidad de la takerAsset
activo que el usuario permitió en el LimitOrderProtocol
contrato.
Estos exploits no son posibles sin órdenes parciales y, más específicamente, órdenes parciales con maliciosos getMakerAmount
y getTakerAmount
implementaciones
El tema principal de la thresholdAmount
la verificación de valor es que solo cubre un lado del intercambio, pero el otro lado se puede manipular a través de frontrunning. No hay garantías de que el valor que el tomador propuso originalmente permanezca sin cambios. Considere eliminar makingAmount
truncamiento de ambas rutas de código y reversión si el pedido no puede admitir un relleno tan grande como se solicitó. Al hacer esto, el thresholdAmount
se puede utilizar para restringir suficientemente el otro lado del intercambio y evitar comportamientos inesperados, incluso en órdenes maliciosas.
Actualizar: Fijado en solicitud de extracción # 83.
Severidad media
[M01] Argumentos estáticos pasados después de argumentos dinámicos
En OrderMixin
contrato, el getTakerAmount
y getMakerAmount
Los campos de bytes se utilizan como argumentos para el _callGetTakerAmount
y _callGetMakerAmount
funciones Estas llamadas brindan una forma de calcular un lado del intercambio en función del otro lado y permiten a los usuarios completar parcialmente los pedidos.
El getTakerAmount
/getMakerAmount
Los campos son variables dinámicas y se empaquetan delante del takerAmount
y makerAmount
valores en el _callGetTakerAmount
y _callGetMakerAmount
funciones Es posible que un fabricante malintencionado proporcione más datos de los esperados en el getTakerAmount
ygetMakerAmount
campos para empujar el takerAmount
y makerAmount
bytes más allá de donde se supone que están cuando se decodifican en la siguiente función. Esto permite que el fabricante cambie la cantidad pasada en el tomador o el fabricante en bytes completos hacia la derecha e incluso los reemplace por completo si se proporcionan 32 bytes adicionales de datos.
Los usuarios ya tienen que revisar manualmente el getTakerAmount
y getMakerAmount
campos en el pedido, pero esta técnica es bastante difícil de detectar. También vale la pena señalar que este ataque incluso se aplica a la confianza interna getMakerAmount
y getTakerAmount
funciones Para la mayoría de los ataques, proporcionar un monto de umbral razonable evitará la pérdida de fondos.
Para evitar esto, considere codificar los argumentos estáticos antes que los argumentos dinámicos para evitar dar a los argumentos dinámicos un método para controlar los argumentos estáticos.
Actualizar: No arreglado. El equipo de 1 pulgada declaró:
Tendremos especial cuidado con la validación de captadores. Intentaremos implementar la validación de cordura de captadores en nuestro SDK que ayudará a filtrar pedidos potencialmente maliciosos.
[M02] Las órdenes ERC721 se pueden manipular
Es posible intercambiar más que solo ERC20 a través del OrderMixin
implementando un contrato que comparte el mismo selector de funciones que el de IERC20 transferFrom
, y siempre que dicho contrato sea el makerAsset
o de takerAsset
en una orden
Los proxies fuera del alcance, a saber, ERC721Proxy
, ERC721ProxySafe
y ERC1155Proxy
contratos siguen este patrón para proporcionar apoyo a ERC721
y ERC1155
fichas Dado que los proxies deben llamarse con el mismo patrón que un IERC20 transferFrom
llamada, la firma debe comenzar con address from
, address to
y uint256 amount
. Cualquier otra cosa que requieran los proxies se puede pasar después, y se define en el orden como makerAssetData
y takerAssetData
.
Los ERC1155 pueden transferir naturalmente varios de los mismos tokens de identificación a la vez, lo que significa que el ERC1155Proxy
contrato hace uso de la amount
campo. Por otro lado, ERC721
s no tienen un uso obvio para el amount
campo. Dado que representan tokens no fungibles, un tokenId específico solo tendrá uno en existencia, lo que hace que el amount
campo inútil. Debido a esto, la implementación para ambos ERC721Proxy
y ERC721ProxySafe
los contratos utilizan los requisitos amount
campo como el tokenId
preferiblemente.
Esta sobrecarga del amount
parámetro crea la posibilidad de llenar parcialmente ERC721
pedidos para comprar tokens enumerados por separado a precios reducidos. Por ejemplo, podría haber un caso en el que un solo usuario tenga múltiples ERC721
s del mismo contrato permitido para ser transferido por el ERC721Proxy
contrato y los lista en órdenes limitadas separadas.
Si las órdenes limitadas también proporcionan la getMakerAmount
y getTakerAmount
campos, será posible llenar parcialmente estos ERC721
pedidos. Desde la orden amount
campo en realidad corresponde a la tokenId
, un usuario malintencionado puede colocar un relleno parcial en el ERC721
con el tokenId más alto, lo que resulta en un makingAmount
/takingAmount
de una ERC721
que podría corresponder a un menor tokenId
. El resultado es el ERC721
con el inferior tokenId
sería transferido al precio de (higher tokenId price) * (lower tokenId's id) / (higher tokenId's id)
.
Este exploit tiene algunos requisitos:
- Múltiple
ERC721
s del mismo contrato para ser permitido en cualquieraERC721
apoderado por un solo dueño. - Pedido abierto para uno de los
ERC721
eso no es lo mas bajotokenId
de los permitidos. - Se permiten rellenos parciales en el pedido.
Para eliminar por completo la posibilidad de parcial ERC721
rellenos, considere separar los amount
y tokenId
argumentos Ya sea que los argumentos estén separados o no, considere también documentarlo para alertar a los usuarios de este comportamiento y evitar este patrón en el futuro.
Actualizar: Fijado en solicitud de extracción # 59.
[M03] Suposiciones decimales no documentadas
El LimitOrderProtocol
contrato hereda el ChainlinkCalculator
contrato a través de la OrderMixin
contrato. Este contrato expone dos funciones para permitir el uso de los oráculos de Chainlink durante el comprobación de predicados y la búsqueda de la cantidad del fabricante/cantidad del tomador.
Sin embargo, el contrato hace suposiciones no documentadas sobre la cantidad de decimales que deben informar los oráculos de Chainlink, así como la cantidad de decimales que deben contener los parámetros de la función. En ciertos escenarios, esto podría dar lugar a comportamientos inesperados, incluida la fijación incorrecta de los precios de los activos y la pérdida involuntaria de fondos.
Más específicamente, a lo largo del contrato, la suposición implícita es que los oráculos de Chainlink informarán con 18 decimales de precisión. Sin embargo, no todos los oráculos de Chainlink informe con este número de decimales. De hecho, si el oráculo informa un par de fichas que está en términos de una moneda (USD, por ejemplo), solo tendrá 8 decimales de precisión. Como no hay restricciones en que se pueden usar oráculos, no se deben hacer suposiciones implícitas sobre el número de decimales con los que informarán.
En relación con esto, existe una suposición implícita de que el amount
parámetro para la ChainlinkCalculator
funciones utilizarán 18 decimales, junto con la declaración explícita engañosa de que el singlePrice
función Calculates price of token relative to ETH scaled by 1e18
. En realidad, incluso con un oráculo que sí informe con 18 decimales, el valor de retorno de la singlePrice
función sería escalada por el número de decimales de la amount
parámetro, que puede no ser necesariamente de 18 decimales.
Del mismo modo, el doublePrice
La función asume que dos oráculos de Chainlink informarán con la misma cantidad de decimales, lo que hace que el resultado de la función se desvíe de las expectativas.
Considere la posibilidad de documentar explícitamente las suposiciones con respecto a la cantidad de decimales que deben tener los parámetros y los valores devueltos. Además, considere limitar los cálculos que dependen de oráculos que rompen esas suposiciones, o hacer que los cálculos relevantes tengan en cuenta el número real de decimales.
Actualizar: Fijado en solicitud de extracción # 75.
Gravedad baja
[L01] Constantes no declaradas explícitamente
Hay algunas ocurrencias de valores literales que se usan con un significado inexplicable en el código base. Por ejemplo:
- En
OrderMixin
contrato, el_remaining
el mapeo está sobrecargado semánticamente (como se explica en el problema Sobrecarga semántica de mapeo) para realizar un seguimiento de la cantidad de activos restantes para un pedido completado parcialmente al igual que si un pedido se ha completado por completo. Específicamente,0
significa que no se han realizado rellenos asociados con un pedido,1
significa que ya no se puede completar un pedido, y cualquier cosa mayor que1
significa que hay una cantidad restante asociada con el pedido que potencialmente se puede completar. - En
ChainlinkCalculator
contrato, el valor literal1e18
se usa en elsinglePrice
función.
Para mejorar la legibilidad del código y facilitar la refactorización, considere definir una constante para cada número mágico, dándole un nombre claro y que se explique por sí mismo. Para valores complejos, considere agregar un comentario en línea que explique cómo se calcularon o por qué se eligieron.
Actualizar: Fijado en solicitud de extracción # 75 y solicitud de extracción # 76.
[L02] Las partes malintencionadas podrían impedir la ejecución de órdenes permitidas
El OrderMixin
El contrato permite a los usuarios del fabricante enviar órdenes permitidas por lo que se pueden ejecutar en una transacción, en lugar de tener que tener una transacción separada para las aprobaciones. Además, los tomadores de pedidos pueden presentar su propio permiso durante el llenado de la orden para el mismo propósito.
Sin embargo, debido a que el permiso del fabricante está contenido dentro del solicite, tanto el permiso del fabricante como el del tomador serían accesibles mientras la transacción de cumplimiento de pedidos esté en el mempool. Esto haría posible que cualquier usuario malintencionado tome esos permisos y los ejecute en los respectivos contratos de activos mientras ejecuta la transacción de relleno. Debido a que estos permisos tienen un nonce
para evitar un ataque de doble gasto, la transacción de ejecución de la orden fallaría como resultado de intentar usar el mismo permiso que se acaba de usar durante la ejecución inicial.
Aunque no hay riesgo de seguridad, y el fabricante podría crear un nuevo pedido y aprobar previamente la transacción, este ataque ciertamente podría afectar la usabilidad de los pedidos permitidos. De hecho, un atacante motivado podría bloquear todos órdenes permisibles con este ataque. Considere validar si el permiso ya se envió, o si la asignación es suficiente, durante el llenado de la orden. También considere informar a los usuarios sobre este posible ataque durante la composición del pedido.
Actualizar: No arreglado. El equipo de 1 pulgada afirma:
Tuvimos verificaciones de aprobación antes, pero decidimos simplificar el flujo de permisos para revertir las aprobaciones fallidas. Pensaremos en las formas de notificar a los fabricantes sobre el problema.
[L03] Código duplicado
Hay instancias de código duplicado dentro del código base. La duplicación de código puede generar problemas más adelante en el ciclo de vida del desarrollo y hace que el proyecto sea más propenso a la introducción de errores. Dichos errores pueden introducirse inadvertidamente cuando los cambios de funcionalidad no se replican en todas las instancias de código que deberían ser idénticas. Los ejemplos de código duplicado incluyen:
En lugar de duplicar el código, considere tener solo un contrato o biblioteca que contenga el código duplicado y usarlo siempre que se requiera la funcionalidad duplicada.
Actualizar: Parcialmente fijo en solicitud de extracción # 60.
[L04] Conjunto de pruebas erróneo o engañoso
Hay instancias en el conjunto de pruebas donde las pruebas se desvían de su comportamiento esperado. Por ejemplo:
- El
ChainlinkCalculator
el contrato es heredado por elOrderMixin
contrato. Sin embargo, durante las pruebas elAmountCalculator.arbitraryStaticCall
La función se usa para llamar alChainlinkCalculator
contrato como un contrato externo e independiente. Aunque el resultado es el esperado, la prueba debe reflejar el comportamiento con el diseño actual del sistema y el caso de uso anticipado llamandoChainlinkCalculator
funciones directamente sin usar la llamada estática arbitraria. - Aunque los contratos de proxy estaban fuera del alcance, notamos que al probar el protocolo con activos ERC721, el
ERC721Proxy
contrato no se utiliza para permutar los activos en su Banco de pruebas.
Dado que el conjunto de pruebas en sí está fuera del alcance de esta auditoría, considere revisar minuciosamente el conjunto de pruebas para asegurarse de que todas las pruebas se ejecuten correctamente de acuerdo con las especificaciones del protocolo.
Actualizar: Fijado en solicitud de extracción # 57, solicitud de extracción # 59y solicitud de extracción # 61.
[L05] Errores y omisiones en eventos
A lo largo del código base, los eventos generalmente se emiten cuando se realizan cambios sensibles en los contratos. Sin embargo, muchos eventos carecen de parámetros indexados y/o les faltan parámetros importantes. Por ejemplo:
También hay acciones sensibles que carecen de eventos, como:
Considere indexar de manera más completa los eventos existentes y agregar nuevos parámetros donde faltan. Además, considere emitir todos los eventos de una manera tan completa que puedan usarse para reconstruir el estado del contrato por servicios fuera de la cadena.
Actualizar: No arreglado. Sin embargo, el equipo de 1 pulgada agregó un orderRemaining
parámetro a la OrderCanceled
evento en solicitud de extracción # 62.
El equipo de 1 pulgada afirma:
Descubrimos que solo se requiere un subconjunto limitado de datos para satisfacer las necesidades de la interfaz. En el caso de un análisis extenso, todos los campos sugeridos están disponibles vía rastreo. Para
OrderRFQMixin
esperamos que los creadores de mercado construyan su propia forma sofisticada de rastrear qué pedidos se han cancelado.
[L06] Cambios de almacenamiento durante la emisión de eventos
En NonceManager
contrato, cuando el NonceIncreased
se emite el evento, el nonce del remitente del mensaje también se incrementa.
Ejecutar múltiples operaciones simultáneamente puede hacer que el código base sea más difícil de razonar, más propenso a errores y puede llevar a que las operaciones se pasen por alto o se malinterpreten.
Para mejorar la intencionalidad general, la legibilidad y la claridad del código, considere aumentar el valor de nonce antes de emitir el evento.
Actualizar: Fijado en solicitud de extracción # 63.
[L07] Metodologías de decodificación inconsistentes podrían causar discrepancias en los resultados
Para respaldar toda su extensibilidad y flexibilidad, el Protocolo de órdenes limitadas tiene que lidiar rutinariamente con datos de bytes dinámicos y valores de retorno arbitrarios de contratos externos. Como resultado, el protocolo incluye una ArgumentsDecoder
biblioteca para convertir de manera más eficiente valores de bytes dinámicos en tipos de datos básicos. Sin embargo, esta biblioteca no se utiliza exclusivamente, y en algunos casos abi.decode
se utiliza en su lugar. Además, algunos contratos están utilizando abi coder v1
mientras otros están usando abi coder v2
. El primero se comporta de manera más similar al ArgumentsDecoder
biblioteca, mientras que este último realiza comprobaciones adicionales al decodificar.
El uso incoherente de estas diferentes metodologías de decodificación puede dar lugar a discrepancias sutiles entre la intención y el comportamiento real del código base.
Por ejemplo, la simulateCalls
La función solo usa el ArgumentsDecoder.decodeBool
función. Si el simulateCalls
se utiliza para verificar las llamadas que se realizarían en la parte predicada de una orden, entonces sus resultados podrían desviarse de lo que realmente ocurre cuando se evalúan las condiciones predicadas, porque se emplean diferentes metodologías de decodificación.
Así, por ejemplo, si un predicado hace un externo staticcall
a alguna función que devuelve un uint256
valor mayor que uno en lugar del esperado bool
, entonces esa llamada se revertirá, porque el valor devuelto es decodificado con abi coder v2
es abi.decode
que no aceptará valores como bool
. Sin embargo, si se hace exactamente la misma llamada con simulateCalls
, Entonces eso simplemente se marcará como true
, porque decodeBool
trata cualquier valor mayor que cero como true
.
Para hacer que el simulateCalls
función refleja completamente el comportamiento de las llamadas de predicado reales, considere modificarlo para usar abi.decode
.
Actualizar: Fijado en solicitud de extracción # 82.
[L08] Falta de validación de entrada
El fillOrderToWithPermit
y fillOrderTo
funciones de la OrderMixin
contrato, así como el fillOrderRFQToWithPermit
y fillOrderRFQTo
funciones de la OrderRFQMixin
contrato, no validar el target
parámetro de dirección.
Esto hace posible que un usuario pase inadvertidamente la dirección cero y, como resultado, bloquee los activos que debe recibir después de completar un pedido.
Para asegurarse de que los usuarios no bloqueen accidentalmente sus fondos, considere validar que el target
dirección no es igual a la dirección cero en las funciones citadas.
Actualizar: Fijado en solicitud de extracción # 78.
[L09] Cobertura de prueba de unidad baja
La cobertura de prueba unitaria para todo el proyecto es de alrededor del 75%, y algunos de los contratos tienen una cobertura particularmente baja.
Teniendo en cuenta la importancia de las pruebas unitarias para validar el código y evitar regresiones al refactorizar y desarrollar nuevas funciones, recomendamos aumentar significativamente la cobertura de las pruebas unitarias hasta al menos el 95 % e incluir casos extremos que cubran incluso situaciones poco probables.
Actualizar: No arreglado.
[L10] Documentación en línea engañosa o incompleta
A lo largo del código base, se identificaron algunos casos de documentación en línea engañosa y/o incompleta y deben corregirse.
Los siguientes son casos de documentación en línea engañosa:
- En
ChainlinkCalculator
contrato, elsinglePrice
la función NatSpec@notice
etiqueta dice que esoCalculates price of token relative to ETH scaled by 1e18
, pero de hecho, su resultado es el propuesta de ofamount
tokens escalados por1e18
, donde el oráculo no puede informar en términos de ETH (para un par que no incluye ETH, por ejemplo). - En
OrderRFQMixin
contrato, elinvalidatorForOrderRFQ
la función NatSpec@return
etiqueta es engañoso, porque es posible que la cotización no se haya completado para que se haya establecido el bit invalidador respectivo. El pedido también puede haber sido cancelado. - En lineas 147, 165y 188 of
OrderMixin.sol
, la especificación nacional@param
las etiquetas son agramaticales. - En linea 20 of
ERC1155Proxy.sol
, la@notice
etiqueta establece que el hash calculado es el resultado de aplicar hash alfunc_733NCGU
función, donde debería estar elfunc_301JL5R
función en su lugar.
Los siguientes son casos de documentación en línea incompleta:
- Funciones en el
AmountCalculator
contrato no describen ninguno de los parámetros. - En
ChainlinkCalculator
contrato, elsinglePrice
ydoublePrice
Las funciones no describen todos los parámetros. - En
ImmutableOwner
contrato, la variable pública y el modificador no tienen NatSpec. - En
InteractiveNotificationReceiver
contrato, elnotifyFillOrder
función no describe ninguno de los parámetros. - En
LimitOrderProtocol
contrato, elDOMAIN_SEPARATOR
La función no tiene NatSpec. - Eventos y mapeos en el
NonceManager
no tiene NatSpec. - En
OrderRFQMixin
contrato,cancelOrderRFQ*
Las funciones no describen los valores devueltos. - En
OrderMixin
contrato, varias funciones carecen de NatSpec completo. - En linea 168 of
OrderMixin.sol
y en linea 71 ofOrderRFQMixin.sol
, le falta el@dev
etiqueta. - Funciones en el
PredicateHelper
contrato no describen todos los parámetros.
La documentación clara en línea es fundamental para delinear las intenciones del código. Las discrepancias entre la documentación en línea y la implementación pueden dar lugar a graves conceptos erróneos sobre cómo se espera que se comporte el sistema. Considere corregir estos errores para evitar confusiones para desarrolladores, usuarios y auditores por igual.
Actualizar: Parcialmente arreglado. Documentación engañosa abordada en solicitud de extracción # 75 y solicitud de extracción # 77.
El equipo de 1 pulgada afirma:
Hemos corregido documentos engañosos. La finalización de los documentos se realizará más tarde.
[L11] Órdenes DoS posibles cuando se usan ganchos
El OrderMixin
contract implementa la funcionalidad para completar órdenes de intercambio genéricas fuera de la cadena que podrían tener condiciones para su éxito. Durante el cumplimiento de la orden, la orden puede comprobar las condiciones predefinidas de "predicado" antes de continuar con la ejecución.
Sin embargo, debido a que estas condiciones predicadas podrían apuntar a la lógica de cualquier contrato arbitrario, un fabricante malicioso podría engañar a los compradores haciéndoles creer que un pedido se comporta correctamente y que es válido cuando lo verifica fuera de la cadena, pero luego falla al intentar completar el mismo pedido. en cadena Este cambio en el comportamiento de los predicados podría realizarse ejecutando algún estado variable del que dependen los predicados, examinando el gas enviado o incluso qué direcciones están involucradas en la llamada, o mediante alguna otra lógica.
Además, si el fabricante definió un interacción durante el intercambio, la interactionTarget
el contrato podría revertirse o revocar la asignación para evitar que se complete con éxito el pedido, lo que esencialmente conduce al mismo resultado que los predicados maliciosos.
Si bien los activos no estarán en riesgo, los usuarios o bots que encuentren un pedido favorable tendrán la mayor carga de tratar de identificar este tipo de pedidos de spam que pueden parecer legítimos en la superficie. En caso de que no identifiquen este tipo de pedidos, incurrirán en costos de gas desperdiciado. Para reducir la cantidad de pedidos de spam, considere restringir los objetivos disponibles para estos ganchos. Considere también advertir a los usuarios sobre esta posibilidad antes de que intenten completar pedidos.
Actualizar: No arreglado. El equipo de 1 pulgada afirma:
Manejamos eso en nuestro backend y pensaremos en las formas de notificar a los posibles beneficiarios sobre el problema.
[L12] El redondeo puede ser desfavorable para taker
En OrderMixin
y OrderRFQMixin
contratos, cuando se está completando una orden y el tomador proporciona solo una makingAmount
or takingAmount
cantidad, el protocolo intenta calcular la cantidad de contrapartida del swap.
Hay dos problemas con estos cálculos, el primero es que no hay documentación o lógica que limite la cantidad de decimales que deben usar los parámetros de cantidad, que abordamos en el Suposiciones decimales no documentadas problema.
La segunda cuestión es que, en el curso de estos cálculos, el protocolo se redondea a favor del fabricante. El problema del redondeo puede verse muy agravado cuando se rompen los supuestos decimales implícitos, pero incluso cuando todo está en los términos esperados, el redondeo ocurrirá con cantidades pequeñas e impares.
Considere permitir que el tomador especifique una cantidad mínima de makerAsset
activo que están dispuestos a recibir junto con una cantidad máxima de takerAsset
activo que están dispuestos a canjear, por lo que la aceptación de cualquier redondeo es más explícita.
Actualizar: No arreglado. El equipo de 1 pulgada afirma:
La cantidad del umbral debería ser suficiente para la protección del tomador.
[L13] Manejo de órdenes contradictorias cuando faltan parámetros
En OrderMixin
contrato, el fillOrderTo
función realiza llamadas internas al _callGetMakerAmount
y _callGetTakerAmount
funciona cada vez que se intenta un llenado y el makingAmount
o de takingAmount
los parámetros son cero, respectivamente, o si el makingAmount
el valor es mayor que el remainingMakerAmount
.
El _callGetMakerAmount
y _callGetTakerAmount
las llamadas darán lugar a reversiones si la orden no se creó con el getMakerAmount
or getTakerAmount
parámetros, respectivamente, y se está ejecutando un llenado parcial.
An comentario en línea junto _callGetMakerAmount
y una comentario en línea junto _callGetTakerAmount
afirmar que "solo se permiten rellenos completos" si el pedido no se creó con getMakerAmount
or getTakerAmount
parámetros.
Sin embargo, hay rutas de código para las que esto no se aplica, porque esas rutas no comprueban la length
s de ambos getMakerAmount
y getTakerAmount
parámetros.
Específicamente, cuando un taker
especifica un takerAmount
valor para un pedido que sólo tiene un getMakerAmount
, a menos que llame a getMakerAmount
devuelve una cantidad mayor que remainingMakerAmount
, se puede ejecutar un llenado parcial en contradicción con la documentación en línea.
Esto deja poco clara la intencionalidad de esas rutas de código. Si este es el comportamiento esperado, considere modificar la documentación en línea para que sea más explícito. Si se trata de un comportamiento no intencional, considere verificar siempre las longitudes de ambos getMakerAmount
y del getTakerAmount
parámetros simultáneamente para que la implementación refuerce el comportamiento descrito por la documentación en línea.
Actualizar: Fijado en solicitud de extracción # 79.
[L14] Usar llamadas de Chainlink en desuso
El ChainlinkCalculator
El contrato está destinado a ser utilizado para consultar los oráculos de Chainlink. Lo hace a través de llamadas a sus latestTimestamp
y latestAnswer
, ambos han sido obsoletos. De hecho, los métodos ya no están presentes en la API de los agregadores de Chainlink. a partir de la versión tres.
Para evitar posibles incompatibilidades futuras con los oráculos de Chainlink, considere usar el latestRoundData
método en su lugar.
Actualizar: Fijado en solicitud de extracción # 67.
Notas e información adicional
[N01] No importar interfaces
El AggregatorInterface
interfaz parece ser un subconjunto de código copiado de ChainLink
repositorio de código público de. La interfaz completa está incluida en ChainLink
paquete npm del contrato de 's.
Cuando sea posible, para disminuir el potencial de discrepancias de interfaz y los problemas resultantes, en lugar de redefinir y/o reescribir las interfaces de otro proyecto, considere usar las interfaces instaladas a través de sus paquetes oficiales de npm.
Actualizar: Fijado en solicitud de extracción # 66.
[N02] Dependencias de proyectos en desuso
Durante la instalación del dependencias del proyecto, NPM advierte que uno de los paquetes instalados, Highlight
, “ya no será compatible ni recibirá actualizaciones de seguridad en el futuro”.
Aunque es poco probable que este paquete pueda causar un riesgo de seguridad, considere actualizar la dependencia que usa este paquete a una versión mantenida.
Actualizar: Fijado en solicitud de extracción # 64.
[N03] Las llamadas externas para ver los métodos no son llamadas estáticas
A lo largo de la mayor parte del código base, el protocolo hace explícitamente llamadas externas a través de OpenZeppelin functionStaticCall
método para restringir la posibilidad de cambios de estado cuando no se esperan o no son deseables. Sin embargo, en el ChainlinkCalculator
contrato, a pesar de la intención de realizar llamadas externas sólo a view
métodos en los oráculos de Chainlink, las llamadas externas en el singlePrice
y doublePrice
las funciones no se realizan de forma explícita staticcall
s.
Si bien no identificamos ningún problema de seguridad inmediato derivado de esto, para reducir la superficie de ataque, mejorar la consistencia y aclarar la intención, considere usar explícito staticcall
s, para todas las llamadas externas a view
funciones en el ChainlinkCalculator
contrato.
Actualizar: No arreglado. El equipo de 1 pulgada afirma:
Creemos que la complicación de la sintaxis anula las mejoras en la consistencia.
[N04] No fallar temprano con pedidos inválidos
En OrderMixin
contrato, el fillOrderTo
La función maneja la condición especial cuando un pedido no se ha enviado previamente (remainingMakerAmount == 0
), pero no maneja explícitamente la condición cuando la orden ya no es válida (remainingMakerAmount == 1
).
En el último escenario, la función eventualmente se revertirá, pero solo después de quemar cantidades no triviales de gas. Para aclarar la intención, aumentar la legibilidad y reducir el uso de gas, considere manejar explícitamente el escenario de pedido no válido hacia el comienzo de la función.
Actualizar: Fijado en solicitud de extracción # 68.
[N05] Contratos de ayudantes no marcados como abstractos
En Solidity, la palabra clave abstract
se utiliza para contratos que no son contratos funcionales por derecho propio o que no están destinados a ser utilizados como tales. En lugar de, abstract
los contratos son heredados por otros contratos en el sistema para crear contratos funcionales independientes.
A lo largo del código base, hay ejemplos de contratos auxiliares que no están marcados como abstractos, a pesar de que no están destinados a implementarse por sí solos. por ejemplo, el AmountCalculator
, ChainlinkCalculator
, ImmutableOwner
, NonceManager
y PredicateHelper
todos los contratos están compuestos por un conjunto básico de funciones que están destinadas a ser utilizadas por los contratos heredados.
Considere marcar los contratos de los ayudantes como abstract
para indicar claramente que están diseñados únicamente para agregar funcionalidad a los contratos que los heredan.
Actualizar: No arreglado. El equipo de 1 pulgada afirma:
Esos ayudantes se pueden implementar por separado. Se heredan sólo por ahorro de gas.
[N06] Ordenamiento de funciones inconsistente
A lo largo del código base, el orden de las funciones generalmente sigue el orden recomendado en la Guía de estilo de Solidity, cual es: constructor
, fallback
, external
, public
, internal
, private
.
Sin embargo, en el OrderMixin
contrato, el public
checkPredicate
función se desvía de la guía de estilo, dividiendo en dos external
funciones.
Para mejorar la legibilidad general del proyecto, considere estandarizar el orden de las funciones en todo el código base, como lo recomienda la Guía de estilo de Solidity.
Actualizar: Fijado en solicitud de extracción # 69.
[N07] Flujo de llenado de pedido inconsistente
El OrderMixin
y RFQOrderMixin
ambos contratos manejan el cumplimiento de los pedidos firmados, pero el flujo general de pedidos entre los dos contratos es inconsistente.
OrderMixin
es fillOrderTo
función sigue este flujo general (pseudo-código):
if ((takingAmount == 0) == (makingAmount == 0))
else if (takingAmount == 0)
else (handle makingAmount == 0) THEN swapTokens
Mientras RFQOrderMixin
es análogo fillOrderRFQTo
función sigue este flujo (pseudo-código):
if (takingAmount == 0 && makingAmount == 0)
else if (takingAmount == 0)
else if (makingAmount == 0)
else revert THEN swapTokens
No hay información de la documentación sobre por qué el primer condicional en cada una de estas dos funciones difiere, o por qué takingAmount
y makingAmount
ambos no pueden ser cero en la última función. Además, el caso en que tanto un makingAmount
y takingAmount
se proporcionan es mucho más fácil de razonar en el fillOrderRFQTo
función, ya que se maneja claramente en el final else
bloquear.
Para aclarar la intención y aumentar la legibilidad general del código, considere estandarizar el flujo general de pedidos en estos dos contratos o documentar explícitamente por qué existen las diferencias.
Actualizar: No arreglado. El equipo de 1 pulgada afirma:
Esto se debe a las funciones de fijación de precios personalizadas en las órdenes limitadas. Ya que
getMakerAmount
potencialmente puede diferir sustancialmente degetTakerAmount
, pensamos que es mejor no hacer una opción predeterminada para el tomador, ya que probablemente los confundirá en los casos en que esos captadores sean diferentes.
[N08] Los mensajes de error tienen un formato inconsistente o engañoso
A lo largo de la base de código, el require
y revert
Se encontró que los mensajes de error, que están destinados a notificar a los usuarios las condiciones particulares que causan que una transacción falle, tienen un formato inconsistente.
Por ejemplo, cada uno de los mensajes de error en las líneas 85 de OrderMixin.sol
, 16 de ERC721ProxySafe.sol
y 26 de Permitable.sol
emplear un estilo diferente.
Además, algunos mensajes de error son engañosos:
Los mensajes de error están destinados a notificar a los usuarios sobre las condiciones de falla, por lo que deben proporcionar suficiente información para que se puedan realizar las correcciones adecuadas para interactuar con el sistema. Los mensajes de error poco informativos dañan en gran medida la experiencia general del usuario, lo que reduce la calidad del sistema. Además, los mensajes de error con formato inconsistente pueden generar una confusión innecesaria. Por lo tanto, considere revisar todo el código base para asegurarse de que cada require
y revert
declaración tiene un mensaje de error que tiene un formato coherente, preciso, informativo y fácil de usar.
Actualizar: Parcialmente fijo en solicitud de extracción # 81.
[N09] Uso inconsistente de variables de retorno con nombre
Hay un uso incoherente de variables de retorno con nombre en el OrderMixin
contrato. Algunas funciones devolver variables con nombre, otros devolver valores explícitos, y otros declarar una variable de retorno nombrada pero anularla con una declaración de retorno explícita.
Considere la posibilidad de adoptar un enfoque coherente para devolver valores en todo el código base eliminando todas las variables de retorno con nombre, declarándolas explícitamente como variables locales y agregando las declaraciones de retorno necesarias cuando corresponda. Esto mejoraría tanto la claridad como la legibilidad del código, y también podría ayudar a reducir las regresiones durante futuras refactorizaciones de código.
Actualizar: Fijado en solicitud de extracción # 73.
[N10] El cálculo hash de la orden no está abierto a la API
El external
funciones remaining
, remainingRaw
y remainingsRaw
todos esperan un hash de orden para una operación exitosa.
Sin embargo, la función auxiliar _hash
, que devuelve el hash de una orden, tiene private
visibilidad. Esto significa que los usuarios tendrán que empaquetar manualmente partes de los pedidos y cadenas de dominio para obtener el hash de un pedido.
Para evitar la posibilidad de cometer errores al calcular los valores hash de los pedidos y proporcionar a los usuarios un método para generar el valor hash respectivo de un pedido, considere ampliar la visibilidad del _hash
funcionar para public
y refactorizando el nombre a hash
para ser coherente con el resto del código.
Actualizar: Fijado en solicitud de extracción # 74.
[N11] Sobrecarga semántica de mapeo
El _remaining
mapeo en el OrderMixin
El contrato está sobrecargado semánticamente para rastrear el estado de los pedidos y la cantidad restante de activos disponibles para esos pedidos.
Los tres estados que puede tomar son:
0
: El hash de la orden aún no se ha visto.1
: El pedido ha sido cancelado o completado por completo.- Alquiler y venta
uint
mayor que1
: El restantemakerAmount
disponible para ser completado en el pedido más1
.
Esta sobrecarga semántica requiere envolver y desenvolver este valor durante lookup
, cancellation
, initialization
y storage
.
La sobrecarga semántica y la lógica necesaria para habilitarla pueden ser propensas a errores y pueden hacer que el código base sea más difícil de entender y razonar, también puede abrir la puerta a regresiones en futuras actualizaciones del código.
Para mejorar la legibilidad del código, considere realizar un seguimiento del estado de finalización de los pedidos en una asignación independiente.
Actualizar: No arreglado. El equipo de 1 pulgada citó que una solución aumentaría los costos de gasolina para el fillOrder
función.
[N12] Órdenes con permiso permiten llamadas a contratos arbitrarios
El OrderMixin
contrato hereda el Permitable
contrato para permitir el llenado de órdenes de transacción única con activos que aceptan tales permit
Llamadas para modificar mesadas.
Sin embargo, a pesar de la llamadas al Permitable
contrato no valide si el objetivo es un activo permitido ni siquiera si es un activo, lo que podría permitir que un usuario malintencionado pase la dirección de un contrato arbitrario que podría ejecutar otra llamada antes de que se complete el pedido.
Aunque el contrato es protegido contra la reentrada, siempre se recomienda reducir la superficie de ataque y evitar la convocatoria de contratos externos durante la ejecución. Considere restringir el activo involucrado en el permiso a los activos involucrados en el pedido o a una lista blanca de activos para el protocolo.
Actualizar: No arreglado. El equipo de 1 pulgada afirma:
OrderMixin
en realidad no tiene información sobre tokens reales comomakerAsset
ytakerAsset
a veces son proxies u otros contratos intermedios y la información sobre tokens reales se almacena en algunos bytes arbitrarios. Por lo tanto, no existe una forma viable de restringir qué activopermit
se llama.
[N13] solhint
nunca se vuelve a habilitar
A lo largo del código base, hay un par de solhint-disable
declaraciones, específicamente aquellas en línea 23 y en linea 41 of RevertReasonParser.sol
, que no terminan con solhint-enable
.
A favor de la explicitud y de ser lo más restrictivo posible a la hora de desactivar solhint
, Considere usar solhint-disable-line
or solhint-disable-next-line
en cambio, similar a la línea 16 del mismo archivo.
Actualizar: Fijado en solicitud de extracción # 72.
[N14] Errores tipográficos
El código base contiene los siguientes errores tipográficos:
Adicionalmente el proyecto README
(fuera del alcance de esta auditoría) contiene los siguientes errores tipográficos:
Considere corregir estos errores tipográficos para mejorar la legibilidad del código.
Actualizar: Fijado en solicitud de extracción # 71 y solicitud de extracción # 77.
[N15] Uso de uint
en lugar de uint256
Para favorecer la claridad, todas las instancias de uint
debe ser declarado como uint256
. En particular, aquellos en el for
bucles en las líneas 98 y 119 of OrderMixin.sol
y lineas 16 y 30 of PredicateHelper.sol
.
Actualizar: Fijado en solicitud de extracción # 70.
Conclusiones
Se encontraron 3 problemas de alta gravedad. Se propusieron algunos cambios para seguir las mejores prácticas y reducir la superficie de ataque potencial.
- &
- 7
- Nuestra Empresa
- de la máquina
- Conforme
- Mi Cuenta
- a través de
- Actúe
- acciones
- Adicionales
- dirección
- Ventaja
- Todos
- Permitir
- ya haya utilizado
- Aunque
- cantidades
- análisis
- abejas
- enfoque
- argumentos
- en torno a
- activo
- Activos
- auditoría
- Back-end
- Comienzo
- "Ser"
- MEJOR
- y las mejores prácticas
- Poco
- los robots
- build
- llamar al
- servicios sociales
- cases
- Causar
- Eslabón de la cadena
- el cambio
- comprobación
- Cheques
- código
- integraciones
- condición
- confusión
- construcción
- contiene
- contrato
- contratos
- correcciones
- Precio
- podría
- Parejas
- creador
- Moneda
- Current
- datos
- acuerdo
- Denegación de servicio
- Desplegando
- Diseño
- desarrolladores
- Desarrollo
- HIZO
- diferir de
- una experiencia diferente
- dominio
- doble
- lugar de trabajo dinámico
- Temprano en la
- Southern Implants
- fomentar
- especialmente
- ETH
- Evento
- Eventos
- todo
- ejemplo
- Intercambio
- esperado
- experience
- Explotar
- Caracteristicas
- Terrenos
- Finalmente
- Nombre
- Fijar
- Flexibilidad
- de tus señales
- seguir
- encontrado
- ser completados
- función
- fondos
- futuras
- juego
- GAS
- General
- Diezmos y Ofrendas
- maravillosa
- guía
- Manejo
- hachís
- Hashing
- es
- ayuda
- Alta
- altamente
- Cómo
- HTTPS
- Híbrido
- Identifique
- Impacto
- implementar
- importante
- importador
- incluido
- Incluye
- aumente
- aumentado
- info
- información
- EN LA MINA
- Insights
- intención
- intereses
- Interfaz
- involucra
- cuestiones
- IT
- large
- mayores
- Lead
- líder
- Biblioteca
- Limitada
- línea
- Liquidez
- Listado
- Listas
- local
- miró
- búsqueda
- gran
- fabricante
- Realizar
- Mercado
- Mempool
- espejo
- modelo
- MEJOR DE TU
- Más popular
- a saber
- Nuevas características
- no fungible
- tokens no fungibles
- oficial
- habiertos
- Operaciones
- Optión
- oráculo
- solicite
- en pedidos de venta.
- Otro
- propietario
- Patrón de Costura
- Popular
- presente
- la prevención
- precio
- cotización
- privada
- proyecto
- proyecta
- Protección
- protocolo
- proporcionar
- proporciona un
- apoderado
- público
- publicar
- comprar
- calidad
- aumento
- Realidad
- reducir
- dependencia
- reporte
- Informes
- repositorio
- Requisitos
- RESTO
- Resultados
- devoluciones
- una estrategia SEO para aparecer en las búsquedas de Google.
- Riesgo
- rondas
- Ejecutar
- Sdk
- EN LINEA
- Servicios
- set
- compartido
- Acciones
- Turno
- similares
- sencillos
- chica
- inteligente
- Contratos Inteligentes
- So
- solidez
- correo no deseado (spam)
- específicamente
- Gastos
- Spot
- comienzo
- Estado
- Posicionamiento
- Zonas
- Estado
- STORAGE
- papa
- Subido
- comercial
- exitosos
- Con éxito
- SOPORTE
- Soportado
- Superficie
- Switch
- te
- Target
- test
- Pruebas
- pruebas
- A través de esta formación, el personal docente y administrativo de escuelas y universidades estará preparado para manejar los recursos disponibles que derivan de la diversidad cultural de sus estudiantes. Además, un mejor y mayor entendimiento sobre estas diferencias y similitudes culturales permitirá alcanzar los objetivos de inclusión previstos.
- a lo largo de
- equipo
- juntos
- ficha
- Tokens
- seguir
- Seguimiento
- transaccional
- Confía en
- único
- Actualizaciones
- us
- usabilidad
- USD
- USDT
- usuarios
- utilidad
- propuesta de
- Ver
- la visibilidad
- esperar
- ¿
- lista blanca
- dentro de
- sin
- Actividades:
- valor
- cero