From 95a09c462a01247b2d84f0d458d6a83801aeef7a Mon Sep 17 00:00:00 2001 From: ZhouMiao <6618609@qq.com> Date: Tue, 28 Mar 2023 18:26:16 +0800 Subject: [PATCH 01/62] add portuguese --- Sources/web3swift/KeystoreManager/BIP39+WordLists.swift | 3 +++ Sources/web3swift/KeystoreManager/BIP39.swift | 5 +++++ 2 files changed, 8 insertions(+) diff --git a/Sources/web3swift/KeystoreManager/BIP39+WordLists.swift b/Sources/web3swift/KeystoreManager/BIP39+WordLists.swift index 9e19180d3..a52b7b053 100755 --- a/Sources/web3swift/KeystoreManager/BIP39+WordLists.swift +++ b/Sources/web3swift/KeystoreManager/BIP39+WordLists.swift @@ -30,5 +30,8 @@ extension BIP39Language { } var spanishWords: [String] { return "ábaco abdomen abeja abierto abogado abono aborto abrazo abrir abuelo abuso acabar academia acceso acción aceite acelga acento aceptar ácido aclarar acné acoger acoso activo acto actriz actuar acudir acuerdo acusar adicto admitir adoptar adorno aduana adulto aéreo afectar afición afinar afirmar ágil agitar agonía agosto agotar agregar agrio agua agudo águila aguja ahogo ahorro aire aislar ajedrez ajeno ajuste alacrán alambre alarma alba álbum alcalde aldea alegre alejar alerta aleta alfiler alga algodón aliado aliento alivio alma almeja almíbar altar alteza altivo alto altura alumno alzar amable amante amapola amargo amasar ámbar ámbito ameno amigo amistad amor amparo amplio ancho anciano ancla andar andén anemia ángulo anillo ánimo anís anotar antena antiguo antojo anual anular anuncio añadir añejo año apagar aparato apetito apio aplicar apodo aporte apoyo aprender aprobar apuesta apuro arado araña arar árbitro árbol arbusto archivo arco arder ardilla arduo área árido aries armonía arnés aroma arpa arpón arreglo arroz arruga arte artista asa asado asalto ascenso asegurar aseo asesor asiento asilo asistir asno asombro áspero astilla astro astuto asumir asunto atajo ataque atar atento ateo ático atleta átomo atraer atroz atún audaz audio auge aula aumento ausente autor aval avance avaro ave avellana avena avestruz avión aviso ayer ayuda ayuno azafrán azar azote azúcar azufre azul baba babor bache bahía baile bajar balanza balcón balde bambú banco banda baño barba barco barniz barro báscula bastón basura batalla batería batir batuta baúl bazar bebé bebida bello besar beso bestia bicho bien bingo blanco bloque blusa boa bobina bobo boca bocina boda bodega boina bola bolero bolsa bomba bondad bonito bono bonsái borde borrar bosque bote botín bóveda bozal bravo brazo brecha breve brillo brinco brisa broca broma bronce brote bruja brusco bruto buceo bucle bueno buey bufanda bufón búho buitre bulto burbuja burla burro buscar butaca buzón caballo cabeza cabina cabra cacao cadáver cadena caer café caída caimán caja cajón cal calamar calcio caldo calidad calle calma calor calvo cama cambio camello camino campo cáncer candil canela canguro canica canto caña cañón caoba caos capaz capitán capote captar capucha cara carbón cárcel careta carga cariño carne carpeta carro carta casa casco casero caspa castor catorce catre caudal causa cazo cebolla ceder cedro celda célebre celoso célula cemento ceniza centro cerca cerdo cereza cero cerrar certeza césped cetro chacal chaleco champú chancla chapa charla chico chiste chivo choque choza chuleta chupar ciclón ciego cielo cien cierto cifra cigarro cima cinco cine cinta ciprés circo ciruela cisne cita ciudad clamor clan claro clase clave cliente clima clínica cobre cocción cochino cocina coco código codo cofre coger cohete cojín cojo cola colcha colegio colgar colina collar colmo columna combate comer comida cómodo compra conde conejo conga conocer consejo contar copa copia corazón corbata corcho cordón corona correr coser cosmos costa cráneo cráter crear crecer creído crema cría crimen cripta crisis cromo crónica croqueta crudo cruz cuadro cuarto cuatro cubo cubrir cuchara cuello cuento cuerda cuesta cueva cuidar culebra culpa culto cumbre cumplir cuna cuneta cuota cupón cúpula curar curioso curso curva cutis dama danza dar dardo dátil deber débil década decir dedo defensa definir dejar delfín delgado delito demora denso dental deporte derecho derrota desayuno deseo desfile desnudo destino desvío detalle detener deuda día diablo diadema diamante diana diario dibujo dictar diente dieta diez difícil digno dilema diluir dinero directo dirigir disco diseño disfraz diva divino doble doce dolor domingo don donar dorado dormir dorso dos dosis dragón droga ducha duda duelo dueño dulce dúo duque durar dureza duro ébano ebrio echar eco ecuador edad edición edificio editor educar efecto eficaz eje ejemplo elefante elegir elemento elevar elipse élite elixir elogio eludir embudo emitir emoción empate empeño empleo empresa enano encargo enchufe encía enemigo enero enfado enfermo engaño enigma enlace enorme enredo ensayo enseñar entero entrar envase envío época equipo erizo escala escena escolar escribir escudo esencia esfera esfuerzo espada espejo espía esposa espuma esquí estar este estilo estufa etapa eterno ética etnia evadir evaluar evento evitar exacto examen exceso excusa exento exigir exilio existir éxito experto explicar exponer extremo fábrica fábula fachada fácil factor faena faja falda fallo falso faltar fama familia famoso faraón farmacia farol farsa fase fatiga fauna favor fax febrero fecha feliz feo feria feroz fértil fervor festín fiable fianza fiar fibra ficción ficha fideo fiebre fiel fiera fiesta figura fijar fijo fila filete filial filtro fin finca fingir finito firma flaco flauta flecha flor flota fluir flujo flúor fobia foca fogata fogón folio folleto fondo forma forro fortuna forzar fosa foto fracaso frágil franja frase fraude freír freno fresa frío frito fruta fuego fuente fuerza fuga fumar función funda furgón furia fusil fútbol futuro gacela gafas gaita gajo gala galería gallo gamba ganar gancho ganga ganso garaje garza gasolina gastar gato gavilán gemelo gemir gen género genio gente geranio gerente germen gesto gigante gimnasio girar giro glaciar globo gloria gol golfo goloso golpe goma gordo gorila gorra gota goteo gozar grada gráfico grano grasa gratis grave grieta grillo gripe gris grito grosor grúa grueso grumo grupo guante guapo guardia guerra guía guiño guion guiso guitarra gusano gustar haber hábil hablar hacer hacha hada hallar hamaca harina haz hazaña hebilla hebra hecho helado helio hembra herir hermano héroe hervir hielo hierro hígado higiene hijo himno historia hocico hogar hoguera hoja hombre hongo honor honra hora hormiga horno hostil hoyo hueco huelga huerta hueso huevo huida huir humano húmedo humilde humo hundir huracán hurto icono ideal idioma ídolo iglesia iglú igual ilegal ilusión imagen imán imitar impar imperio imponer impulso incapaz índice inerte infiel informe ingenio inicio inmenso inmune innato insecto instante interés íntimo intuir inútil invierno ira iris ironía isla islote jabalí jabón jamón jarabe jardín jarra jaula jazmín jefe jeringa jinete jornada joroba joven joya juerga jueves juez jugador jugo juguete juicio junco jungla junio juntar júpiter jurar justo juvenil juzgar kilo koala labio lacio lacra lado ladrón lagarto lágrima laguna laico lamer lámina lámpara lana lancha langosta lanza lápiz largo larva lástima lata látex latir laurel lavar lazo leal lección leche lector leer legión legumbre lejano lengua lento leña león leopardo lesión letal letra leve leyenda libertad libro licor líder lidiar lienzo liga ligero lima límite limón limpio lince lindo línea lingote lino linterna líquido liso lista litera litio litro llaga llama llanto llave llegar llenar llevar llorar llover lluvia lobo loción loco locura lógica logro lombriz lomo lonja lote lucha lucir lugar lujo luna lunes lupa lustro luto luz maceta macho madera madre maduro maestro mafia magia mago maíz maldad maleta malla malo mamá mambo mamut manco mando manejar manga maniquí manjar mano manso manta mañana mapa máquina mar marco marea marfil margen marido mármol marrón martes marzo masa máscara masivo matar materia matiz matriz máximo mayor mazorca mecha medalla medio médula mejilla mejor melena melón memoria menor mensaje mente menú mercado merengue mérito mes mesón meta meter método metro mezcla miedo miel miembro miga mil milagro militar millón mimo mina minero mínimo minuto miope mirar misa miseria misil mismo mitad mito mochila moción moda modelo moho mojar molde moler molino momento momia monarca moneda monja monto moño morada morder moreno morir morro morsa mortal mosca mostrar motivo mover móvil mozo mucho mudar mueble muela muerte muestra mugre mujer mula muleta multa mundo muñeca mural muro músculo museo musgo música muslo nácar nación nadar naipe naranja nariz narrar nasal natal nativo natural náusea naval nave navidad necio néctar negar negocio negro neón nervio neto neutro nevar nevera nicho nido niebla nieto niñez niño nítido nivel nobleza noche nómina noria norma norte nota noticia novato novela novio nube nuca núcleo nudillo nudo nuera nueve nuez nulo número nutria oasis obeso obispo objeto obra obrero observar obtener obvio oca ocaso océano ochenta ocho ocio ocre octavo octubre oculto ocupar ocurrir odiar odio odisea oeste ofensa oferta oficio ofrecer ogro oído oír ojo ola oleada olfato olivo olla olmo olor olvido ombligo onda onza opaco opción ópera opinar oponer optar óptica opuesto oración orador oral órbita orca orden oreja órgano orgía orgullo oriente origen orilla oro orquesta oruga osadía oscuro osezno oso ostra otoño otro oveja óvulo óxido oxígeno oyente ozono pacto padre paella página pago país pájaro palabra palco paleta pálido palma paloma palpar pan panal pánico pantera pañuelo papá papel papilla paquete parar parcela pared parir paro párpado parque párrafo parte pasar paseo pasión paso pasta pata patio patria pausa pauta pavo payaso peatón pecado pecera pecho pedal pedir pegar peine pelar peldaño pelea peligro pellejo pelo peluca pena pensar peñón peón peor pepino pequeño pera percha perder pereza perfil perico perla permiso perro persona pesa pesca pésimo pestaña pétalo petróleo pez pezuña picar pichón pie piedra pierna pieza pijama pilar piloto pimienta pino pintor pinza piña piojo pipa pirata pisar piscina piso pista pitón pizca placa plan plata playa plaza pleito pleno plomo pluma plural pobre poco poder podio poema poesía poeta polen policía pollo polvo pomada pomelo pomo pompa poner porción portal posada poseer posible poste potencia potro pozo prado precoz pregunta premio prensa preso previo primo príncipe prisión privar proa probar proceso producto proeza profesor programa prole promesa pronto propio próximo prueba público puchero pudor pueblo puerta puesto pulga pulir pulmón pulpo pulso puma punto puñal puño pupa pupila puré quedar queja quemar querer queso quieto química quince quitar rábano rabia rabo ración radical raíz rama rampa rancho rango rapaz rápido rapto rasgo raspa rato rayo raza razón reacción realidad rebaño rebote recaer receta rechazo recoger recreo recto recurso red redondo reducir reflejo reforma refrán refugio regalo regir regla regreso rehén reino reír reja relato relevo relieve relleno reloj remar remedio remo rencor rendir renta reparto repetir reposo reptil res rescate resina respeto resto resumen retiro retorno retrato reunir revés revista rey rezar rico riego rienda riesgo rifa rígido rigor rincón riñón río riqueza risa ritmo rito rizo roble roce rociar rodar rodeo rodilla roer rojizo rojo romero romper ron ronco ronda ropa ropero rosa rosca rostro rotar rubí rubor rudo rueda rugir ruido ruina ruleta rulo rumbo rumor ruptura ruta rutina sábado saber sabio sable sacar sagaz sagrado sala saldo salero salir salmón salón salsa salto salud salvar samba sanción sandía sanear sangre sanidad sano santo sapo saque sardina sartén sastre satán sauna saxofón sección seco secreto secta sed seguir seis sello selva semana semilla senda sensor señal señor separar sepia sequía ser serie sermón servir sesenta sesión seta setenta severo sexo sexto sidra siesta siete siglo signo sílaba silbar silencio silla símbolo simio sirena sistema sitio situar sobre socio sodio sol solapa soldado soledad sólido soltar solución sombra sondeo sonido sonoro sonrisa sopa soplar soporte sordo sorpresa sorteo sostén sótano suave subir suceso sudor suegra suelo sueño suerte sufrir sujeto sultán sumar superar suplir suponer supremo sur surco sureño surgir susto sutil tabaco tabique tabla tabú taco tacto tajo talar talco talento talla talón tamaño tambor tango tanque tapa tapete tapia tapón taquilla tarde tarea tarifa tarjeta tarot tarro tarta tatuaje tauro taza tazón teatro techo tecla técnica tejado tejer tejido tela teléfono tema temor templo tenaz tender tener tenis tenso teoría terapia terco término ternura terror tesis tesoro testigo tetera texto tez tibio tiburón tiempo tienda tierra tieso tigre tijera tilde timbre tímido timo tinta tío típico tipo tira tirón titán títere título tiza toalla tobillo tocar tocino todo toga toldo tomar tono tonto topar tope toque tórax torero tormenta torneo toro torpedo torre torso tortuga tos tosco toser tóxico trabajo tractor traer tráfico trago traje tramo trance trato trauma trazar trébol tregua treinta tren trepar tres tribu trigo tripa triste triunfo trofeo trompa tronco tropa trote trozo truco trueno trufa tubería tubo tuerto tumba tumor túnel túnica turbina turismo turno tutor ubicar úlcera umbral unidad unir universo uno untar uña urbano urbe urgente urna usar usuario útil utopía uva vaca vacío vacuna vagar vago vaina vajilla vale válido valle valor válvula vampiro vara variar varón vaso vecino vector vehículo veinte vejez vela velero veloz vena vencer venda veneno vengar venir venta venus ver verano verbo verde vereda verja verso verter vía viaje vibrar vicio víctima vida vídeo vidrio viejo viernes vigor vil villa vinagre vino viñedo violín viral virgo virtud visor víspera vista vitamina viudo vivaz vivero vivir vivo volcán volumen volver voraz votar voto voz vuelo vulgar yacer yate yegua yema yerno yeso yodo yoga yogur zafiro zanja zapato zarza zona zorro zumo zurdo".components(separatedBy: " ") + var portugueseWords: [String] { + return "abacate abaixo abalar abater abduzir abelha aberto abismo abotoar abranger abreviar abrigar abrupto absinto absoluto absurdo abutre acabado acalmar acampar acanhar acaso aceitar acelerar acenar acervo acessar acetona achatar acidez acima acionado acirrar aclamar aclive acolhida acomodar acoplar acordar acumular acusador adaptar adega adentro adepto adequar aderente adesivo adeus adiante aditivo adjetivo adjunto admirar adorar adquirir adubo adverso advogado aeronave afastar aferir afetivo afinador afivelar aflito afluente afrontar agachar agarrar agasalho agenciar agilizar agiota agitado agora agradar agreste agrupar aguardar agulha ajoelhar ajudar ajustar alameda alarme alastrar alavanca albergue albino alcatra aldeia alecrim alegria alertar alface alfinete algum alheio aliar alicate alienar alinhar aliviar almofada alocar alpiste alterar altitude alucinar alugar aluno alusivo alvo amaciar amador amarelo amassar ambas ambiente ameixa amenizar amido amistoso amizade amolador amontoar amoroso amostra amparar ampliar ampola anagrama analisar anarquia anatomia andaime anel anexo angular animar anjo anomalia anotado ansioso anterior anuidade anunciar anzol apagador apalpar apanhado apego apelido apertada apesar apetite apito aplauso aplicada apoio apontar aposta aprendiz aprovar aquecer arame aranha arara arcada ardente areia arejar arenito aresta argiloso argola arma arquivo arraial arrebate arriscar arroba arrumar arsenal arterial artigo arvoredo asfaltar asilado aspirar assador assinar assoalho assunto astral atacado atadura atalho atarefar atear atender aterro ateu atingir atirador ativo atoleiro atracar atrevido atriz atual atum auditor aumentar aura aurora autismo autoria autuar avaliar avante avaria avental avesso aviador avisar avulso axila azarar azedo azeite azulejo babar babosa bacalhau bacharel bacia bagagem baiano bailar baioneta bairro baixista bajular baleia baliza balsa banal bandeira banho banir banquete barato barbado baronesa barraca barulho baseado bastante batata batedor batida batom batucar baunilha beber beijo beirada beisebol beldade beleza belga beliscar bendito bengala benzer berimbau berlinda berro besouro bexiga bezerro bico bicudo bienal bifocal bifurcar bigorna bilhete bimestre bimotor biologia biombo biosfera bipolar birrento biscoito bisneto bispo bissexto bitola bizarro blindado bloco bloquear boato bobagem bocado bocejo bochecha boicotar bolada boletim bolha bolo bombeiro bonde boneco bonita borbulha borda boreal borracha bovino boxeador branco brasa braveza breu briga brilho brincar broa brochura bronzear broto bruxo bucha budismo bufar bule buraco busca busto buzina cabana cabelo cabide cabo cabrito cacau cacetada cachorro cacique cadastro cadeado cafezal caiaque caipira caixote cajado caju calafrio calcular caldeira calibrar calmante calota camada cambista camisa camomila campanha camuflar canavial cancelar caneta canguru canhoto canivete canoa cansado cantar canudo capacho capela capinar capotar capricho captador capuz caracol carbono cardeal careca carimbar carneiro carpete carreira cartaz carvalho casaco casca casebre castelo casulo catarata cativar caule causador cautelar cavalo caverna cebola cedilha cegonha celebrar celular cenoura censo centeio cercar cerrado certeiro cerveja cetim cevada chacota chaleira chamado chapada charme chatice chave chefe chegada cheiro cheque chicote chifre chinelo chocalho chover chumbo chutar chuva cicatriz ciclone cidade cidreira ciente cigana cimento cinto cinza ciranda circuito cirurgia citar clareza clero clicar clone clube coado coagir cobaia cobertor cobrar cocada coelho coentro coeso cogumelo coibir coifa coiote colar coleira colher colidir colmeia colono coluna comando combinar comentar comitiva comover complexo comum concha condor conectar confuso congelar conhecer conjugar consumir contrato convite cooperar copeiro copiador copo coquetel coragem cordial corneta coronha corporal correio cortejo coruja corvo cosseno costela cotonete couro couve covil cozinha cratera cravo creche credor creme crer crespo criada criminal crioulo crise criticar crosta crua cruzeiro cubano cueca cuidado cujo culatra culminar culpar cultura cumprir cunhado cupido curativo curral cursar curto cuspir custear cutelo damasco datar debater debitar deboche debulhar decalque decimal declive decote decretar dedal dedicado deduzir defesa defumar degelo degrau degustar deitado deixar delator delegado delinear delonga demanda demitir demolido dentista depenado depilar depois depressa depurar deriva derramar desafio desbotar descanso desenho desfiado desgaste desigual deslize desmamar desova despesa destaque desviar detalhar detentor detonar detrito deusa dever devido devotado dezena diagrama dialeto didata difuso digitar dilatado diluente diminuir dinastia dinheiro diocese direto discreta disfarce disparo disquete dissipar distante ditador diurno diverso divisor divulgar dizer dobrador dolorido domador dominado donativo donzela dormente dorsal dosagem dourado doutor drenagem drible drogaria duelar duende dueto duplo duquesa durante duvidoso eclodir ecoar ecologia edificar edital educado efeito efetivar ejetar elaborar eleger eleitor elenco elevador eliminar elogiar embargo embolado embrulho embutido emenda emergir emissor empatia empenho empinado empolgar emprego empurrar emulador encaixe encenado enchente encontro endeusar endossar enfaixar enfeite enfim engajado engenho englobar engomado engraxar enguia enjoar enlatar enquanto enraizar enrolado enrugar ensaio enseada ensino ensopado entanto enteado entidade entortar entrada entulho envergar enviado envolver enxame enxerto enxofre enxuto epiderme equipar ereto erguido errata erva ervilha esbanjar esbelto escama escola escrita escuta esfinge esfolar esfregar esfumado esgrima esmalte espanto espelho espiga esponja espreita espumar esquerda estaca esteira esticar estofado estrela estudo esvaziar etanol etiqueta euforia europeu evacuar evaporar evasivo eventual evidente evoluir exagero exalar examinar exato exausto excesso excitar exclamar executar exemplo exibir exigente exonerar expandir expelir expirar explanar exposto expresso expulsar externo extinto extrato fabricar fabuloso faceta facial fada fadiga faixa falar falta familiar fandango fanfarra fantoche fardado farelo farinha farofa farpa fartura fatia fator favorita faxina fazenda fechado feijoada feirante felino feminino fenda feno fera feriado ferrugem ferver festejar fetal feudal fiapo fibrose ficar ficheiro figurado fileira filho filme filtrar firmeza fisgada fissura fita fivela fixador fixo flacidez flamingo flanela flechada flora flutuar fluxo focal focinho fofocar fogo foguete foice folgado folheto forjar formiga forno forte fosco fossa fragata fralda frango frasco fraterno freira frente fretar frieza friso fritura fronha frustrar fruteira fugir fulano fuligem fundar fungo funil furador furioso futebol gabarito gabinete gado gaiato gaiola gaivota galega galho galinha galocha ganhar garagem garfo gargalo garimpo garoupa garrafa gasoduto gasto gata gatilho gaveta gazela gelado geleia gelo gemada gemer gemido generoso gengiva genial genoma genro geologia gerador germinar gesso gestor ginasta gincana gingado girafa girino glacial glicose global glorioso goela goiaba golfe golpear gordura gorjeta gorro gostoso goteira governar gracejo gradual grafite gralha grampo granada gratuito graveto graxa grego grelhar greve grilo grisalho gritaria grosso grotesco grudado grunhido gruta guache guarani guaxinim guerrear guiar guincho guisado gula guloso guru habitar harmonia haste haver hectare herdar heresia hesitar hiato hibernar hidratar hiena hino hipismo hipnose hipoteca hoje holofote homem honesto honrado hormonal hospedar humorado iate ideia idoso ignorado igreja iguana ileso ilha iludido iluminar ilustrar imagem imediato imenso imersivo iminente imitador imortal impacto impedir implante impor imprensa impune imunizar inalador inapto inativo incenso inchar incidir incluir incolor indeciso indireto indutor ineficaz inerente infantil infestar infinito inflamar informal infrator ingerir inibido inicial inimigo injetar inocente inodoro inovador inox inquieto inscrito inseto insistir inspetor instalar insulto intacto integral intimar intocado intriga invasor inverno invicto invocar iogurte iraniano ironizar irreal irritado isca isento isolado isqueiro italiano janeiro jangada janta jararaca jardim jarro jasmim jato javali jazida jejum joaninha joelhada jogador joia jornal jorrar jovem juba judeu judoca juiz julgador julho jurado jurista juro justa labareda laboral lacre lactante ladrilho lagarta lagoa laje lamber lamentar laminar lampejo lanche lapidar lapso laranja lareira largura lasanha lastro lateral latido lavanda lavoura lavrador laxante lazer lealdade lebre legado legendar legista leigo leiloar leitura lembrete leme lenhador lentilha leoa lesma leste letivo letreiro levar leveza levitar liberal libido liderar ligar ligeiro limitar limoeiro limpador linda linear linhagem liquidez listagem lisura litoral livro lixa lixeira locador locutor lojista lombo lona longe lontra lorde lotado loteria loucura lousa louvar luar lucidez lucro luneta lustre lutador luva macaco macete machado macio madeira madrinha magnata magreza maior mais malandro malha malote maluco mamilo mamoeiro mamute manada mancha mandato manequim manhoso manivela manobrar mansa manter manusear mapeado maquinar marcador maresia marfim margem marinho marmita maroto marquise marreco martelo marujo mascote masmorra massagem mastigar matagal materno matinal matutar maxilar medalha medida medusa megafone meiga melancia melhor membro memorial menino menos mensagem mental merecer mergulho mesada mesclar mesmo mesquita mestre metade meteoro metragem mexer mexicano micro migalha migrar milagre milenar milhar mimado minerar minhoca ministro minoria miolo mirante mirtilo misturar mocidade moderno modular moeda moer moinho moita moldura moleza molho molinete molusco montanha moqueca morango morcego mordomo morena mosaico mosquete mostarda motel motim moto motriz muda muito mulata mulher multar mundial munido muralha murcho muscular museu musical nacional nadador naja namoro narina narrado nascer nativa natureza navalha navegar navio neblina nebuloso negativa negociar negrito nervoso neta neural nevasca nevoeiro ninar ninho nitidez nivelar nobreza noite noiva nomear nominal nordeste nortear notar noticiar noturno novelo novilho novo nublado nudez numeral nupcial nutrir nuvem obcecado obedecer objetivo obrigado obscuro obstetra obter obturar ocidente ocioso ocorrer oculista ocupado ofegante ofensiva oferenda oficina ofuscado ogiva olaria oleoso olhar oliveira ombro omelete omisso omitir ondulado oneroso ontem opcional operador oponente oportuno oposto orar orbitar ordem ordinal orfanato orgasmo orgulho oriental origem oriundo orla ortodoxo orvalho oscilar ossada osso ostentar otimismo ousadia outono outubro ouvido ovelha ovular oxidar oxigenar pacato paciente pacote pactuar padaria padrinho pagar pagode painel pairar paisagem palavra palestra palheta palito palmada palpitar pancada panela panfleto panqueca pantanal papagaio papelada papiro parafina parcial pardal parede partida pasmo passado pastel patamar patente patinar patrono paulada pausar peculiar pedalar pedestre pediatra pedra pegada peitoral peixe pele pelicano penca pendurar peneira penhasco pensador pente perceber perfeito pergunta perito permitir perna perplexo persiana pertence peruca pescado pesquisa pessoa petiscar piada picado piedade pigmento pilastra pilhado pilotar pimenta pincel pinguim pinha pinote pintar pioneiro pipoca piquete piranha pires pirueta piscar pistola pitanga pivete planta plaqueta platina plebeu plumagem pluvial pneu poda poeira poetisa polegada policiar poluente polvilho pomar pomba ponderar pontaria populoso porta possuir postal pote poupar pouso povoar praia prancha prato praxe prece predador prefeito premiar prensar preparar presilha pretexto prevenir prezar primata princesa prisma privado processo produto profeta proibido projeto prometer propagar prosa protetor provador publicar pudim pular pulmonar pulseira punhal punir pupilo pureza puxador quadra quantia quarto quase quebrar queda queijo quente querido quimono quina quiosque rabanada rabisco rachar racionar radial raiar rainha raio raiva rajada ralado ramal ranger ranhura rapadura rapel rapidez raposa raquete raridade rasante rascunho rasgar raspador rasteira rasurar ratazana ratoeira realeza reanimar reaver rebaixar rebelde rebolar recado recente recheio recibo recordar recrutar recuar rede redimir redonda reduzida reenvio refinar refletir refogar refresco refugiar regalia regime regra reinado reitor rejeitar relativo remador remendo remorso renovado reparo repelir repleto repolho represa repudiar requerer resenha resfriar resgatar residir resolver respeito ressaca restante resumir retalho reter retirar retomada retratar revelar revisor revolta riacho rica rigidez rigoroso rimar ringue risada risco risonho robalo rochedo rodada rodeio rodovia roedor roleta romano roncar rosado roseira rosto rota roteiro rotina rotular rouco roupa roxo rubro rugido rugoso ruivo rumo rupestre russo sabor saciar sacola sacudir sadio safira saga sagrada saibro salada saleiro salgado saliva salpicar salsicha saltar salvador sambar samurai sanar sanfona sangue sanidade sapato sarda sargento sarjeta saturar saudade saxofone sazonal secar secular seda sedento sediado sedoso sedutor segmento segredo segundo seiva seleto selvagem semanal semente senador senhor sensual sentado separado sereia seringa serra servo setembro setor sigilo silhueta silicone simetria simpatia simular sinal sincero singular sinopse sintonia sirene siri situado soberano sobra socorro sogro soja solda soletrar solteiro sombrio sonata sondar sonegar sonhador sono soprano soquete sorrir sorteio sossego sotaque soterrar sovado sozinho suavizar subida submerso subsolo subtrair sucata sucesso suco sudeste sufixo sugador sugerir sujeito sulfato sumir suor superior suplicar suposto suprimir surdina surfista surpresa surreal surtir suspiro sustento tabela tablete tabuada tacho tagarela talher talo talvez tamanho tamborim tampa tangente tanto tapar tapioca tardio tarefa tarja tarraxa tatuagem taurino taxativo taxista teatral tecer tecido teclado tedioso teia teimar telefone telhado tempero tenente tensor tentar termal terno terreno tese tesoura testado teto textura texugo tiara tigela tijolo timbrar timidez tingido tinteiro tiragem titular toalha tocha tolerar tolice tomada tomilho tonel tontura topete tora torcido torneio torque torrada torto tostar touca toupeira toxina trabalho tracejar tradutor trafegar trajeto trama trancar trapo traseiro tratador travar treino tremer trepidar trevo triagem tribo triciclo tridente trilogia trindade triplo triturar triunfal trocar trombeta trova trunfo truque tubular tucano tudo tulipa tupi turbo turma turquesa tutelar tutorial uivar umbigo unha unidade uniforme urologia urso urtiga urubu usado usina usufruir vacina vadiar vagaroso vaidoso vala valente validade valores vantagem vaqueiro varanda vareta varrer vascular vasilha vassoura vazar vazio veado vedar vegetar veicular veleiro velhice veludo vencedor vendaval venerar ventre verbal verdade vereador vergonha vermelho verniz versar vertente vespa vestido vetorial viaduto viagem viajar viatura vibrador videira vidraria viela viga vigente vigiar vigorar vilarejo vinco vinheta vinil violeta virada virtude visitar visto vitral viveiro vizinho voador voar vogal volante voleibol voltagem volumoso vontade vulto vuvuzela xadrez xarope xeque xeretar xerife xingar zangado zarpar zebu zelador zombar zoologia zumbido" + } } } diff --git a/Sources/web3swift/KeystoreManager/BIP39.swift b/Sources/web3swift/KeystoreManager/BIP39.swift index ca884a0c4..9eeeb30fc 100755 --- a/Sources/web3swift/KeystoreManager/BIP39.swift +++ b/Sources/web3swift/KeystoreManager/BIP39.swift @@ -16,6 +16,7 @@ public enum BIP39Language { case french case italian case spanish + case portuguese public var words: [String] { switch self { case .english: @@ -34,6 +35,8 @@ public enum BIP39Language { return italianWords case .spanish: return spanishWords + case .portuguese: + return portugueseWords } } public var separator: String { @@ -63,6 +66,8 @@ public enum BIP39Language { self = .italian case "spanish": self = .spanish + case "portuguese": + self = .portuguese default: return nil } From d2fd021dab324218cf5cf53c06ea70b52b50f3ef Mon Sep 17 00:00:00 2001 From: ZhouMiao <6618609@qq.com> Date: Tue, 28 Mar 2023 18:35:01 +0800 Subject: [PATCH 02/62] add logic --- Sources/web3swift/KeystoreManager/BIP39+WordLists.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/web3swift/KeystoreManager/BIP39+WordLists.swift b/Sources/web3swift/KeystoreManager/BIP39+WordLists.swift index a52b7b053..a67c22c4a 100755 --- a/Sources/web3swift/KeystoreManager/BIP39+WordLists.swift +++ b/Sources/web3swift/KeystoreManager/BIP39+WordLists.swift @@ -31,7 +31,7 @@ extension BIP39Language { var spanishWords: [String] { return "ábaco abdomen abeja abierto abogado abono aborto abrazo abrir abuelo abuso acabar academia acceso acción aceite acelga acento aceptar ácido aclarar acné acoger acoso activo acto actriz actuar acudir acuerdo acusar adicto admitir adoptar adorno aduana adulto aéreo afectar afición afinar afirmar ágil agitar agonía agosto agotar agregar agrio agua agudo águila aguja ahogo ahorro aire aislar ajedrez ajeno ajuste alacrán alambre alarma alba álbum alcalde aldea alegre alejar alerta aleta alfiler alga algodón aliado aliento alivio alma almeja almíbar altar alteza altivo alto altura alumno alzar amable amante amapola amargo amasar ámbar ámbito ameno amigo amistad amor amparo amplio ancho anciano ancla andar andén anemia ángulo anillo ánimo anís anotar antena antiguo antojo anual anular anuncio añadir añejo año apagar aparato apetito apio aplicar apodo aporte apoyo aprender aprobar apuesta apuro arado araña arar árbitro árbol arbusto archivo arco arder ardilla arduo área árido aries armonía arnés aroma arpa arpón arreglo arroz arruga arte artista asa asado asalto ascenso asegurar aseo asesor asiento asilo asistir asno asombro áspero astilla astro astuto asumir asunto atajo ataque atar atento ateo ático atleta átomo atraer atroz atún audaz audio auge aula aumento ausente autor aval avance avaro ave avellana avena avestruz avión aviso ayer ayuda ayuno azafrán azar azote azúcar azufre azul baba babor bache bahía baile bajar balanza balcón balde bambú banco banda baño barba barco barniz barro báscula bastón basura batalla batería batir batuta baúl bazar bebé bebida bello besar beso bestia bicho bien bingo blanco bloque blusa boa bobina bobo boca bocina boda bodega boina bola bolero bolsa bomba bondad bonito bono bonsái borde borrar bosque bote botín bóveda bozal bravo brazo brecha breve brillo brinco brisa broca broma bronce brote bruja brusco bruto buceo bucle bueno buey bufanda bufón búho buitre bulto burbuja burla burro buscar butaca buzón caballo cabeza cabina cabra cacao cadáver cadena caer café caída caimán caja cajón cal calamar calcio caldo calidad calle calma calor calvo cama cambio camello camino campo cáncer candil canela canguro canica canto caña cañón caoba caos capaz capitán capote captar capucha cara carbón cárcel careta carga cariño carne carpeta carro carta casa casco casero caspa castor catorce catre caudal causa cazo cebolla ceder cedro celda célebre celoso célula cemento ceniza centro cerca cerdo cereza cero cerrar certeza césped cetro chacal chaleco champú chancla chapa charla chico chiste chivo choque choza chuleta chupar ciclón ciego cielo cien cierto cifra cigarro cima cinco cine cinta ciprés circo ciruela cisne cita ciudad clamor clan claro clase clave cliente clima clínica cobre cocción cochino cocina coco código codo cofre coger cohete cojín cojo cola colcha colegio colgar colina collar colmo columna combate comer comida cómodo compra conde conejo conga conocer consejo contar copa copia corazón corbata corcho cordón corona correr coser cosmos costa cráneo cráter crear crecer creído crema cría crimen cripta crisis cromo crónica croqueta crudo cruz cuadro cuarto cuatro cubo cubrir cuchara cuello cuento cuerda cuesta cueva cuidar culebra culpa culto cumbre cumplir cuna cuneta cuota cupón cúpula curar curioso curso curva cutis dama danza dar dardo dátil deber débil década decir dedo defensa definir dejar delfín delgado delito demora denso dental deporte derecho derrota desayuno deseo desfile desnudo destino desvío detalle detener deuda día diablo diadema diamante diana diario dibujo dictar diente dieta diez difícil digno dilema diluir dinero directo dirigir disco diseño disfraz diva divino doble doce dolor domingo don donar dorado dormir dorso dos dosis dragón droga ducha duda duelo dueño dulce dúo duque durar dureza duro ébano ebrio echar eco ecuador edad edición edificio editor educar efecto eficaz eje ejemplo elefante elegir elemento elevar elipse élite elixir elogio eludir embudo emitir emoción empate empeño empleo empresa enano encargo enchufe encía enemigo enero enfado enfermo engaño enigma enlace enorme enredo ensayo enseñar entero entrar envase envío época equipo erizo escala escena escolar escribir escudo esencia esfera esfuerzo espada espejo espía esposa espuma esquí estar este estilo estufa etapa eterno ética etnia evadir evaluar evento evitar exacto examen exceso excusa exento exigir exilio existir éxito experto explicar exponer extremo fábrica fábula fachada fácil factor faena faja falda fallo falso faltar fama familia famoso faraón farmacia farol farsa fase fatiga fauna favor fax febrero fecha feliz feo feria feroz fértil fervor festín fiable fianza fiar fibra ficción ficha fideo fiebre fiel fiera fiesta figura fijar fijo fila filete filial filtro fin finca fingir finito firma flaco flauta flecha flor flota fluir flujo flúor fobia foca fogata fogón folio folleto fondo forma forro fortuna forzar fosa foto fracaso frágil franja frase fraude freír freno fresa frío frito fruta fuego fuente fuerza fuga fumar función funda furgón furia fusil fútbol futuro gacela gafas gaita gajo gala galería gallo gamba ganar gancho ganga ganso garaje garza gasolina gastar gato gavilán gemelo gemir gen género genio gente geranio gerente germen gesto gigante gimnasio girar giro glaciar globo gloria gol golfo goloso golpe goma gordo gorila gorra gota goteo gozar grada gráfico grano grasa gratis grave grieta grillo gripe gris grito grosor grúa grueso grumo grupo guante guapo guardia guerra guía guiño guion guiso guitarra gusano gustar haber hábil hablar hacer hacha hada hallar hamaca harina haz hazaña hebilla hebra hecho helado helio hembra herir hermano héroe hervir hielo hierro hígado higiene hijo himno historia hocico hogar hoguera hoja hombre hongo honor honra hora hormiga horno hostil hoyo hueco huelga huerta hueso huevo huida huir humano húmedo humilde humo hundir huracán hurto icono ideal idioma ídolo iglesia iglú igual ilegal ilusión imagen imán imitar impar imperio imponer impulso incapaz índice inerte infiel informe ingenio inicio inmenso inmune innato insecto instante interés íntimo intuir inútil invierno ira iris ironía isla islote jabalí jabón jamón jarabe jardín jarra jaula jazmín jefe jeringa jinete jornada joroba joven joya juerga jueves juez jugador jugo juguete juicio junco jungla junio juntar júpiter jurar justo juvenil juzgar kilo koala labio lacio lacra lado ladrón lagarto lágrima laguna laico lamer lámina lámpara lana lancha langosta lanza lápiz largo larva lástima lata látex latir laurel lavar lazo leal lección leche lector leer legión legumbre lejano lengua lento leña león leopardo lesión letal letra leve leyenda libertad libro licor líder lidiar lienzo liga ligero lima límite limón limpio lince lindo línea lingote lino linterna líquido liso lista litera litio litro llaga llama llanto llave llegar llenar llevar llorar llover lluvia lobo loción loco locura lógica logro lombriz lomo lonja lote lucha lucir lugar lujo luna lunes lupa lustro luto luz maceta macho madera madre maduro maestro mafia magia mago maíz maldad maleta malla malo mamá mambo mamut manco mando manejar manga maniquí manjar mano manso manta mañana mapa máquina mar marco marea marfil margen marido mármol marrón martes marzo masa máscara masivo matar materia matiz matriz máximo mayor mazorca mecha medalla medio médula mejilla mejor melena melón memoria menor mensaje mente menú mercado merengue mérito mes mesón meta meter método metro mezcla miedo miel miembro miga mil milagro militar millón mimo mina minero mínimo minuto miope mirar misa miseria misil mismo mitad mito mochila moción moda modelo moho mojar molde moler molino momento momia monarca moneda monja monto moño morada morder moreno morir morro morsa mortal mosca mostrar motivo mover móvil mozo mucho mudar mueble muela muerte muestra mugre mujer mula muleta multa mundo muñeca mural muro músculo museo musgo música muslo nácar nación nadar naipe naranja nariz narrar nasal natal nativo natural náusea naval nave navidad necio néctar negar negocio negro neón nervio neto neutro nevar nevera nicho nido niebla nieto niñez niño nítido nivel nobleza noche nómina noria norma norte nota noticia novato novela novio nube nuca núcleo nudillo nudo nuera nueve nuez nulo número nutria oasis obeso obispo objeto obra obrero observar obtener obvio oca ocaso océano ochenta ocho ocio ocre octavo octubre oculto ocupar ocurrir odiar odio odisea oeste ofensa oferta oficio ofrecer ogro oído oír ojo ola oleada olfato olivo olla olmo olor olvido ombligo onda onza opaco opción ópera opinar oponer optar óptica opuesto oración orador oral órbita orca orden oreja órgano orgía orgullo oriente origen orilla oro orquesta oruga osadía oscuro osezno oso ostra otoño otro oveja óvulo óxido oxígeno oyente ozono pacto padre paella página pago país pájaro palabra palco paleta pálido palma paloma palpar pan panal pánico pantera pañuelo papá papel papilla paquete parar parcela pared parir paro párpado parque párrafo parte pasar paseo pasión paso pasta pata patio patria pausa pauta pavo payaso peatón pecado pecera pecho pedal pedir pegar peine pelar peldaño pelea peligro pellejo pelo peluca pena pensar peñón peón peor pepino pequeño pera percha perder pereza perfil perico perla permiso perro persona pesa pesca pésimo pestaña pétalo petróleo pez pezuña picar pichón pie piedra pierna pieza pijama pilar piloto pimienta pino pintor pinza piña piojo pipa pirata pisar piscina piso pista pitón pizca placa plan plata playa plaza pleito pleno plomo pluma plural pobre poco poder podio poema poesía poeta polen policía pollo polvo pomada pomelo pomo pompa poner porción portal posada poseer posible poste potencia potro pozo prado precoz pregunta premio prensa preso previo primo príncipe prisión privar proa probar proceso producto proeza profesor programa prole promesa pronto propio próximo prueba público puchero pudor pueblo puerta puesto pulga pulir pulmón pulpo pulso puma punto puñal puño pupa pupila puré quedar queja quemar querer queso quieto química quince quitar rábano rabia rabo ración radical raíz rama rampa rancho rango rapaz rápido rapto rasgo raspa rato rayo raza razón reacción realidad rebaño rebote recaer receta rechazo recoger recreo recto recurso red redondo reducir reflejo reforma refrán refugio regalo regir regla regreso rehén reino reír reja relato relevo relieve relleno reloj remar remedio remo rencor rendir renta reparto repetir reposo reptil res rescate resina respeto resto resumen retiro retorno retrato reunir revés revista rey rezar rico riego rienda riesgo rifa rígido rigor rincón riñón río riqueza risa ritmo rito rizo roble roce rociar rodar rodeo rodilla roer rojizo rojo romero romper ron ronco ronda ropa ropero rosa rosca rostro rotar rubí rubor rudo rueda rugir ruido ruina ruleta rulo rumbo rumor ruptura ruta rutina sábado saber sabio sable sacar sagaz sagrado sala saldo salero salir salmón salón salsa salto salud salvar samba sanción sandía sanear sangre sanidad sano santo sapo saque sardina sartén sastre satán sauna saxofón sección seco secreto secta sed seguir seis sello selva semana semilla senda sensor señal señor separar sepia sequía ser serie sermón servir sesenta sesión seta setenta severo sexo sexto sidra siesta siete siglo signo sílaba silbar silencio silla símbolo simio sirena sistema sitio situar sobre socio sodio sol solapa soldado soledad sólido soltar solución sombra sondeo sonido sonoro sonrisa sopa soplar soporte sordo sorpresa sorteo sostén sótano suave subir suceso sudor suegra suelo sueño suerte sufrir sujeto sultán sumar superar suplir suponer supremo sur surco sureño surgir susto sutil tabaco tabique tabla tabú taco tacto tajo talar talco talento talla talón tamaño tambor tango tanque tapa tapete tapia tapón taquilla tarde tarea tarifa tarjeta tarot tarro tarta tatuaje tauro taza tazón teatro techo tecla técnica tejado tejer tejido tela teléfono tema temor templo tenaz tender tener tenis tenso teoría terapia terco término ternura terror tesis tesoro testigo tetera texto tez tibio tiburón tiempo tienda tierra tieso tigre tijera tilde timbre tímido timo tinta tío típico tipo tira tirón titán títere título tiza toalla tobillo tocar tocino todo toga toldo tomar tono tonto topar tope toque tórax torero tormenta torneo toro torpedo torre torso tortuga tos tosco toser tóxico trabajo tractor traer tráfico trago traje tramo trance trato trauma trazar trébol tregua treinta tren trepar tres tribu trigo tripa triste triunfo trofeo trompa tronco tropa trote trozo truco trueno trufa tubería tubo tuerto tumba tumor túnel túnica turbina turismo turno tutor ubicar úlcera umbral unidad unir universo uno untar uña urbano urbe urgente urna usar usuario útil utopía uva vaca vacío vacuna vagar vago vaina vajilla vale válido valle valor válvula vampiro vara variar varón vaso vecino vector vehículo veinte vejez vela velero veloz vena vencer venda veneno vengar venir venta venus ver verano verbo verde vereda verja verso verter vía viaje vibrar vicio víctima vida vídeo vidrio viejo viernes vigor vil villa vinagre vino viñedo violín viral virgo virtud visor víspera vista vitamina viudo vivaz vivero vivir vivo volcán volumen volver voraz votar voto voz vuelo vulgar yacer yate yegua yema yerno yeso yodo yoga yogur zafiro zanja zapato zarza zona zorro zumo zurdo".components(separatedBy: " ") var portugueseWords: [String] { - return "abacate abaixo abalar abater abduzir abelha aberto abismo abotoar abranger abreviar abrigar abrupto absinto absoluto absurdo abutre acabado acalmar acampar acanhar acaso aceitar acelerar acenar acervo acessar acetona achatar acidez acima acionado acirrar aclamar aclive acolhida acomodar acoplar acordar acumular acusador adaptar adega adentro adepto adequar aderente adesivo adeus adiante aditivo adjetivo adjunto admirar adorar adquirir adubo adverso advogado aeronave afastar aferir afetivo afinador afivelar aflito afluente afrontar agachar agarrar agasalho agenciar agilizar agiota agitado agora agradar agreste agrupar aguardar agulha ajoelhar ajudar ajustar alameda alarme alastrar alavanca albergue albino alcatra aldeia alecrim alegria alertar alface alfinete algum alheio aliar alicate alienar alinhar aliviar almofada alocar alpiste alterar altitude alucinar alugar aluno alusivo alvo amaciar amador amarelo amassar ambas ambiente ameixa amenizar amido amistoso amizade amolador amontoar amoroso amostra amparar ampliar ampola anagrama analisar anarquia anatomia andaime anel anexo angular animar anjo anomalia anotado ansioso anterior anuidade anunciar anzol apagador apalpar apanhado apego apelido apertada apesar apetite apito aplauso aplicada apoio apontar aposta aprendiz aprovar aquecer arame aranha arara arcada ardente areia arejar arenito aresta argiloso argola arma arquivo arraial arrebate arriscar arroba arrumar arsenal arterial artigo arvoredo asfaltar asilado aspirar assador assinar assoalho assunto astral atacado atadura atalho atarefar atear atender aterro ateu atingir atirador ativo atoleiro atracar atrevido atriz atual atum auditor aumentar aura aurora autismo autoria autuar avaliar avante avaria avental avesso aviador avisar avulso axila azarar azedo azeite azulejo babar babosa bacalhau bacharel bacia bagagem baiano bailar baioneta bairro baixista bajular baleia baliza balsa banal bandeira banho banir banquete barato barbado baronesa barraca barulho baseado bastante batata batedor batida batom batucar baunilha beber beijo beirada beisebol beldade beleza belga beliscar bendito bengala benzer berimbau berlinda berro besouro bexiga bezerro bico bicudo bienal bifocal bifurcar bigorna bilhete bimestre bimotor biologia biombo biosfera bipolar birrento biscoito bisneto bispo bissexto bitola bizarro blindado bloco bloquear boato bobagem bocado bocejo bochecha boicotar bolada boletim bolha bolo bombeiro bonde boneco bonita borbulha borda boreal borracha bovino boxeador branco brasa braveza breu briga brilho brincar broa brochura bronzear broto bruxo bucha budismo bufar bule buraco busca busto buzina cabana cabelo cabide cabo cabrito cacau cacetada cachorro cacique cadastro cadeado cafezal caiaque caipira caixote cajado caju calafrio calcular caldeira calibrar calmante calota camada cambista camisa camomila campanha camuflar canavial cancelar caneta canguru canhoto canivete canoa cansado cantar canudo capacho capela capinar capotar capricho captador capuz caracol carbono cardeal careca carimbar carneiro carpete carreira cartaz carvalho casaco casca casebre castelo casulo catarata cativar caule causador cautelar cavalo caverna cebola cedilha cegonha celebrar celular cenoura censo centeio cercar cerrado certeiro cerveja cetim cevada chacota chaleira chamado chapada charme chatice chave chefe chegada cheiro cheque chicote chifre chinelo chocalho chover chumbo chutar chuva cicatriz ciclone cidade cidreira ciente cigana cimento cinto cinza ciranda circuito cirurgia citar clareza clero clicar clone clube coado coagir cobaia cobertor cobrar cocada coelho coentro coeso cogumelo coibir coifa coiote colar coleira colher colidir colmeia colono coluna comando combinar comentar comitiva comover complexo comum concha condor conectar confuso congelar conhecer conjugar consumir contrato convite cooperar copeiro copiador copo coquetel coragem cordial corneta coronha corporal correio cortejo coruja corvo cosseno costela cotonete couro couve covil cozinha cratera cravo creche credor creme crer crespo criada criminal crioulo crise criticar crosta crua cruzeiro cubano cueca cuidado cujo culatra culminar culpar cultura cumprir cunhado cupido curativo curral cursar curto cuspir custear cutelo damasco datar debater debitar deboche debulhar decalque decimal declive decote decretar dedal dedicado deduzir defesa defumar degelo degrau degustar deitado deixar delator delegado delinear delonga demanda demitir demolido dentista depenado depilar depois depressa depurar deriva derramar desafio desbotar descanso desenho desfiado desgaste desigual deslize desmamar desova despesa destaque desviar detalhar detentor detonar detrito deusa dever devido devotado dezena diagrama dialeto didata difuso digitar dilatado diluente diminuir dinastia dinheiro diocese direto discreta disfarce disparo disquete dissipar distante ditador diurno diverso divisor divulgar dizer dobrador dolorido domador dominado donativo donzela dormente dorsal dosagem dourado doutor drenagem drible drogaria duelar duende dueto duplo duquesa durante duvidoso eclodir ecoar ecologia edificar edital educado efeito efetivar ejetar elaborar eleger eleitor elenco elevador eliminar elogiar embargo embolado embrulho embutido emenda emergir emissor empatia empenho empinado empolgar emprego empurrar emulador encaixe encenado enchente encontro endeusar endossar enfaixar enfeite enfim engajado engenho englobar engomado engraxar enguia enjoar enlatar enquanto enraizar enrolado enrugar ensaio enseada ensino ensopado entanto enteado entidade entortar entrada entulho envergar enviado envolver enxame enxerto enxofre enxuto epiderme equipar ereto erguido errata erva ervilha esbanjar esbelto escama escola escrita escuta esfinge esfolar esfregar esfumado esgrima esmalte espanto espelho espiga esponja espreita espumar esquerda estaca esteira esticar estofado estrela estudo esvaziar etanol etiqueta euforia europeu evacuar evaporar evasivo eventual evidente evoluir exagero exalar examinar exato exausto excesso excitar exclamar executar exemplo exibir exigente exonerar expandir expelir expirar explanar exposto expresso expulsar externo extinto extrato fabricar fabuloso faceta facial fada fadiga faixa falar falta familiar fandango fanfarra fantoche fardado farelo farinha farofa farpa fartura fatia fator favorita faxina fazenda fechado feijoada feirante felino feminino fenda feno fera feriado ferrugem ferver festejar fetal feudal fiapo fibrose ficar ficheiro figurado fileira filho filme filtrar firmeza fisgada fissura fita fivela fixador fixo flacidez flamingo flanela flechada flora flutuar fluxo focal focinho fofocar fogo foguete foice folgado folheto forjar formiga forno forte fosco fossa fragata fralda frango frasco fraterno freira frente fretar frieza friso fritura fronha frustrar fruteira fugir fulano fuligem fundar fungo funil furador furioso futebol gabarito gabinete gado gaiato gaiola gaivota galega galho galinha galocha ganhar garagem garfo gargalo garimpo garoupa garrafa gasoduto gasto gata gatilho gaveta gazela gelado geleia gelo gemada gemer gemido generoso gengiva genial genoma genro geologia gerador germinar gesso gestor ginasta gincana gingado girafa girino glacial glicose global glorioso goela goiaba golfe golpear gordura gorjeta gorro gostoso goteira governar gracejo gradual grafite gralha grampo granada gratuito graveto graxa grego grelhar greve grilo grisalho gritaria grosso grotesco grudado grunhido gruta guache guarani guaxinim guerrear guiar guincho guisado gula guloso guru habitar harmonia haste haver hectare herdar heresia hesitar hiato hibernar hidratar hiena hino hipismo hipnose hipoteca hoje holofote homem honesto honrado hormonal hospedar humorado iate ideia idoso ignorado igreja iguana ileso ilha iludido iluminar ilustrar imagem imediato imenso imersivo iminente imitador imortal impacto impedir implante impor imprensa impune imunizar inalador inapto inativo incenso inchar incidir incluir incolor indeciso indireto indutor ineficaz inerente infantil infestar infinito inflamar informal infrator ingerir inibido inicial inimigo injetar inocente inodoro inovador inox inquieto inscrito inseto insistir inspetor instalar insulto intacto integral intimar intocado intriga invasor inverno invicto invocar iogurte iraniano ironizar irreal irritado isca isento isolado isqueiro italiano janeiro jangada janta jararaca jardim jarro jasmim jato javali jazida jejum joaninha joelhada jogador joia jornal jorrar jovem juba judeu judoca juiz julgador julho jurado jurista juro justa labareda laboral lacre lactante ladrilho lagarta lagoa laje lamber lamentar laminar lampejo lanche lapidar lapso laranja lareira largura lasanha lastro lateral latido lavanda lavoura lavrador laxante lazer lealdade lebre legado legendar legista leigo leiloar leitura lembrete leme lenhador lentilha leoa lesma leste letivo letreiro levar leveza levitar liberal libido liderar ligar ligeiro limitar limoeiro limpador linda linear linhagem liquidez listagem lisura litoral livro lixa lixeira locador locutor lojista lombo lona longe lontra lorde lotado loteria loucura lousa louvar luar lucidez lucro luneta lustre lutador luva macaco macete machado macio madeira madrinha magnata magreza maior mais malandro malha malote maluco mamilo mamoeiro mamute manada mancha mandato manequim manhoso manivela manobrar mansa manter manusear mapeado maquinar marcador maresia marfim margem marinho marmita maroto marquise marreco martelo marujo mascote masmorra massagem mastigar matagal materno matinal matutar maxilar medalha medida medusa megafone meiga melancia melhor membro memorial menino menos mensagem mental merecer mergulho mesada mesclar mesmo mesquita mestre metade meteoro metragem mexer mexicano micro migalha migrar milagre milenar milhar mimado minerar minhoca ministro minoria miolo mirante mirtilo misturar mocidade moderno modular moeda moer moinho moita moldura moleza molho molinete molusco montanha moqueca morango morcego mordomo morena mosaico mosquete mostarda motel motim moto motriz muda muito mulata mulher multar mundial munido muralha murcho muscular museu musical nacional nadador naja namoro narina narrado nascer nativa natureza navalha navegar navio neblina nebuloso negativa negociar negrito nervoso neta neural nevasca nevoeiro ninar ninho nitidez nivelar nobreza noite noiva nomear nominal nordeste nortear notar noticiar noturno novelo novilho novo nublado nudez numeral nupcial nutrir nuvem obcecado obedecer objetivo obrigado obscuro obstetra obter obturar ocidente ocioso ocorrer oculista ocupado ofegante ofensiva oferenda oficina ofuscado ogiva olaria oleoso olhar oliveira ombro omelete omisso omitir ondulado oneroso ontem opcional operador oponente oportuno oposto orar orbitar ordem ordinal orfanato orgasmo orgulho oriental origem oriundo orla ortodoxo orvalho oscilar ossada osso ostentar otimismo ousadia outono outubro ouvido ovelha ovular oxidar oxigenar pacato paciente pacote pactuar padaria padrinho pagar pagode painel pairar paisagem palavra palestra palheta palito palmada palpitar pancada panela panfleto panqueca pantanal papagaio papelada papiro parafina parcial pardal parede partida pasmo passado pastel patamar patente patinar patrono paulada pausar peculiar pedalar pedestre pediatra pedra pegada peitoral peixe pele pelicano penca pendurar peneira penhasco pensador pente perceber perfeito pergunta perito permitir perna perplexo persiana pertence peruca pescado pesquisa pessoa petiscar piada picado piedade pigmento pilastra pilhado pilotar pimenta pincel pinguim pinha pinote pintar pioneiro pipoca piquete piranha pires pirueta piscar pistola pitanga pivete planta plaqueta platina plebeu plumagem pluvial pneu poda poeira poetisa polegada policiar poluente polvilho pomar pomba ponderar pontaria populoso porta possuir postal pote poupar pouso povoar praia prancha prato praxe prece predador prefeito premiar prensar preparar presilha pretexto prevenir prezar primata princesa prisma privado processo produto profeta proibido projeto prometer propagar prosa protetor provador publicar pudim pular pulmonar pulseira punhal punir pupilo pureza puxador quadra quantia quarto quase quebrar queda queijo quente querido quimono quina quiosque rabanada rabisco rachar racionar radial raiar rainha raio raiva rajada ralado ramal ranger ranhura rapadura rapel rapidez raposa raquete raridade rasante rascunho rasgar raspador rasteira rasurar ratazana ratoeira realeza reanimar reaver rebaixar rebelde rebolar recado recente recheio recibo recordar recrutar recuar rede redimir redonda reduzida reenvio refinar refletir refogar refresco refugiar regalia regime regra reinado reitor rejeitar relativo remador remendo remorso renovado reparo repelir repleto repolho represa repudiar requerer resenha resfriar resgatar residir resolver respeito ressaca restante resumir retalho reter retirar retomada retratar revelar revisor revolta riacho rica rigidez rigoroso rimar ringue risada risco risonho robalo rochedo rodada rodeio rodovia roedor roleta romano roncar rosado roseira rosto rota roteiro rotina rotular rouco roupa roxo rubro rugido rugoso ruivo rumo rupestre russo sabor saciar sacola sacudir sadio safira saga sagrada saibro salada saleiro salgado saliva salpicar salsicha saltar salvador sambar samurai sanar sanfona sangue sanidade sapato sarda sargento sarjeta saturar saudade saxofone sazonal secar secular seda sedento sediado sedoso sedutor segmento segredo segundo seiva seleto selvagem semanal semente senador senhor sensual sentado separado sereia seringa serra servo setembro setor sigilo silhueta silicone simetria simpatia simular sinal sincero singular sinopse sintonia sirene siri situado soberano sobra socorro sogro soja solda soletrar solteiro sombrio sonata sondar sonegar sonhador sono soprano soquete sorrir sorteio sossego sotaque soterrar sovado sozinho suavizar subida submerso subsolo subtrair sucata sucesso suco sudeste sufixo sugador sugerir sujeito sulfato sumir suor superior suplicar suposto suprimir surdina surfista surpresa surreal surtir suspiro sustento tabela tablete tabuada tacho tagarela talher talo talvez tamanho tamborim tampa tangente tanto tapar tapioca tardio tarefa tarja tarraxa tatuagem taurino taxativo taxista teatral tecer tecido teclado tedioso teia teimar telefone telhado tempero tenente tensor tentar termal terno terreno tese tesoura testado teto textura texugo tiara tigela tijolo timbrar timidez tingido tinteiro tiragem titular toalha tocha tolerar tolice tomada tomilho tonel tontura topete tora torcido torneio torque torrada torto tostar touca toupeira toxina trabalho tracejar tradutor trafegar trajeto trama trancar trapo traseiro tratador travar treino tremer trepidar trevo triagem tribo triciclo tridente trilogia trindade triplo triturar triunfal trocar trombeta trova trunfo truque tubular tucano tudo tulipa tupi turbo turma turquesa tutelar tutorial uivar umbigo unha unidade uniforme urologia urso urtiga urubu usado usina usufruir vacina vadiar vagaroso vaidoso vala valente validade valores vantagem vaqueiro varanda vareta varrer vascular vasilha vassoura vazar vazio veado vedar vegetar veicular veleiro velhice veludo vencedor vendaval venerar ventre verbal verdade vereador vergonha vermelho verniz versar vertente vespa vestido vetorial viaduto viagem viajar viatura vibrador videira vidraria viela viga vigente vigiar vigorar vilarejo vinco vinheta vinil violeta virada virtude visitar visto vitral viveiro vizinho voador voar vogal volante voleibol voltagem volumoso vontade vulto vuvuzela xadrez xarope xeque xeretar xerife xingar zangado zarpar zebu zelador zombar zoologia zumbido" + return "abacate abaixo abalar abater abduzir abelha aberto abismo abotoar abranger abreviar abrigar abrupto absinto absoluto absurdo abutre acabado acalmar acampar acanhar acaso aceitar acelerar acenar acervo acessar acetona achatar acidez acima acionado acirrar aclamar aclive acolhida acomodar acoplar acordar acumular acusador adaptar adega adentro adepto adequar aderente adesivo adeus adiante aditivo adjetivo adjunto admirar adorar adquirir adubo adverso advogado aeronave afastar aferir afetivo afinador afivelar aflito afluente afrontar agachar agarrar agasalho agenciar agilizar agiota agitado agora agradar agreste agrupar aguardar agulha ajoelhar ajudar ajustar alameda alarme alastrar alavanca albergue albino alcatra aldeia alecrim alegria alertar alface alfinete algum alheio aliar alicate alienar alinhar aliviar almofada alocar alpiste alterar altitude alucinar alugar aluno alusivo alvo amaciar amador amarelo amassar ambas ambiente ameixa amenizar amido amistoso amizade amolador amontoar amoroso amostra amparar ampliar ampola anagrama analisar anarquia anatomia andaime anel anexo angular animar anjo anomalia anotado ansioso anterior anuidade anunciar anzol apagador apalpar apanhado apego apelido apertada apesar apetite apito aplauso aplicada apoio apontar aposta aprendiz aprovar aquecer arame aranha arara arcada ardente areia arejar arenito aresta argiloso argola arma arquivo arraial arrebate arriscar arroba arrumar arsenal arterial artigo arvoredo asfaltar asilado aspirar assador assinar assoalho assunto astral atacado atadura atalho atarefar atear atender aterro ateu atingir atirador ativo atoleiro atracar atrevido atriz atual atum auditor aumentar aura aurora autismo autoria autuar avaliar avante avaria avental avesso aviador avisar avulso axila azarar azedo azeite azulejo babar babosa bacalhau bacharel bacia bagagem baiano bailar baioneta bairro baixista bajular baleia baliza balsa banal bandeira banho banir banquete barato barbado baronesa barraca barulho baseado bastante batata batedor batida batom batucar baunilha beber beijo beirada beisebol beldade beleza belga beliscar bendito bengala benzer berimbau berlinda berro besouro bexiga bezerro bico bicudo bienal bifocal bifurcar bigorna bilhete bimestre bimotor biologia biombo biosfera bipolar birrento biscoito bisneto bispo bissexto bitola bizarro blindado bloco bloquear boato bobagem bocado bocejo bochecha boicotar bolada boletim bolha bolo bombeiro bonde boneco bonita borbulha borda boreal borracha bovino boxeador branco brasa braveza breu briga brilho brincar broa brochura bronzear broto bruxo bucha budismo bufar bule buraco busca busto buzina cabana cabelo cabide cabo cabrito cacau cacetada cachorro cacique cadastro cadeado cafezal caiaque caipira caixote cajado caju calafrio calcular caldeira calibrar calmante calota camada cambista camisa camomila campanha camuflar canavial cancelar caneta canguru canhoto canivete canoa cansado cantar canudo capacho capela capinar capotar capricho captador capuz caracol carbono cardeal careca carimbar carneiro carpete carreira cartaz carvalho casaco casca casebre castelo casulo catarata cativar caule causador cautelar cavalo caverna cebola cedilha cegonha celebrar celular cenoura censo centeio cercar cerrado certeiro cerveja cetim cevada chacota chaleira chamado chapada charme chatice chave chefe chegada cheiro cheque chicote chifre chinelo chocalho chover chumbo chutar chuva cicatriz ciclone cidade cidreira ciente cigana cimento cinto cinza ciranda circuito cirurgia citar clareza clero clicar clone clube coado coagir cobaia cobertor cobrar cocada coelho coentro coeso cogumelo coibir coifa coiote colar coleira colher colidir colmeia colono coluna comando combinar comentar comitiva comover complexo comum concha condor conectar confuso congelar conhecer conjugar consumir contrato convite cooperar copeiro copiador copo coquetel coragem cordial corneta coronha corporal correio cortejo coruja corvo cosseno costela cotonete couro couve covil cozinha cratera cravo creche credor creme crer crespo criada criminal crioulo crise criticar crosta crua cruzeiro cubano cueca cuidado cujo culatra culminar culpar cultura cumprir cunhado cupido curativo curral cursar curto cuspir custear cutelo damasco datar debater debitar deboche debulhar decalque decimal declive decote decretar dedal dedicado deduzir defesa defumar degelo degrau degustar deitado deixar delator delegado delinear delonga demanda demitir demolido dentista depenado depilar depois depressa depurar deriva derramar desafio desbotar descanso desenho desfiado desgaste desigual deslize desmamar desova despesa destaque desviar detalhar detentor detonar detrito deusa dever devido devotado dezena diagrama dialeto didata difuso digitar dilatado diluente diminuir dinastia dinheiro diocese direto discreta disfarce disparo disquete dissipar distante ditador diurno diverso divisor divulgar dizer dobrador dolorido domador dominado donativo donzela dormente dorsal dosagem dourado doutor drenagem drible drogaria duelar duende dueto duplo duquesa durante duvidoso eclodir ecoar ecologia edificar edital educado efeito efetivar ejetar elaborar eleger eleitor elenco elevador eliminar elogiar embargo embolado embrulho embutido emenda emergir emissor empatia empenho empinado empolgar emprego empurrar emulador encaixe encenado enchente encontro endeusar endossar enfaixar enfeite enfim engajado engenho englobar engomado engraxar enguia enjoar enlatar enquanto enraizar enrolado enrugar ensaio enseada ensino ensopado entanto enteado entidade entortar entrada entulho envergar enviado envolver enxame enxerto enxofre enxuto epiderme equipar ereto erguido errata erva ervilha esbanjar esbelto escama escola escrita escuta esfinge esfolar esfregar esfumado esgrima esmalte espanto espelho espiga esponja espreita espumar esquerda estaca esteira esticar estofado estrela estudo esvaziar etanol etiqueta euforia europeu evacuar evaporar evasivo eventual evidente evoluir exagero exalar examinar exato exausto excesso excitar exclamar executar exemplo exibir exigente exonerar expandir expelir expirar explanar exposto expresso expulsar externo extinto extrato fabricar fabuloso faceta facial fada fadiga faixa falar falta familiar fandango fanfarra fantoche fardado farelo farinha farofa farpa fartura fatia fator favorita faxina fazenda fechado feijoada feirante felino feminino fenda feno fera feriado ferrugem ferver festejar fetal feudal fiapo fibrose ficar ficheiro figurado fileira filho filme filtrar firmeza fisgada fissura fita fivela fixador fixo flacidez flamingo flanela flechada flora flutuar fluxo focal focinho fofocar fogo foguete foice folgado folheto forjar formiga forno forte fosco fossa fragata fralda frango frasco fraterno freira frente fretar frieza friso fritura fronha frustrar fruteira fugir fulano fuligem fundar fungo funil furador furioso futebol gabarito gabinete gado gaiato gaiola gaivota galega galho galinha galocha ganhar garagem garfo gargalo garimpo garoupa garrafa gasoduto gasto gata gatilho gaveta gazela gelado geleia gelo gemada gemer gemido generoso gengiva genial genoma genro geologia gerador germinar gesso gestor ginasta gincana gingado girafa girino glacial glicose global glorioso goela goiaba golfe golpear gordura gorjeta gorro gostoso goteira governar gracejo gradual grafite gralha grampo granada gratuito graveto graxa grego grelhar greve grilo grisalho gritaria grosso grotesco grudado grunhido gruta guache guarani guaxinim guerrear guiar guincho guisado gula guloso guru habitar harmonia haste haver hectare herdar heresia hesitar hiato hibernar hidratar hiena hino hipismo hipnose hipoteca hoje holofote homem honesto honrado hormonal hospedar humorado iate ideia idoso ignorado igreja iguana ileso ilha iludido iluminar ilustrar imagem imediato imenso imersivo iminente imitador imortal impacto impedir implante impor imprensa impune imunizar inalador inapto inativo incenso inchar incidir incluir incolor indeciso indireto indutor ineficaz inerente infantil infestar infinito inflamar informal infrator ingerir inibido inicial inimigo injetar inocente inodoro inovador inox inquieto inscrito inseto insistir inspetor instalar insulto intacto integral intimar intocado intriga invasor inverno invicto invocar iogurte iraniano ironizar irreal irritado isca isento isolado isqueiro italiano janeiro jangada janta jararaca jardim jarro jasmim jato javali jazida jejum joaninha joelhada jogador joia jornal jorrar jovem juba judeu judoca juiz julgador julho jurado jurista juro justa labareda laboral lacre lactante ladrilho lagarta lagoa laje lamber lamentar laminar lampejo lanche lapidar lapso laranja lareira largura lasanha lastro lateral latido lavanda lavoura lavrador laxante lazer lealdade lebre legado legendar legista leigo leiloar leitura lembrete leme lenhador lentilha leoa lesma leste letivo letreiro levar leveza levitar liberal libido liderar ligar ligeiro limitar limoeiro limpador linda linear linhagem liquidez listagem lisura litoral livro lixa lixeira locador locutor lojista lombo lona longe lontra lorde lotado loteria loucura lousa louvar luar lucidez lucro luneta lustre lutador luva macaco macete machado macio madeira madrinha magnata magreza maior mais malandro malha malote maluco mamilo mamoeiro mamute manada mancha mandato manequim manhoso manivela manobrar mansa manter manusear mapeado maquinar marcador maresia marfim margem marinho marmita maroto marquise marreco martelo marujo mascote masmorra massagem mastigar matagal materno matinal matutar maxilar medalha medida medusa megafone meiga melancia melhor membro memorial menino menos mensagem mental merecer mergulho mesada mesclar mesmo mesquita mestre metade meteoro metragem mexer mexicano micro migalha migrar milagre milenar milhar mimado minerar minhoca ministro minoria miolo mirante mirtilo misturar mocidade moderno modular moeda moer moinho moita moldura moleza molho molinete molusco montanha moqueca morango morcego mordomo morena mosaico mosquete mostarda motel motim moto motriz muda muito mulata mulher multar mundial munido muralha murcho muscular museu musical nacional nadador naja namoro narina narrado nascer nativa natureza navalha navegar navio neblina nebuloso negativa negociar negrito nervoso neta neural nevasca nevoeiro ninar ninho nitidez nivelar nobreza noite noiva nomear nominal nordeste nortear notar noticiar noturno novelo novilho novo nublado nudez numeral nupcial nutrir nuvem obcecado obedecer objetivo obrigado obscuro obstetra obter obturar ocidente ocioso ocorrer oculista ocupado ofegante ofensiva oferenda oficina ofuscado ogiva olaria oleoso olhar oliveira ombro omelete omisso omitir ondulado oneroso ontem opcional operador oponente oportuno oposto orar orbitar ordem ordinal orfanato orgasmo orgulho oriental origem oriundo orla ortodoxo orvalho oscilar ossada osso ostentar otimismo ousadia outono outubro ouvido ovelha ovular oxidar oxigenar pacato paciente pacote pactuar padaria padrinho pagar pagode painel pairar paisagem palavra palestra palheta palito palmada palpitar pancada panela panfleto panqueca pantanal papagaio papelada papiro parafina parcial pardal parede partida pasmo passado pastel patamar patente patinar patrono paulada pausar peculiar pedalar pedestre pediatra pedra pegada peitoral peixe pele pelicano penca pendurar peneira penhasco pensador pente perceber perfeito pergunta perito permitir perna perplexo persiana pertence peruca pescado pesquisa pessoa petiscar piada picado piedade pigmento pilastra pilhado pilotar pimenta pincel pinguim pinha pinote pintar pioneiro pipoca piquete piranha pires pirueta piscar pistola pitanga pivete planta plaqueta platina plebeu plumagem pluvial pneu poda poeira poetisa polegada policiar poluente polvilho pomar pomba ponderar pontaria populoso porta possuir postal pote poupar pouso povoar praia prancha prato praxe prece predador prefeito premiar prensar preparar presilha pretexto prevenir prezar primata princesa prisma privado processo produto profeta proibido projeto prometer propagar prosa protetor provador publicar pudim pular pulmonar pulseira punhal punir pupilo pureza puxador quadra quantia quarto quase quebrar queda queijo quente querido quimono quina quiosque rabanada rabisco rachar racionar radial raiar rainha raio raiva rajada ralado ramal ranger ranhura rapadura rapel rapidez raposa raquete raridade rasante rascunho rasgar raspador rasteira rasurar ratazana ratoeira realeza reanimar reaver rebaixar rebelde rebolar recado recente recheio recibo recordar recrutar recuar rede redimir redonda reduzida reenvio refinar refletir refogar refresco refugiar regalia regime regra reinado reitor rejeitar relativo remador remendo remorso renovado reparo repelir repleto repolho represa repudiar requerer resenha resfriar resgatar residir resolver respeito ressaca restante resumir retalho reter retirar retomada retratar revelar revisor revolta riacho rica rigidez rigoroso rimar ringue risada risco risonho robalo rochedo rodada rodeio rodovia roedor roleta romano roncar rosado roseira rosto rota roteiro rotina rotular rouco roupa roxo rubro rugido rugoso ruivo rumo rupestre russo sabor saciar sacola sacudir sadio safira saga sagrada saibro salada saleiro salgado saliva salpicar salsicha saltar salvador sambar samurai sanar sanfona sangue sanidade sapato sarda sargento sarjeta saturar saudade saxofone sazonal secar secular seda sedento sediado sedoso sedutor segmento segredo segundo seiva seleto selvagem semanal semente senador senhor sensual sentado separado sereia seringa serra servo setembro setor sigilo silhueta silicone simetria simpatia simular sinal sincero singular sinopse sintonia sirene siri situado soberano sobra socorro sogro soja solda soletrar solteiro sombrio sonata sondar sonegar sonhador sono soprano soquete sorrir sorteio sossego sotaque soterrar sovado sozinho suavizar subida submerso subsolo subtrair sucata sucesso suco sudeste sufixo sugador sugerir sujeito sulfato sumir suor superior suplicar suposto suprimir surdina surfista surpresa surreal surtir suspiro sustento tabela tablete tabuada tacho tagarela talher talo talvez tamanho tamborim tampa tangente tanto tapar tapioca tardio tarefa tarja tarraxa tatuagem taurino taxativo taxista teatral tecer tecido teclado tedioso teia teimar telefone telhado tempero tenente tensor tentar termal terno terreno tese tesoura testado teto textura texugo tiara tigela tijolo timbrar timidez tingido tinteiro tiragem titular toalha tocha tolerar tolice tomada tomilho tonel tontura topete tora torcido torneio torque torrada torto tostar touca toupeira toxina trabalho tracejar tradutor trafegar trajeto trama trancar trapo traseiro tratador travar treino tremer trepidar trevo triagem tribo triciclo tridente trilogia trindade triplo triturar triunfal trocar trombeta trova trunfo truque tubular tucano tudo tulipa tupi turbo turma turquesa tutelar tutorial uivar umbigo unha unidade uniforme urologia urso urtiga urubu usado usina usufruir vacina vadiar vagaroso vaidoso vala valente validade valores vantagem vaqueiro varanda vareta varrer vascular vasilha vassoura vazar vazio veado vedar vegetar veicular veleiro velhice veludo vencedor vendaval venerar ventre verbal verdade vereador vergonha vermelho verniz versar vertente vespa vestido vetorial viaduto viagem viajar viatura vibrador videira vidraria viela viga vigente vigiar vigorar vilarejo vinco vinheta vinil violeta virada virtude visitar visto vitral viveiro vizinho voador voar vogal volante voleibol voltagem volumoso vontade vulto vuvuzela xadrez xarope xeque xeretar xerife xingar zangado zarpar zebu zelador zombar zoologia zumbido".components(separatedBy: " ") } } } From 9529fc85d6ae88264768ea75906c108f2f984380 Mon Sep 17 00:00:00 2001 From: ZhouMiao <6618609@qq.com> Date: Tue, 28 Mar 2023 18:39:43 +0800 Subject: [PATCH 03/62] logic --- Sources/web3swift/KeystoreManager/BIP39+WordLists.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Sources/web3swift/KeystoreManager/BIP39+WordLists.swift b/Sources/web3swift/KeystoreManager/BIP39+WordLists.swift index a67c22c4a..32b6f91aa 100755 --- a/Sources/web3swift/KeystoreManager/BIP39+WordLists.swift +++ b/Sources/web3swift/KeystoreManager/BIP39+WordLists.swift @@ -30,8 +30,8 @@ extension BIP39Language { } var spanishWords: [String] { return "ábaco abdomen abeja abierto abogado abono aborto abrazo abrir abuelo abuso acabar academia acceso acción aceite acelga acento aceptar ácido aclarar acné acoger acoso activo acto actriz actuar acudir acuerdo acusar adicto admitir adoptar adorno aduana adulto aéreo afectar afición afinar afirmar ágil agitar agonía agosto agotar agregar agrio agua agudo águila aguja ahogo ahorro aire aislar ajedrez ajeno ajuste alacrán alambre alarma alba álbum alcalde aldea alegre alejar alerta aleta alfiler alga algodón aliado aliento alivio alma almeja almíbar altar alteza altivo alto altura alumno alzar amable amante amapola amargo amasar ámbar ámbito ameno amigo amistad amor amparo amplio ancho anciano ancla andar andén anemia ángulo anillo ánimo anís anotar antena antiguo antojo anual anular anuncio añadir añejo año apagar aparato apetito apio aplicar apodo aporte apoyo aprender aprobar apuesta apuro arado araña arar árbitro árbol arbusto archivo arco arder ardilla arduo área árido aries armonía arnés aroma arpa arpón arreglo arroz arruga arte artista asa asado asalto ascenso asegurar aseo asesor asiento asilo asistir asno asombro áspero astilla astro astuto asumir asunto atajo ataque atar atento ateo ático atleta átomo atraer atroz atún audaz audio auge aula aumento ausente autor aval avance avaro ave avellana avena avestruz avión aviso ayer ayuda ayuno azafrán azar azote azúcar azufre azul baba babor bache bahía baile bajar balanza balcón balde bambú banco banda baño barba barco barniz barro báscula bastón basura batalla batería batir batuta baúl bazar bebé bebida bello besar beso bestia bicho bien bingo blanco bloque blusa boa bobina bobo boca bocina boda bodega boina bola bolero bolsa bomba bondad bonito bono bonsái borde borrar bosque bote botín bóveda bozal bravo brazo brecha breve brillo brinco brisa broca broma bronce brote bruja brusco bruto buceo bucle bueno buey bufanda bufón búho buitre bulto burbuja burla burro buscar butaca buzón caballo cabeza cabina cabra cacao cadáver cadena caer café caída caimán caja cajón cal calamar calcio caldo calidad calle calma calor calvo cama cambio camello camino campo cáncer candil canela canguro canica canto caña cañón caoba caos capaz capitán capote captar capucha cara carbón cárcel careta carga cariño carne carpeta carro carta casa casco casero caspa castor catorce catre caudal causa cazo cebolla ceder cedro celda célebre celoso célula cemento ceniza centro cerca cerdo cereza cero cerrar certeza césped cetro chacal chaleco champú chancla chapa charla chico chiste chivo choque choza chuleta chupar ciclón ciego cielo cien cierto cifra cigarro cima cinco cine cinta ciprés circo ciruela cisne cita ciudad clamor clan claro clase clave cliente clima clínica cobre cocción cochino cocina coco código codo cofre coger cohete cojín cojo cola colcha colegio colgar colina collar colmo columna combate comer comida cómodo compra conde conejo conga conocer consejo contar copa copia corazón corbata corcho cordón corona correr coser cosmos costa cráneo cráter crear crecer creído crema cría crimen cripta crisis cromo crónica croqueta crudo cruz cuadro cuarto cuatro cubo cubrir cuchara cuello cuento cuerda cuesta cueva cuidar culebra culpa culto cumbre cumplir cuna cuneta cuota cupón cúpula curar curioso curso curva cutis dama danza dar dardo dátil deber débil década decir dedo defensa definir dejar delfín delgado delito demora denso dental deporte derecho derrota desayuno deseo desfile desnudo destino desvío detalle detener deuda día diablo diadema diamante diana diario dibujo dictar diente dieta diez difícil digno dilema diluir dinero directo dirigir disco diseño disfraz diva divino doble doce dolor domingo don donar dorado dormir dorso dos dosis dragón droga ducha duda duelo dueño dulce dúo duque durar dureza duro ébano ebrio echar eco ecuador edad edición edificio editor educar efecto eficaz eje ejemplo elefante elegir elemento elevar elipse élite elixir elogio eludir embudo emitir emoción empate empeño empleo empresa enano encargo enchufe encía enemigo enero enfado enfermo engaño enigma enlace enorme enredo ensayo enseñar entero entrar envase envío época equipo erizo escala escena escolar escribir escudo esencia esfera esfuerzo espada espejo espía esposa espuma esquí estar este estilo estufa etapa eterno ética etnia evadir evaluar evento evitar exacto examen exceso excusa exento exigir exilio existir éxito experto explicar exponer extremo fábrica fábula fachada fácil factor faena faja falda fallo falso faltar fama familia famoso faraón farmacia farol farsa fase fatiga fauna favor fax febrero fecha feliz feo feria feroz fértil fervor festín fiable fianza fiar fibra ficción ficha fideo fiebre fiel fiera fiesta figura fijar fijo fila filete filial filtro fin finca fingir finito firma flaco flauta flecha flor flota fluir flujo flúor fobia foca fogata fogón folio folleto fondo forma forro fortuna forzar fosa foto fracaso frágil franja frase fraude freír freno fresa frío frito fruta fuego fuente fuerza fuga fumar función funda furgón furia fusil fútbol futuro gacela gafas gaita gajo gala galería gallo gamba ganar gancho ganga ganso garaje garza gasolina gastar gato gavilán gemelo gemir gen género genio gente geranio gerente germen gesto gigante gimnasio girar giro glaciar globo gloria gol golfo goloso golpe goma gordo gorila gorra gota goteo gozar grada gráfico grano grasa gratis grave grieta grillo gripe gris grito grosor grúa grueso grumo grupo guante guapo guardia guerra guía guiño guion guiso guitarra gusano gustar haber hábil hablar hacer hacha hada hallar hamaca harina haz hazaña hebilla hebra hecho helado helio hembra herir hermano héroe hervir hielo hierro hígado higiene hijo himno historia hocico hogar hoguera hoja hombre hongo honor honra hora hormiga horno hostil hoyo hueco huelga huerta hueso huevo huida huir humano húmedo humilde humo hundir huracán hurto icono ideal idioma ídolo iglesia iglú igual ilegal ilusión imagen imán imitar impar imperio imponer impulso incapaz índice inerte infiel informe ingenio inicio inmenso inmune innato insecto instante interés íntimo intuir inútil invierno ira iris ironía isla islote jabalí jabón jamón jarabe jardín jarra jaula jazmín jefe jeringa jinete jornada joroba joven joya juerga jueves juez jugador jugo juguete juicio junco jungla junio juntar júpiter jurar justo juvenil juzgar kilo koala labio lacio lacra lado ladrón lagarto lágrima laguna laico lamer lámina lámpara lana lancha langosta lanza lápiz largo larva lástima lata látex latir laurel lavar lazo leal lección leche lector leer legión legumbre lejano lengua lento leña león leopardo lesión letal letra leve leyenda libertad libro licor líder lidiar lienzo liga ligero lima límite limón limpio lince lindo línea lingote lino linterna líquido liso lista litera litio litro llaga llama llanto llave llegar llenar llevar llorar llover lluvia lobo loción loco locura lógica logro lombriz lomo lonja lote lucha lucir lugar lujo luna lunes lupa lustro luto luz maceta macho madera madre maduro maestro mafia magia mago maíz maldad maleta malla malo mamá mambo mamut manco mando manejar manga maniquí manjar mano manso manta mañana mapa máquina mar marco marea marfil margen marido mármol marrón martes marzo masa máscara masivo matar materia matiz matriz máximo mayor mazorca mecha medalla medio médula mejilla mejor melena melón memoria menor mensaje mente menú mercado merengue mérito mes mesón meta meter método metro mezcla miedo miel miembro miga mil milagro militar millón mimo mina minero mínimo minuto miope mirar misa miseria misil mismo mitad mito mochila moción moda modelo moho mojar molde moler molino momento momia monarca moneda monja monto moño morada morder moreno morir morro morsa mortal mosca mostrar motivo mover móvil mozo mucho mudar mueble muela muerte muestra mugre mujer mula muleta multa mundo muñeca mural muro músculo museo musgo música muslo nácar nación nadar naipe naranja nariz narrar nasal natal nativo natural náusea naval nave navidad necio néctar negar negocio negro neón nervio neto neutro nevar nevera nicho nido niebla nieto niñez niño nítido nivel nobleza noche nómina noria norma norte nota noticia novato novela novio nube nuca núcleo nudillo nudo nuera nueve nuez nulo número nutria oasis obeso obispo objeto obra obrero observar obtener obvio oca ocaso océano ochenta ocho ocio ocre octavo octubre oculto ocupar ocurrir odiar odio odisea oeste ofensa oferta oficio ofrecer ogro oído oír ojo ola oleada olfato olivo olla olmo olor olvido ombligo onda onza opaco opción ópera opinar oponer optar óptica opuesto oración orador oral órbita orca orden oreja órgano orgía orgullo oriente origen orilla oro orquesta oruga osadía oscuro osezno oso ostra otoño otro oveja óvulo óxido oxígeno oyente ozono pacto padre paella página pago país pájaro palabra palco paleta pálido palma paloma palpar pan panal pánico pantera pañuelo papá papel papilla paquete parar parcela pared parir paro párpado parque párrafo parte pasar paseo pasión paso pasta pata patio patria pausa pauta pavo payaso peatón pecado pecera pecho pedal pedir pegar peine pelar peldaño pelea peligro pellejo pelo peluca pena pensar peñón peón peor pepino pequeño pera percha perder pereza perfil perico perla permiso perro persona pesa pesca pésimo pestaña pétalo petróleo pez pezuña picar pichón pie piedra pierna pieza pijama pilar piloto pimienta pino pintor pinza piña piojo pipa pirata pisar piscina piso pista pitón pizca placa plan plata playa plaza pleito pleno plomo pluma plural pobre poco poder podio poema poesía poeta polen policía pollo polvo pomada pomelo pomo pompa poner porción portal posada poseer posible poste potencia potro pozo prado precoz pregunta premio prensa preso previo primo príncipe prisión privar proa probar proceso producto proeza profesor programa prole promesa pronto propio próximo prueba público puchero pudor pueblo puerta puesto pulga pulir pulmón pulpo pulso puma punto puñal puño pupa pupila puré quedar queja quemar querer queso quieto química quince quitar rábano rabia rabo ración radical raíz rama rampa rancho rango rapaz rápido rapto rasgo raspa rato rayo raza razón reacción realidad rebaño rebote recaer receta rechazo recoger recreo recto recurso red redondo reducir reflejo reforma refrán refugio regalo regir regla regreso rehén reino reír reja relato relevo relieve relleno reloj remar remedio remo rencor rendir renta reparto repetir reposo reptil res rescate resina respeto resto resumen retiro retorno retrato reunir revés revista rey rezar rico riego rienda riesgo rifa rígido rigor rincón riñón río riqueza risa ritmo rito rizo roble roce rociar rodar rodeo rodilla roer rojizo rojo romero romper ron ronco ronda ropa ropero rosa rosca rostro rotar rubí rubor rudo rueda rugir ruido ruina ruleta rulo rumbo rumor ruptura ruta rutina sábado saber sabio sable sacar sagaz sagrado sala saldo salero salir salmón salón salsa salto salud salvar samba sanción sandía sanear sangre sanidad sano santo sapo saque sardina sartén sastre satán sauna saxofón sección seco secreto secta sed seguir seis sello selva semana semilla senda sensor señal señor separar sepia sequía ser serie sermón servir sesenta sesión seta setenta severo sexo sexto sidra siesta siete siglo signo sílaba silbar silencio silla símbolo simio sirena sistema sitio situar sobre socio sodio sol solapa soldado soledad sólido soltar solución sombra sondeo sonido sonoro sonrisa sopa soplar soporte sordo sorpresa sorteo sostén sótano suave subir suceso sudor suegra suelo sueño suerte sufrir sujeto sultán sumar superar suplir suponer supremo sur surco sureño surgir susto sutil tabaco tabique tabla tabú taco tacto tajo talar talco talento talla talón tamaño tambor tango tanque tapa tapete tapia tapón taquilla tarde tarea tarifa tarjeta tarot tarro tarta tatuaje tauro taza tazón teatro techo tecla técnica tejado tejer tejido tela teléfono tema temor templo tenaz tender tener tenis tenso teoría terapia terco término ternura terror tesis tesoro testigo tetera texto tez tibio tiburón tiempo tienda tierra tieso tigre tijera tilde timbre tímido timo tinta tío típico tipo tira tirón titán títere título tiza toalla tobillo tocar tocino todo toga toldo tomar tono tonto topar tope toque tórax torero tormenta torneo toro torpedo torre torso tortuga tos tosco toser tóxico trabajo tractor traer tráfico trago traje tramo trance trato trauma trazar trébol tregua treinta tren trepar tres tribu trigo tripa triste triunfo trofeo trompa tronco tropa trote trozo truco trueno trufa tubería tubo tuerto tumba tumor túnel túnica turbina turismo turno tutor ubicar úlcera umbral unidad unir universo uno untar uña urbano urbe urgente urna usar usuario útil utopía uva vaca vacío vacuna vagar vago vaina vajilla vale válido valle valor válvula vampiro vara variar varón vaso vecino vector vehículo veinte vejez vela velero veloz vena vencer venda veneno vengar venir venta venus ver verano verbo verde vereda verja verso verter vía viaje vibrar vicio víctima vida vídeo vidrio viejo viernes vigor vil villa vinagre vino viñedo violín viral virgo virtud visor víspera vista vitamina viudo vivaz vivero vivir vivo volcán volumen volver voraz votar voto voz vuelo vulgar yacer yate yegua yema yerno yeso yodo yoga yogur zafiro zanja zapato zarza zona zorro zumo zurdo".components(separatedBy: " ") - var portugueseWords: [String] { - return "abacate abaixo abalar abater abduzir abelha aberto abismo abotoar abranger abreviar abrigar abrupto absinto absoluto absurdo abutre acabado acalmar acampar acanhar acaso aceitar acelerar acenar acervo acessar acetona achatar acidez acima acionado acirrar aclamar aclive acolhida acomodar acoplar acordar acumular acusador adaptar adega adentro adepto adequar aderente adesivo adeus adiante aditivo adjetivo adjunto admirar adorar adquirir adubo adverso advogado aeronave afastar aferir afetivo afinador afivelar aflito afluente afrontar agachar agarrar agasalho agenciar agilizar agiota agitado agora agradar agreste agrupar aguardar agulha ajoelhar ajudar ajustar alameda alarme alastrar alavanca albergue albino alcatra aldeia alecrim alegria alertar alface alfinete algum alheio aliar alicate alienar alinhar aliviar almofada alocar alpiste alterar altitude alucinar alugar aluno alusivo alvo amaciar amador amarelo amassar ambas ambiente ameixa amenizar amido amistoso amizade amolador amontoar amoroso amostra amparar ampliar ampola anagrama analisar anarquia anatomia andaime anel anexo angular animar anjo anomalia anotado ansioso anterior anuidade anunciar anzol apagador apalpar apanhado apego apelido apertada apesar apetite apito aplauso aplicada apoio apontar aposta aprendiz aprovar aquecer arame aranha arara arcada ardente areia arejar arenito aresta argiloso argola arma arquivo arraial arrebate arriscar arroba arrumar arsenal arterial artigo arvoredo asfaltar asilado aspirar assador assinar assoalho assunto astral atacado atadura atalho atarefar atear atender aterro ateu atingir atirador ativo atoleiro atracar atrevido atriz atual atum auditor aumentar aura aurora autismo autoria autuar avaliar avante avaria avental avesso aviador avisar avulso axila azarar azedo azeite azulejo babar babosa bacalhau bacharel bacia bagagem baiano bailar baioneta bairro baixista bajular baleia baliza balsa banal bandeira banho banir banquete barato barbado baronesa barraca barulho baseado bastante batata batedor batida batom batucar baunilha beber beijo beirada beisebol beldade beleza belga beliscar bendito bengala benzer berimbau berlinda berro besouro bexiga bezerro bico bicudo bienal bifocal bifurcar bigorna bilhete bimestre bimotor biologia biombo biosfera bipolar birrento biscoito bisneto bispo bissexto bitola bizarro blindado bloco bloquear boato bobagem bocado bocejo bochecha boicotar bolada boletim bolha bolo bombeiro bonde boneco bonita borbulha borda boreal borracha bovino boxeador branco brasa braveza breu briga brilho brincar broa brochura bronzear broto bruxo bucha budismo bufar bule buraco busca busto buzina cabana cabelo cabide cabo cabrito cacau cacetada cachorro cacique cadastro cadeado cafezal caiaque caipira caixote cajado caju calafrio calcular caldeira calibrar calmante calota camada cambista camisa camomila campanha camuflar canavial cancelar caneta canguru canhoto canivete canoa cansado cantar canudo capacho capela capinar capotar capricho captador capuz caracol carbono cardeal careca carimbar carneiro carpete carreira cartaz carvalho casaco casca casebre castelo casulo catarata cativar caule causador cautelar cavalo caverna cebola cedilha cegonha celebrar celular cenoura censo centeio cercar cerrado certeiro cerveja cetim cevada chacota chaleira chamado chapada charme chatice chave chefe chegada cheiro cheque chicote chifre chinelo chocalho chover chumbo chutar chuva cicatriz ciclone cidade cidreira ciente cigana cimento cinto cinza ciranda circuito cirurgia citar clareza clero clicar clone clube coado coagir cobaia cobertor cobrar cocada coelho coentro coeso cogumelo coibir coifa coiote colar coleira colher colidir colmeia colono coluna comando combinar comentar comitiva comover complexo comum concha condor conectar confuso congelar conhecer conjugar consumir contrato convite cooperar copeiro copiador copo coquetel coragem cordial corneta coronha corporal correio cortejo coruja corvo cosseno costela cotonete couro couve covil cozinha cratera cravo creche credor creme crer crespo criada criminal crioulo crise criticar crosta crua cruzeiro cubano cueca cuidado cujo culatra culminar culpar cultura cumprir cunhado cupido curativo curral cursar curto cuspir custear cutelo damasco datar debater debitar deboche debulhar decalque decimal declive decote decretar dedal dedicado deduzir defesa defumar degelo degrau degustar deitado deixar delator delegado delinear delonga demanda demitir demolido dentista depenado depilar depois depressa depurar deriva derramar desafio desbotar descanso desenho desfiado desgaste desigual deslize desmamar desova despesa destaque desviar detalhar detentor detonar detrito deusa dever devido devotado dezena diagrama dialeto didata difuso digitar dilatado diluente diminuir dinastia dinheiro diocese direto discreta disfarce disparo disquete dissipar distante ditador diurno diverso divisor divulgar dizer dobrador dolorido domador dominado donativo donzela dormente dorsal dosagem dourado doutor drenagem drible drogaria duelar duende dueto duplo duquesa durante duvidoso eclodir ecoar ecologia edificar edital educado efeito efetivar ejetar elaborar eleger eleitor elenco elevador eliminar elogiar embargo embolado embrulho embutido emenda emergir emissor empatia empenho empinado empolgar emprego empurrar emulador encaixe encenado enchente encontro endeusar endossar enfaixar enfeite enfim engajado engenho englobar engomado engraxar enguia enjoar enlatar enquanto enraizar enrolado enrugar ensaio enseada ensino ensopado entanto enteado entidade entortar entrada entulho envergar enviado envolver enxame enxerto enxofre enxuto epiderme equipar ereto erguido errata erva ervilha esbanjar esbelto escama escola escrita escuta esfinge esfolar esfregar esfumado esgrima esmalte espanto espelho espiga esponja espreita espumar esquerda estaca esteira esticar estofado estrela estudo esvaziar etanol etiqueta euforia europeu evacuar evaporar evasivo eventual evidente evoluir exagero exalar examinar exato exausto excesso excitar exclamar executar exemplo exibir exigente exonerar expandir expelir expirar explanar exposto expresso expulsar externo extinto extrato fabricar fabuloso faceta facial fada fadiga faixa falar falta familiar fandango fanfarra fantoche fardado farelo farinha farofa farpa fartura fatia fator favorita faxina fazenda fechado feijoada feirante felino feminino fenda feno fera feriado ferrugem ferver festejar fetal feudal fiapo fibrose ficar ficheiro figurado fileira filho filme filtrar firmeza fisgada fissura fita fivela fixador fixo flacidez flamingo flanela flechada flora flutuar fluxo focal focinho fofocar fogo foguete foice folgado folheto forjar formiga forno forte fosco fossa fragata fralda frango frasco fraterno freira frente fretar frieza friso fritura fronha frustrar fruteira fugir fulano fuligem fundar fungo funil furador furioso futebol gabarito gabinete gado gaiato gaiola gaivota galega galho galinha galocha ganhar garagem garfo gargalo garimpo garoupa garrafa gasoduto gasto gata gatilho gaveta gazela gelado geleia gelo gemada gemer gemido generoso gengiva genial genoma genro geologia gerador germinar gesso gestor ginasta gincana gingado girafa girino glacial glicose global glorioso goela goiaba golfe golpear gordura gorjeta gorro gostoso goteira governar gracejo gradual grafite gralha grampo granada gratuito graveto graxa grego grelhar greve grilo grisalho gritaria grosso grotesco grudado grunhido gruta guache guarani guaxinim guerrear guiar guincho guisado gula guloso guru habitar harmonia haste haver hectare herdar heresia hesitar hiato hibernar hidratar hiena hino hipismo hipnose hipoteca hoje holofote homem honesto honrado hormonal hospedar humorado iate ideia idoso ignorado igreja iguana ileso ilha iludido iluminar ilustrar imagem imediato imenso imersivo iminente imitador imortal impacto impedir implante impor imprensa impune imunizar inalador inapto inativo incenso inchar incidir incluir incolor indeciso indireto indutor ineficaz inerente infantil infestar infinito inflamar informal infrator ingerir inibido inicial inimigo injetar inocente inodoro inovador inox inquieto inscrito inseto insistir inspetor instalar insulto intacto integral intimar intocado intriga invasor inverno invicto invocar iogurte iraniano ironizar irreal irritado isca isento isolado isqueiro italiano janeiro jangada janta jararaca jardim jarro jasmim jato javali jazida jejum joaninha joelhada jogador joia jornal jorrar jovem juba judeu judoca juiz julgador julho jurado jurista juro justa labareda laboral lacre lactante ladrilho lagarta lagoa laje lamber lamentar laminar lampejo lanche lapidar lapso laranja lareira largura lasanha lastro lateral latido lavanda lavoura lavrador laxante lazer lealdade lebre legado legendar legista leigo leiloar leitura lembrete leme lenhador lentilha leoa lesma leste letivo letreiro levar leveza levitar liberal libido liderar ligar ligeiro limitar limoeiro limpador linda linear linhagem liquidez listagem lisura litoral livro lixa lixeira locador locutor lojista lombo lona longe lontra lorde lotado loteria loucura lousa louvar luar lucidez lucro luneta lustre lutador luva macaco macete machado macio madeira madrinha magnata magreza maior mais malandro malha malote maluco mamilo mamoeiro mamute manada mancha mandato manequim manhoso manivela manobrar mansa manter manusear mapeado maquinar marcador maresia marfim margem marinho marmita maroto marquise marreco martelo marujo mascote masmorra massagem mastigar matagal materno matinal matutar maxilar medalha medida medusa megafone meiga melancia melhor membro memorial menino menos mensagem mental merecer mergulho mesada mesclar mesmo mesquita mestre metade meteoro metragem mexer mexicano micro migalha migrar milagre milenar milhar mimado minerar minhoca ministro minoria miolo mirante mirtilo misturar mocidade moderno modular moeda moer moinho moita moldura moleza molho molinete molusco montanha moqueca morango morcego mordomo morena mosaico mosquete mostarda motel motim moto motriz muda muito mulata mulher multar mundial munido muralha murcho muscular museu musical nacional nadador naja namoro narina narrado nascer nativa natureza navalha navegar navio neblina nebuloso negativa negociar negrito nervoso neta neural nevasca nevoeiro ninar ninho nitidez nivelar nobreza noite noiva nomear nominal nordeste nortear notar noticiar noturno novelo novilho novo nublado nudez numeral nupcial nutrir nuvem obcecado obedecer objetivo obrigado obscuro obstetra obter obturar ocidente ocioso ocorrer oculista ocupado ofegante ofensiva oferenda oficina ofuscado ogiva olaria oleoso olhar oliveira ombro omelete omisso omitir ondulado oneroso ontem opcional operador oponente oportuno oposto orar orbitar ordem ordinal orfanato orgasmo orgulho oriental origem oriundo orla ortodoxo orvalho oscilar ossada osso ostentar otimismo ousadia outono outubro ouvido ovelha ovular oxidar oxigenar pacato paciente pacote pactuar padaria padrinho pagar pagode painel pairar paisagem palavra palestra palheta palito palmada palpitar pancada panela panfleto panqueca pantanal papagaio papelada papiro parafina parcial pardal parede partida pasmo passado pastel patamar patente patinar patrono paulada pausar peculiar pedalar pedestre pediatra pedra pegada peitoral peixe pele pelicano penca pendurar peneira penhasco pensador pente perceber perfeito pergunta perito permitir perna perplexo persiana pertence peruca pescado pesquisa pessoa petiscar piada picado piedade pigmento pilastra pilhado pilotar pimenta pincel pinguim pinha pinote pintar pioneiro pipoca piquete piranha pires pirueta piscar pistola pitanga pivete planta plaqueta platina plebeu plumagem pluvial pneu poda poeira poetisa polegada policiar poluente polvilho pomar pomba ponderar pontaria populoso porta possuir postal pote poupar pouso povoar praia prancha prato praxe prece predador prefeito premiar prensar preparar presilha pretexto prevenir prezar primata princesa prisma privado processo produto profeta proibido projeto prometer propagar prosa protetor provador publicar pudim pular pulmonar pulseira punhal punir pupilo pureza puxador quadra quantia quarto quase quebrar queda queijo quente querido quimono quina quiosque rabanada rabisco rachar racionar radial raiar rainha raio raiva rajada ralado ramal ranger ranhura rapadura rapel rapidez raposa raquete raridade rasante rascunho rasgar raspador rasteira rasurar ratazana ratoeira realeza reanimar reaver rebaixar rebelde rebolar recado recente recheio recibo recordar recrutar recuar rede redimir redonda reduzida reenvio refinar refletir refogar refresco refugiar regalia regime regra reinado reitor rejeitar relativo remador remendo remorso renovado reparo repelir repleto repolho represa repudiar requerer resenha resfriar resgatar residir resolver respeito ressaca restante resumir retalho reter retirar retomada retratar revelar revisor revolta riacho rica rigidez rigoroso rimar ringue risada risco risonho robalo rochedo rodada rodeio rodovia roedor roleta romano roncar rosado roseira rosto rota roteiro rotina rotular rouco roupa roxo rubro rugido rugoso ruivo rumo rupestre russo sabor saciar sacola sacudir sadio safira saga sagrada saibro salada saleiro salgado saliva salpicar salsicha saltar salvador sambar samurai sanar sanfona sangue sanidade sapato sarda sargento sarjeta saturar saudade saxofone sazonal secar secular seda sedento sediado sedoso sedutor segmento segredo segundo seiva seleto selvagem semanal semente senador senhor sensual sentado separado sereia seringa serra servo setembro setor sigilo silhueta silicone simetria simpatia simular sinal sincero singular sinopse sintonia sirene siri situado soberano sobra socorro sogro soja solda soletrar solteiro sombrio sonata sondar sonegar sonhador sono soprano soquete sorrir sorteio sossego sotaque soterrar sovado sozinho suavizar subida submerso subsolo subtrair sucata sucesso suco sudeste sufixo sugador sugerir sujeito sulfato sumir suor superior suplicar suposto suprimir surdina surfista surpresa surreal surtir suspiro sustento tabela tablete tabuada tacho tagarela talher talo talvez tamanho tamborim tampa tangente tanto tapar tapioca tardio tarefa tarja tarraxa tatuagem taurino taxativo taxista teatral tecer tecido teclado tedioso teia teimar telefone telhado tempero tenente tensor tentar termal terno terreno tese tesoura testado teto textura texugo tiara tigela tijolo timbrar timidez tingido tinteiro tiragem titular toalha tocha tolerar tolice tomada tomilho tonel tontura topete tora torcido torneio torque torrada torto tostar touca toupeira toxina trabalho tracejar tradutor trafegar trajeto trama trancar trapo traseiro tratador travar treino tremer trepidar trevo triagem tribo triciclo tridente trilogia trindade triplo triturar triunfal trocar trombeta trova trunfo truque tubular tucano tudo tulipa tupi turbo turma turquesa tutelar tutorial uivar umbigo unha unidade uniforme urologia urso urtiga urubu usado usina usufruir vacina vadiar vagaroso vaidoso vala valente validade valores vantagem vaqueiro varanda vareta varrer vascular vasilha vassoura vazar vazio veado vedar vegetar veicular veleiro velhice veludo vencedor vendaval venerar ventre verbal verdade vereador vergonha vermelho verniz versar vertente vespa vestido vetorial viaduto viagem viajar viatura vibrador videira vidraria viela viga vigente vigiar vigorar vilarejo vinco vinheta vinil violeta virada virtude visitar visto vitral viveiro vizinho voador voar vogal volante voleibol voltagem volumoso vontade vulto vuvuzela xadrez xarope xeque xeretar xerife xingar zangado zarpar zebu zelador zombar zoologia zumbido".components(separatedBy: " ") - } + } + var portugueseWords: [String] { + return "abacate abaixo abalar abater abduzir abelha aberto abismo abotoar abranger abreviar abrigar abrupto absinto absoluto absurdo abutre acabado acalmar acampar acanhar acaso aceitar acelerar acenar acervo acessar acetona achatar acidez acima acionado acirrar aclamar aclive acolhida acomodar acoplar acordar acumular acusador adaptar adega adentro adepto adequar aderente adesivo adeus adiante aditivo adjetivo adjunto admirar adorar adquirir adubo adverso advogado aeronave afastar aferir afetivo afinador afivelar aflito afluente afrontar agachar agarrar agasalho agenciar agilizar agiota agitado agora agradar agreste agrupar aguardar agulha ajoelhar ajudar ajustar alameda alarme alastrar alavanca albergue albino alcatra aldeia alecrim alegria alertar alface alfinete algum alheio aliar alicate alienar alinhar aliviar almofada alocar alpiste alterar altitude alucinar alugar aluno alusivo alvo amaciar amador amarelo amassar ambas ambiente ameixa amenizar amido amistoso amizade amolador amontoar amoroso amostra amparar ampliar ampola anagrama analisar anarquia anatomia andaime anel anexo angular animar anjo anomalia anotado ansioso anterior anuidade anunciar anzol apagador apalpar apanhado apego apelido apertada apesar apetite apito aplauso aplicada apoio apontar aposta aprendiz aprovar aquecer arame aranha arara arcada ardente areia arejar arenito aresta argiloso argola arma arquivo arraial arrebate arriscar arroba arrumar arsenal arterial artigo arvoredo asfaltar asilado aspirar assador assinar assoalho assunto astral atacado atadura atalho atarefar atear atender aterro ateu atingir atirador ativo atoleiro atracar atrevido atriz atual atum auditor aumentar aura aurora autismo autoria autuar avaliar avante avaria avental avesso aviador avisar avulso axila azarar azedo azeite azulejo babar babosa bacalhau bacharel bacia bagagem baiano bailar baioneta bairro baixista bajular baleia baliza balsa banal bandeira banho banir banquete barato barbado baronesa barraca barulho baseado bastante batata batedor batida batom batucar baunilha beber beijo beirada beisebol beldade beleza belga beliscar bendito bengala benzer berimbau berlinda berro besouro bexiga bezerro bico bicudo bienal bifocal bifurcar bigorna bilhete bimestre bimotor biologia biombo biosfera bipolar birrento biscoito bisneto bispo bissexto bitola bizarro blindado bloco bloquear boato bobagem bocado bocejo bochecha boicotar bolada boletim bolha bolo bombeiro bonde boneco bonita borbulha borda boreal borracha bovino boxeador branco brasa braveza breu briga brilho brincar broa brochura bronzear broto bruxo bucha budismo bufar bule buraco busca busto buzina cabana cabelo cabide cabo cabrito cacau cacetada cachorro cacique cadastro cadeado cafezal caiaque caipira caixote cajado caju calafrio calcular caldeira calibrar calmante calota camada cambista camisa camomila campanha camuflar canavial cancelar caneta canguru canhoto canivete canoa cansado cantar canudo capacho capela capinar capotar capricho captador capuz caracol carbono cardeal careca carimbar carneiro carpete carreira cartaz carvalho casaco casca casebre castelo casulo catarata cativar caule causador cautelar cavalo caverna cebola cedilha cegonha celebrar celular cenoura censo centeio cercar cerrado certeiro cerveja cetim cevada chacota chaleira chamado chapada charme chatice chave chefe chegada cheiro cheque chicote chifre chinelo chocalho chover chumbo chutar chuva cicatriz ciclone cidade cidreira ciente cigana cimento cinto cinza ciranda circuito cirurgia citar clareza clero clicar clone clube coado coagir cobaia cobertor cobrar cocada coelho coentro coeso cogumelo coibir coifa coiote colar coleira colher colidir colmeia colono coluna comando combinar comentar comitiva comover complexo comum concha condor conectar confuso congelar conhecer conjugar consumir contrato convite cooperar copeiro copiador copo coquetel coragem cordial corneta coronha corporal correio cortejo coruja corvo cosseno costela cotonete couro couve covil cozinha cratera cravo creche credor creme crer crespo criada criminal crioulo crise criticar crosta crua cruzeiro cubano cueca cuidado cujo culatra culminar culpar cultura cumprir cunhado cupido curativo curral cursar curto cuspir custear cutelo damasco datar debater debitar deboche debulhar decalque decimal declive decote decretar dedal dedicado deduzir defesa defumar degelo degrau degustar deitado deixar delator delegado delinear delonga demanda demitir demolido dentista depenado depilar depois depressa depurar deriva derramar desafio desbotar descanso desenho desfiado desgaste desigual deslize desmamar desova despesa destaque desviar detalhar detentor detonar detrito deusa dever devido devotado dezena diagrama dialeto didata difuso digitar dilatado diluente diminuir dinastia dinheiro diocese direto discreta disfarce disparo disquete dissipar distante ditador diurno diverso divisor divulgar dizer dobrador dolorido domador dominado donativo donzela dormente dorsal dosagem dourado doutor drenagem drible drogaria duelar duende dueto duplo duquesa durante duvidoso eclodir ecoar ecologia edificar edital educado efeito efetivar ejetar elaborar eleger eleitor elenco elevador eliminar elogiar embargo embolado embrulho embutido emenda emergir emissor empatia empenho empinado empolgar emprego empurrar emulador encaixe encenado enchente encontro endeusar endossar enfaixar enfeite enfim engajado engenho englobar engomado engraxar enguia enjoar enlatar enquanto enraizar enrolado enrugar ensaio enseada ensino ensopado entanto enteado entidade entortar entrada entulho envergar enviado envolver enxame enxerto enxofre enxuto epiderme equipar ereto erguido errata erva ervilha esbanjar esbelto escama escola escrita escuta esfinge esfolar esfregar esfumado esgrima esmalte espanto espelho espiga esponja espreita espumar esquerda estaca esteira esticar estofado estrela estudo esvaziar etanol etiqueta euforia europeu evacuar evaporar evasivo eventual evidente evoluir exagero exalar examinar exato exausto excesso excitar exclamar executar exemplo exibir exigente exonerar expandir expelir expirar explanar exposto expresso expulsar externo extinto extrato fabricar fabuloso faceta facial fada fadiga faixa falar falta familiar fandango fanfarra fantoche fardado farelo farinha farofa farpa fartura fatia fator favorita faxina fazenda fechado feijoada feirante felino feminino fenda feno fera feriado ferrugem ferver festejar fetal feudal fiapo fibrose ficar ficheiro figurado fileira filho filme filtrar firmeza fisgada fissura fita fivela fixador fixo flacidez flamingo flanela flechada flora flutuar fluxo focal focinho fofocar fogo foguete foice folgado folheto forjar formiga forno forte fosco fossa fragata fralda frango frasco fraterno freira frente fretar frieza friso fritura fronha frustrar fruteira fugir fulano fuligem fundar fungo funil furador furioso futebol gabarito gabinete gado gaiato gaiola gaivota galega galho galinha galocha ganhar garagem garfo gargalo garimpo garoupa garrafa gasoduto gasto gata gatilho gaveta gazela gelado geleia gelo gemada gemer gemido generoso gengiva genial genoma genro geologia gerador germinar gesso gestor ginasta gincana gingado girafa girino glacial glicose global glorioso goela goiaba golfe golpear gordura gorjeta gorro gostoso goteira governar gracejo gradual grafite gralha grampo granada gratuito graveto graxa grego grelhar greve grilo grisalho gritaria grosso grotesco grudado grunhido gruta guache guarani guaxinim guerrear guiar guincho guisado gula guloso guru habitar harmonia haste haver hectare herdar heresia hesitar hiato hibernar hidratar hiena hino hipismo hipnose hipoteca hoje holofote homem honesto honrado hormonal hospedar humorado iate ideia idoso ignorado igreja iguana ileso ilha iludido iluminar ilustrar imagem imediato imenso imersivo iminente imitador imortal impacto impedir implante impor imprensa impune imunizar inalador inapto inativo incenso inchar incidir incluir incolor indeciso indireto indutor ineficaz inerente infantil infestar infinito inflamar informal infrator ingerir inibido inicial inimigo injetar inocente inodoro inovador inox inquieto inscrito inseto insistir inspetor instalar insulto intacto integral intimar intocado intriga invasor inverno invicto invocar iogurte iraniano ironizar irreal irritado isca isento isolado isqueiro italiano janeiro jangada janta jararaca jardim jarro jasmim jato javali jazida jejum joaninha joelhada jogador joia jornal jorrar jovem juba judeu judoca juiz julgador julho jurado jurista juro justa labareda laboral lacre lactante ladrilho lagarta lagoa laje lamber lamentar laminar lampejo lanche lapidar lapso laranja lareira largura lasanha lastro lateral latido lavanda lavoura lavrador laxante lazer lealdade lebre legado legendar legista leigo leiloar leitura lembrete leme lenhador lentilha leoa lesma leste letivo letreiro levar leveza levitar liberal libido liderar ligar ligeiro limitar limoeiro limpador linda linear linhagem liquidez listagem lisura litoral livro lixa lixeira locador locutor lojista lombo lona longe lontra lorde lotado loteria loucura lousa louvar luar lucidez lucro luneta lustre lutador luva macaco macete machado macio madeira madrinha magnata magreza maior mais malandro malha malote maluco mamilo mamoeiro mamute manada mancha mandato manequim manhoso manivela manobrar mansa manter manusear mapeado maquinar marcador maresia marfim margem marinho marmita maroto marquise marreco martelo marujo mascote masmorra massagem mastigar matagal materno matinal matutar maxilar medalha medida medusa megafone meiga melancia melhor membro memorial menino menos mensagem mental merecer mergulho mesada mesclar mesmo mesquita mestre metade meteoro metragem mexer mexicano micro migalha migrar milagre milenar milhar mimado minerar minhoca ministro minoria miolo mirante mirtilo misturar mocidade moderno modular moeda moer moinho moita moldura moleza molho molinete molusco montanha moqueca morango morcego mordomo morena mosaico mosquete mostarda motel motim moto motriz muda muito mulata mulher multar mundial munido muralha murcho muscular museu musical nacional nadador naja namoro narina narrado nascer nativa natureza navalha navegar navio neblina nebuloso negativa negociar negrito nervoso neta neural nevasca nevoeiro ninar ninho nitidez nivelar nobreza noite noiva nomear nominal nordeste nortear notar noticiar noturno novelo novilho novo nublado nudez numeral nupcial nutrir nuvem obcecado obedecer objetivo obrigado obscuro obstetra obter obturar ocidente ocioso ocorrer oculista ocupado ofegante ofensiva oferenda oficina ofuscado ogiva olaria oleoso olhar oliveira ombro omelete omisso omitir ondulado oneroso ontem opcional operador oponente oportuno oposto orar orbitar ordem ordinal orfanato orgasmo orgulho oriental origem oriundo orla ortodoxo orvalho oscilar ossada osso ostentar otimismo ousadia outono outubro ouvido ovelha ovular oxidar oxigenar pacato paciente pacote pactuar padaria padrinho pagar pagode painel pairar paisagem palavra palestra palheta palito palmada palpitar pancada panela panfleto panqueca pantanal papagaio papelada papiro parafina parcial pardal parede partida pasmo passado pastel patamar patente patinar patrono paulada pausar peculiar pedalar pedestre pediatra pedra pegada peitoral peixe pele pelicano penca pendurar peneira penhasco pensador pente perceber perfeito pergunta perito permitir perna perplexo persiana pertence peruca pescado pesquisa pessoa petiscar piada picado piedade pigmento pilastra pilhado pilotar pimenta pincel pinguim pinha pinote pintar pioneiro pipoca piquete piranha pires pirueta piscar pistola pitanga pivete planta plaqueta platina plebeu plumagem pluvial pneu poda poeira poetisa polegada policiar poluente polvilho pomar pomba ponderar pontaria populoso porta possuir postal pote poupar pouso povoar praia prancha prato praxe prece predador prefeito premiar prensar preparar presilha pretexto prevenir prezar primata princesa prisma privado processo produto profeta proibido projeto prometer propagar prosa protetor provador publicar pudim pular pulmonar pulseira punhal punir pupilo pureza puxador quadra quantia quarto quase quebrar queda queijo quente querido quimono quina quiosque rabanada rabisco rachar racionar radial raiar rainha raio raiva rajada ralado ramal ranger ranhura rapadura rapel rapidez raposa raquete raridade rasante rascunho rasgar raspador rasteira rasurar ratazana ratoeira realeza reanimar reaver rebaixar rebelde rebolar recado recente recheio recibo recordar recrutar recuar rede redimir redonda reduzida reenvio refinar refletir refogar refresco refugiar regalia regime regra reinado reitor rejeitar relativo remador remendo remorso renovado reparo repelir repleto repolho represa repudiar requerer resenha resfriar resgatar residir resolver respeito ressaca restante resumir retalho reter retirar retomada retratar revelar revisor revolta riacho rica rigidez rigoroso rimar ringue risada risco risonho robalo rochedo rodada rodeio rodovia roedor roleta romano roncar rosado roseira rosto rota roteiro rotina rotular rouco roupa roxo rubro rugido rugoso ruivo rumo rupestre russo sabor saciar sacola sacudir sadio safira saga sagrada saibro salada saleiro salgado saliva salpicar salsicha saltar salvador sambar samurai sanar sanfona sangue sanidade sapato sarda sargento sarjeta saturar saudade saxofone sazonal secar secular seda sedento sediado sedoso sedutor segmento segredo segundo seiva seleto selvagem semanal semente senador senhor sensual sentado separado sereia seringa serra servo setembro setor sigilo silhueta silicone simetria simpatia simular sinal sincero singular sinopse sintonia sirene siri situado soberano sobra socorro sogro soja solda soletrar solteiro sombrio sonata sondar sonegar sonhador sono soprano soquete sorrir sorteio sossego sotaque soterrar sovado sozinho suavizar subida submerso subsolo subtrair sucata sucesso suco sudeste sufixo sugador sugerir sujeito sulfato sumir suor superior suplicar suposto suprimir surdina surfista surpresa surreal surtir suspiro sustento tabela tablete tabuada tacho tagarela talher talo talvez tamanho tamborim tampa tangente tanto tapar tapioca tardio tarefa tarja tarraxa tatuagem taurino taxativo taxista teatral tecer tecido teclado tedioso teia teimar telefone telhado tempero tenente tensor tentar termal terno terreno tese tesoura testado teto textura texugo tiara tigela tijolo timbrar timidez tingido tinteiro tiragem titular toalha tocha tolerar tolice tomada tomilho tonel tontura topete tora torcido torneio torque torrada torto tostar touca toupeira toxina trabalho tracejar tradutor trafegar trajeto trama trancar trapo traseiro tratador travar treino tremer trepidar trevo triagem tribo triciclo tridente trilogia trindade triplo triturar triunfal trocar trombeta trova trunfo truque tubular tucano tudo tulipa tupi turbo turma turquesa tutelar tutorial uivar umbigo unha unidade uniforme urologia urso urtiga urubu usado usina usufruir vacina vadiar vagaroso vaidoso vala valente validade valores vantagem vaqueiro varanda vareta varrer vascular vasilha vassoura vazar vazio veado vedar vegetar veicular veleiro velhice veludo vencedor vendaval venerar ventre verbal verdade vereador vergonha vermelho verniz versar vertente vespa vestido vetorial viaduto viagem viajar viatura vibrador videira vidraria viela viga vigente vigiar vigorar vilarejo vinco vinheta vinil violeta virada virtude visitar visto vitral viveiro vizinho voador voar vogal volante voleibol voltagem volumoso vontade vulto vuvuzela xadrez xarope xeque xeretar xerife xingar zangado zarpar zebu zelador zombar zoologia zumbido".components(separatedBy: " ") } } From d880c7c36063e9eda4f5011c7afa976b29ade9d4 Mon Sep 17 00:00:00 2001 From: ZhouMiao <6618609@qq.com> Date: Fri, 31 Mar 2023 18:23:30 +0800 Subject: [PATCH 04/62] update podspec --- web3swift.podspec | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/web3swift.podspec b/web3swift.podspec index 3bf3d4c90..090007e2a 100755 --- a/web3swift.podspec +++ b/web3swift.podspec @@ -13,9 +13,9 @@ Pod::Spec.new do |spec| spec.resource_bundle = { "Browser" => "Sources/web3swift/Browser/*.js" } spec.swift_version = '5.0' spec.frameworks = 'CoreImage' - spec.dependency 'BigInt', '5.2.0' - spec.dependency 'Starscream', '4.0.4' - spec.dependency 'CryptoSwift', '1.5.1' - spec.dependency 'secp256k1.c', '0.1.2' - spec.dependency 'PromiseKit', '6.15.3' + spec.dependency 'BigInt' + spec.dependency 'Starscream' + spec.dependency 'CryptoSwift' + spec.dependency 'secp256k1.c' + spec.dependency 'PromiseKit' end From bffba8147e404d8ecd7712aa1ade15866fcd2c20 Mon Sep 17 00:00:00 2001 From: ZhouMiao <6618609@qq.com> Date: Fri, 31 Mar 2023 18:30:59 +0800 Subject: [PATCH 05/62] CryptoSwift 1.6.0 --- web3swift.podspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web3swift.podspec b/web3swift.podspec index 090007e2a..1873113e6 100755 --- a/web3swift.podspec +++ b/web3swift.podspec @@ -15,7 +15,7 @@ Pod::Spec.new do |spec| spec.frameworks = 'CoreImage' spec.dependency 'BigInt' spec.dependency 'Starscream' - spec.dependency 'CryptoSwift' + spec.dependency 'CryptoSwift', '1.6.0' spec.dependency 'secp256k1.c' spec.dependency 'PromiseKit' end From 1fc6cd67563f58b37694b4453556c5be0feb8938 Mon Sep 17 00:00:00 2001 From: august Date: Tue, 18 Apr 2023 11:38:12 +0800 Subject: [PATCH 06/62] fix: init Web3HttpProvider without chain id --- .../EthereumNetwork/Request/APIRequest+ComputedProperties.swift | 2 +- .../EthereumNetwork/Request/APIRequest+UtilityTypes.swift | 2 +- Sources/web3swift/Web3/Web3+HttpProvider.swift | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Sources/Web3Core/EthereumNetwork/Request/APIRequest+ComputedProperties.swift b/Sources/Web3Core/EthereumNetwork/Request/APIRequest+ComputedProperties.swift index cfa7f5190..986e8332d 100644 --- a/Sources/Web3Core/EthereumNetwork/Request/APIRequest+ComputedProperties.swift +++ b/Sources/Web3Core/EthereumNetwork/Request/APIRequest+ComputedProperties.swift @@ -8,7 +8,7 @@ import Foundation extension APIRequest { - var method: REST { + public var method: REST { .POST } diff --git a/Sources/Web3Core/EthereumNetwork/Request/APIRequest+UtilityTypes.swift b/Sources/Web3Core/EthereumNetwork/Request/APIRequest+UtilityTypes.swift index ca55d622f..ba8a362f6 100644 --- a/Sources/Web3Core/EthereumNetwork/Request/APIRequest+UtilityTypes.swift +++ b/Sources/Web3Core/EthereumNetwork/Request/APIRequest+UtilityTypes.swift @@ -14,7 +14,7 @@ public struct APIResponse: Decodable where Result: APIResultType { public var result: Result } -enum REST: String { +public enum REST: String { case POST case GET } diff --git a/Sources/web3swift/Web3/Web3+HttpProvider.swift b/Sources/web3swift/Web3/Web3+HttpProvider.swift index a6411552d..da37f1df9 100755 --- a/Sources/web3swift/Web3/Web3+HttpProvider.swift +++ b/Sources/web3swift/Web3/Web3+HttpProvider.swift @@ -36,7 +36,7 @@ public class Web3HttpProvider: Web3Provider { var urlRequest = URLRequest(url: url, cachePolicy: .reloadIgnoringCacheData) urlRequest.setValue("application/json", forHTTPHeaderField: "Content-Type") urlRequest.setValue("application/json", forHTTPHeaderField: "Accept") - urlRequest.httpMethod = APIRequest.getNetwork.call + urlRequest.httpMethod = APIRequest.getNetwork.method.rawValue urlRequest.httpBody = APIRequest.getNetwork.encodedBody let response: APIResponse = try await APIRequest.send(uRLRequest: urlRequest, with: session) self.network = Networks.fromInt(response.result) From 77193792d67e39c8f9111e5dee686125b10e4229 Mon Sep 17 00:00:00 2001 From: august Date: Thu, 24 Aug 2023 17:15:31 +0800 Subject: [PATCH 07/62] feat: enable encode Event and IETH.getLogs --- .../Web3Core/Contract/ContractProtocol.swift | 7 ++ .../Web3Core/EthereumABI/ABIElements.swift | 56 ++++++++++- .../Transaction/EventfilterParameters.swift | 15 ++- .../Ethereum/IEth+Defaults.swift | 6 ++ .../EthereumAPICalls/Ethereum/IEth.swift | 2 + .../localTests/EventTests.swift | 97 +++++++++++++++++++ .../remoteTests/EventFilterTests.swift | 30 ++++++ 7 files changed, 210 insertions(+), 3 deletions(-) create mode 100644 Tests/web3swiftTests/localTests/EventTests.swift create mode 100644 Tests/web3swiftTests/remoteTests/EventFilterTests.swift diff --git a/Sources/Web3Core/Contract/ContractProtocol.swift b/Sources/Web3Core/Contract/ContractProtocol.swift index 43a4c1543..b82d8aa8c 100755 --- a/Sources/Web3Core/Contract/ContractProtocol.swift +++ b/Sources/Web3Core/Contract/ContractProtocol.swift @@ -280,6 +280,13 @@ extension DefaultContractProtocol { return encodedData } + public func event(_ event: String, parameters: [Any]) -> [EventFilterParameters.Topic?] { + guard let event = events[event] else { + return [] + } + return event.encodeParameters(parameters) + } + public func parseEvent(_ eventLog: EventLog) -> (eventName: String?, eventData: [String: Any]?) { for (eName, ev) in self.events { if !ev.anonymous { diff --git a/Sources/Web3Core/EthereumABI/ABIElements.swift b/Sources/Web3Core/EthereumABI/ABIElements.swift index 5dca0b331..aac04199a 100755 --- a/Sources/Web3Core/EthereumABI/ABIElements.swift +++ b/Sources/Web3Core/EthereumABI/ABIElements.swift @@ -211,13 +211,67 @@ extension ABI.Element.Function { } } -// MARK: - Event logs decoding +// MARK: - Event logs decoding & encoding extension ABI.Element.Event { public func decodeReturnedLogs(eventLogTopics: [Data], eventLogData: Data) -> [String: Any]? { guard let eventContent = ABIDecoder.decodeLog(event: self, eventLogTopics: eventLogTopics, eventLogData: eventLogData) else { return nil } return eventContent } + + func encodeTopic(input: ABI.Element.Event.Input, value: Any) -> EventFilterParameters.Topic? { + if case .string = input.type { + guard let string = value as? String else { + return nil + } + return .string(string.sha3(.keccak256).addHexPrefix()) + } else if case .dynamicBytes = input.type { + guard let data = ABIEncoder.convertToData(value) else { + return nil + } + return .string(data.sha3(.keccak256).toHexString().addHexPrefix()) + } else if case .address = input.type { + guard let encoded = ABIEncoder.encode(types: [input.type], values: [value]) else { + return nil + } + return .string(encoded.toHexString().addHexPrefix()) + } + guard let data = ABIEncoder.convertToData(value)!.setLengthLeft(32) else { + return nil + } + return .string(data.toHexString().addHexPrefix()) + } + + public func encodeParameters(_ parameters: [Any?]) -> [EventFilterParameters.Topic?] { + guard parameters.count <= inputs.count else { + // too many arguments for fragment + return [] + } + var topics: [EventFilterParameters.Topic?] = [] + + if !anonymous { + topics.append(.string(topic.toHexString().addHexPrefix())) + } + + for (i, p) in parameters.enumerated() { + let input = inputs[i] + if !input.indexed { + // cannot filter non-indexed parameters; must be null + return [] + } + if p == nil { + topics.append(.string(nil)) + } else if input.type.isArray { + // filtering with tuples or arrays not supported + return [] + } else if let p = p as? Array { + topics.append(.strings(p.map { encodeTopic(input: input, value: $0) })) + } else { + topics.append(encodeTopic(input: input, value: p!)) + } + } + return topics + } } // MARK: - Function input/output decoding diff --git a/Sources/Web3Core/Transaction/EventfilterParameters.swift b/Sources/Web3Core/Transaction/EventfilterParameters.swift index 9850feb72..eb3c9342d 100755 --- a/Sources/Web3Core/Transaction/EventfilterParameters.swift +++ b/Sources/Web3Core/Transaction/EventfilterParameters.swift @@ -50,8 +50,8 @@ extension EventFilterParameters { var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(fromBlock.description, forKey: .fromBlock) try container.encode(toBlock.description, forKey: .toBlock) - try container.encode(address.description, forKey: .address) - try container.encode(topics.textRepresentation, forKey: .topics) + try container.encode(address, forKey: .address) + try container.encode(topics, forKey: .topics) } } @@ -96,6 +96,17 @@ extension EventFilterParameters { case string(String?) case strings([Topic?]?) + public func encode(to encoder: Encoder) throws { + switch self { + case let .string(s): + var container = encoder.singleValueContainer() + try container.encode(s) + case let .strings(ss): + var container = encoder.unkeyedContainer() + try container.encode(contentsOf: ss ?? []) + } + } + var rawValue: String { switch self { case let .string(string): diff --git a/Sources/web3swift/EthereumAPICalls/Ethereum/IEth+Defaults.swift b/Sources/web3swift/EthereumAPICalls/Ethereum/IEth+Defaults.swift index c2732c66e..8d695cba2 100644 --- a/Sources/web3swift/EthereumAPICalls/Ethereum/IEth+Defaults.swift +++ b/Sources/web3swift/EthereumAPICalls/Ethereum/IEth+Defaults.swift @@ -130,6 +130,12 @@ public extension IEth { } } +public extension IEth { + func getLogs(eventFilter: EventFilterParameters) async throws -> [EventLog] { + try await APIRequest.sendRequest(with: self.provider, for: .getLogs(eventFilter)).result + } +} + public extension IEth { func send(_ transaction: CodableTransaction) async throws -> TransactionSendingResult { let request = APIRequest.sendTransaction(transaction) diff --git a/Sources/web3swift/EthereumAPICalls/Ethereum/IEth.swift b/Sources/web3swift/EthereumAPICalls/Ethereum/IEth.swift index ddade0c00..0ce33372f 100755 --- a/Sources/web3swift/EthereumAPICalls/Ethereum/IEth.swift +++ b/Sources/web3swift/EthereumAPICalls/Ethereum/IEth.swift @@ -25,6 +25,8 @@ public protocol IEth { func code(for address: EthereumAddress, onBlock: BlockNumber) async throws -> Hash + func getLogs(eventFilter: EventFilterParameters) async throws -> [EventLog] + func gasPrice() async throws -> BigUInt func getTransactionCount(for address: EthereumAddress, onBlock: BlockNumber) async throws -> BigUInt diff --git a/Tests/web3swiftTests/localTests/EventTests.swift b/Tests/web3swiftTests/localTests/EventTests.swift new file mode 100644 index 000000000..a05472a30 --- /dev/null +++ b/Tests/web3swiftTests/localTests/EventTests.swift @@ -0,0 +1,97 @@ +// +// EventTests.swift +// +// +// Created by liugang zhang on 2023/8/24. +// + +import XCTest +import Web3Core + +@testable import web3swift + +class EventTests: XCTestCase { + func testEncodeTopc() throws { + let encoder = JSONEncoder() + let t1: [EventFilterParameters.Topic] = [] + let t2: [EventFilterParameters.Topic] = [.string(nil)] + let t3: [EventFilterParameters.Topic] = [.strings([.string(nil), .string("1")])] + XCTAssertNoThrow(try encoder.encode(t1)) + XCTAssertNoThrow(try encoder.encode(t2)) + XCTAssertNoThrow(try encoder.encode(t3)) + + let t4: [EventFilterParameters.Topic] = [ + .string("1"), + .strings([ + .string("2"), + .string("3"), + ] + )] + let encoded = try encoder.encode(t4) + let json = try JSONSerialization.jsonObject(with: encoded) + XCTAssertEqual(json as? NSArray, ["1", ["2", "3"]]) + } + + func testEncodeLogs() throws { + let contract = try EthereumContract(TestEvent) + let logs = contract.events["UserOperationEvent"]?.encodeParameters( + [ + "0x2c16c07e1c68d502e9c7ad05f0402b365671a0e6517cb807b2de4edd95657042", + "0x581074D2d9e50913eB37665b07CAFa9bFFdd1640", + "hello,world", + true, + "0x02c16c07e1c68d50", + nil + ] + ) + + XCTAssert(logs?.count == 7) + } + + let TestEvent = """ + [{ + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "userOpHash", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "indexed": true, + "internalType": "string", + "name": "a", + "type": "string" + }, + { + "indexed": true, + "internalType": "bool", + "name": "b", + "type": "bool" + }, + { + "indexed": true, + "internalType": "bytes", + "name": "c", + "type": "bytes" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "d", + "type": "uint256" + }, + ], + "name": "UserOperationEvent", + "type": "event" + } + ] + """ +} diff --git a/Tests/web3swiftTests/remoteTests/EventFilterTests.swift b/Tests/web3swiftTests/remoteTests/EventFilterTests.swift new file mode 100644 index 000000000..d4c14e4bd --- /dev/null +++ b/Tests/web3swiftTests/remoteTests/EventFilterTests.swift @@ -0,0 +1,30 @@ +// +// EventFilterTests.swift +// +// +// Created by liugang zhang on 2023/8/24. +// + +import XCTest +import Web3Core + +@testable import web3swift + +class EventFilerTests: XCTestCase { + + func testErc20Transfer() async throws { + let web3 = try await Web3.InfuraMainnetWeb3(accessToken: Constants.infuraToken) + let address = EthereumAddress("0xdac17f958d2ee523a2206206994597c13d831ec7")! + let erc20 = ERC20(web3: web3, provider: web3.provider, address: address) + + let topics = erc20.contract.contract.event("Transfer", parameters: [ + "0x003e36550908907c2a2da960fd19a419b9a774b7" + ]) + let block = try await web3.eth.block(by: .latest) + let parameters = EventFilterParameters(fromBlock: .exact(block.number - 1000), address: [address], topics: topics) + let result = try await web3.eth.getLogs(eventFilter: parameters) + + // result not always has object in it. + print(result) + } +} From 909a1b044ebe87fa686cd1f5c4c54ac55a7c6283 Mon Sep 17 00:00:00 2001 From: august Date: Thu, 24 Aug 2023 17:27:35 +0800 Subject: [PATCH 08/62] remove a force unwrap --- Sources/Web3Core/EthereumABI/ABIElements.swift | 2 +- Tests/web3swiftTests/remoteTests/EventFilterTests.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/Web3Core/EthereumABI/ABIElements.swift b/Sources/Web3Core/EthereumABI/ABIElements.swift index aac04199a..0582d01b7 100755 --- a/Sources/Web3Core/EthereumABI/ABIElements.swift +++ b/Sources/Web3Core/EthereumABI/ABIElements.swift @@ -236,7 +236,7 @@ extension ABI.Element.Event { } return .string(encoded.toHexString().addHexPrefix()) } - guard let data = ABIEncoder.convertToData(value)!.setLengthLeft(32) else { + guard let data = ABIEncoder.convertToData(value)?.setLengthLeft(32) else { return nil } return .string(data.toHexString().addHexPrefix()) diff --git a/Tests/web3swiftTests/remoteTests/EventFilterTests.swift b/Tests/web3swiftTests/remoteTests/EventFilterTests.swift index d4c14e4bd..d5f5ea216 100644 --- a/Tests/web3swiftTests/remoteTests/EventFilterTests.swift +++ b/Tests/web3swiftTests/remoteTests/EventFilterTests.swift @@ -24,7 +24,7 @@ class EventFilerTests: XCTestCase { let parameters = EventFilterParameters(fromBlock: .exact(block.number - 1000), address: [address], topics: topics) let result = try await web3.eth.getLogs(eventFilter: parameters) - // result not always has object in it. + // result not always has a log in it. print(result) } } From df870304448fb2bfc9d36570649436c1ea83e4c6 Mon Sep 17 00:00:00 2001 From: august Date: Thu, 24 Aug 2023 17:35:37 +0800 Subject: [PATCH 09/62] Trim Trailing Whitespace --- .../localTests/EventTests.swift | 47 +------------------ .../remoteTests/EventFilterTests.swift | 2 +- 2 files changed, 3 insertions(+), 46 deletions(-) diff --git a/Tests/web3swiftTests/localTests/EventTests.swift b/Tests/web3swiftTests/localTests/EventTests.swift index a05472a30..438cb7098 100644 --- a/Tests/web3swiftTests/localTests/EventTests.swift +++ b/Tests/web3swiftTests/localTests/EventTests.swift @@ -1,6 +1,6 @@ // // EventTests.swift -// +// // // Created by liugang zhang on 2023/8/24. // @@ -49,49 +49,6 @@ class EventTests: XCTestCase { } let TestEvent = """ - [{ - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "bytes32", - "name": "userOpHash", - "type": "bytes32" - }, - { - "indexed": true, - "internalType": "address", - "name": "sender", - "type": "address" - }, - { - "indexed": true, - "internalType": "string", - "name": "a", - "type": "string" - }, - { - "indexed": true, - "internalType": "bool", - "name": "b", - "type": "bool" - }, - { - "indexed": true, - "internalType": "bytes", - "name": "c", - "type": "bytes" - }, - { - "indexed": true, - "internalType": "uint256", - "name": "d", - "type": "uint256" - }, - ], - "name": "UserOperationEvent", - "type": "event" - } - ] + [{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"userOpHash","type":"bytes32"},{"indexed":true,"internalType":"address","name":"sender","type":"address"},{"indexed":true,"internalType":"string","name":"a","type":"string"},{"indexed":true,"internalType":"bool","name":"b","type":"bool"},{"indexed":true,"internalType":"bytes","name":"c","type":"bytes"},{"indexed":true,"internalType":"uint256","name":"d","type":"uint256"}],"name":"UserOperationEvent","type":"event"}] """ } diff --git a/Tests/web3swiftTests/remoteTests/EventFilterTests.swift b/Tests/web3swiftTests/remoteTests/EventFilterTests.swift index d5f5ea216..d6324e533 100644 --- a/Tests/web3swiftTests/remoteTests/EventFilterTests.swift +++ b/Tests/web3swiftTests/remoteTests/EventFilterTests.swift @@ -1,6 +1,6 @@ // // EventFilterTests.swift -// +// // // Created by liugang zhang on 2023/8/24. // From edd8e723419468e2a871303dc491360ab9a043cc Mon Sep 17 00:00:00 2001 From: august Date: Mon, 28 Aug 2023 15:55:42 +0800 Subject: [PATCH 10/62] feat: improve decode error --- .../Web3Core/Contract/ContractProtocol.swift | 64 ++++++- .../Web3Core/EthereumABI/ABIElements.swift | 51 ++++- .../EthereumABI/ABIParameterTypes.swift | 14 ++ .../EthereumABI/Sequence+ABIExtension.swift | 2 + .../APIRequest+ComputedProperties.swift | 7 +- .../Request/APIRequest+Methods.swift | 152 ++++++++++++--- .../Request/APIRequest+UtilityTypes.swift | 27 ++- .../Web3Core/Utility/String+Extension.swift | 4 + Sources/Web3Core/Web3Error/Web3Error.swift | 11 ++ .../web3swift/Operations/ReadOperation.swift | 5 +- Sources/web3swift/Web3/Web3+Contract.swift | 6 + .../web3swift/Web3/Web3+HttpProvider.swift | 9 +- .../localTests/ABIDecoderSliceTests.swift | 14 +- .../ABIElementErrorDecodingTest.swift | 177 ++++++++++-------- .../localTests/UtilitiesTests.swift | 5 + .../remoteTests/DecodeRemoteErrorTests.swift | 53 ++++++ .../remoteTests/InfuraTests.swift | 6 + 17 files changed, 462 insertions(+), 145 deletions(-) create mode 100644 Tests/web3swiftTests/remoteTests/DecodeRemoteErrorTests.swift diff --git a/Sources/Web3Core/Contract/ContractProtocol.swift b/Sources/Web3Core/Contract/ContractProtocol.swift index 43a4c1543..b35f49050 100755 --- a/Sources/Web3Core/Contract/ContractProtocol.swift +++ b/Sources/Web3Core/Contract/ContractProtocol.swift @@ -144,7 +144,8 @@ public protocol ContractProtocol { /// - method signature (with or without `0x` prefix, case insensitive): `0xFFffFFff`; /// - data: non empty bytes to decode; /// - Returns: dictionary with decoded values. `nil` if decoding failed. - func decodeReturnData(_ method: String, data: Data) -> [String: Any]? + @discardableResult + func decodeReturnData(_ method: String, data: Data) throws -> [String: Any] /// Decode input arguments of a function. /// - Parameters: @@ -313,13 +314,40 @@ extension DefaultContractProtocol { return bloom.test(topic: event.topic) } - public func decodeReturnData(_ method: String, data: Data) -> [String: Any]? { + @discardableResult + public func decodeReturnData(_ method: String, data: Data) throws -> [String: Any] { if method == "fallback" { - return [String: Any]() + return [:] + } + + guard let function = methods[method]?.first else { + throw Web3Error.inputError(desc: "Function method does not exist.") + } + + switch data.count % 32 { + case 0: + return try function.decodeReturnData(data) + case 4: + let selector = data[0..<4] + if selector.toHexString() == "08c379a0", let reason = ABI.Element.EthError.decodeStringError(data[4...]) { + throw Web3Error.revert("revert(string)` or `require(expression, string)` was executed. reason: \(reason)", reason: reason) + } + else if selector.toHexString() == "4e487b71", let reason = ABI.Element.EthError.decodePanicError(data[4...]) { + let panicCode = String(format: "%02X", Int(reason)).addHexPrefix() + throw Web3Error.revert("Error: call revert exception; VM Exception while processing transaction: reverted with panic code \(panicCode)", reason: panicCode) + } + else if let customError = errors[selector.toHexString().addHexPrefix().lowercased()] { + if let errorArgs = customError.decodeEthError(data[4...]) { + throw Web3Error.revertCustom(customError.signature, errorArgs) + } else { + throw Web3Error.inputError(desc: "Signature matches \(customError.errorDeclaration) but failed to be decoded.") + } + } else { + throw Web3Error.inputError(desc: "Found no matched error") + } + default: + throw Web3Error.inputError(desc: "Invalid data count") } - return methods[method]?.compactMap({ function in - return function.decodeReturnData(data) - }).first } public func decodeInputData(_ method: String, data: Data) -> [String: Any]? { @@ -339,8 +367,32 @@ extension DefaultContractProtocol { return function.decodeInputData(Data(data[data.startIndex + 4 ..< data.startIndex + data.count])) } + public func decodeEthError(_ data: Data) -> [String: Any]? { + guard data.count >= 4, + let err = errors.first(where: { $0.value.methodEncoding == data[0..<4] })?.value else { + return nil + } + return err.decodeEthError(data[4...]) + } + public func getFunctionCalled(_ data: Data) -> ABI.Element.Function? { guard data.count >= 4 else { return nil } return methods[data[data.startIndex ..< data.startIndex + 4].toHexString().addHexPrefix()]?.first } } + +extension DefaultContractProtocol { + @discardableResult + public func callStatic(_ method: String, parameters: [Any], provider: Web3Provider) async throws -> [String: Any] { + guard let address = address else { + throw Web3Error.inputError(desc: "address field is missing") + } + guard let data = self.method(method, parameters: parameters, extraData: nil) else { + throw Web3Error.dataError + } + let transaction = CodableTransaction(to: address, data: data) + + let result: Data = try await APIRequest.sendRequest(with: provider, for: .call(transaction, .latest)).result + return try decodeReturnData(method, data: result) + } +} diff --git a/Sources/Web3Core/EthereumABI/ABIElements.swift b/Sources/Web3Core/EthereumABI/ABIElements.swift index 5dca0b331..deda8e25d 100755 --- a/Sources/Web3Core/EthereumABI/ABIElements.swift +++ b/Sources/Web3Core/EthereumABI/ABIElements.swift @@ -220,6 +220,40 @@ extension ABI.Element.Event { } } +// MARK: - Decode custom error + +extension ABI.Element.EthError { + public func decodeEthError(_ data: Data) -> [String: Any]? { + guard inputs.count * 32 <= data.count, + let decoded = ABIDecoder.decode(types: inputs, data: data) else { + return nil + } + + var result = [String: Any]() + for (index, out) in inputs.enumerated() { + result["\(index)"] = decoded[index] + if !out.name.isEmpty { + result[out.name] = decoded[index] + } + } + return result + } + + /// Decodes `revert(string)` and `require(expression, string)` calls. + /// These calls are decomposed as `Error(string` error. + public static func decodeStringError(_ data: Data) -> String? { + let decoded = ABIDecoder.decode(types: [.init(name: "", type: .string)], data: data) + return decoded?.first as? String + } + + /// Decodes `Panic(uint256)` errors. + /// See more about panic code explain at: https://docs.soliditylang.org/en/v0.8.21/control-structures.html#panic-via-assert-and-error-via-require + public static func decodePanicError(_ data: Data) -> BigUInt? { + let decoded = ABIDecoder.decode(types: [.init(name: "", type: .uint(bits: 256))], data: data) + return decoded?.first as? BigUInt + } +} + // MARK: - Function input/output decoding extension ABI.Element { @@ -232,7 +266,7 @@ extension ABI.Element { case .fallback: return nil case .function(let function): - return function.decodeReturnData(data) + return try? function.decodeReturnData(data) case .receive: return nil case .error: @@ -314,25 +348,21 @@ extension ABI.Element.Function { /// - next 32 bytes are the error message length; /// - the next N bytes, where N >= 32, are the message bytes /// - the rest are 0 bytes padding. - public func decodeReturnData(_ data: Data, errors: [String: ABI.Element.EthError]? = nil) -> [String: Any] { - if let decodedError = decodeErrorResponse(data, errors: errors) { - return decodedError - } - + public func decodeReturnData(_ data: Data) throws -> [String: Any] { guard !outputs.isEmpty else { NSLog("Function doesn't have any output types to decode given data.") - return ["_success": true] + return [:] } guard outputs.count * 32 <= data.count else { - return ["_success": false, "_failureReason": "Bytes count must be at least \(outputs.count * 32). Given \(data.count). Decoding will fail."] + throw Web3Error.revert("Bytes count must be at least \(outputs.count * 32). Given \(data.count). Decoding will fail.", reason: nil) } // TODO: need improvement - we should be able to tell which value failed to be decoded guard let values = ABIDecoder.decode(types: outputs, data: data) else { - return ["_success": false, "_failureReason": "Failed to decode at least one value."] + throw Web3Error.revert("Failed to decode at least one value.", reason: nil) } - var returnArray: [String: Any] = ["_success": true] + var returnArray: [String: Any] = [:] for i in outputs.indices { returnArray["\(i)"] = values[i] if !outputs[i].name.isEmpty { @@ -412,6 +442,7 @@ extension ABI.Element.Function { let errors = errors, let customError = errors[data[data.startIndex ..< data.startIndex + 4].toHexString().stripHexPrefix()] { var errorResponse: [String: Any] = ["_success": false, "_abortedByRevertOrRequire": true, "_error": customError.errorDeclaration] +// customError.decodeEthError(data[4...]) if (data.count > 32 && !customError.inputs.isEmpty), let decodedInputs = ABIDecoder.decode(types: customError.inputs, data: Data(data[data.startIndex + 4 ..< data.startIndex + data.count])) { diff --git a/Sources/Web3Core/EthereumABI/ABIParameterTypes.swift b/Sources/Web3Core/EthereumABI/ABIParameterTypes.swift index eb536237e..4acee18a9 100755 --- a/Sources/Web3Core/EthereumABI/ABIParameterTypes.swift +++ b/Sources/Web3Core/EthereumABI/ABIParameterTypes.swift @@ -192,6 +192,20 @@ extension ABI.Element.Event { } } +extension ABI.Element.EthError { + public var signature: String { + return "\(name)(\(inputs.map { $0.type.abiRepresentation }.joined(separator: ",")))" + } + + public var methodString: String { + return String(signature.sha3(.keccak256).prefix(8)) + } + + public var methodEncoding: Data { + return signature.data(using: .ascii)!.sha3(.keccak256)[0...3] + } +} + extension ABI.Element.ParameterType: ABIEncoding { public var abiRepresentation: String { switch self { diff --git a/Sources/Web3Core/EthereumABI/Sequence+ABIExtension.swift b/Sources/Web3Core/EthereumABI/Sequence+ABIExtension.swift index 1d2f32744..390b928ee 100644 --- a/Sources/Web3Core/EthereumABI/Sequence+ABIExtension.swift +++ b/Sources/Web3Core/EthereumABI/Sequence+ABIExtension.swift @@ -56,6 +56,8 @@ public extension Sequence where Element == ABI.Element { var errors = [String: ABI.Element.EthError]() for case let .error(error) in self { errors[error.name] = error + errors[error.signature] = error + errors[error.methodString.addHexPrefix().lowercased()] = error } return errors } diff --git a/Sources/Web3Core/EthereumNetwork/Request/APIRequest+ComputedProperties.swift b/Sources/Web3Core/EthereumNetwork/Request/APIRequest+ComputedProperties.swift index 986e8332d..fc711ce4b 100644 --- a/Sources/Web3Core/EthereumNetwork/Request/APIRequest+ComputedProperties.swift +++ b/Sources/Web3Core/EthereumNetwork/Request/APIRequest+ComputedProperties.swift @@ -12,11 +12,8 @@ extension APIRequest { .POST } - public var encodedBody: Data { - let request = RequestBody(method: call, params: parameters) - // this is safe to force try this here - // Because request must failed to compile if it not conformable with `Encodable` protocol - return try! JSONEncoder().encode(request) + public var encodedBody: Data { + RequestBody(method: call, params: parameters).encodedBody } var parameters: [RequestParameter] { diff --git a/Sources/Web3Core/EthereumNetwork/Request/APIRequest+Methods.swift b/Sources/Web3Core/EthereumNetwork/Request/APIRequest+Methods.swift index 58e13aa0f..cfb2de22f 100644 --- a/Sources/Web3Core/EthereumNetwork/Request/APIRequest+Methods.swift +++ b/Sources/Web3Core/EthereumNetwork/Request/APIRequest+Methods.swift @@ -8,22 +8,112 @@ import Foundation import BigInt +/// TODO: should we do more error explain like ethers.js? +/// https://github.com/ethers-io/ethers.js/blob/0bfa7f497dc5793b66df7adfb42c6b846c51d794/packages/providers/src.ts/json-rpc-provider.ts#L55 +func checkError(method: String, error: JsonRpcErrorObject.RpcError) throws -> String { + if method == "eth_call" { + if let result = spelunkData(value: error) { + return result.data + } + throw Web3Error.nodeError(desc: "missing revert data in call exception; Transaction reverted without a reason string") + } + + throw Web3Error.nodeError(desc: error.message) +} + +func spelunkData(value: Any?) -> (message: String, data: String)? { + func spelunkRpcError(_ message: String, data: String) -> (message: String, data: String)? { + if message.contains("revert") && data.isHex { + return (message, data) + } else { + return nil + } + } + + if (value == nil) { + return nil + } + + if let error = value as? JsonRpcErrorObject.RpcError, let data = error.data as? String { + return spelunkRpcError(error.message, data: data) + } + + // Spelunk further... + if let object = value as? [String: Any] { + if let message = object["message"] as? String, + let data = object["data"] as? String { + return spelunkRpcError(message, data: data) + } + + for value in object.values { + if let result = spelunkData(value: value) { + return result + } + return nil + } + } + if let array = value as? [Any] { + for e in array { + if let result = spelunkData(value: e) { + return result + } + return nil + } + } + + // Might be a JSON string we can further descend... + if let string = value as? String, let data = string.data(using: .utf8) { + let json = try? JSONSerialization.jsonObject(with: data) + return spelunkData(value: json) + } + + return nil +} + extension APIRequest { public static func sendRequest(with provider: Web3Provider, for call: APIRequest) async throws -> APIResponse { - let request = setupRequest(for: call, with: provider) - return try await APIRequest.send(uRLRequest: request, with: provider.session) + try await send(call.method.rawValue, parameter: call.parameters, with: provider) } - static func setupRequest(for call: APIRequest, with provider: Web3Provider) -> URLRequest { + static func setupRequest(for body: RequestBody, with provider: Web3Provider) -> URLRequest { var urlRequest = URLRequest(url: provider.url, cachePolicy: .reloadIgnoringCacheData) urlRequest.setValue("application/json", forHTTPHeaderField: "Content-Type") urlRequest.setValue("application/json", forHTTPHeaderField: "Accept") - urlRequest.httpMethod = call.method.rawValue - urlRequest.httpBody = call.encodedBody + urlRequest.httpMethod = "POST" + urlRequest.httpBody = body.encodedBody return urlRequest } - public static func send(uRLRequest: URLRequest, with session: URLSession) async throws -> APIResponse { + public static func send(_ method: String, parameter: [Encodable], with provider: Web3Provider) async throws -> APIResponse { + let body = RequestBody(method: method, params: parameter) + let uRLRequest = setupRequest(for: body, with: provider) + + let data: Data + do { + data = try await send(uRLRequest: uRLRequest, with: provider.session) + } catch Web3Error.rpcError(let error) { + let responseAsString = try checkError(method: method, error: error) + guard let LiteralType = Result.self as? LiteralInitiableFromString.Type, + let literalValue = LiteralType.init(from: responseAsString), + let result = literalValue as? Result else { + throw Web3Error.dataError + } + return APIResponse(id: 2, result: result) + } + + /// This bit of code is purposed to work with literal types that comes in ``Response`` in hexString type. + /// Currently it's just `Data` and any kind of Integers `(U)Int`, `Big(U)Int`. + if let LiteralType = Result.self as? LiteralInitiableFromString.Type { + guard let responseAsString = try? JSONDecoder().decode(APIResponse.self, from: data) else { throw Web3Error.dataError } + guard let literalValue = LiteralType.init(from: responseAsString.result) else { throw Web3Error.dataError } + /// `literalValue` conforms `LiteralInitiableFromString`, that conforming to an `APIResponseType` type, so it's never fails. + guard let result = literalValue as? Result else { throw Web3Error.typeError } + return APIResponse(id: responseAsString.id, jsonrpc: responseAsString.jsonrpc, result: result) + } + return try JSONDecoder().decode(APIResponse.self, from: data) + } + + public static func send(uRLRequest: URLRequest, with session: URLSession) async throws -> Data { let (data, response) = try await session.data(for: uRLRequest) guard 200 ..< 400 ~= response.statusCode else { @@ -34,9 +124,9 @@ extension APIRequest { } } - if let error = (try? JSONDecoder().decode(JsonRpcErrorObject.self, from: data))?.error { + if let error = JsonRpcErrorObject.init(from: data)?.error { guard let parsedErrorCode = error.parsedErrorCode else { - throw Web3Error.nodeError(desc: "\(error.message)\nError code: \(error.code)") + throw Web3Error.rpcError(error) } let description = "\(parsedErrorCode.errorName). Error code: \(error.code). \(error.message)" switch parsedErrorCode { @@ -49,35 +139,51 @@ extension APIRequest { } } - /// This bit of code is purposed to work with literal types that comes in ``Response`` in hexString type. - /// Currently it's just `Data` and any kind of Integers `(U)Int`, `Big(U)Int`. - if let LiteralType = Result.self as? LiteralInitiableFromString.Type { - guard let responseAsString = try? JSONDecoder().decode(APIResponse.self, from: data) else { throw Web3Error.dataError } - guard let literalValue = LiteralType.init(from: responseAsString.result) else { throw Web3Error.dataError } - /// `literalValue` conforms `LiteralInitiableFromString`, that conforming to an `APIResponseType` type, so it's never fails. - guard let result = literalValue as? Result else { throw Web3Error.typeError } - return APIResponse(id: responseAsString.id, jsonrpc: responseAsString.jsonrpc, result: result) - } - return try JSONDecoder().decode(APIResponse.self, from: data) + return data } } /// JSON RPC Error object. See official specification https://www.jsonrpc.org/specification#error_object -private struct JsonRpcErrorObject: Decodable { +public struct JsonRpcErrorObject { public let error: RpcError? - class RpcError: Decodable { - let message: String - let code: Int + public class RpcError { + public let message: String + public let code: Int + public let data: Any? + + init(message: String, code: Int, data: Any?) { + self.message = message + self.code = code + self.data = data + } + var parsedErrorCode: JsonRpcErrorCode? { JsonRpcErrorCode.from(code) } } + + init?(from data: Data) { + guard let root = try? JSONSerialization.jsonObject(with: data) as? [String: Any] else { + return nil + } + if let error = root["error"] as? [String: Any], + let message = error["message"] as? String, + let code = error["code"] as? Int { + guard let errorData = error["data"] else { + self.error = RpcError(message: message, code: code, data: nil) + return + } + self.error = RpcError(message: message, code: code, data: errorData) + } else { + self.error = nil + } + } } /// For error codes specification see chapter `5.1 Error object` /// https://www.jsonrpc.org/specification#error_object -private enum JsonRpcErrorCode { +enum JsonRpcErrorCode { /// -32700 /// Invalid JSON was received by the server. An error occurred on the server while parsing the JSON case parseError diff --git a/Sources/Web3Core/EthereumNetwork/Request/APIRequest+UtilityTypes.swift b/Sources/Web3Core/EthereumNetwork/Request/APIRequest+UtilityTypes.swift index ba8a362f6..66df0a49b 100644 --- a/Sources/Web3Core/EthereumNetwork/Request/APIRequest+UtilityTypes.swift +++ b/Sources/Web3Core/EthereumNetwork/Request/APIRequest+UtilityTypes.swift @@ -24,5 +24,30 @@ struct RequestBody: Encodable { var id = Counter.increment() var method: String - var params: [RequestParameter] + var params: [Encodable] + + enum CodingKeys: String, CodingKey { + case jsonrpc + case id + case method + case params + } + + func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(jsonrpc, forKey: .jsonrpc) + try container.encode(id, forKey: .id) + try container.encode(method, forKey: .method) + + var paramsContainer = container.superEncoder(forKey: .params).unkeyedContainer() + try params.forEach { a in + try paramsContainer.encode(a) + } + } + + public var encodedBody: Data { + // this is safe to force try this here + // Because request must failed to compile if it not conformable with `Encodable` protocol + return try! JSONEncoder().encode(self) + } } diff --git a/Sources/Web3Core/Utility/String+Extension.swift b/Sources/Web3Core/Utility/String+Extension.swift index dbe0a10ca..e0e9b94ca 100755 --- a/Sources/Web3Core/Utility/String+Extension.swift +++ b/Sources/Web3Core/Utility/String+Extension.swift @@ -135,6 +135,10 @@ extension String { func trim() -> String { trimmingCharacters(in: .whitespacesAndNewlines) } + + public var isHex: Bool { + stripHexPrefix().reduce(true, { $0 && $1.isHexDigit } ) + } } extension Character { diff --git a/Sources/Web3Core/Web3Error/Web3Error.swift b/Sources/Web3Core/Web3Error/Web3Error.swift index a9e77a188..5b6770ea2 100644 --- a/Sources/Web3Core/Web3Error/Web3Error.swift +++ b/Sources/Web3Core/Web3Error/Web3Error.swift @@ -25,6 +25,11 @@ public enum Web3Error: LocalizedError { case generalError(err: Error) case unknownError + case rpcError(JsonRpcErrorObject.RpcError) + case revert(String, reason: String?) + case revertCustom(String, [String: Any]) + + public var errorDescription: String? { switch self { case .transactionSerializationError: @@ -55,6 +60,12 @@ public enum Web3Error: LocalizedError { return "Client error: \(code)" case .valueError(let errorDescription): return (errorDescription?.isEmpty ?? true) ? "You're passing value that isn't supported by this method" : errorDescription! + case .rpcError(let error): + return error.message + case .revert(let message, let reason): + return "\(message); reverted with reason string: \(reason ?? "")" + case .revertCustom(let error, _): + return "reverted with custom error: \(error)" } } } diff --git a/Sources/web3swift/Operations/ReadOperation.swift b/Sources/web3swift/Operations/ReadOperation.swift index 50dc30ab4..58945a32e 100755 --- a/Sources/web3swift/Operations/ReadOperation.swift +++ b/Sources/web3swift/Operations/ReadOperation.swift @@ -43,9 +43,6 @@ public class ReadOperation { let resultHex = data.toHexString().addHexPrefix() return ["result": resultHex] } - guard let decodedData = self.contract.decodeReturnData(self.method, data: data) else { - throw Web3Error.processingError(desc: "Can not decode returned parameters") - } - return decodedData + return try self.contract.decodeReturnData(self.method, data: data) } } diff --git a/Sources/web3swift/Web3/Web3+Contract.swift b/Sources/web3swift/Web3/Web3+Contract.swift index 7bf192710..6112b6e20 100755 --- a/Sources/web3swift/Web3/Web3+Contract.swift +++ b/Sources/web3swift/Web3/Web3+Contract.swift @@ -112,5 +112,11 @@ extension Web3 { } return .init(transaction: transaction, web3: web3, contract: contract, method: method) } + + /// Combines `createReadOperation`& `callContractMethod` + @discardableResult + public func callStatic(_ method: String, parameters: [Any]) async throws -> [String: Any] { + try await contract.callStatic(method, parameters: parameters, provider: web3.provider) + } } } diff --git a/Sources/web3swift/Web3/Web3+HttpProvider.swift b/Sources/web3swift/Web3/Web3+HttpProvider.swift index da37f1df9..aa1135e2b 100755 --- a/Sources/web3swift/Web3/Web3+HttpProvider.swift +++ b/Sources/web3swift/Web3/Web3+HttpProvider.swift @@ -33,13 +33,8 @@ public class Web3HttpProvider: Web3Provider { if let net = net { network = net } else { - var urlRequest = URLRequest(url: url, cachePolicy: .reloadIgnoringCacheData) - urlRequest.setValue("application/json", forHTTPHeaderField: "Content-Type") - urlRequest.setValue("application/json", forHTTPHeaderField: "Accept") - urlRequest.httpMethod = APIRequest.getNetwork.method.rawValue - urlRequest.httpBody = APIRequest.getNetwork.encodedBody - let response: APIResponse = try await APIRequest.send(uRLRequest: urlRequest, with: session) - self.network = Networks.fromInt(response.result) + let response: UInt = try await APIRequest.send(APIRequest.getNetwork.call, parameter: [], with: self).result + self.network = Networks.fromInt(response) } attachedKeystoreManager = manager } diff --git a/Tests/web3swiftTests/localTests/ABIDecoderSliceTests.swift b/Tests/web3swiftTests/localTests/ABIDecoderSliceTests.swift index a3fbeb43c..3e7f077d8 100644 --- a/Tests/web3swiftTests/localTests/ABIDecoderSliceTests.swift +++ b/Tests/web3swiftTests/localTests/ABIDecoderSliceTests.swift @@ -27,7 +27,7 @@ final class ABIDecoderSliceTests: XCTestCase { while startIndex < data.count { let slice = data[startIndex ..< startIndex + answerSize] startIndex += answerSize - guard let bigInt = balanceofMethod.decodeReturnData(slice)["0"] as? BigUInt else { + guard let bigInt = try balanceofMethod.decodeReturnData(slice)["0"] as? BigUInt else { throw Web3Error.processingError(desc: "Can not decode returned parameters") } let value = Utilities.formatToPrecision(bigInt, units: .wei) @@ -52,9 +52,7 @@ final class ABIDecoderSliceTests: XCTestCase { XCTAssertEqual(methods.count, 3) /// Act - guard let decodedData = multiCall2Contract.decodeReturnData("aggregate", data: data) else { - throw Web3Error.processingError(desc: "Can not decode returned parameters") - } + let decodedData = try multiCall2Contract.decodeReturnData("aggregate", data: data) guard let returnData = decodedData["returnData"] as? [Data] else { throw Web3Error.dataError @@ -63,7 +61,7 @@ final class ABIDecoderSliceTests: XCTestCase { XCTAssertEqual(returnData.count, 3) for item in methods.enumerated() { - XCTAssertNotNil(item.element.decodeReturnData(returnData[item.offset])["0"]) + XCTAssertNotNil(try item.element.decodeReturnData(returnData[item.offset])["0"]) } } @@ -74,9 +72,7 @@ final class ABIDecoderSliceTests: XCTestCase { let erc20_balanceof = try EthereumContract(Web3.Utils.erc20ABI).methods["balanceOf"]!.first! /// Act - guard let decodedData = contract.decodeReturnData("tryAggregate", data: data) else { - throw Web3Error.processingError(desc: "Can not decode returned parameters") - } + let decodedData = try contract.decodeReturnData("tryAggregate", data: data) guard let returnData = decodedData["returnData"] as? [[Any]] else { throw Web3Error.dataError @@ -84,7 +80,7 @@ final class ABIDecoderSliceTests: XCTestCase { var resultArray = [BigUInt]() for i in 0..<2 { guard let data = returnData[i][1] as? Data, - let balance = erc20_balanceof.decodeReturnData(data)["0"] as? BigUInt else { + let balance = try? erc20_balanceof.decodeReturnData(data)["0"] as? BigUInt else { resultArray.append(0) continue } diff --git a/Tests/web3swiftTests/localTests/ABIElementErrorDecodingTest.swift b/Tests/web3swiftTests/localTests/ABIElementErrorDecodingTest.swift index 181337d19..d0aa1b9d8 100644 --- a/Tests/web3swiftTests/localTests/ABIElementErrorDecodingTest.swift +++ b/Tests/web3swiftTests/localTests/ABIElementErrorDecodingTest.swift @@ -65,123 +65,140 @@ class ABIElementErrorDecodingTest: XCTestCase { XCTAssertTrue(emptyFunction.decodeErrorResponse(Data()) == nil) } - func testDecodeEmptyErrorOnOneOutputFunction() { - guard let errorData = oneOutputFunction.decodeErrorResponse(Data()) else { - XCTFail("Empty Data must be decoded as a `revert()` or `require(false)` call if function used to decode it has at least one output parameter.") - return + /// `require(expression)` and `revert()` without a message return 0 bytes, + /// we can noly catch an error when function has a return value + func testDecodeEmptyErrorOnOneOutputFunction() throws { + let contract = try EthereumContract(abi: [.function(emptyFunction)]) + do { + try contract.decodeReturnData(emptyFunction.signature, data: Data()) + } catch { + XCTFail() } - XCTAssertEqual(errorData["_success"] as? Bool, false) - XCTAssertNotNil(errorData["_failureReason"] as? String) - - let decodedOutput = oneOutputFunction.decodeReturnData(Data()) - - XCTAssertEqual(errorData["_success"] as? Bool, decodedOutput["_success"] as? Bool) - XCTAssertEqual(errorData["_failureReason"] as? String, decodedOutput["_failureReason"] as? String) + let contract2 = try EthereumContract(abi: [.function(oneOutputFunction)]) + do { + try contract2.decodeReturnData(oneOutputFunction.signature, data: Data()) + XCTFail() + } catch { + print(error) + } } /// Data is decoded as a call of `revert` or `require` with a message no matter the number of outputs configured in the ``ABI/Element/Function``. /// `revert(message)` and `require(false,message)`return at least 128 bytes. We cannot differentiate between `require` or `revert`. - func testDecodeDefaultErrorWithMessage() { + func testDecodeDefaultErrorWithMessage() throws { /// 08c379a0 - Error(string) function selector /// 0000000000000000000000000000000000000000000000000000000000000020 - Data offset /// 000000000000000000000000000000000000000000000000000000000000001a - Message length /// 4e6f7420656e6f7567682045746865722070726f76696465642e000000000000 - Message + 0 bytes padding /// 0000... - some more 0 bytes padding to make the number of bytes match 32 bytes chunks - let errorResponse = Data.fromHex("08c379a00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001a4e6f7420656e6f7567682045746865722070726f76696465642e00000000000000000000000000000000000000000000000000000000000000000000")! - guard let errorData = emptyFunction.decodeErrorResponse(errorResponse) else { - XCTFail("Data must be decoded as a `revert(\"Not enough Ether provided.\")` or `require(false, \"Not enough Ether provided.\")` but decoding failed completely.") - return + let errorResponse = Data.fromHex("08c379a00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001a4e6f7420656e6f7567682045746865722070726f76696465642e0000000000000000000000000000000000000000000000000000000000000000000000000000")! + let contract = try EthereumContract(abi: [.function(emptyFunction)]) + + do { + try contract.decodeReturnData(emptyFunction.signature, data: errorResponse) + XCTFail("decode function should throw an error") + } catch Web3Error.revert(_, let reason) { + XCTAssertEqual(reason, "Not enough Ether provided.") } - XCTAssertEqual(errorData["_success"] as? Bool, false) - XCTAssertEqual(errorData["_abortedByRevertOrRequire"] as? Bool, true) - XCTAssertEqual(errorData["_errorMessage"] as? String, "Not enough Ether provided.") - XCTAssertNotNil(errorData["_failureReason"] as? String) - - let decodedOutput = oneOutputFunction.decodeReturnData(errorResponse) - - XCTAssertEqual(errorData["_success"] as? Bool, decodedOutput["_success"] as? Bool) - XCTAssertEqual(errorData["_failureReason"] as? String, decodedOutput["_failureReason"] as? String) - XCTAssertEqual(errorData["_abortedByRevertOrRequire"] as? Bool, decodedOutput["_abortedByRevertOrRequire"] as? Bool) - XCTAssertEqual(errorData["_errorMessage"] as? String, decodedOutput["_errorMessage"] as? String) - XCTAssertEqual(decodedOutput["_errorMessage"] as? String, "Not enough Ether provided.") + XCTAssertEqual(EthError.decodeStringError(errorResponse[4...]), "Not enough Ether provided.") } - /// Data is decoded as a call of `revert Unauthorized()`. Decoded only if custom error ABI is given. - func testDecodeRevertWithCustomError() { + /// Data is decoded as a call of `revert Unauthorized()` + func testDecodeRevertWithCustomError() throws { /// 82b42900 - Unauthorized() function selector /// 00000000000000000000000000000000000000000000000000000000 - padding bytes - let errorResponse = Data.fromHex("82b4290000000000000000000000000000000000000000000000000000000000")! - let errors: [String: EthError] = ["82b42900": .init(name: "Unauthorized", inputs: [])] - guard let errorData = emptyFunction.decodeErrorResponse(errorResponse, errors: errors) else { - XCTFail("Data must be decoded as a `revert(\"Not enough Ether provided.\")` or `require(false, \"Not enough Ether provided.\")` but decoding failed completely.") - return + let errorResponse = Data.fromHex("82b429000000000000000000000000000000000000000000000000000000000000000000")! + let error = ABI.Element.EthError(name: "Unauthorized", inputs: []) + let contract = try EthereumContract(abi: [.function(emptyFunction), .error(error)] ) + + do { + try contract.decodeReturnData(emptyFunction.signature, data: errorResponse) + XCTFail("decode function should throw an error") + } catch Web3Error.revertCustom(let signature, let args) { + XCTAssertEqual(signature, "Unauthorized()") + XCTAssertTrue(args.isEmpty) } - XCTAssertEqual(errorData["_success"] as? Bool, false) - XCTAssertEqual(errorData["_abortedByRevertOrRequire"] as? Bool, true) - XCTAssertEqual(errorData["_error"] as? String, "Unauthorized()") - - let decodedOutput = oneOutputFunction.decodeReturnData(errorResponse, errors: errors) - - XCTAssertEqual(errorData["_success"] as? Bool, decodedOutput["_success"] as? Bool) - XCTAssertEqual(errorData["_abortedByRevertOrRequire"] as? Bool, decodedOutput["_abortedByRevertOrRequire"] as? Bool) - XCTAssertEqual(errorData["_error"] as? String, decodedOutput["_error"] as? String) + guard let decoded = error.decodeEthError(errorResponse[4...]) else { + XCTFail("decode response failed.") + return + } + XCTAssertTrue(decoded.isEmpty) } - /// Data is decoded as a call of `revert Unauthorized()`. Decoded only if custom error ABI is given. + /// Data is decoded as a call of `revert Unauthorized(bool)`. /// Trying to decode as `Unauthorized(string)`. Must fail. - func testDecodeRevertWithCustomErrorFailed() { - /// 82b42900 - Unauthorized() function selector + func testDecodeRevertWithCustomErrorFailed() throws { + /// 5caef992 - Unauthorized(bool) function selector /// 00000000000000000000000000000000000000000000000000000000 - padding bytes - let errorResponse = Data.fromHex("82b4290000000000000000000000000000000000000000000000000000000000")! - let errors: [String: EthError] = ["82b42900": .init(name: "Unauthorized", inputs: [.init(name: "", type: .string)])] - guard let errorData = emptyFunction.decodeErrorResponse(errorResponse, errors: errors) else { - XCTFail("Data must be decoded as a `revert(\"Not enough Ether provided.\")` or `require(false, \"Not enough Ether provided.\")` but decoding failed completely.") - return + let errorResponse = Data.fromHex("5caef9920000000000000000000000000000000000000000000000000000000000000000")! + let error = ABI.Element.EthError(name: "Unauthorized", inputs: [.init(name: "", type: .bool)]) + let contract = try EthereumContract(abi: [.function(oneOutputFunction), .error(error)] ) + + do { + try contract.decodeReturnData(oneOutputFunction.signature, data: errorResponse) + XCTFail("decode function should throw an error") + } catch Web3Error.revertCustom(let signature, let args) { + XCTAssertEqual(signature, "Unauthorized(bool)") + XCTAssertEqual(args["0"] as? Bool, false) } - XCTAssertEqual(errorData["_success"] as? Bool, false) - XCTAssertEqual(errorData["_abortedByRevertOrRequire"] as? Bool, true) - XCTAssertEqual(errorData["_error"] as? String, "Unauthorized(string)") - XCTAssertEqual(errorData["_parsingError"] as? String, "Data matches Unauthorized(string) but failed to be decoded.") - - let decodedOutput = oneOutputFunction.decodeReturnData(errorResponse, errors: errors) - - XCTAssertEqual(errorData["_success"] as? Bool, decodedOutput["_success"] as? Bool) - XCTAssertEqual(errorData["_abortedByRevertOrRequire"] as? Bool, decodedOutput["_abortedByRevertOrRequire"] as? Bool) - XCTAssertEqual(errorData["_error"] as? String, decodedOutput["_error"] as? String) - XCTAssertEqual(errorData["_parsingError"] as? String, decodedOutput["_parsingError"] as? String) + guard let decoded = error.decodeEthError(errorResponse[4...]) else { + XCTFail("decode response failed.") + return + } + XCTAssertEqual(decoded["0"] as? Bool, false) } /// Data is decoded as a call of `revert Unauthorized("Reason")`. Decoded only if custom error ABI is given. /// The custom error argument must be extractable by index and name if the name is available. - func testDecodeRevertWithCustomErrorWithArguments() { + func testDecodeRevertWithCustomErrorWithArguments() throws { /// 973d02cb - `Unauthorized(string)` function selector /// 0000000000000000000000000000000000000000000000000000000000000020 - data offset /// 0000000000000000000000000000000000000000000000000000000000000006 - first custom argument length /// 526561736f6e0000000000000000000000000000000000000000000000000000 - first custom argument bytes + 0 bytes padding /// 0000... - some more 0 bytes padding to make the number of bytes match 32 bytes chunks - let errorResponse = Data.fromHex("973d02cb00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000006526561736f6e000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")! - let errors: [String: EthError] = ["973d02cb": .init(name: "Unauthorized", inputs: [.init(name: "message_arg", type: .string)])] - guard let errorData = emptyFunction.decodeErrorResponse(errorResponse, errors: errors) else { - XCTFail("Data must be decoded as a `revert(\"Not enough Ether provided.\")` or `require(false, \"Not enough Ether provided.\")` but decoding failed completely.") - return + let errorResponse = Data.fromHex("973d02cb00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000006526561736f6e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")! + let error = ABI.Element.EthError(name: "Unauthorized", inputs: [.init(name: "message_arg", type: .string)]) + let contract = try EthereumContract(abi: [.function(emptyFunction), .error(error)]) + + do { + try contract.decodeReturnData(emptyFunction.signature, data: errorResponse) + XCTFail("decode function should throw an error") + } catch Web3Error.revertCustom(let signature, let args) { + XCTAssertEqual(signature, "Unauthorized(string)") + XCTAssertEqual(args["0"] as? String, "Reason") + XCTAssertEqual(args["message_arg"] as? String, "Reason") } - XCTAssertEqual(errorData["_success"] as? Bool, false) - XCTAssertEqual(errorData["_abortedByRevertOrRequire"] as? Bool, true) - XCTAssertEqual(errorData["_error"] as? String, "Unauthorized(string message_arg)") - XCTAssertEqual(errorData["0"] as? String, "Reason") - XCTAssertEqual(errorData["0"] as? String, errorData["message_arg"] as? String) + guard let decoded = error.decodeEthError(errorResponse[4...]) else { + XCTFail("decode response failed.") + return + } + XCTAssertEqual(decoded["0"] as? String, "Reason") + XCTAssertEqual(decoded["message_arg"] as? String, "Reason") + } - let decodedOutput = oneOutputFunction.decodeReturnData(errorResponse, errors: errors) + /// Data is decoded as a panic exception is generated. + /// Example: + /// ``` solidity + /// function panicError() public { + /// assert(false); + /// } + /// ``` + func testDecodePanicError() throws { + let errorResponse = Data(hex: "4e487b710000000000000000000000000000000000000000000000000000000000000001") + let contract = try EthereumContract(abi: [.function(emptyFunction)]) + + do { + try contract.decodeReturnData(emptyFunction.signature, data: errorResponse) + } catch Web3Error.revert(let message, let code) { + XCTAssertTrue(message.contains("reverted with panic code 0x01")) + XCTAssertEqual(code, "0x01") + } - XCTAssertEqual(errorData["_success"] as? Bool, decodedOutput["_success"] as? Bool) - XCTAssertEqual(errorData["_abortedByRevertOrRequire"] as? Bool, decodedOutput["_abortedByRevertOrRequire"] as? Bool) - XCTAssertEqual(errorData["_error"] as? String, decodedOutput["_error"] as? String) - XCTAssertEqual(errorData["0"] as? String, decodedOutput["0"] as? String) - XCTAssertEqual(errorData["message_arg"] as? String, decodedOutput["message_arg"] as? String) + XCTAssertEqual(EthError.decodePanicError(errorResponse[4...]), 1) } } diff --git a/Tests/web3swiftTests/localTests/UtilitiesTests.swift b/Tests/web3swiftTests/localTests/UtilitiesTests.swift index 4780805b0..057d99be2 100644 --- a/Tests/web3swiftTests/localTests/UtilitiesTests.swift +++ b/Tests/web3swiftTests/localTests/UtilitiesTests.swift @@ -82,4 +82,9 @@ class UtilitiesTests: XCTestCase { address = Utilities.publicToAddress(Data.fromHex("0x0852972572d465d016d4c501887b8df303eee3ed602c056b1eb09260dfa0da0ab2")!)?.address XCTAssertEqual(address, nil) } + + func testStringIsHex() { + XCTAssertTrue("1234567890abcdef".isHex) + XCTAssertFalse("ghijklmn".isHex) + } } diff --git a/Tests/web3swiftTests/remoteTests/DecodeRemoteErrorTests.swift b/Tests/web3swiftTests/remoteTests/DecodeRemoteErrorTests.swift new file mode 100644 index 000000000..faf790730 --- /dev/null +++ b/Tests/web3swiftTests/remoteTests/DecodeRemoteErrorTests.swift @@ -0,0 +1,53 @@ +// +// DecodeRemoteErrorTests.swift +// +// Created by liugang zhang on 2023/8/25. +// + +import XCTest +import Web3Core + +@testable import web3swift + +final class DecodeRemoteErrorTests: XCTestCase { + + let entryPoint = EthereumAddress("0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789")! + let factory = EthereumAddress("0x9406Cc6185a346906296840746125a0E44976454")! + let address = EthereumAddress("0x581074D2d9e50913eB37665b07CAFa9bFFdd1640")! + + func testDecodeRemoteFunc() async throws { + let web3 = try await Web3.InfuraMainnetWeb3(accessToken: Constants.infuraToken) + + let entryABI = try EthereumContract(abi: [ + .error(.init(name: "SenderAddressResult", + inputs: [.init(name: "sender", type: .address)])), + .function(.init(name: "getSenderAddress", + inputs: [.init(name: "initCode", type: .dynamicBytes)], + outputs: [], + constant: false, + payable: false)) + ], at: entryPoint) + + let factoryABI = try EthereumContract(abi: [ + .function(.init(name: "createAccount", + inputs: [ + .init(name: "owner", type: .address), + .init(name: "salt", type: .uint(bits: 256)) + ], + outputs: [], + constant: false, + payable: false)) + ]) + + let initCode = factory.addressData + factoryABI.method("createAccount", parameters: [address, 0], extraData: nil)! + + do { + try await entryABI.callStatic("getSenderAddress", parameters: [initCode], provider: web3.provider) + XCTFail() + } catch Web3Error.revertCustom(let signature, let args) { + XCTAssertEqual(signature, "SenderAddressResult(address)") + XCTAssertEqual((args["sender"] as? EthereumAddress)?.address, "0x9CF91286f22a1b799770fB5De0E66f3C4cc165d1") + XCTAssertEqual((args["0"] as? EthereumAddress)?.address, "0x9CF91286f22a1b799770fB5De0E66f3C4cc165d1") + } + } +} diff --git a/Tests/web3swiftTests/remoteTests/InfuraTests.swift b/Tests/web3swiftTests/remoteTests/InfuraTests.swift index fec9289ef..7771393df 100755 --- a/Tests/web3swiftTests/remoteTests/InfuraTests.swift +++ b/Tests/web3swiftTests/remoteTests/InfuraTests.swift @@ -11,6 +11,12 @@ import Web3Core // MARK: Works only with network connection class InfuraTests: XCTestCase { + func testGetNetwork() async throws { + let requestURLstring = "https://" + Networks.Mainnet.name + Constants.infuraHttpScheme + Constants.infuraToken + let web3 = try await Web3HttpProvider(url: URL(string: requestURLstring)!, network: nil) + XCTAssertEqual(web3.network?.chainID, 1) + } + func testGetBalance() async throws { let web3 = try await Web3.InfuraMainnetWeb3(accessToken: Constants.infuraToken) let address = EthereumAddress("0xd61b5ca425F8C8775882d4defefC68A6979DBbce")! From 89a98636a913f8fe6c8d441904cdcfb621c3fc1a Mon Sep 17 00:00:00 2001 From: august Date: Mon, 28 Aug 2023 18:28:39 +0800 Subject: [PATCH 11/62] add more test for encodeTopic --- .../Web3Core/EthereumABI/ABIElements.swift | 44 +++++++++++----- .../localTests/EventTests.swift | 51 +++++++++++++++---- .../remoteTests/EventFilterTests.swift | 26 ++++++++-- 3 files changed, 94 insertions(+), 27 deletions(-) diff --git a/Sources/Web3Core/EthereumABI/ABIElements.swift b/Sources/Web3Core/EthereumABI/ABIElements.swift index 0582d01b7..73004021c 100755 --- a/Sources/Web3Core/EthereumABI/ABIElements.swift +++ b/Sources/Web3Core/EthereumABI/ABIElements.swift @@ -219,27 +219,34 @@ extension ABI.Element.Event { return eventContent } - func encodeTopic(input: ABI.Element.Event.Input, value: Any) -> EventFilterParameters.Topic? { - if case .string = input.type { + public static func encodeTopic(input: ABI.Element.Event.Input, value: Any) -> EventFilterParameters.Topic? { + switch input.type { + case .string: guard let string = value as? String else { return nil } return .string(string.sha3(.keccak256).addHexPrefix()) - } else if case .dynamicBytes = input.type { + case .dynamicBytes: guard let data = ABIEncoder.convertToData(value) else { return nil } return .string(data.sha3(.keccak256).toHexString().addHexPrefix()) - } else if case .address = input.type { - guard let encoded = ABIEncoder.encode(types: [input.type], values: [value]) else { + case .bytes(length: _): + guard let data = value as? Data, let data = data.setLengthLeft(32) else { + return nil + } + return .string(data.toHexString().addHexPrefix()) + case .address, .uint(bits: _), .int(bits: _), .bool: + guard let encoded = ABIEncoder.encodeSingleType(type: input.type, value: value) else { return nil } return .string(encoded.toHexString().addHexPrefix()) + default: + guard let data = try? ABIEncoder.abiEncode(value).setLengthLeft(32) else { + return nil + } + return .string(data.toHexString().addHexPrefix()) } - guard let data = ABIEncoder.convertToData(value)?.setLengthLeft(32) else { - return nil - } - return .string(data.toHexString().addHexPrefix()) } public func encodeParameters(_ parameters: [Any?]) -> [EventFilterParameters.Topic?] { @@ -260,14 +267,25 @@ extension ABI.Element.Event { return [] } if p == nil { - topics.append(.string(nil)) - } else if input.type.isArray { + topics.append(nil) + } else if input.type.isArray || input.type.isTuple { // filtering with tuples or arrays not supported return [] } else if let p = p as? Array { - topics.append(.strings(p.map { encodeTopic(input: input, value: $0) })) + topics.append(.strings(p.map { Self.encodeTopic(input: input, value: $0) })) + } else { + topics.append(Self.encodeTopic(input: input, value: p!)) + } + } + + // Trim off trailing nulls + while let last = topics.last { + if last == nil { + topics.removeLast() + } else if case .string(let string) = last, string == nil { + topics.removeLast() } else { - topics.append(encodeTopic(input: input, value: p!)) + break } } return topics diff --git a/Tests/web3swiftTests/localTests/EventTests.swift b/Tests/web3swiftTests/localTests/EventTests.swift index 438cb7098..823a122da 100644 --- a/Tests/web3swiftTests/localTests/EventTests.swift +++ b/Tests/web3swiftTests/localTests/EventTests.swift @@ -7,34 +7,44 @@ import XCTest import Web3Core +import BigInt @testable import web3swift class EventTests: XCTestCase { - func testEncodeTopc() throws { + + /// Solidity event allows up to 3 indexed field, this is just for test. + let testEvent = """ + [{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"userOpHash","type":"bytes32"},{"indexed":true,"internalType":"address","name":"sender","type":"address"},{"indexed":true,"internalType":"string","name":"a","type":"string"},{"indexed":true,"internalType":"bool","name":"b","type":"bool"},{"indexed":true,"internalType":"bytes","name":"c","type":"bytes"},{"indexed":true,"internalType":"uint256","name":"d","type":"uint256"}],"name":"UserOperationEvent","type":"event"}] + """ + + func testEncodeTopicToJSON() throws { let encoder = JSONEncoder() let t1: [EventFilterParameters.Topic] = [] let t2: [EventFilterParameters.Topic] = [.string(nil)] let t3: [EventFilterParameters.Topic] = [.strings([.string(nil), .string("1")])] + let t4: [EventFilterParameters.Topic] = [.strings([nil, .string("1")])] XCTAssertNoThrow(try encoder.encode(t1)) XCTAssertNoThrow(try encoder.encode(t2)) XCTAssertNoThrow(try encoder.encode(t3)) + XCTAssertNoThrow(try encoder.encode(t4)) - let t4: [EventFilterParameters.Topic] = [ + let topics: [EventFilterParameters.Topic] = [ .string("1"), .strings([ .string("2"), .string("3"), ] )] - let encoded = try encoder.encode(t4) + let encoded = try encoder.encode(topics) let json = try JSONSerialization.jsonObject(with: encoded) XCTAssertEqual(json as? NSArray, ["1", ["2", "3"]]) } func testEncodeLogs() throws { - let contract = try EthereumContract(TestEvent) - let logs = contract.events["UserOperationEvent"]?.encodeParameters( + let contract = try EthereumContract(testEvent) + let topic = contract.events["UserOperationEvent"]!.topic + let logs = contract.events["UserOperationEvent"]!.encodeParameters( [ "0x2c16c07e1c68d502e9c7ad05f0402b365671a0e6517cb807b2de4edd95657042", "0x581074D2d9e50913eB37665b07CAFa9bFFdd1640", @@ -44,11 +54,34 @@ class EventTests: XCTestCase { nil ] ) + XCTAssertEqual(logs.count, 6) - XCTAssert(logs?.count == 7) + XCTAssertTrue(logs[0] == topic.toHexString().addHexPrefix()) + XCTAssertTrue(logs[1] == nil) + XCTAssertTrue(logs[2] == "0x000000000000000000000000581074d2d9e50913eb37665b07cafa9bffdd1640") + XCTAssertTrue(logs[3] == "0xab036729af8b8f9b610af4e11b14fa30c348f40c2c230cce92ef6ef37726fee7") + XCTAssertTrue(logs[4] == "0x0000000000000000000000000000000000000000000000000000000000000001") + XCTAssertTrue(logs[5] == "0x56f5a6cba57d26b32db8dc756fda960dcd3687770a300575a5f8107591eff63f") } - let TestEvent = """ - [{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"userOpHash","type":"bytes32"},{"indexed":true,"internalType":"address","name":"sender","type":"address"},{"indexed":true,"internalType":"string","name":"a","type":"string"},{"indexed":true,"internalType":"bool","name":"b","type":"bool"},{"indexed":true,"internalType":"bytes","name":"c","type":"bytes"},{"indexed":true,"internalType":"uint256","name":"d","type":"uint256"}],"name":"UserOperationEvent","type":"event"}] - """ + func testEncodeTopic() throws { + XCTAssertTrue(ABI.Element.Event.encodeTopic(input: .init(name: "", type: .string, indexed: true), value: "hello,world") == "0xab036729af8b8f9b610af4e11b14fa30c348f40c2c230cce92ef6ef37726fee7") + XCTAssertTrue(ABI.Element.Event.encodeTopic(input: .init(name: "", type: .address, indexed: true), value: "0x003e36550908907c2a2da960fd19a419b9a774b7") == "0x000000000000000000000000003e36550908907c2a2da960fd19a419b9a774b7") + XCTAssertTrue(ABI.Element.Event.encodeTopic(input: .init(name: "", type: .address, indexed: true), value: EthereumAddress("0x003e36550908907c2a2da960fd19a419b9a774b7")!) == "0x000000000000000000000000003e36550908907c2a2da960fd19a419b9a774b7") + XCTAssertTrue(ABI.Element.Event.encodeTopic(input: .init(name: "", type: .bool, indexed: true), value: true) == "0x0000000000000000000000000000000000000000000000000000000000000001") + XCTAssertTrue(ABI.Element.Event.encodeTopic(input: .init(name: "", type: .uint(bits: 256), indexed: true), value: BigUInt("dbe20a", radix: 16)!) == "0x0000000000000000000000000000000000000000000000000000000000dbe20a") + XCTAssertTrue(ABI.Element.Event.encodeTopic(input: .init(name: "", type: .int(bits: 32), indexed: true), value: 100) == "0x0000000000000000000000000000000000000000000000000000000000000064") + XCTAssertTrue(ABI.Element.Event.encodeTopic(input: .init(name: "", type: .dynamicBytes, indexed: true), value: Data(hex: "6761766f66796f726b")) == "0xe0859ceea0a2fd2474deef2b2183f10f4c741ebba702e9a07d337522c0af55fb") + XCTAssertTrue(ABI.Element.Event.encodeTopic(input: .init(name: "", type: .bytes(length: 32), indexed: true), value: Data(hex: "6761766f66796f726b")) == "0x00000000000000000000000000000000000000000000006761766f66796f726b") + } +} + +private func ==(lhs: EventFilterParameters.Topic?, rhs: String?) -> Bool { + if let lhs = lhs, case .string(let string) = lhs { + return string == rhs + } + if lhs == nil && rhs == nil { + return true + } + return false } diff --git a/Tests/web3swiftTests/remoteTests/EventFilterTests.swift b/Tests/web3swiftTests/remoteTests/EventFilterTests.swift index d6324e533..99a29683b 100644 --- a/Tests/web3swiftTests/remoteTests/EventFilterTests.swift +++ b/Tests/web3swiftTests/remoteTests/EventFilterTests.swift @@ -7,11 +7,14 @@ import XCTest import Web3Core - +import BigInt +import CryptoSwift @testable import web3swift class EventFilerTests: XCTestCase { + /// This test tx can be found at here: + /// https://etherscan.io/tx/0x1a1daac5b3158f16399baec9abba2c8a4b4b7ffea5992490079b6bfc4ce70004 func testErc20Transfer() async throws { let web3 = try await Web3.InfuraMainnetWeb3(accessToken: Constants.infuraToken) let address = EthereumAddress("0xdac17f958d2ee523a2206206994597c13d831ec7")! @@ -20,11 +23,24 @@ class EventFilerTests: XCTestCase { let topics = erc20.contract.contract.event("Transfer", parameters: [ "0x003e36550908907c2a2da960fd19a419b9a774b7" ]) - let block = try await web3.eth.block(by: .latest) - let parameters = EventFilterParameters(fromBlock: .exact(block.number - 1000), address: [address], topics: topics) + + let parameters = EventFilterParameters(fromBlock: .exact(17983395), toBlock: .exact(17983395), address: [address], topics: topics) let result = try await web3.eth.getLogs(eventFilter: parameters) - // result not always has a log in it. - print(result) + XCTAssertEqual(result.count, 1) + + let log = result.first! + XCTAssertEqual(log.address.address.lowercased(), "0xdac17f958d2ee523a2206206994597c13d831ec7") + XCTAssertEqual(log.transactionHash.toHexString().lowercased(), "1a1daac5b3158f16399baec9abba2c8a4b4b7ffea5992490079b6bfc4ce70004") + + let logTopics = log.topics.map { $0.toHexString() } + topics.compactMap { t -> String? in + if let t = t, case EventFilterParameters.Topic.string(let topic) = t { + return topic + } + return nil + }.forEach { t in + XCTAssertTrue(logTopics.contains(t.stripHexPrefix())) + } } } From 9a7212ea8b97683e340a5ca5135432bc966b87c6 Mon Sep 17 00:00:00 2001 From: august Date: Mon, 28 Aug 2023 21:46:52 +0800 Subject: [PATCH 12/62] fix: checkError --- .../EthereumNetwork/Request/APIRequest+Methods.swift | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Sources/Web3Core/EthereumNetwork/Request/APIRequest+Methods.swift b/Sources/Web3Core/EthereumNetwork/Request/APIRequest+Methods.swift index cfb2de22f..ce5360f85 100644 --- a/Sources/Web3Core/EthereumNetwork/Request/APIRequest+Methods.swift +++ b/Sources/Web3Core/EthereumNetwork/Request/APIRequest+Methods.swift @@ -34,8 +34,12 @@ func spelunkData(value: Any?) -> (message: String, data: String)? { return nil } - if let error = value as? JsonRpcErrorObject.RpcError, let data = error.data as? String { - return spelunkRpcError(error.message, data: data) + if let error = value as? JsonRpcErrorObject.RpcError { + if let data = error.data as? String { + return spelunkRpcError(error.message, data: data) + } else { + return spelunkData(value: error.data) + } } // Spelunk further... From 25dc43ceec1f3e069f929792d217c1b2431d6a2c Mon Sep 17 00:00:00 2001 From: august Date: Mon, 28 Aug 2023 21:51:06 +0800 Subject: [PATCH 13/62] Trim Trailing Whitespace --- Tests/web3swiftTests/remoteTests/DecodeRemoteErrorTests.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/web3swiftTests/remoteTests/DecodeRemoteErrorTests.swift b/Tests/web3swiftTests/remoteTests/DecodeRemoteErrorTests.swift index faf790730..ed5a48bf2 100644 --- a/Tests/web3swiftTests/remoteTests/DecodeRemoteErrorTests.swift +++ b/Tests/web3swiftTests/remoteTests/DecodeRemoteErrorTests.swift @@ -17,7 +17,7 @@ final class DecodeRemoteErrorTests: XCTestCase { func testDecodeRemoteFunc() async throws { let web3 = try await Web3.InfuraMainnetWeb3(accessToken: Constants.infuraToken) - + let entryABI = try EthereumContract(abi: [ .error(.init(name: "SenderAddressResult", inputs: [.init(name: "sender", type: .address)])), From 5c3652b6cbffbceef9a541cf0a1ae4db71a7753d Mon Sep 17 00:00:00 2001 From: august Date: Mon, 28 Aug 2023 22:32:12 +0800 Subject: [PATCH 14/62] fix eth method --- .../Web3Core/EthereumNetwork/Request/APIRequest+Methods.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/Web3Core/EthereumNetwork/Request/APIRequest+Methods.swift b/Sources/Web3Core/EthereumNetwork/Request/APIRequest+Methods.swift index ce5360f85..9c4e40880 100644 --- a/Sources/Web3Core/EthereumNetwork/Request/APIRequest+Methods.swift +++ b/Sources/Web3Core/EthereumNetwork/Request/APIRequest+Methods.swift @@ -76,7 +76,7 @@ func spelunkData(value: Any?) -> (message: String, data: String)? { extension APIRequest { public static func sendRequest(with provider: Web3Provider, for call: APIRequest) async throws -> APIResponse { - try await send(call.method.rawValue, parameter: call.parameters, with: provider) + try await send(call.call, parameter: call.parameters, with: provider) } static func setupRequest(for body: RequestBody, with provider: Web3Provider) -> URLRequest { From 20bfa7a29481ee89c51f542393dc18083a0c9a56 Mon Sep 17 00:00:00 2001 From: august Date: Tue, 29 Aug 2023 01:48:16 +0800 Subject: [PATCH 15/62] add missing test contract bytecode --- .../web3swift/Web3/Web3+HttpProvider.swift | 11 +- .../localTests/AdvancedABIv2Tests.swift | 108 +++++------------- .../localTests/LocalTestCase.swift | 17 +++ .../localTests/UncategorizedTests.swift | 20 ++-- 4 files changed, 66 insertions(+), 90 deletions(-) diff --git a/Sources/web3swift/Web3/Web3+HttpProvider.swift b/Sources/web3swift/Web3/Web3+HttpProvider.swift index aa1135e2b..99a1fc8fc 100755 --- a/Sources/web3swift/Web3/Web3+HttpProvider.swift +++ b/Sources/web3swift/Web3/Web3+HttpProvider.swift @@ -33,8 +33,15 @@ public class Web3HttpProvider: Web3Provider { if let net = net { network = net } else { - let response: UInt = try await APIRequest.send(APIRequest.getNetwork.call, parameter: [], with: self).result - self.network = Networks.fromInt(response) + /// chain id could be a hex string or an int value. + let response: String = try await APIRequest.send(APIRequest.getNetwork.call, parameter: [], with: self).result + let result: UInt + if response.hasHexPrefix() { + result = UInt(BigUInt(response, radix: 16) ?? Networks.Mainnet.chainID) + } else { + result = UInt(response) ?? UInt(Networks.Mainnet.chainID) + } + self.network = Networks.fromInt(result) } attachedKeystoreManager = manager } diff --git a/Tests/web3swiftTests/localTests/AdvancedABIv2Tests.swift b/Tests/web3swiftTests/localTests/AdvancedABIv2Tests.swift index 066a1b7a8..e3e9f8cc1 100755 --- a/Tests/web3swiftTests/localTests/AdvancedABIv2Tests.swift +++ b/Tests/web3swiftTests/localTests/AdvancedABIv2Tests.swift @@ -17,20 +17,7 @@ class AdvancedABIv2Tests: LocalTestCase { let bytecode = Data.fromHex("6080604052341561000f57600080fd5b610cb18061001e6000396000f30060806040526004361061006d576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063189533931461007257806338163ff51461009b57806388be6c65146100c4578063e7c1a47c146100ed578063f376e01314610116575b600080fd5b341561007d57600080fd5b61008561013f565b6040516100929190610a9f565b60405180910390f35b34156100a657600080fd5b6100ae610220565b6040516100bb9190610a7d565b60405180910390f35b34156100cf57600080fd5b6100d76102c5565b6040516100e49190610ae3565b60405180910390f35b34156100f857600080fd5b610100610350565b60405161010d9190610ac1565b60405180910390f35b341561012157600080fd5b610129610399565b6040516101369190610b05565b60405180910390f35b6060600260405190808252806020026020018201604052801561017657816020015b60608152602001906001900390816101615790505b5090506040805190810160405280600581526020017f48656c6c6f0000000000000000000000000000000000000000000000000000008152508160008151811015156101be57fe5b906020019060200201819052506040805190810160405280600581526020017f576f726c6400000000000000000000000000000000000000000000000000000081525081600181518110151561021057fe5b9060200190602002018190525090565b610228610546565b6040805190810160405280600581526020017f48656c6c6f00000000000000000000000000000000000000000000000000000081525081600060028110151561026d57fe5b60200201819052506040805190810160405280600581526020017f576f726c640000000000000000000000000000000000000000000000000000008152508160016002811015156102ba57fe5b602002018190525090565b6060600260405190808252806020026020018201604052801561030257816020015b6102ef61056d565b8152602001906001900390816102e75790505b50905061030d610399565b81600081518110151561031c57fe5b90602001906020020181905250610331610399565b81600181518110151561034057fe5b9060200190602002018190525090565b6103586105a9565b610360610399565b81600060028110151561036f57fe5b602002018190525061037f610399565b81600160028110151561038e57fe5b602002018190525090565b6103a16105d8565b60606103ab610614565b6103b3610546565b60036040519080825280602002602001820160405280156103e35781602001602082028038833980820191505090505b50925060008360008151811015156103f757fe5b9060200190602002018181525050600183600181518110151561041657fe5b9060200190602002018181525050600283600281518110151561043557fe5b90602001906020020181815250506040805190810160405280600081526020016001815250915060408051908101604052806040805190810160405280600581526020017f48656c6c6f00000000000000000000000000000000000000000000000000000081525081526020016040805190810160405280600581526020017f576f726c64000000000000000000000000000000000000000000000000000000815250815250905060a060405190810160405280600181526020016040805190810160405280600b81526020017f48656c6c6f20776f726c64000000000000000000000000000000000000000000815250815260200183815260200184815260200182815250935083935050505090565b60408051908101604052806002905b60608152602001906001900390816105555790505090565b60e060405190810160405280600081526020016060815260200161058f610636565b8152602001606081526020016105a3610658565b81525090565b6101c0604051908101604052806002905b6105c261056d565b8152602001906001900390816105ba5790505090565b60e06040519081016040528060008152602001606081526020016105fa610636565b81526020016060815260200161060e610658565b81525090565b6040805190810160405280600290602082028038833980820191505090505090565b6040805190810160405280600290602082028038833980820191505090505090565b60408051908101604052806002905b60608152602001906001900390816106675790505090565b600061068a82610b81565b8360208202850161069a85610b31565b60005b848110156106d35783830388526106b5838351610930565b92506106c082610bdb565b915060208801975060018101905061069d565b508196508694505050505092915050565b60006106ef82610b76565b836020820285016106ff85610b27565b60005b8481101561073857838303885261071a838351610930565b925061072582610bce565b9150602088019750600181019050610702565b508196508694505050505092915050565b600061075482610b8c565b8084526020840193508360208202850161076d85610b3b565b60005b848110156107a6578383038852610788838351610930565b925061079382610be8565b9150602088019750600181019050610770565b508196508694505050505092915050565b60006107c282610b97565b836020820285016107d285610b48565b60005b8481101561080b5783830388526107ed8383516109ea565b92506107f882610bf5565b91506020880197506001810190506107d5565b508196508694505050505092915050565b600061082782610ba2565b8084526020840193508360208202850161084085610b52565b60005b8481101561087957838303885261085b8383516109ea565b925061086682610c02565b9150602088019750600181019050610843565b508196508694505050505092915050565b61089381610bad565b61089c82610b5f565b60005b828110156108ce576108b2858351610a6e565b6108bb82610c0f565b915060208501945060018101905061089f565b5050505050565b60006108e082610bb8565b8084526020840193506108f283610b69565b60005b8281101561092457610908868351610a6e565b61091182610c1c565b91506020860195506001810190506108f5565b50849250505092915050565b600061093b82610bc3565b80845261094f816020860160208601610c33565b61095881610c66565b602085010191505092915050565b600060c08301600083015161097e6000860182610a6e565b50602083015184820360208601526109968282610930565b91505060408301516109ab604086018261088a565b50606083015184820360808601526109c382826108d5565b915050608083015184820360a08601526109dd82826106e4565b9150508091505092915050565b600060c083016000830151610a026000860182610a6e565b5060208301518482036020860152610a1a8282610930565b9150506040830151610a2f604086018261088a565b5060608301518482036080860152610a4782826108d5565b915050608083015184820360a0860152610a6182826106e4565b9150508091505092915050565b610a7781610c29565b82525050565b60006020820190508181036000830152610a97818461067f565b905092915050565b60006020820190508181036000830152610ab98184610749565b905092915050565b60006020820190508181036000830152610adb81846107b7565b905092915050565b60006020820190508181036000830152610afd818461081c565b905092915050565b60006020820190508181036000830152610b1f8184610966565b905092915050565b6000819050919050565b6000819050919050565b6000602082019050919050565b6000819050919050565b6000602082019050919050565b6000819050919050565b6000602082019050919050565b600060029050919050565b600060029050919050565b600081519050919050565b600060029050919050565b600081519050919050565b600060029050919050565b600081519050919050565b600081519050919050565b6000602082019050919050565b6000602082019050919050565b6000602082019050919050565b6000602082019050919050565b6000602082019050919050565b6000602082019050919050565b6000602082019050919050565b6000819050919050565b60005b83811015610c51578082015181840152602081019050610c36565b83811115610c60576000848401525b50505050565b6000601f19601f83011690509190505600a265627a7a72305820fdaf8ce6fe282a46498c8066d5b5ac382b69969eee38e1ad03c0d501b27f65366c6578706572696d656e74616cf50037")! let web3 = try await Web3.new(LocalTestCase.url) - let allAddresses = try await web3.eth.ownedAccounts() - var contract = web3.contract(abiString, at: nil, abiVersion: 2)! - - // MARK: Writing Data flow - let deployTx = contract.prepareDeploy(bytecode: bytecode)! - deployTx.transaction.from = allAddresses[0] - // MARK: Sending Data flow - let policies = Policies(gasLimitPolicy: .manual(3000000)) - let result = try await deployTx.writeToChain(password: "web3swift", policies: policies, sendRaw: false) - let txHash = result.hash.stripHexPrefix() - - Thread.sleep(forTimeInterval: 1.0) - - let receipt = try await web3.eth.transactionReceipt(Data.fromHex(txHash)!) + let receipt = try await deployContract(bytecode: bytecode, abiString: abiString) switch receipt.status { case .notYetProcessed: @@ -39,7 +26,7 @@ class AdvancedABIv2Tests: LocalTestCase { break } - contract = web3.contract(abiString, at: receipt.contractAddress, abiVersion: 2)! + let contract = web3.contract(abiString, at: receipt.contractAddress, abiVersion: 2)! // MARK: Read data from ABI flow // MARK: - Encoding ABI Data flow let tx = contract.createReadOperation("testSingle") @@ -51,20 +38,7 @@ class AdvancedABIv2Tests: LocalTestCase { let bytecode = Data.fromHex("6080604052341561000f57600080fd5b610cb18061001e6000396000f30060806040526004361061006d576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063189533931461007257806338163ff51461009b57806388be6c65146100c4578063e7c1a47c146100ed578063f376e01314610116575b600080fd5b341561007d57600080fd5b61008561013f565b6040516100929190610a9f565b60405180910390f35b34156100a657600080fd5b6100ae610220565b6040516100bb9190610a7d565b60405180910390f35b34156100cf57600080fd5b6100d76102c5565b6040516100e49190610ae3565b60405180910390f35b34156100f857600080fd5b610100610350565b60405161010d9190610ac1565b60405180910390f35b341561012157600080fd5b610129610399565b6040516101369190610b05565b60405180910390f35b6060600260405190808252806020026020018201604052801561017657816020015b60608152602001906001900390816101615790505b5090506040805190810160405280600581526020017f48656c6c6f0000000000000000000000000000000000000000000000000000008152508160008151811015156101be57fe5b906020019060200201819052506040805190810160405280600581526020017f576f726c6400000000000000000000000000000000000000000000000000000081525081600181518110151561021057fe5b9060200190602002018190525090565b610228610546565b6040805190810160405280600581526020017f48656c6c6f00000000000000000000000000000000000000000000000000000081525081600060028110151561026d57fe5b60200201819052506040805190810160405280600581526020017f576f726c640000000000000000000000000000000000000000000000000000008152508160016002811015156102ba57fe5b602002018190525090565b6060600260405190808252806020026020018201604052801561030257816020015b6102ef61056d565b8152602001906001900390816102e75790505b50905061030d610399565b81600081518110151561031c57fe5b90602001906020020181905250610331610399565b81600181518110151561034057fe5b9060200190602002018190525090565b6103586105a9565b610360610399565b81600060028110151561036f57fe5b602002018190525061037f610399565b81600160028110151561038e57fe5b602002018190525090565b6103a16105d8565b60606103ab610614565b6103b3610546565b60036040519080825280602002602001820160405280156103e35781602001602082028038833980820191505090505b50925060008360008151811015156103f757fe5b9060200190602002018181525050600183600181518110151561041657fe5b9060200190602002018181525050600283600281518110151561043557fe5b90602001906020020181815250506040805190810160405280600081526020016001815250915060408051908101604052806040805190810160405280600581526020017f48656c6c6f00000000000000000000000000000000000000000000000000000081525081526020016040805190810160405280600581526020017f576f726c64000000000000000000000000000000000000000000000000000000815250815250905060a060405190810160405280600181526020016040805190810160405280600b81526020017f48656c6c6f20776f726c64000000000000000000000000000000000000000000815250815260200183815260200184815260200182815250935083935050505090565b60408051908101604052806002905b60608152602001906001900390816105555790505090565b60e060405190810160405280600081526020016060815260200161058f610636565b8152602001606081526020016105a3610658565b81525090565b6101c0604051908101604052806002905b6105c261056d565b8152602001906001900390816105ba5790505090565b60e06040519081016040528060008152602001606081526020016105fa610636565b81526020016060815260200161060e610658565b81525090565b6040805190810160405280600290602082028038833980820191505090505090565b6040805190810160405280600290602082028038833980820191505090505090565b60408051908101604052806002905b60608152602001906001900390816106675790505090565b600061068a82610b81565b8360208202850161069a85610b31565b60005b848110156106d35783830388526106b5838351610930565b92506106c082610bdb565b915060208801975060018101905061069d565b508196508694505050505092915050565b60006106ef82610b76565b836020820285016106ff85610b27565b60005b8481101561073857838303885261071a838351610930565b925061072582610bce565b9150602088019750600181019050610702565b508196508694505050505092915050565b600061075482610b8c565b8084526020840193508360208202850161076d85610b3b565b60005b848110156107a6578383038852610788838351610930565b925061079382610be8565b9150602088019750600181019050610770565b508196508694505050505092915050565b60006107c282610b97565b836020820285016107d285610b48565b60005b8481101561080b5783830388526107ed8383516109ea565b92506107f882610bf5565b91506020880197506001810190506107d5565b508196508694505050505092915050565b600061082782610ba2565b8084526020840193508360208202850161084085610b52565b60005b8481101561087957838303885261085b8383516109ea565b925061086682610c02565b9150602088019750600181019050610843565b508196508694505050505092915050565b61089381610bad565b61089c82610b5f565b60005b828110156108ce576108b2858351610a6e565b6108bb82610c0f565b915060208501945060018101905061089f565b5050505050565b60006108e082610bb8565b8084526020840193506108f283610b69565b60005b8281101561092457610908868351610a6e565b61091182610c1c565b91506020860195506001810190506108f5565b50849250505092915050565b600061093b82610bc3565b80845261094f816020860160208601610c33565b61095881610c66565b602085010191505092915050565b600060c08301600083015161097e6000860182610a6e565b50602083015184820360208601526109968282610930565b91505060408301516109ab604086018261088a565b50606083015184820360808601526109c382826108d5565b915050608083015184820360a08601526109dd82826106e4565b9150508091505092915050565b600060c083016000830151610a026000860182610a6e565b5060208301518482036020860152610a1a8282610930565b9150506040830151610a2f604086018261088a565b5060608301518482036080860152610a4782826108d5565b915050608083015184820360a0860152610a6182826106e4565b9150508091505092915050565b610a7781610c29565b82525050565b60006020820190508181036000830152610a97818461067f565b905092915050565b60006020820190508181036000830152610ab98184610749565b905092915050565b60006020820190508181036000830152610adb81846107b7565b905092915050565b60006020820190508181036000830152610afd818461081c565b905092915050565b60006020820190508181036000830152610b1f8184610966565b905092915050565b6000819050919050565b6000819050919050565b6000602082019050919050565b6000819050919050565b6000602082019050919050565b6000819050919050565b6000602082019050919050565b600060029050919050565b600060029050919050565b600081519050919050565b600060029050919050565b600081519050919050565b600060029050919050565b600081519050919050565b600081519050919050565b6000602082019050919050565b6000602082019050919050565b6000602082019050919050565b6000602082019050919050565b6000602082019050919050565b6000602082019050919050565b6000602082019050919050565b6000819050919050565b60005b83811015610c51578082015181840152602081019050610c36565b83811115610c60576000848401525b50505050565b6000601f19601f83011690509190505600a265627a7a72305820fdaf8ce6fe282a46498c8066d5b5ac382b69969eee38e1ad03c0d501b27f65366c6578706572696d656e74616cf50037")! let web3 = try await Web3.new(LocalTestCase.url) - let allAddresses = try await web3.eth.ownedAccounts() - var contract = web3.contract(abiString, at: nil, abiVersion: 2)! - - let parameters: [Any] = [] - // MARK: Writing Data flow - let deployTx = contract.prepareDeploy(bytecode: bytecode, parameters: parameters)! - deployTx.transaction.from = allAddresses[0] - let policies = Policies(gasLimitPolicy: .manual(3000000)) - let result = try await deployTx.writeToChain(password: "web3swift", policies: policies, sendRaw: false) - let txHash = result.hash.stripHexPrefix() - - Thread.sleep(forTimeInterval: 1.0) - - let receipt = try await web3.eth.transactionReceipt(Data.fromHex(txHash)!) + let receipt = try await deployContract(bytecode: bytecode, abiString: abiString) switch receipt.status { case .notYetProcessed: @@ -73,7 +47,7 @@ class AdvancedABIv2Tests: LocalTestCase { break } - contract = web3.contract(abiString, at: receipt.contractAddress, abiVersion: 2)! + let contract = web3.contract(abiString, at: receipt.contractAddress, abiVersion: 2)! // MARK: Read data from ABI flow // MARK: - Encoding ABI Data flow let tx = contract.createReadOperation("testStaticArray") @@ -85,20 +59,7 @@ class AdvancedABIv2Tests: LocalTestCase { let bytecode = Data.fromHex("6080604052341561000f57600080fd5b610cb18061001e6000396000f30060806040526004361061006d576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063189533931461007257806338163ff51461009b57806388be6c65146100c4578063e7c1a47c146100ed578063f376e01314610116575b600080fd5b341561007d57600080fd5b61008561013f565b6040516100929190610a9f565b60405180910390f35b34156100a657600080fd5b6100ae610220565b6040516100bb9190610a7d565b60405180910390f35b34156100cf57600080fd5b6100d76102c5565b6040516100e49190610ae3565b60405180910390f35b34156100f857600080fd5b610100610350565b60405161010d9190610ac1565b60405180910390f35b341561012157600080fd5b610129610399565b6040516101369190610b05565b60405180910390f35b6060600260405190808252806020026020018201604052801561017657816020015b60608152602001906001900390816101615790505b5090506040805190810160405280600581526020017f48656c6c6f0000000000000000000000000000000000000000000000000000008152508160008151811015156101be57fe5b906020019060200201819052506040805190810160405280600581526020017f576f726c6400000000000000000000000000000000000000000000000000000081525081600181518110151561021057fe5b9060200190602002018190525090565b610228610546565b6040805190810160405280600581526020017f48656c6c6f00000000000000000000000000000000000000000000000000000081525081600060028110151561026d57fe5b60200201819052506040805190810160405280600581526020017f576f726c640000000000000000000000000000000000000000000000000000008152508160016002811015156102ba57fe5b602002018190525090565b6060600260405190808252806020026020018201604052801561030257816020015b6102ef61056d565b8152602001906001900390816102e75790505b50905061030d610399565b81600081518110151561031c57fe5b90602001906020020181905250610331610399565b81600181518110151561034057fe5b9060200190602002018190525090565b6103586105a9565b610360610399565b81600060028110151561036f57fe5b602002018190525061037f610399565b81600160028110151561038e57fe5b602002018190525090565b6103a16105d8565b60606103ab610614565b6103b3610546565b60036040519080825280602002602001820160405280156103e35781602001602082028038833980820191505090505b50925060008360008151811015156103f757fe5b9060200190602002018181525050600183600181518110151561041657fe5b9060200190602002018181525050600283600281518110151561043557fe5b90602001906020020181815250506040805190810160405280600081526020016001815250915060408051908101604052806040805190810160405280600581526020017f48656c6c6f00000000000000000000000000000000000000000000000000000081525081526020016040805190810160405280600581526020017f576f726c64000000000000000000000000000000000000000000000000000000815250815250905060a060405190810160405280600181526020016040805190810160405280600b81526020017f48656c6c6f20776f726c64000000000000000000000000000000000000000000815250815260200183815260200184815260200182815250935083935050505090565b60408051908101604052806002905b60608152602001906001900390816105555790505090565b60e060405190810160405280600081526020016060815260200161058f610636565b8152602001606081526020016105a3610658565b81525090565b6101c0604051908101604052806002905b6105c261056d565b8152602001906001900390816105ba5790505090565b60e06040519081016040528060008152602001606081526020016105fa610636565b81526020016060815260200161060e610658565b81525090565b6040805190810160405280600290602082028038833980820191505090505090565b6040805190810160405280600290602082028038833980820191505090505090565b60408051908101604052806002905b60608152602001906001900390816106675790505090565b600061068a82610b81565b8360208202850161069a85610b31565b60005b848110156106d35783830388526106b5838351610930565b92506106c082610bdb565b915060208801975060018101905061069d565b508196508694505050505092915050565b60006106ef82610b76565b836020820285016106ff85610b27565b60005b8481101561073857838303885261071a838351610930565b925061072582610bce565b9150602088019750600181019050610702565b508196508694505050505092915050565b600061075482610b8c565b8084526020840193508360208202850161076d85610b3b565b60005b848110156107a6578383038852610788838351610930565b925061079382610be8565b9150602088019750600181019050610770565b508196508694505050505092915050565b60006107c282610b97565b836020820285016107d285610b48565b60005b8481101561080b5783830388526107ed8383516109ea565b92506107f882610bf5565b91506020880197506001810190506107d5565b508196508694505050505092915050565b600061082782610ba2565b8084526020840193508360208202850161084085610b52565b60005b8481101561087957838303885261085b8383516109ea565b925061086682610c02565b9150602088019750600181019050610843565b508196508694505050505092915050565b61089381610bad565b61089c82610b5f565b60005b828110156108ce576108b2858351610a6e565b6108bb82610c0f565b915060208501945060018101905061089f565b5050505050565b60006108e082610bb8565b8084526020840193506108f283610b69565b60005b8281101561092457610908868351610a6e565b61091182610c1c565b91506020860195506001810190506108f5565b50849250505092915050565b600061093b82610bc3565b80845261094f816020860160208601610c33565b61095881610c66565b602085010191505092915050565b600060c08301600083015161097e6000860182610a6e565b50602083015184820360208601526109968282610930565b91505060408301516109ab604086018261088a565b50606083015184820360808601526109c382826108d5565b915050608083015184820360a08601526109dd82826106e4565b9150508091505092915050565b600060c083016000830151610a026000860182610a6e565b5060208301518482036020860152610a1a8282610930565b9150506040830151610a2f604086018261088a565b5060608301518482036080860152610a4782826108d5565b915050608083015184820360a0860152610a6182826106e4565b9150508091505092915050565b610a7781610c29565b82525050565b60006020820190508181036000830152610a97818461067f565b905092915050565b60006020820190508181036000830152610ab98184610749565b905092915050565b60006020820190508181036000830152610adb81846107b7565b905092915050565b60006020820190508181036000830152610afd818461081c565b905092915050565b60006020820190508181036000830152610b1f8184610966565b905092915050565b6000819050919050565b6000819050919050565b6000602082019050919050565b6000819050919050565b6000602082019050919050565b6000819050919050565b6000602082019050919050565b600060029050919050565b600060029050919050565b600081519050919050565b600060029050919050565b600081519050919050565b600060029050919050565b600081519050919050565b600081519050919050565b6000602082019050919050565b6000602082019050919050565b6000602082019050919050565b6000602082019050919050565b6000602082019050919050565b6000602082019050919050565b6000602082019050919050565b6000819050919050565b60005b83811015610c51578082015181840152602081019050610c36565b83811115610c60576000848401525b50505050565b6000601f19601f83011690509190505600a265627a7a72305820fdaf8ce6fe282a46498c8066d5b5ac382b69969eee38e1ad03c0d501b27f65366c6578706572696d656e74616cf50037")! let web3 = try await Web3.new(LocalTestCase.url) - let allAddresses = try await web3.eth.ownedAccounts() - var contract = web3.contract(abiString, at: nil, abiVersion: 2)! - - let parameters: [Any] = [] - // MARK: Writing Data flow - let deployTx = contract.prepareDeploy(bytecode: bytecode, parameters: parameters)! - deployTx.transaction.from = allAddresses[0] - let policies = Policies(gasLimitPolicy: .manual(3000000)) - let result = try await deployTx.writeToChain(password: "web3swift", policies: policies, sendRaw: false) - let txHash = result.hash.stripHexPrefix() - - Thread.sleep(forTimeInterval: 1.0) - - let receipt = try await web3.eth.transactionReceipt(Data.fromHex(txHash)!) + let receipt = try await deployContract(bytecode: bytecode, abiString: abiString) switch receipt.status { case .notYetProcessed: @@ -107,7 +68,7 @@ class AdvancedABIv2Tests: LocalTestCase { break } - contract = web3.contract(abiString, at: receipt.contractAddress, abiVersion: 2)! + let contract = web3.contract(abiString, at: receipt.contractAddress, abiVersion: 2)! let tx = contract.createReadOperation("testDynArray") _ = try await tx!.callContractMethod() @@ -118,20 +79,7 @@ class AdvancedABIv2Tests: LocalTestCase { let bytecode = Data.fromHex("6080604052341561000f57600080fd5b610cb18061001e6000396000f30060806040526004361061006d576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063189533931461007257806338163ff51461009b57806388be6c65146100c4578063e7c1a47c146100ed578063f376e01314610116575b600080fd5b341561007d57600080fd5b61008561013f565b6040516100929190610a9f565b60405180910390f35b34156100a657600080fd5b6100ae610220565b6040516100bb9190610a7d565b60405180910390f35b34156100cf57600080fd5b6100d76102c5565b6040516100e49190610ae3565b60405180910390f35b34156100f857600080fd5b610100610350565b60405161010d9190610ac1565b60405180910390f35b341561012157600080fd5b610129610399565b6040516101369190610b05565b60405180910390f35b6060600260405190808252806020026020018201604052801561017657816020015b60608152602001906001900390816101615790505b5090506040805190810160405280600581526020017f48656c6c6f0000000000000000000000000000000000000000000000000000008152508160008151811015156101be57fe5b906020019060200201819052506040805190810160405280600581526020017f576f726c6400000000000000000000000000000000000000000000000000000081525081600181518110151561021057fe5b9060200190602002018190525090565b610228610546565b6040805190810160405280600581526020017f48656c6c6f00000000000000000000000000000000000000000000000000000081525081600060028110151561026d57fe5b60200201819052506040805190810160405280600581526020017f576f726c640000000000000000000000000000000000000000000000000000008152508160016002811015156102ba57fe5b602002018190525090565b6060600260405190808252806020026020018201604052801561030257816020015b6102ef61056d565b8152602001906001900390816102e75790505b50905061030d610399565b81600081518110151561031c57fe5b90602001906020020181905250610331610399565b81600181518110151561034057fe5b9060200190602002018190525090565b6103586105a9565b610360610399565b81600060028110151561036f57fe5b602002018190525061037f610399565b81600160028110151561038e57fe5b602002018190525090565b6103a16105d8565b60606103ab610614565b6103b3610546565b60036040519080825280602002602001820160405280156103e35781602001602082028038833980820191505090505b50925060008360008151811015156103f757fe5b9060200190602002018181525050600183600181518110151561041657fe5b9060200190602002018181525050600283600281518110151561043557fe5b90602001906020020181815250506040805190810160405280600081526020016001815250915060408051908101604052806040805190810160405280600581526020017f48656c6c6f00000000000000000000000000000000000000000000000000000081525081526020016040805190810160405280600581526020017f576f726c64000000000000000000000000000000000000000000000000000000815250815250905060a060405190810160405280600181526020016040805190810160405280600b81526020017f48656c6c6f20776f726c64000000000000000000000000000000000000000000815250815260200183815260200184815260200182815250935083935050505090565b60408051908101604052806002905b60608152602001906001900390816105555790505090565b60e060405190810160405280600081526020016060815260200161058f610636565b8152602001606081526020016105a3610658565b81525090565b6101c0604051908101604052806002905b6105c261056d565b8152602001906001900390816105ba5790505090565b60e06040519081016040528060008152602001606081526020016105fa610636565b81526020016060815260200161060e610658565b81525090565b6040805190810160405280600290602082028038833980820191505090505090565b6040805190810160405280600290602082028038833980820191505090505090565b60408051908101604052806002905b60608152602001906001900390816106675790505090565b600061068a82610b81565b8360208202850161069a85610b31565b60005b848110156106d35783830388526106b5838351610930565b92506106c082610bdb565b915060208801975060018101905061069d565b508196508694505050505092915050565b60006106ef82610b76565b836020820285016106ff85610b27565b60005b8481101561073857838303885261071a838351610930565b925061072582610bce565b9150602088019750600181019050610702565b508196508694505050505092915050565b600061075482610b8c565b8084526020840193508360208202850161076d85610b3b565b60005b848110156107a6578383038852610788838351610930565b925061079382610be8565b9150602088019750600181019050610770565b508196508694505050505092915050565b60006107c282610b97565b836020820285016107d285610b48565b60005b8481101561080b5783830388526107ed8383516109ea565b92506107f882610bf5565b91506020880197506001810190506107d5565b508196508694505050505092915050565b600061082782610ba2565b8084526020840193508360208202850161084085610b52565b60005b8481101561087957838303885261085b8383516109ea565b925061086682610c02565b9150602088019750600181019050610843565b508196508694505050505092915050565b61089381610bad565b61089c82610b5f565b60005b828110156108ce576108b2858351610a6e565b6108bb82610c0f565b915060208501945060018101905061089f565b5050505050565b60006108e082610bb8565b8084526020840193506108f283610b69565b60005b8281101561092457610908868351610a6e565b61091182610c1c565b91506020860195506001810190506108f5565b50849250505092915050565b600061093b82610bc3565b80845261094f816020860160208601610c33565b61095881610c66565b602085010191505092915050565b600060c08301600083015161097e6000860182610a6e565b50602083015184820360208601526109968282610930565b91505060408301516109ab604086018261088a565b50606083015184820360808601526109c382826108d5565b915050608083015184820360a08601526109dd82826106e4565b9150508091505092915050565b600060c083016000830151610a026000860182610a6e565b5060208301518482036020860152610a1a8282610930565b9150506040830151610a2f604086018261088a565b5060608301518482036080860152610a4782826108d5565b915050608083015184820360a0860152610a6182826106e4565b9150508091505092915050565b610a7781610c29565b82525050565b60006020820190508181036000830152610a97818461067f565b905092915050565b60006020820190508181036000830152610ab98184610749565b905092915050565b60006020820190508181036000830152610adb81846107b7565b905092915050565b60006020820190508181036000830152610afd818461081c565b905092915050565b60006020820190508181036000830152610b1f8184610966565b905092915050565b6000819050919050565b6000819050919050565b6000602082019050919050565b6000819050919050565b6000602082019050919050565b6000819050919050565b6000602082019050919050565b600060029050919050565b600060029050919050565b600081519050919050565b600060029050919050565b600081519050919050565b600060029050919050565b600081519050919050565b600081519050919050565b6000602082019050919050565b6000602082019050919050565b6000602082019050919050565b6000602082019050919050565b6000602082019050919050565b6000602082019050919050565b6000602082019050919050565b6000819050919050565b60005b83811015610c51578082015181840152602081019050610c36565b83811115610c60576000848401525b50505050565b6000601f19601f83011690509190505600a265627a7a72305820fdaf8ce6fe282a46498c8066d5b5ac382b69969eee38e1ad03c0d501b27f65366c6578706572696d656e74616cf50037")! let web3 = try await Web3.new(LocalTestCase.url) - let allAddresses = try await web3.eth.ownedAccounts() - var contract = web3.contract(abiString, at: nil, abiVersion: 2)! - - let parameters: [Any] = [] - // MARK: Writing Data flow - let deployTx = contract.prepareDeploy(bytecode: bytecode, parameters: parameters)! - deployTx.transaction.from = allAddresses[0] - let policies = Policies(gasLimitPolicy: .manual(3000000)) - let result = try await deployTx.writeToChain(password: "web3swift", policies: policies, sendRaw: false) - let txHash = result.hash.stripHexPrefix() - - Thread.sleep(forTimeInterval: 1.0) - - let receipt = try await web3.eth.transactionReceipt(Data.fromHex(txHash)!) + let receipt = try await deployContract(bytecode: bytecode, abiString: abiString) switch receipt.status { case .notYetProcessed: @@ -140,7 +88,7 @@ class AdvancedABIv2Tests: LocalTestCase { break } - contract = web3.contract(abiString, at: receipt.contractAddress, abiVersion: 2)! + let contract = web3.contract(abiString, at: receipt.contractAddress, abiVersion: 2)! // MARK: Read data from ABI flow // MARK: - Encoding ABI Data flow let tx = contract.createReadOperation("testDynOfDyn") @@ -152,20 +100,7 @@ class AdvancedABIv2Tests: LocalTestCase { let bytecode = Data.fromHex("6080604052341561000f57600080fd5b610cb18061001e6000396000f30060806040526004361061006d576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063189533931461007257806338163ff51461009b57806388be6c65146100c4578063e7c1a47c146100ed578063f376e01314610116575b600080fd5b341561007d57600080fd5b61008561013f565b6040516100929190610a9f565b60405180910390f35b34156100a657600080fd5b6100ae610220565b6040516100bb9190610a7d565b60405180910390f35b34156100cf57600080fd5b6100d76102c5565b6040516100e49190610ae3565b60405180910390f35b34156100f857600080fd5b610100610350565b60405161010d9190610ac1565b60405180910390f35b341561012157600080fd5b610129610399565b6040516101369190610b05565b60405180910390f35b6060600260405190808252806020026020018201604052801561017657816020015b60608152602001906001900390816101615790505b5090506040805190810160405280600581526020017f48656c6c6f0000000000000000000000000000000000000000000000000000008152508160008151811015156101be57fe5b906020019060200201819052506040805190810160405280600581526020017f576f726c6400000000000000000000000000000000000000000000000000000081525081600181518110151561021057fe5b9060200190602002018190525090565b610228610546565b6040805190810160405280600581526020017f48656c6c6f00000000000000000000000000000000000000000000000000000081525081600060028110151561026d57fe5b60200201819052506040805190810160405280600581526020017f576f726c640000000000000000000000000000000000000000000000000000008152508160016002811015156102ba57fe5b602002018190525090565b6060600260405190808252806020026020018201604052801561030257816020015b6102ef61056d565b8152602001906001900390816102e75790505b50905061030d610399565b81600081518110151561031c57fe5b90602001906020020181905250610331610399565b81600181518110151561034057fe5b9060200190602002018190525090565b6103586105a9565b610360610399565b81600060028110151561036f57fe5b602002018190525061037f610399565b81600160028110151561038e57fe5b602002018190525090565b6103a16105d8565b60606103ab610614565b6103b3610546565b60036040519080825280602002602001820160405280156103e35781602001602082028038833980820191505090505b50925060008360008151811015156103f757fe5b9060200190602002018181525050600183600181518110151561041657fe5b9060200190602002018181525050600283600281518110151561043557fe5b90602001906020020181815250506040805190810160405280600081526020016001815250915060408051908101604052806040805190810160405280600581526020017f48656c6c6f00000000000000000000000000000000000000000000000000000081525081526020016040805190810160405280600581526020017f576f726c64000000000000000000000000000000000000000000000000000000815250815250905060a060405190810160405280600181526020016040805190810160405280600b81526020017f48656c6c6f20776f726c64000000000000000000000000000000000000000000815250815260200183815260200184815260200182815250935083935050505090565b60408051908101604052806002905b60608152602001906001900390816105555790505090565b60e060405190810160405280600081526020016060815260200161058f610636565b8152602001606081526020016105a3610658565b81525090565b6101c0604051908101604052806002905b6105c261056d565b8152602001906001900390816105ba5790505090565b60e06040519081016040528060008152602001606081526020016105fa610636565b81526020016060815260200161060e610658565b81525090565b6040805190810160405280600290602082028038833980820191505090505090565b6040805190810160405280600290602082028038833980820191505090505090565b60408051908101604052806002905b60608152602001906001900390816106675790505090565b600061068a82610b81565b8360208202850161069a85610b31565b60005b848110156106d35783830388526106b5838351610930565b92506106c082610bdb565b915060208801975060018101905061069d565b508196508694505050505092915050565b60006106ef82610b76565b836020820285016106ff85610b27565b60005b8481101561073857838303885261071a838351610930565b925061072582610bce565b9150602088019750600181019050610702565b508196508694505050505092915050565b600061075482610b8c565b8084526020840193508360208202850161076d85610b3b565b60005b848110156107a6578383038852610788838351610930565b925061079382610be8565b9150602088019750600181019050610770565b508196508694505050505092915050565b60006107c282610b97565b836020820285016107d285610b48565b60005b8481101561080b5783830388526107ed8383516109ea565b92506107f882610bf5565b91506020880197506001810190506107d5565b508196508694505050505092915050565b600061082782610ba2565b8084526020840193508360208202850161084085610b52565b60005b8481101561087957838303885261085b8383516109ea565b925061086682610c02565b9150602088019750600181019050610843565b508196508694505050505092915050565b61089381610bad565b61089c82610b5f565b60005b828110156108ce576108b2858351610a6e565b6108bb82610c0f565b915060208501945060018101905061089f565b5050505050565b60006108e082610bb8565b8084526020840193506108f283610b69565b60005b8281101561092457610908868351610a6e565b61091182610c1c565b91506020860195506001810190506108f5565b50849250505092915050565b600061093b82610bc3565b80845261094f816020860160208601610c33565b61095881610c66565b602085010191505092915050565b600060c08301600083015161097e6000860182610a6e565b50602083015184820360208601526109968282610930565b91505060408301516109ab604086018261088a565b50606083015184820360808601526109c382826108d5565b915050608083015184820360a08601526109dd82826106e4565b9150508091505092915050565b600060c083016000830151610a026000860182610a6e565b5060208301518482036020860152610a1a8282610930565b9150506040830151610a2f604086018261088a565b5060608301518482036080860152610a4782826108d5565b915050608083015184820360a0860152610a6182826106e4565b9150508091505092915050565b610a7781610c29565b82525050565b60006020820190508181036000830152610a97818461067f565b905092915050565b60006020820190508181036000830152610ab98184610749565b905092915050565b60006020820190508181036000830152610adb81846107b7565b905092915050565b60006020820190508181036000830152610afd818461081c565b905092915050565b60006020820190508181036000830152610b1f8184610966565b905092915050565b6000819050919050565b6000819050919050565b6000602082019050919050565b6000819050919050565b6000602082019050919050565b6000819050919050565b6000602082019050919050565b600060029050919050565b600060029050919050565b600081519050919050565b600060029050919050565b600081519050919050565b600060029050919050565b600081519050919050565b600081519050919050565b6000602082019050919050565b6000602082019050919050565b6000602082019050919050565b6000602082019050919050565b6000602082019050919050565b6000602082019050919050565b6000602082019050919050565b6000819050919050565b60005b83811015610c51578082015181840152602081019050610c36565b83811115610c60576000848401525b50505050565b6000601f19601f83011690509190505600a265627a7a72305820fdaf8ce6fe282a46498c8066d5b5ac382b69969eee38e1ad03c0d501b27f65366c6578706572696d656e74616cf50037")! let web3 = try await Web3.new(LocalTestCase.url) - let allAddresses = try await web3.eth.ownedAccounts() - var contract = web3.contract(abiString, at: nil, abiVersion: 2)! - - let parameters: [Any] = [] - // MARK: Writing Data flow - let deployTx = contract.prepareDeploy(bytecode: bytecode, parameters: parameters)! - deployTx.transaction.from = allAddresses[0] - let policies = Policies(gasLimitPolicy: .manual(3000000)) - let result = try await deployTx.writeToChain(password: "web3swift", policies: policies, sendRaw: false) - let txHash = result.hash.stripHexPrefix() - - Thread.sleep(forTimeInterval: 1.0) - - let receipt = try await web3.eth.transactionReceipt(Data.fromHex(txHash)!) + let receipt = try await deployContract(bytecode: bytecode, abiString: abiString) switch receipt.status { case .notYetProcessed: @@ -174,7 +109,7 @@ class AdvancedABIv2Tests: LocalTestCase { break } - contract = web3.contract(abiString, at: receipt.contractAddress, abiVersion: 2)! + let contract = web3.contract(abiString, at: receipt.contractAddress, abiVersion: 2)! // MARK: Read data from ABI flow // MARK: - Encoding ABI Data flow let tx = contract.createReadOperation("testStOfDyn") @@ -182,8 +117,10 @@ class AdvancedABIv2Tests: LocalTestCase { } func testEmptyArrayDecoding() async throws { + let bytecode = Data(hex: "608060405234801561001057600080fd5b5061027b806100206000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c8063f2a75fe414610030575b600080fd5b61003861004e565b60405161004591906101e0565b60405180910390f35b60606000600267ffffffffffffffff811115610093577f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6040519080825280602002602001820160405280156100c15781602001602082028036833780820191505090505b509050600181600081518110610100577f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b602002602001018181525050600281600181518110610148577f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b6020026020010181815250508091505090565b600061016783836101d1565b60208301905092915050565b600061017e82610212565b610188818561022a565b935061019383610202565b8060005b838110156101c45781516101ab888261015b565b97506101b68361021d565b925050600181019050610197565b5085935050505092915050565b6101da8161023b565b82525050565b600060208201905081810360008301526101fa8184610173565b905092915050565b6000819050602082019050919050565b600081519050919050565b6000602082019050919050565b600082825260208201905092915050565b600081905091905056fea2646970667358221220d1b52dfe3238df01604ccfbb6c6cee01edbaa74fcffd8a57c4041b0b19e6887664736f6c63430008040033") let abiString = "[{\"inputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"constant\":true,\"inputs\":[],\"name\":\"empty\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256[]\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"}]" - let contractAddress = EthereumAddress("0x200eb5ccda1c35b0f5bf82552fdd65a8aee98e79")! + let receipt = try await deployContract(bytecode: bytecode, abiString: abiString) + let contractAddress = receipt.contractAddress! let web3 = try await Web3.new(LocalTestCase.url) let contract = web3.contract(abiString, at: contractAddress, abiVersion: 2) XCTAssert(contract != nil) @@ -191,20 +128,31 @@ class AdvancedABIv2Tests: LocalTestCase { // MARK: - Encoding ABI Data flow let tx = contract?.createReadOperation("empty") XCTAssertNotNil(tx) - _ = try await tx!.callContractMethod() + let result = try await tx!.callContractMethod() + XCTAssertEqual(result.count, 1) + XCTAssertEqual((result["0"] as? Array)?[0], 1) + XCTAssertEqual((result["0"] as? Array)?[1], 2) } func testUserCase() async throws { + let bytecode = Data(hex: "0x608060405234801561001057600080fd5b50610484806100206000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c8063a16e94bf1461003b578063a46b5b6b14610059575b600080fd5b610043610075565b60405161005091906102a6565b60405180910390f35b610073600480360381019061006e919061022c565b610107565b005b6060600080546100849061037c565b80601f01602080910402602001604051908101604052809291908181526020018280546100b09061037c565b80156100fd5780601f106100d2576101008083540402835291602001916100fd565b820191906000526020600020905b8154815290600101906020018083116100e057829003601f168201915b5050505050905090565b806000908051906020019061011d929190610121565b5050565b82805461012d9061037c565b90600052602060002090601f01602090048101928261014f5760008555610196565b82601f1061016857805160ff1916838001178555610196565b82800160010185558215610196579182015b8281111561019557825182559160200191906001019061017a565b5b5090506101a391906101a7565b5090565b5b808211156101c05760008160009055506001016101a8565b5090565b60006101d76101d2846102ed565b6102c8565b9050828152602081018484840111156101ef57600080fd5b6101fa84828561033a565b509392505050565b600082601f83011261021357600080fd5b81356102238482602086016101c4565b91505092915050565b60006020828403121561023e57600080fd5b600082013567ffffffffffffffff81111561025857600080fd5b61026484828501610202565b91505092915050565b60006102788261031e565b6102828185610329565b9350610292818560208601610349565b61029b8161043d565b840191505092915050565b600060208201905081810360008301526102c0818461026d565b905092915050565b60006102d26102e3565b90506102de82826103ae565b919050565b6000604051905090565b600067ffffffffffffffff8211156103085761030761040e565b5b6103118261043d565b9050602081019050919050565b600081519050919050565b600082825260208201905092915050565b82818337600083830152505050565b60005b8381101561036757808201518184015260208101905061034c565b83811115610376576000848401525b50505050565b6000600282049050600182168061039457607f821691505b602082108114156103a8576103a76103df565b5b50919050565b6103b78261043d565b810181811067ffffffffffffffff821117156103d6576103d561040e565b5b80604052505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6000601f19601f830116905091905056fea2646970667358221220264496be069122017e0e6bd1c3b06e335df83ac7d058c0063a81e9227786693564736f6c63430008040033") let abiString = "[{\"constant\":true,\"inputs\":[],\"name\":\"getFlagData\",\"outputs\":[{\"name\":\"data\",\"type\":\"string\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"data\",\"type\":\"string\"}],\"name\":\"setFlagData\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]" - let contractAddress = EthereumAddress("0x811411e3cdfd4750cdd3552feb3b89a46ddb612e") - let web3 = try await Web3.new(LocalTestCase.url) + let receipt = try await deployContract(bytecode: bytecode, abiString: abiString) + let contractAddress = receipt.contractAddress! + let web3 = try await Web3(provider: Web3HttpProvider(url: LocalTestCase.url, network: nil)) let contract = web3.contract(abiString, at: contractAddress, abiVersion: 2) XCTAssert(contract != nil) + let allAddresses = try await web3.eth.ownedAccounts() + let balance = try await web3.eth.getBalance(for: allAddresses[0]) + let writeOperation = contract?.createWriteOperation("setFlagData", parameters: ["abcdefg"]) + writeOperation?.transaction.from = allAddresses[0] + try await writeOperation?.writeToChain(password: "web3swift", policies: Policies(gasLimitPolicy: .manual(3000000)), sendRaw: false) // MARK: Read data from ABI flow // MARK: - Encoding ABI Data flow let tx = contract?.createReadOperation("getFlagData") XCTAssertNotNil(tx) - _ = try await tx!.callContractMethod() + let result = try await tx!.callContractMethod() + XCTAssertEqual(result["0"] as? String, "abcdefg") } } diff --git a/Tests/web3swiftTests/localTests/LocalTestCase.swift b/Tests/web3swiftTests/localTests/LocalTestCase.swift index 7b79589c4..707fbde93 100644 --- a/Tests/web3swiftTests/localTests/LocalTestCase.swift +++ b/Tests/web3swiftTests/localTests/LocalTestCase.swift @@ -31,4 +31,21 @@ class LocalTestCase: XCTestCase { _ = try! await writeTX.writeToChain(password: "", policies: policies, sendRaw: false) } } + + func deployContract(bytecode: Data, abiString: String) async throws -> TransactionReceipt { + let web3 = try await Web3.new(LocalTestCase.url) + let allAddresses = try await web3.eth.ownedAccounts() + var contract = web3.contract(abiString, at: nil, abiVersion: 2)! + + let parameters: [Any] = [] + // MARK: Writing Data flow + let deployTx = contract.prepareDeploy(bytecode: bytecode, parameters: parameters)! + deployTx.transaction.from = allAddresses[0] + let policies = Policies(gasLimitPolicy: .manual(3000000)) + let result = try await deployTx.writeToChain(password: "web3swift", policies: policies, sendRaw: false) + let txHash = result.hash.stripHexPrefix() + Thread.sleep(forTimeInterval: 1.0) + let receipt = try await web3.eth.transactionReceipt(Data.fromHex(txHash)!) + return receipt + } } diff --git a/Tests/web3swiftTests/localTests/UncategorizedTests.swift b/Tests/web3swiftTests/localTests/UncategorizedTests.swift index f47ccdeec..440dd9dbd 100755 --- a/Tests/web3swiftTests/localTests/UncategorizedTests.swift +++ b/Tests/web3swiftTests/localTests/UncategorizedTests.swift @@ -10,7 +10,7 @@ import BigInt @testable import Web3Core @testable import web3swift -class UncategorizedTests: XCTestCase { +class UncategorizedTests: LocalTestCase { func testBitFunctions () throws { let data = Data([0xf0, 0x02, 0x03]) let firstBit = data.bitsInRange(0, 1) @@ -109,24 +109,28 @@ class UncategorizedTests: XCTestCase { // } func testPublicMappingsAccess() async throws { + let bytecode = Data(hex: "0x608060405234801561001057600080fd5b5061023d806100206000396000f3fe608060405234801561001057600080fd5b50600436106100415760003560e01c8063365b98b2146100465780635e79ab6014610076578063bff1f9e1146100a6575b600080fd5b610060600480360381019061005b919061014a565b6100c4565b60405161006d9190610182565b60405180910390f35b610090600480360381019061008b9190610121565b6100ce565b60405161009d9190610182565b60405180910390f35b6100ae6100ee565b6040516100bb9190610182565b60405180910390f35b6000819050919050565b60008173ffffffffffffffffffffffffffffffffffffffff169050919050565b60006064905090565b600081359050610106816101d9565b92915050565b60008135905061011b816101f0565b92915050565b60006020828403121561013357600080fd5b6000610141848285016100f7565b91505092915050565b60006020828403121561015c57600080fd5b600061016a8482850161010c565b91505092915050565b61017c816101cf565b82525050565b60006020820190506101976000830184610173565b92915050565b60006101a8826101af565b9050919050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000819050919050565b6101e28161019d565b81146101ed57600080fd5b50565b6101f9816101cf565b811461020457600080fd5b5056fea26469706673582212207373b0db986284793522a82bff7bf03e30323defa94e6d25f7141e7d63e1ee0564736f6c63430008040033") let jsonString = "[{\"constant\":true,\"inputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"name\":\"users\",\"outputs\":[{\"name\":\"name\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"\",\"type\":\"address\"}],\"name\":\"userDeviceCount\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"totalUsers\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"}]" + let receipt = try await deployContract(bytecode: bytecode, abiString: jsonString) let web3 = try await Web3.new(LocalTestCase.url) - guard let addr = EthereumAddress("0xdef61132a0c1259464b19e4590e33666aae38574") else {return XCTFail()} - let contract = web3.contract(jsonString, at: addr, abiVersion: 2) - XCTAssert(contract != nil) - let allMethods = contract!.contract.allMethods + guard let addr = receipt.contractAddress else {return XCTFail()} + let contract = web3.contract(jsonString, at: receipt.contractAddress!, abiVersion: 2) + let userDeviceCount = try await contract! - .createReadOperation("userDeviceCount", parameters: [addr])? + .createReadOperation("userDeviceCount", parameters: [addr])! .callContractMethod() let totalUsers = try await contract! - .createReadOperation("totalUsers")? + .createReadOperation("totalUsers")! .callContractMethod() let user = try await contract! - .createReadOperation("users", parameters: [0])? + .createReadOperation("users", parameters: [0])! .callContractMethod() + XCTAssertEqual((userDeviceCount["0"] as? BigUInt)?.hexString.lowercased(), addr.address.lowercased()) + XCTAssertEqual(totalUsers["0"] as? BigUInt, 100) + XCTAssertEqual(user["0"] as? BigUInt, 0) } func testBloomFilterPerformance() throws { From d744289fdc87a7ae297bcd3cd996508ddfa51e6f Mon Sep 17 00:00:00 2001 From: august Date: Sat, 2 Sep 2023 21:22:44 +0800 Subject: [PATCH 16/62] encodeTopic bytes should accept hex string --- Sources/Web3Core/EthereumABI/ABIElements.swift | 2 +- Tests/web3swiftTests/localTests/EventTests.swift | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/Sources/Web3Core/EthereumABI/ABIElements.swift b/Sources/Web3Core/EthereumABI/ABIElements.swift index 73004021c..f238e543f 100755 --- a/Sources/Web3Core/EthereumABI/ABIElements.swift +++ b/Sources/Web3Core/EthereumABI/ABIElements.swift @@ -232,7 +232,7 @@ extension ABI.Element.Event { } return .string(data.sha3(.keccak256).toHexString().addHexPrefix()) case .bytes(length: _): - guard let data = value as? Data, let data = data.setLengthLeft(32) else { + guard let data = ABIEncoder.convertToData(value), let data = data.setLengthLeft(32) else { return nil } return .string(data.toHexString().addHexPrefix()) diff --git a/Tests/web3swiftTests/localTests/EventTests.swift b/Tests/web3swiftTests/localTests/EventTests.swift index 823a122da..7c1b4187b 100644 --- a/Tests/web3swiftTests/localTests/EventTests.swift +++ b/Tests/web3swiftTests/localTests/EventTests.swift @@ -57,7 +57,7 @@ class EventTests: XCTestCase { XCTAssertEqual(logs.count, 6) XCTAssertTrue(logs[0] == topic.toHexString().addHexPrefix()) - XCTAssertTrue(logs[1] == nil) + XCTAssertTrue(logs[1] == "0x2c16c07e1c68d502e9c7ad05f0402b365671a0e6517cb807b2de4edd95657042") XCTAssertTrue(logs[2] == "0x000000000000000000000000581074d2d9e50913eb37665b07cafa9bffdd1640") XCTAssertTrue(logs[3] == "0xab036729af8b8f9b610af4e11b14fa30c348f40c2c230cce92ef6ef37726fee7") XCTAssertTrue(logs[4] == "0x0000000000000000000000000000000000000000000000000000000000000001") @@ -69,10 +69,13 @@ class EventTests: XCTestCase { XCTAssertTrue(ABI.Element.Event.encodeTopic(input: .init(name: "", type: .address, indexed: true), value: "0x003e36550908907c2a2da960fd19a419b9a774b7") == "0x000000000000000000000000003e36550908907c2a2da960fd19a419b9a774b7") XCTAssertTrue(ABI.Element.Event.encodeTopic(input: .init(name: "", type: .address, indexed: true), value: EthereumAddress("0x003e36550908907c2a2da960fd19a419b9a774b7")!) == "0x000000000000000000000000003e36550908907c2a2da960fd19a419b9a774b7") XCTAssertTrue(ABI.Element.Event.encodeTopic(input: .init(name: "", type: .bool, indexed: true), value: true) == "0x0000000000000000000000000000000000000000000000000000000000000001") + XCTAssertTrue(ABI.Element.Event.encodeTopic(input: .init(name: "", type: .bool, indexed: true), value: false) == "0x0000000000000000000000000000000000000000000000000000000000000000") XCTAssertTrue(ABI.Element.Event.encodeTopic(input: .init(name: "", type: .uint(bits: 256), indexed: true), value: BigUInt("dbe20a", radix: 16)!) == "0x0000000000000000000000000000000000000000000000000000000000dbe20a") + XCTAssertTrue(ABI.Element.Event.encodeTopic(input: .init(name: "", type: .uint(bits: 256), indexed: true), value: "dbe20a") == "0x0000000000000000000000000000000000000000000000000000000000dbe20a") XCTAssertTrue(ABI.Element.Event.encodeTopic(input: .init(name: "", type: .int(bits: 32), indexed: true), value: 100) == "0x0000000000000000000000000000000000000000000000000000000000000064") XCTAssertTrue(ABI.Element.Event.encodeTopic(input: .init(name: "", type: .dynamicBytes, indexed: true), value: Data(hex: "6761766f66796f726b")) == "0xe0859ceea0a2fd2474deef2b2183f10f4c741ebba702e9a07d337522c0af55fb") XCTAssertTrue(ABI.Element.Event.encodeTopic(input: .init(name: "", type: .bytes(length: 32), indexed: true), value: Data(hex: "6761766f66796f726b")) == "0x00000000000000000000000000000000000000000000006761766f66796f726b") + XCTAssertTrue(ABI.Element.Event.encodeTopic(input: .init(name: "", type: .bytes(length: 32), indexed: true), value: "0x6761766f66796f726b") == "0x00000000000000000000000000000000000000000000006761766f66796f726b") } } From acd66fe9fb2367fb6bc3f25c78e93b28c4b8a3b5 Mon Sep 17 00:00:00 2001 From: august Date: Sat, 2 Sep 2023 23:03:51 +0800 Subject: [PATCH 17/62] resolve reviews suggestions --- .../Web3Core/Contract/ContractProtocol.swift | 4 +-- .../Web3Core/EthereumABI/ABIElements.swift | 4 +-- .../web3swift/Web3/Web3+HttpProvider.swift | 10 ++++-- .../remoteTests/InfuraTests.swift | 7 ---- .../remoteTests/Web3HttpProviderTests.swift | 33 +++++++++++++++++++ 5 files changed, 45 insertions(+), 13 deletions(-) create mode 100644 Tests/web3swiftTests/remoteTests/Web3HttpProviderTests.swift diff --git a/Sources/Web3Core/Contract/ContractProtocol.swift b/Sources/Web3Core/Contract/ContractProtocol.swift index b35f49050..b1b24ebe1 100755 --- a/Sources/Web3Core/Contract/ContractProtocol.swift +++ b/Sources/Web3Core/Contract/ContractProtocol.swift @@ -321,7 +321,7 @@ extension DefaultContractProtocol { } guard let function = methods[method]?.first else { - throw Web3Error.inputError(desc: "Function method does not exist.") + throw Web3Error.inputError(desc: "Make sure ABI you use contains '\(method)' method.") } switch data.count % 32 { @@ -343,7 +343,7 @@ extension DefaultContractProtocol { throw Web3Error.inputError(desc: "Signature matches \(customError.errorDeclaration) but failed to be decoded.") } } else { - throw Web3Error.inputError(desc: "Found no matched error") + throw Web3Error.inputError(desc: "Make sure ABI you use contains error that can match signature: 0x\(selector.toHexString())") } default: throw Web3Error.inputError(desc: "Invalid data count") diff --git a/Sources/Web3Core/EthereumABI/ABIElements.swift b/Sources/Web3Core/EthereumABI/ABIElements.swift index deda8e25d..22b1dec75 100755 --- a/Sources/Web3Core/EthereumABI/ABIElements.swift +++ b/Sources/Web3Core/EthereumABI/ABIElements.swift @@ -240,7 +240,7 @@ extension ABI.Element.EthError { } /// Decodes `revert(string)` and `require(expression, string)` calls. - /// These calls are decomposed as `Error(string` error. + /// These calls are decomposed as `Error(string)` error. public static func decodeStringError(_ data: Data) -> String? { let decoded = ABIDecoder.decode(types: [.init(name: "", type: .string)], data: data) return decoded?.first as? String @@ -411,6 +411,7 @@ extension ABI.Element.Function { /// // "_parsingError" is optional and is present only if decoding of custom error arguments failed /// "_parsingError": "Data matches MyCustomError(uint256, address senderAddress) but failed to be decoded."] /// ``` + @available(*, deprecated, message: "Use `ABI.Element.EthError.decodeEthError(_:)` instead") public func decodeErrorResponse(_ data: Data, errors: [String: ABI.Element.EthError]? = nil) -> [String: Any]? { /// If data is empty and outputs are expected it is treated as a `require(expression)` or `revert()` call with no message. /// In solidity `require(false)` and `revert()` calls return empty error response. @@ -442,7 +443,6 @@ extension ABI.Element.Function { let errors = errors, let customError = errors[data[data.startIndex ..< data.startIndex + 4].toHexString().stripHexPrefix()] { var errorResponse: [String: Any] = ["_success": false, "_abortedByRevertOrRequire": true, "_error": customError.errorDeclaration] -// customError.decodeEthError(data[4...]) if (data.count > 32 && !customError.inputs.isEmpty), let decodedInputs = ABIDecoder.decode(types: customError.inputs, data: Data(data[data.startIndex + 4 ..< data.startIndex + data.count])) { diff --git a/Sources/web3swift/Web3/Web3+HttpProvider.swift b/Sources/web3swift/Web3/Web3+HttpProvider.swift index 99a1fc8fc..3996f7118 100755 --- a/Sources/web3swift/Web3/Web3+HttpProvider.swift +++ b/Sources/web3swift/Web3/Web3+HttpProvider.swift @@ -37,9 +37,15 @@ public class Web3HttpProvider: Web3Provider { let response: String = try await APIRequest.send(APIRequest.getNetwork.call, parameter: [], with: self).result let result: UInt if response.hasHexPrefix() { - result = UInt(BigUInt(response, radix: 16) ?? Networks.Mainnet.chainID) + guard let num = BigUInt(response, radix: 16) else { + throw Web3Error.processingError(desc: "Get network successed but can't be parsed to a valid chain id") + } + result = UInt(num) } else { - result = UInt(response) ?? UInt(Networks.Mainnet.chainID) + guard let num = UInt(response) else { + throw Web3Error.processingError(desc: "Get network successed but can't be parsed to a valid chain id") + } + result = num } self.network = Networks.fromInt(result) } diff --git a/Tests/web3swiftTests/remoteTests/InfuraTests.swift b/Tests/web3swiftTests/remoteTests/InfuraTests.swift index 7771393df..da1cdf412 100755 --- a/Tests/web3swiftTests/remoteTests/InfuraTests.swift +++ b/Tests/web3swiftTests/remoteTests/InfuraTests.swift @@ -10,13 +10,6 @@ import Web3Core // MARK: Works only with network connection class InfuraTests: XCTestCase { - - func testGetNetwork() async throws { - let requestURLstring = "https://" + Networks.Mainnet.name + Constants.infuraHttpScheme + Constants.infuraToken - let web3 = try await Web3HttpProvider(url: URL(string: requestURLstring)!, network: nil) - XCTAssertEqual(web3.network?.chainID, 1) - } - func testGetBalance() async throws { let web3 = try await Web3.InfuraMainnetWeb3(accessToken: Constants.infuraToken) let address = EthereumAddress("0xd61b5ca425F8C8775882d4defefC68A6979DBbce")! diff --git a/Tests/web3swiftTests/remoteTests/Web3HttpProviderTests.swift b/Tests/web3swiftTests/remoteTests/Web3HttpProviderTests.swift new file mode 100644 index 000000000..5a0558bd6 --- /dev/null +++ b/Tests/web3swiftTests/remoteTests/Web3HttpProviderTests.swift @@ -0,0 +1,33 @@ +// +// Web3HttpProviderTests.swift +// +// +// Created by liugang zhang on 2023/9/2. +// + +import XCTest +import Web3Core + +@testable import web3swift + +final class Web3HttpProviderTests: XCTestCase { + + /// if one of these rpc server lose efficacy, find a substitution from https://chainlist.org/ + func testGetNetwork() async throws { + let requestURLstring = "https://" + Networks.Mainnet.name + Constants.infuraHttpScheme + Constants.infuraToken + var web3 = try await Web3HttpProvider(url: URL(string: requestURLstring)!, network: nil) + XCTAssertEqual(web3.network?.chainID, 1) + + web3 = try await Web3HttpProvider(url: URL(string: "https://arbitrum-one.publicnode.com")!, network: nil) + XCTAssertEqual(web3.network?.chainID, 42161) + + web3 = try await Web3HttpProvider(url: URL(string: "https://rpc.ankr.com/bsc")!, network: nil) + XCTAssertEqual(web3.network?.chainID, 56) + + web3 = try await Web3HttpProvider(url: URL(string: "https://rpc-mainnet.maticvigil.com/")!, network: nil) + XCTAssertEqual(web3.network?.chainID, 137) + + web3 = try await Web3HttpProvider(url: URL(string: "https://optimism.gateway.tenderly.co")!, network: nil) + XCTAssertEqual(web3.network?.chainID, 10) + } +} From 4521eb250ebd86c7d980e63b15e9e5ed89860bf0 Mon Sep 17 00:00:00 2001 From: august Date: Sun, 3 Sep 2023 00:51:48 +0800 Subject: [PATCH 18/62] fix codespell --- Sources/web3swift/Web3/Web3+HttpProvider.swift | 4 ++-- Tests/web3swiftTests/remoteTests/Web3HttpProviderTests.swift | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Sources/web3swift/Web3/Web3+HttpProvider.swift b/Sources/web3swift/Web3/Web3+HttpProvider.swift index 3996f7118..b3b8539b2 100755 --- a/Sources/web3swift/Web3/Web3+HttpProvider.swift +++ b/Sources/web3swift/Web3/Web3+HttpProvider.swift @@ -38,12 +38,12 @@ public class Web3HttpProvider: Web3Provider { let result: UInt if response.hasHexPrefix() { guard let num = BigUInt(response, radix: 16) else { - throw Web3Error.processingError(desc: "Get network successed but can't be parsed to a valid chain id") + throw Web3Error.processingError(desc: "Get network succeeded but can't be parsed to a valid chain id.") } result = UInt(num) } else { guard let num = UInt(response) else { - throw Web3Error.processingError(desc: "Get network successed but can't be parsed to a valid chain id") + throw Web3Error.processingError(desc: "Get network succeeded but can't be parsed to a valid chain id.") } result = num } diff --git a/Tests/web3swiftTests/remoteTests/Web3HttpProviderTests.swift b/Tests/web3swiftTests/remoteTests/Web3HttpProviderTests.swift index 5a0558bd6..b2a5e144b 100644 --- a/Tests/web3swiftTests/remoteTests/Web3HttpProviderTests.swift +++ b/Tests/web3swiftTests/remoteTests/Web3HttpProviderTests.swift @@ -11,7 +11,7 @@ import Web3Core @testable import web3swift final class Web3HttpProviderTests: XCTestCase { - + /// if one of these rpc server lose efficacy, find a substitution from https://chainlist.org/ func testGetNetwork() async throws { let requestURLstring = "https://" + Networks.Mainnet.name + Constants.infuraHttpScheme + Constants.infuraToken From eb32f20aabd73a48511dfda6fc941d40ff41ef16 Mon Sep 17 00:00:00 2001 From: august Date: Sun, 3 Sep 2023 22:03:00 +0800 Subject: [PATCH 19/62] update function documentation --- .../Web3Core/Contract/ContractProtocol.swift | 5 +- .../Web3Core/EthereumABI/ABIElements.swift | 58 +++++-------------- 2 files changed, 19 insertions(+), 44 deletions(-) diff --git a/Sources/Web3Core/Contract/ContractProtocol.swift b/Sources/Web3Core/Contract/ContractProtocol.swift index b1b24ebe1..65f254d3b 100755 --- a/Sources/Web3Core/Contract/ContractProtocol.swift +++ b/Sources/Web3Core/Contract/ContractProtocol.swift @@ -143,7 +143,10 @@ public protocol ContractProtocol { /// - name with arguments:`myFunction(uint256)`. /// - method signature (with or without `0x` prefix, case insensitive): `0xFFffFFff`; /// - data: non empty bytes to decode; - /// - Returns: dictionary with decoded values. `nil` if decoding failed. + /// - Returns: dictionary with decoded values. + /// - Throws: + /// - `Web3Error.revert(String, String?)` when function call aborted by `revert(string)` and `require(expression, string)`. + /// - `Web3Error.revertCustom(String, Dictionary)` when function call aborted by `revert CustomError()`. @discardableResult func decodeReturnData(_ method: String, data: Data) throws -> [String: Any] diff --git a/Sources/Web3Core/EthereumABI/ABIElements.swift b/Sources/Web3Core/EthereumABI/ABIElements.swift index 22b1dec75..7c8f7f60e 100755 --- a/Sources/Web3Core/EthereumABI/ABIElements.swift +++ b/Sources/Web3Core/EthereumABI/ABIElements.swift @@ -202,7 +202,7 @@ extension ABI.Element.Constructor { extension ABI.Element.Function { /// Encode parameters of a given contract method - /// - Parameter parameters: Parameters to pass to Ethereum contract + /// - Parameters: Parameters to pass to Ethereum contract /// - Returns: Encoded data public func encodeParameters(_ parameters: [Any]) -> Data? { guard parameters.count == inputs.count, @@ -223,6 +223,10 @@ extension ABI.Element.Event { // MARK: - Decode custom error extension ABI.Element.EthError { + /// Decodes `revert CustomError(_)` calls. + /// - Parameters: + /// - data: bytes returned by a function call that stripped error signature hash. + /// - Returns: a dictionary containing decoded data mappend to indices and names of returned values or nil if decoding failed. public func decodeEthError(_ data: Data) -> [String: Any]? { guard inputs.count * 32 <= data.count, let decoded = ABIDecoder.decode(types: inputs, data: data) else { @@ -239,7 +243,7 @@ extension ABI.Element.EthError { return result } - /// Decodes `revert(string)` and `require(expression, string)` calls. + /// Decodes `revert(string)` or `require(expression, string)` calls. /// These calls are decomposed as `Error(string)` error. public static func decodeStringError(_ data: Data) -> String? { let decoded = ABIDecoder.decode(types: [.init(name: "", type: .string)], data: data) @@ -299,55 +303,23 @@ extension ABI.Element.Function { return ABIDecoder.decodeInputData(rawData, methodEncoding: methodEncoding, inputs: inputs) } - /// Decodes data returned by a function call. Able to decode `revert(string)`, `revert CustomError(...)` and `require(expression, string)` calls. + /// Decodes data returned by a function call. /// - Parameters: /// - data: bytes returned by a function call; - /// - errors: optional dictionary of known errors that could be returned by the function you called. Used to decode the error information. /// - Returns: a dictionary containing decoded data mappend to indices and names of returned values if these are not `nil`. - /// If `data` is an error response returns dictionary containing all available information about that specific error. Read more for details. + /// - Throws: + /// - `Web3Error.processingError(desc: String)` when decode process failed. /// /// Return cases: - /// - when no `outputs` declared and `data` is not an error response: + /// - when no `outputs` declared: /// ```swift - /// ["_success": true] + /// [:] /// ``` /// - when `outputs` declared and decoding completed successfully: /// ```swift - /// ["_success": true, "0": value_1, "1": value_2, ...] + /// ["0": value_1, "1": value_2, ...] /// ``` /// Additionally this dictionary will have mappings to output names if these names are specified in the ABI; - /// - function call was aborted using `revert(message)` or `require(expression, message)`: - /// ```swift - /// ["_success": false, "_abortedByRevertOrRequire": true, "_errorMessage": message]` - /// ``` - /// - function call was aborted using `revert CustomMessage()` and `errors` argument contains the ABI of that custom error type: - /// ```swift - /// ["_success": false, - /// "_abortedByRevertOrRequire": true, - /// "_error": error_name_and_types, // e.g. `MyCustomError(uint256, address senderAddress)` - /// "0": error_arg1, - /// "1": error_arg2, - /// ..., - /// "error_arg1_name": error_arg1, // Only named arguments will be mapped to their names, e.g. `"senderAddress": EthereumAddress` - /// "error_arg2_name": error_arg2, // Otherwise, you can query them by position index. - /// ...] - /// ``` - /// - in case of any error: - /// ```swift - /// ["_success": false, "_failureReason": String] - /// ``` - /// Error reasons include: - /// - `outputs` declared but at least one value failed to be decoded; - /// - `data.count` is less than `outputs.count * 32`; - /// - `outputs` defined and `data` is empty; - /// - `data` represent reverted transaction - /// - /// How `revert(string)` and `require(expression, string)` return value is decomposed: - /// - `08C379A0` function selector for `Error(string)`; - /// - next 32 bytes are the data offset; - /// - next 32 bytes are the error message length; - /// - the next N bytes, where N >= 32, are the message bytes - /// - the rest are 0 bytes padding. public func decodeReturnData(_ data: Data) throws -> [String: Any] { guard !outputs.isEmpty else { NSLog("Function doesn't have any output types to decode given data.") @@ -355,12 +327,12 @@ extension ABI.Element.Function { } guard outputs.count * 32 <= data.count else { - throw Web3Error.revert("Bytes count must be at least \(outputs.count * 32). Given \(data.count). Decoding will fail.", reason: nil) + throw Web3Error.processingError(desc: "Bytes count must be at least \(outputs.count * 32). Given \(data.count). Decoding will fail.") } // TODO: need improvement - we should be able to tell which value failed to be decoded guard let values = ABIDecoder.decode(types: outputs, data: data) else { - throw Web3Error.revert("Failed to decode at least one value.", reason: nil) + throw Web3Error.processingError(desc: "Failed to decode at least one value.") } var returnArray: [String: Any] = [:] for i in outputs.indices { @@ -411,7 +383,7 @@ extension ABI.Element.Function { /// // "_parsingError" is optional and is present only if decoding of custom error arguments failed /// "_parsingError": "Data matches MyCustomError(uint256, address senderAddress) but failed to be decoded."] /// ``` - @available(*, deprecated, message: "Use `ABI.Element.EthError.decodeEthError(_:)` instead") + @available(*, deprecated, message: "Use decode function from `ABI.Element.EthError` instead") public func decodeErrorResponse(_ data: Data, errors: [String: ABI.Element.EthError]? = nil) -> [String: Any]? { /// If data is empty and outputs are expected it is treated as a `require(expression)` or `revert()` call with no message. /// In solidity `require(false)` and `revert()` calls return empty error response. From 354589f426e3be9e6a00d010cebf14b03ab2b4a9 Mon Sep 17 00:00:00 2001 From: Jenea Vranceanu Date: Wed, 18 Oct 2023 10:51:33 +0300 Subject: [PATCH 20/62] feat(EIP712): parsing of TypedData payload; encoding + hashing; --- .../Web3Core/Utility/String+Extension.swift | 2 +- .../Utils/EIP/{ => EIP712}/EIP712.swift | 78 +++++++++- .../Utils/EIP/EIP712/EIP712Parser.swift | 140 ++++++++++++++++++ .../Utils/Extensions/Data+Extension.swift | 14 ++ .../Utils/Extensions/String+Extension.swift | 16 ++ Sources/web3swift/Web3/Web3+Signing.swift | 17 ++- .../localTests/EIP712Tests.swift | 73 ++++++++- 7 files changed, 333 insertions(+), 7 deletions(-) rename Sources/web3swift/Utils/EIP/{ => EIP712}/EIP712.swift (59%) create mode 100644 Sources/web3swift/Utils/EIP/EIP712/EIP712Parser.swift create mode 100644 Sources/web3swift/Utils/Extensions/Data+Extension.swift create mode 100644 Sources/web3swift/Utils/Extensions/String+Extension.swift diff --git a/Sources/Web3Core/Utility/String+Extension.swift b/Sources/Web3Core/Utility/String+Extension.swift index dbe0a10ca..695d4ec63 100755 --- a/Sources/Web3Core/Utility/String+Extension.swift +++ b/Sources/Web3Core/Utility/String+Extension.swift @@ -120,7 +120,7 @@ extension String { let to16 = utf16.index(utf16.startIndex, offsetBy: nsRange.location + nsRange.length, limitedBy: utf16.endIndex), let from = from16.samePosition(in: self), let to = to16.samePosition(in: self) - else { return nil } + else { return nil } return from ..< to } diff --git a/Sources/web3swift/Utils/EIP/EIP712.swift b/Sources/web3swift/Utils/EIP/EIP712/EIP712.swift similarity index 59% rename from Sources/web3swift/Utils/EIP/EIP712.swift rename to Sources/web3swift/Utils/EIP/EIP712/EIP712.swift index e3a4bcb87..1d0b85147 100644 --- a/Sources/web3swift/Utils/EIP/EIP712.swift +++ b/Sources/web3swift/Utils/EIP/EIP712/EIP712.swift @@ -16,6 +16,12 @@ public class EIP712 { public typealias Bytes = Data } +// FIXME: this type is wrong - The minimum number of optional fields is 5, and those are +// string name the user readable name of signing domain, i.e. the name of the DApp or the protocol. +// string version the current major version of the signing domain. Signatures from different versions are not compatible. +// uint256 chainId the EIP-155 chain id. The user-agent should refuse signing if it does not match the currently active chain. +// address verifyingContract the address of the contract that will verify the signature. The user-agent may do contract specific phishing prevention. +// bytes32 salt an disambiguating salt for the protocol. This can be used as a domain separator of last resort. public struct EIP712Domain: EIP712Hashable { public let chainId: EIP712.UInt256? public let verifyingContract: EIP712.Address @@ -54,7 +60,10 @@ public extension EIP712Hashable { result = ABIEncoder.encodeSingleType(type: .uint(bits: 256), value: field)! case is EIP712.Address: result = ABIEncoder.encodeSingleType(type: .address, value: field)! + case let boolean as Bool: + result = ABIEncoder.encodeSingleType(type: .uint(bits: 8), value: boolean ? 1 : 0)! case let hashable as EIP712Hashable: + // TODO: should it be hashed here? result = try hashable.hash() default: /// Cast to `AnyObject` is required. Otherwise, `nil` value will fail this condition. @@ -64,16 +73,77 @@ public extension EIP712Hashable { preconditionFailure("Not solidity type") } } - guard result.count == 32 else { preconditionFailure("ABI encode error") } + guard result.count % 32 == 0 else { preconditionFailure("ABI encode error") } parameters.append(result) } return Data(parameters.flatMap { $0.bytes }).sha3(.keccak256) } } -public func eip712encode(domainSeparator: EIP712Hashable, message: EIP712Hashable) throws -> Data { - let data = try Data([UInt8(0x19), UInt8(0x01)]) + domainSeparator.hash() + message.hash() - return data.sha3(.keccak256) +public func eip712hash(domainSeparator: EIP712Hashable, message: EIP712Hashable) throws -> Data { + try eip712hash(domainSeparatorHash: domainSeparator.hash(), messageHash: message.hash()) +} + +public func eip712hash(_ eip712TypedData: EIP712TypedData) throws -> Data { + guard let chainId = eip712TypedData.domain["chainId"] as? Int64, + let verifyingContract = eip712TypedData.domain["verifyingContract"] as? String, + let verifyingContractAddress = EIP712.Address(verifyingContract) + else { + throw Web3Error.inputError(desc: "Failed to parse chainId or verifyingContract address. Domain object is \(eip712TypedData.domain).") + } + + let domainHash = try EIP712Domain(chainId: EIP712.UInt256(chainId), verifyingContract: verifyingContractAddress).hash() + guard let primaryTypeData = eip712TypedData.types[eip712TypedData.primaryType] else { + throw Web3Error.inputError(desc: "EIP712 hashing error. Given primary type name is not present amongst types. primaryType - \(eip712TypedData.primaryType); available types - \(eip712TypedData.types.values)") + } + + let messageHash = try hashEip712Message(eip712TypedData, + eip712TypedData.message, + messageTypeData: primaryTypeData) + return eip712hash(domainSeparatorHash: domainHash, messageHash: messageHash) +} + +func hashEip712Message(_ typedData: EIP712TypedData, _ message: [String: AnyObject], messageTypeData: [EIP712TypeProperty]) throws -> Data { + var messageData: [Data] = [] + for field in messageTypeData { + guard let fieldValue = message[field.name] else { + throw Web3Error.inputError(desc: "EIP712 message doesn't have field with name \(field.name).") + } + + if let customType = typedData.types[field.type] { + guard let objectAttribute = fieldValue as? [String: AnyObject] else { + throw Web3Error.processingError(desc: "Failed to hash EIP712 message. A property from 'message' field with custom type cannot be represented as object and thus encoded & hashed. Property name \(field.name); value \(String(describing: message[field.name])).") + } + try messageData.append(hashEip712Message(typedData, objectAttribute, messageTypeData: customType)) + } else { + let type = try ABITypeParser.parseTypeString(field.type) + var data: Data? + switch type { + case .dynamicBytes, .bytes: + if let bytes = fieldValue as? Data { + data = bytes.sha3(.keccak256) + } + case .string: + if let string = fieldValue as? String { + data = Data(string.bytes).sha3(.keccak256) + } + default: + data = ABIEncoder.encodeSingleType(type: type, value: fieldValue) + } + + if let data = data { + messageData.append(data) + } else { + throw Web3Error.processingError(desc: "Failed to encode property of EIP712 message. Property name \(field.name); value \(String(describing: message[field.name]))") + } + } + } + + return Data(messageData.flatMap { $0.bytes }).sha3(.keccak256) +} + +public func eip712hash(domainSeparatorHash: Data, messageHash: Data) -> Data { + (Data([UInt8(0x19), UInt8(0x01)]) + domainSeparatorHash + messageHash).sha3(.keccak256) } // MARK: - Additional private and public extensions with support members diff --git a/Sources/web3swift/Utils/EIP/EIP712/EIP712Parser.swift b/Sources/web3swift/Utils/EIP/EIP712/EIP712Parser.swift new file mode 100644 index 000000000..31c72b6ee --- /dev/null +++ b/Sources/web3swift/Utils/EIP/EIP712/EIP712Parser.swift @@ -0,0 +1,140 @@ +// +// EIP712Parser.swift +// +// Created by JeneaVranceanu on 17.10.2023. +// + +import Foundation +import Web3Core + +/// The only purpose of this class is to parse raw JSON and output an EIP712 hash. +/// Example of a payload that is received via `eth_signTypedData` for signing: +/// ``` +/// { +/// "types":{ +/// "EIP712Domain":[ +/// { +/// "name":"name", +/// "type":"string" +/// }, +/// { +/// "name":"version", +/// "type":"string" +/// }, +/// { +/// "name":"chainId", +/// "type":"uint256" +/// }, +/// { +/// "name":"verifyingContract", +/// "type":"address" +/// } +/// ], +/// "Person":[ +/// { +/// "name":"name", +/// "type":"string" +/// }, +/// { +/// "name":"wallet", +/// "type":"address" +/// } +/// ], +/// "Mail":[ +/// { +/// "name":"from", +/// "type":"Person" +/// }, +/// { +/// "name":"to", +/// "type":"Person" +/// }, +/// { +/// "name":"contents", +/// "type":"string" +/// } +/// ] +/// }, +/// "primaryType":"Mail", +/// "domain":{ +/// "name":"Ether Mail", +/// "version":"1", +/// "chainId":1, +/// "verifyingContract":"0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC" +/// }, +/// "message":{ +/// "from":{ +/// "name":"Cow", +/// "wallet":"0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826" +/// }, +/// "to":{ +/// "name":"Bob", +/// "wallet":"0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB" +/// }, +/// "contents":"Hello, Bob!" +/// } +/// } +/// ``` +public class EIP712Parser { + static func toData(_ json: String) throws -> Data { + guard let json = json.data(using: .utf8) else { + throw Web3Error.inputError(desc: "Failed to parse EIP712 payload. Given string is not valid UTF8 string. \(json)") + } + return json + } + + public static func parse(_ rawJson: String) throws -> EIP712TypedData { + try parse(try toData(rawJson)) + } + + public static func parse(_ rawJson: Data) throws -> EIP712TypedData { + let decoder = JSONDecoder() + let types = try decoder.decode(EIP712TypeArray.self, from: rawJson).types + guard let json = try rawJson.asJsonDictionary(), + let primaryType = json["primaryType"] as? String, + let domain = json["domain"] as? [String : AnyObject], + let message = json["message"] as? [String : AnyObject] + else { + throw Web3Error.inputError(desc: "EIP712Parser: cannot decode EIP712TypedData object. Failed to parse one of primaryType, domain or message fields. Is any field missing?") + } + + return EIP712TypedData(types: types, primaryType: primaryType, domain: domain, message: message) + } +} + +internal struct EIP712TypeArray: Codable { + public let types: [String : [EIP712TypeProperty]] +} + +public struct EIP712TypeProperty: Codable { + /// Property name. An arbitrary string. + public let name: String + /// Property type. A type that's ABI encodable. + public let type: String + + public init(name: String, type: String) { + self.name = name + self.type = type + } +} + +public struct EIP712TypedData { + public let types: [String: [EIP712TypeProperty]] + /// A name of one of the types from `types`. + public let primaryType: String + /// A JSON object as a string + public let domain: [String : AnyObject] + /// A JSON object as a string + public let message: [String : AnyObject] + + public init(types: [String : [EIP712TypeProperty]], + primaryType: String, + domain: [String : AnyObject], + message: [String : AnyObject]) { + self.types = types + self.primaryType = primaryType + self.domain = domain + self.message = message + } + +} diff --git a/Sources/web3swift/Utils/Extensions/Data+Extension.swift b/Sources/web3swift/Utils/Extensions/Data+Extension.swift new file mode 100644 index 000000000..32aeb2853 --- /dev/null +++ b/Sources/web3swift/Utils/Extensions/Data+Extension.swift @@ -0,0 +1,14 @@ +// +// Data+Extension.swift +// +// Created by JeneaVranceanu on 18.10.2023. +// + +import Foundation + +extension Data { + + func asJsonDictionary() throws -> [String: AnyObject]? { + try JSONSerialization.jsonObject(with: self, options: .mutableContainers) as? [String:AnyObject] + } +} diff --git a/Sources/web3swift/Utils/Extensions/String+Extension.swift b/Sources/web3swift/Utils/Extensions/String+Extension.swift new file mode 100644 index 000000000..704897c8b --- /dev/null +++ b/Sources/web3swift/Utils/Extensions/String+Extension.swift @@ -0,0 +1,16 @@ +// +// String+Extension.swift +// +// +// Created by JeneaVranceanu on 17.10.2023. +// + +import Foundation + +extension String { + + func asJsonDictionary() throws -> [String: AnyObject]? { + guard let data = data(using: .utf8) else { return nil } + return try data.asJsonDictionary() + } +} diff --git a/Sources/web3swift/Web3/Web3+Signing.swift b/Sources/web3swift/Web3/Web3+Signing.swift index 46fe01ce5..d87c4f3dc 100755 --- a/Sources/web3swift/Web3/Web3+Signing.swift +++ b/Sources/web3swift/Web3/Web3+Signing.swift @@ -40,6 +40,21 @@ public struct Web3Signer { return compressedSignature } + public static func signEIP712(_ eip712TypedDataPayload: EIP712TypedData, + keystore: BIP32Keystore, + account: EthereumAddress, + password: String? = nil) throws -> Data { + let hash = try eip712hash(eip712TypedDataPayload) + guard let signature = try Web3Signer.signPersonalMessage(hash, + keystore: keystore, + account: account, + password: password ?? "") + else { + throw Web3Error.dataError + } + return signature + } + public static func signEIP712(_ eip712Hashable: EIP712Hashable, keystore: BIP32Keystore, verifyingContract: EthereumAddress, @@ -48,7 +63,7 @@ public struct Web3Signer { chainId: BigUInt? = nil) throws -> Data { let domainSeparator: EIP712Hashable = EIP712Domain(chainId: chainId, verifyingContract: verifyingContract) - let hash = try eip712encode(domainSeparator: domainSeparator, message: eip712Hashable) + let hash = try eip712hash(domainSeparator: domainSeparator, message: eip712Hashable) guard let signature = try Web3Signer.signPersonalMessage(hash, keystore: keystore, account: account, diff --git a/Tests/web3swiftTests/localTests/EIP712Tests.swift b/Tests/web3swiftTests/localTests/EIP712Tests.swift index 3f527d2cf..00a40e1fc 100644 --- a/Tests/web3swiftTests/localTests/EIP712Tests.swift +++ b/Tests/web3swiftTests/localTests/EIP712Tests.swift @@ -2,7 +2,74 @@ import XCTest import Web3Core @testable import web3swift -class EIP712Tests: LocalTestCase { +class EIP712Tests: XCTestCase { + let testTypedDataPayload = """ + { + "types":{ + "EIP712Domain":[ + { + "name":"name", + "type":"string" + }, + { + "name":"version", + "type":"string" + }, + { + "name":"chainId", + "type":"uint256" + }, + { + "name":"verifyingContract", + "type":"address" + } + ], + "Person":[ + { + "name":"name", + "type":"string" + }, + { + "name":"wallet", + "type":"address" + } + ], + "Mail":[ + { + "name":"from", + "type":"Person" + }, + { + "name":"to", + "type":"Person" + }, + { + "name":"contents", + "type":"string" + } + ] + }, + "primaryType":"Mail", + "domain":{ + "name":"Ether Mail", + "version":"1", + "chainId":1, + "verifyingContract":"0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC" + }, + "message":{ + "from":{ + "name":"Cow", + "wallet":"0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826" + }, + "to":{ + "name":"Bob", + "wallet":"0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB" + }, + "contents":"Hello, Bob!" + } + } + """ + func testWithoutChainId() throws { let to = EthereumAddress("0x3F06bAAdA68bB997daB03d91DBD0B73e196c5A4d")! let value = EIP712.UInt256(0) @@ -104,4 +171,8 @@ class EIP712Tests: LocalTestCase { chainId: chainId) XCTAssertEqual(signature.toHexString(), "9ee2aadf14739e1cafc3bc1a0b48457c12419d5b480a8ffa86eb7df538c82d0753ca2a6f8024dea576b383cbcbe5e2b181b087e489298674bf6512756cabc5b01b") } + + func testEIP712Parser() throws { + try NSLog(String.init(describing: EIP712Parser.parse(testTypedDataPayload))) + } } From 0342f911c8caf121453936ddfb3f4d9c4b03c515 Mon Sep 17 00:00:00 2001 From: Jenea Vranceanu Date: Wed, 18 Oct 2023 13:43:29 +0300 Subject: [PATCH 21/62] featEIP712): parser test --- .../localTests/EIP712ParserTests.swift | 124 ++++++++++++++++++ .../localTests/EIP712Tests.swift | 70 ---------- 2 files changed, 124 insertions(+), 70 deletions(-) create mode 100644 Tests/web3swiftTests/localTests/EIP712ParserTests.swift diff --git a/Tests/web3swiftTests/localTests/EIP712ParserTests.swift b/Tests/web3swiftTests/localTests/EIP712ParserTests.swift new file mode 100644 index 000000000..54274c46f --- /dev/null +++ b/Tests/web3swiftTests/localTests/EIP712ParserTests.swift @@ -0,0 +1,124 @@ +// +// EIP712ParserTests.swift +// +// +// Created by JeneaVranceanu on 18.10.2023. +// + +import Foundation +import XCTest +import web3swift +import Web3Core + +class EIP712ParserTests: XCTestCase { + let testTypedDataPayload = """ + { + "types":{ + "EIP712Domain":[ + { + "name":"name", + "type":"string" + }, + { + "name":"version", + "type":"string" + }, + { + "name":"chainId", + "type":"uint256" + }, + { + "name":"verifyingContract", + "type":"address" + } + ], + "Person":[ + { + "name":"name", + "type":"string" + }, + { + "name":"wallet", + "type":"address" + } + ], + "Mail":[ + { + "name":"from", + "type":"Person" + }, + { + "name":"to", + "type":"Person" + }, + { + "name":"contents", + "type":"string" + } + ] + }, + "primaryType":"Mail", + "domain":{ + "name":"Ether Mail", + "version":"1", + "chainId":1, + "verifyingContract":"0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC" + }, + "message":{ + "from":{ + "name":"Cow", + "wallet":"0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826" + }, + "to":{ + "name":"Bob", + "wallet":"0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB" + }, + "contents":"Hello, Bob!" + } + } + """ + + func testEIP712Parser() throws { + let parsedEip712TypedData = try EIP712Parser.parse(testTypedDataPayload) + + XCTAssertEqual(parsedEip712TypedData.types.count, 3) + let eip712Domain = parsedEip712TypedData.types["EIP712Domain"] + XCTAssertNotNil(eip712Domain) + let person = parsedEip712TypedData.types["Person"] + XCTAssertNotNil(person) + let mail = parsedEip712TypedData.types["Mail"] + XCTAssertNotNil(mail) + + + XCTAssertNotNil(eip712Domain?.first { $0.name == "name" && $0.type == "string"}) + XCTAssertNotNil(eip712Domain?.first { $0.name == "version" && $0.type == "string"}) + XCTAssertNotNil(eip712Domain?.first { $0.name == "chainId" && $0.type == "uint256"}) + XCTAssertNotNil(eip712Domain?.first { $0.name == "verifyingContract" && $0.type == "address"}) + + + XCTAssertNotNil(person?.first { $0.name == "name" && $0.type == "string"}) + XCTAssertNotNil(person?.first { $0.name == "wallet" && $0.type == "address"}) + + XCTAssertNotNil(mail?.first { $0.name == "from" && $0.type == "Person"}) + XCTAssertNotNil(mail?.first { $0.name == "to" && $0.type == "Person"}) + XCTAssertNotNil(mail?.first { $0.name == "contents" && $0.type == "string"}) + + XCTAssertEqual(parsedEip712TypedData.primaryType, "Mail") + + XCTAssertEqual(parsedEip712TypedData.domain.count, 4) + XCTAssertEqual(parsedEip712TypedData.domain["name"] as? String, "Ether Mail") + XCTAssertEqual(parsedEip712TypedData.domain["version"] as? String, "1") + XCTAssertEqual(parsedEip712TypedData.domain["chainId"] as? Int, 1) + XCTAssertEqual(parsedEip712TypedData.domain["verifyingContract"] as? String, "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC") + + + XCTAssertEqual(parsedEip712TypedData.message.count, 3) + XCTAssertEqual(parsedEip712TypedData.message["from"] as? [String : String], + ["name" : "Cow", + "wallet" : "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826"]) + XCTAssertEqual(parsedEip712TypedData.message["to"] as? [String : String], + ["name" : "Bob", + "wallet" : "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB"]) + XCTAssertEqual(parsedEip712TypedData.message["contents"] as? String, "Hello, Bob!") + } +} diff --git a/Tests/web3swiftTests/localTests/EIP712Tests.swift b/Tests/web3swiftTests/localTests/EIP712Tests.swift index 00a40e1fc..908490e88 100644 --- a/Tests/web3swiftTests/localTests/EIP712Tests.swift +++ b/Tests/web3swiftTests/localTests/EIP712Tests.swift @@ -3,72 +3,6 @@ import Web3Core @testable import web3swift class EIP712Tests: XCTestCase { - let testTypedDataPayload = """ - { - "types":{ - "EIP712Domain":[ - { - "name":"name", - "type":"string" - }, - { - "name":"version", - "type":"string" - }, - { - "name":"chainId", - "type":"uint256" - }, - { - "name":"verifyingContract", - "type":"address" - } - ], - "Person":[ - { - "name":"name", - "type":"string" - }, - { - "name":"wallet", - "type":"address" - } - ], - "Mail":[ - { - "name":"from", - "type":"Person" - }, - { - "name":"to", - "type":"Person" - }, - { - "name":"contents", - "type":"string" - } - ] - }, - "primaryType":"Mail", - "domain":{ - "name":"Ether Mail", - "version":"1", - "chainId":1, - "verifyingContract":"0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC" - }, - "message":{ - "from":{ - "name":"Cow", - "wallet":"0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826" - }, - "to":{ - "name":"Bob", - "wallet":"0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB" - }, - "contents":"Hello, Bob!" - } - } - """ func testWithoutChainId() throws { let to = EthereumAddress("0x3F06bAAdA68bB997daB03d91DBD0B73e196c5A4d")! @@ -171,8 +105,4 @@ class EIP712Tests: XCTestCase { chainId: chainId) XCTAssertEqual(signature.toHexString(), "9ee2aadf14739e1cafc3bc1a0b48457c12419d5b480a8ffa86eb7df538c82d0753ca2a6f8024dea576b383cbcbe5e2b181b087e489298674bf6512756cabc5b01b") } - - func testEIP712Parser() throws { - try NSLog(String.init(describing: EIP712Parser.parse(testTypedDataPayload))) - } } From a9b23fb76690be6c7c613c76de5a0fb3e7ea41bf Mon Sep 17 00:00:00 2001 From: Jenea Vranceanu Date: Wed, 18 Oct 2023 13:45:01 +0300 Subject: [PATCH 22/62] chore(EIP712): renamed test file --- ...12ParserTests.swift => EIP712TypedDataPayloadTests.swift} | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) rename Tests/web3swiftTests/localTests/{EIP712ParserTests.swift => EIP712TypedDataPayloadTests.swift} (97%) diff --git a/Tests/web3swiftTests/localTests/EIP712ParserTests.swift b/Tests/web3swiftTests/localTests/EIP712TypedDataPayloadTests.swift similarity index 97% rename from Tests/web3swiftTests/localTests/EIP712ParserTests.swift rename to Tests/web3swiftTests/localTests/EIP712TypedDataPayloadTests.swift index 54274c46f..54f985c8f 100644 --- a/Tests/web3swiftTests/localTests/EIP712ParserTests.swift +++ b/Tests/web3swiftTests/localTests/EIP712TypedDataPayloadTests.swift @@ -1,6 +1,5 @@ // -// EIP712ParserTests.swift -// +// EIP712TypedDataPayloadTests.swift // // Created by JeneaVranceanu on 18.10.2023. // @@ -10,7 +9,7 @@ import XCTest import web3swift import Web3Core -class EIP712ParserTests: XCTestCase { +class EIP712TypedDataPayloadTests: XCTestCase { let testTypedDataPayload = """ { "types":{ From ce459b6ef675c761997f42db71e36201b5865649 Mon Sep 17 00:00:00 2001 From: Jenea Vranceanu Date: Wed, 18 Oct 2023 15:28:30 +0300 Subject: [PATCH 23/62] feat(EIP712): type encoding, type hashing, circular dependency checks, tests; --- .../Utils/EIP/EIP712/EIP712Parser.swift | 100 +++++++++++++++++- .../EIP712TypedDataPayloadTests.swift | 99 +++++++++++++++++ 2 files changed, 197 insertions(+), 2 deletions(-) diff --git a/Sources/web3swift/Utils/EIP/EIP712/EIP712Parser.swift b/Sources/web3swift/Utils/EIP/EIP712/EIP712Parser.swift index 31c72b6ee..8e1370a75 100644 --- a/Sources/web3swift/Utils/EIP/EIP712/EIP712Parser.swift +++ b/Sources/web3swift/Utils/EIP/EIP712/EIP712Parser.swift @@ -98,7 +98,7 @@ public class EIP712Parser { throw Web3Error.inputError(desc: "EIP712Parser: cannot decode EIP712TypedData object. Failed to parse one of primaryType, domain or message fields. Is any field missing?") } - return EIP712TypedData(types: types, primaryType: primaryType, domain: domain, message: message) + return try EIP712TypedData(types: types, primaryType: primaryType, domain: domain, message: message) } } @@ -130,11 +130,107 @@ public struct EIP712TypedData { public init(types: [String : [EIP712TypeProperty]], primaryType: String, domain: [String : AnyObject], - message: [String : AnyObject]) { + message: [String : AnyObject]) throws { self.types = types self.primaryType = primaryType self.domain = domain self.message = message + if let problematicType = hasCircularDependency() { + throw Web3Error.inputError(desc: "Created EIP712TypedData has a circular dependency amongst it's types. Cycle was first identified in '\(problematicType)'. Review it's uses in 'types'.") + } } + /// Checks for a circular dependency among the given types. + /// + /// If a circular dependency is detected, it returns the name of the type where the cycle was first identified. + /// Otherwise, it returns `nil`. + /// + /// - Returns: The type name where a circular dependency is detected, or `nil` if no circular dependency exists. + /// - Note: The function utilizes depth-first search to identify the circular dependencies. + func hasCircularDependency() -> String? { + + /// Generates an adjacency list for the given types, representing their dependencies. + /// + /// - Parameter types: A dictionary mapping type names to their property definitions. + /// - Returns: An adjacency list representing type dependencies. + func createAdjacencyList(types: [String: [EIP712TypeProperty]]) -> [String: [String]] { + var adjList: [String: [String]] = [:] + + for (typeName, fields) in types { + adjList[typeName] = [] + for field in fields { + if types.keys.contains(field.type) { + adjList[typeName]?.append(field.type) + } + } + } + + return adjList + } + + let adjList = createAdjacencyList(types: types) + + /// Depth-first search to check for circular dependencies. + /// + /// - Parameters: + /// - node: The current type being checked. + /// - visited: A dictionary keeping track of the visited types. + /// - stack: A dictionary used for checking the current path for cycles. + /// + /// - Returns: `true` if a cycle is detected from the current node, `false` otherwise. + func depthFirstSearch(node: String, visited: inout [String: Bool], stack: inout [String: Bool]) -> Bool { + visited[node] = true + stack[node] = true + + for neighbor in adjList[node] ?? [] { + if visited[neighbor] == nil { + if depthFirstSearch(node: neighbor, visited: &visited, stack: &stack) { + return true + } + } else if stack[neighbor] == true { + return true + } + } + + stack[node] = false + return false + } + + var visited: [String: Bool] = [:] + var stack: [String: Bool] = [:] + + for typeName in adjList.keys { + if visited[typeName] == nil { + if depthFirstSearch(node: typeName, visited: &visited, stack: &stack) { + return typeName + } + } + } + + return nil + } + + public func encodeType(_ type: String) throws -> String { + guard let typeData = types[type] else { + throw Web3Error.processingError(desc: "EIP712. Attempting to encode type that doesn't exist in this payload. Given type: \(type). Available types: \(types.values).") + } + return try encodeType(type, typeData) + } + + public func typeHash(_ type: String) throws -> String { + try encodeType(type).sha3(.keccak256).addHexPrefix() + } + + internal func encodeType(_ type: String, _ typeData: [EIP712TypeProperty], typesCovered: [String] = []) throws -> String { + var typesCovered = typesCovered + var encodedSubtypes: [String] = [] + let parameters = try typeData.map { attributeType in + if let innerTypes = types[attributeType.type], !typesCovered.contains(attributeType.type) { + encodedSubtypes.append(try encodeType(attributeType.type, innerTypes)) + typesCovered.append(attributeType.type) + } + return "\(attributeType.type) \(attributeType.name)" + } + return type + "(" + parameters.joined(separator: ",") + ")" + encodedSubtypes.joined(separator: "") + } } diff --git a/Tests/web3swiftTests/localTests/EIP712TypedDataPayloadTests.swift b/Tests/web3swiftTests/localTests/EIP712TypedDataPayloadTests.swift index 54f985c8f..ddf6321ad 100644 --- a/Tests/web3swiftTests/localTests/EIP712TypedDataPayloadTests.swift +++ b/Tests/web3swiftTests/localTests/EIP712TypedDataPayloadTests.swift @@ -120,4 +120,103 @@ class EIP712TypedDataPayloadTests: XCTestCase { "wallet" : "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB"]) XCTAssertEqual(parsedEip712TypedData.message["contents"] as? String, "Hello, Bob!") } + + func testEIP712CircularDependency() throws { + let problematicTypeExample = """ + { + "types":{ + "EIP712Domain":[ + { + "name":"name", + "type":"string" + }, + { + "name":"version", + "type":"string" + }, + { + "name":"chainId", + "type":"uint256" + }, + { + "name":"verifyingContract", + "type":"address" + } + ], + "Person":[ + { + "name":"name", + "type":"string" + }, + { + "name":"wallet", + "type":"address" + }, + { + "name":"mail", + "type":"Mail" + } + ], + "Mail":[ + { + "name":"from", + "type":"Person" + }, + { + "name":"to", + "type":"Person" + }, + { + "name":"contents", + "type":"string" + } + ] + }, + "primaryType":"Mail", + "domain":{ + "name":"Ether Mail", + "version":"1", + "chainId":1, + "verifyingContract":"0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC" + }, + "message":{ + "from":{ + "name":"Cow", + "wallet":"0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826" + }, + "to":{ + "name":"Bob", + "wallet":"0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB" + }, + "contents":"Hello, Bob!" + } + } + """ + XCTAssertThrowsError(try EIP712Parser.parse(problematicTypeExample)) { error in + guard let error = error as? Web3Error else { + XCTFail("Thrown error is not Web3Error.") + return + } + + if case let .inputError(desc) = error { + XCTAssertTrue(desc.hasPrefix("Created EIP712TypedData has a circular dependency amongst it's types.")) + } else { + XCTFail("A different Web3Error is thrown. Something changed?") + } + } + } + + func testEIP712EncodeType() throws { + let parsedEip712TypedData = try EIP712Parser.parse(testTypedDataPayload) + try XCTAssertEqual(parsedEip712TypedData.encodeType("EIP712Domain"), "EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)") + try XCTAssertEqual(parsedEip712TypedData.encodeType("Person"), "Person(string name,address wallet)") + try XCTAssertEqual(parsedEip712TypedData.encodeType("Mail"), "Mail(Person from,Person to,string contents)Person(string name,address wallet)") + } + + func testEIP712TypeHash() throws { + let parsedEip712TypedData = try EIP712Parser.parse(testTypedDataPayload) + try XCTAssertEqual(parsedEip712TypedData.typeHash("EIP712Domain"), "0x8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f") + try XCTAssertEqual(parsedEip712TypedData.typeHash("Person"), "0xb9d8c78acf9b987311de6c7b45bb6a9c8e1bf361fa7fd3467a2163f994c79500") + try XCTAssertEqual(parsedEip712TypedData.typeHash("Mail"), "0xa0cedeb2dc280ba39b857546d74f5549c3a1d7bdc2dd96bf881f76108e23dac2") + } } From 89595ad46ed5161d18e3459b2c77ac7fc7593ed0 Mon Sep 17 00:00:00 2001 From: Jenea Vranceanu Date: Wed, 18 Oct 2023 15:32:15 +0300 Subject: [PATCH 24/62] chore(EIP712): added link to the tests + implementation source --- .../web3swiftTests/localTests/EIP712TypedDataPayloadTests.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Tests/web3swiftTests/localTests/EIP712TypedDataPayloadTests.swift b/Tests/web3swiftTests/localTests/EIP712TypedDataPayloadTests.swift index ddf6321ad..d5c9cb11c 100644 --- a/Tests/web3swiftTests/localTests/EIP712TypedDataPayloadTests.swift +++ b/Tests/web3swiftTests/localTests/EIP712TypedDataPayloadTests.swift @@ -9,6 +9,8 @@ import XCTest import web3swift import Web3Core + +/// Tests based primarily on the following example https://eips.ethereum.org/assets/eip-712/Example.js class EIP712TypedDataPayloadTests: XCTestCase { let testTypedDataPayload = """ { From 27d5b611d9a7194453773402660a43f6a00d6b28 Mon Sep 17 00:00:00 2001 From: Jenea Vranceanu Date: Thu, 19 Oct 2023 00:07:46 +0300 Subject: [PATCH 25/62] feat(EIP712): impl of encodeData, structHash and signHash functions for EIP712TypedData --- .../web3swift/Utils/EIP/EIP712/EIP712.swift | 58 ----------------- .../Utils/EIP/EIP712/EIP712Parser.swift | 62 +++++++++++++++++++ Sources/web3swift/Web3/Web3+Signing.swift | 8 ++- .../EIP712TypedDataPayloadTests.swift | 54 +++++++++++++++- 4 files changed, 120 insertions(+), 62 deletions(-) diff --git a/Sources/web3swift/Utils/EIP/EIP712/EIP712.swift b/Sources/web3swift/Utils/EIP/EIP712/EIP712.swift index 1d0b85147..245fab7e6 100644 --- a/Sources/web3swift/Utils/EIP/EIP712/EIP712.swift +++ b/Sources/web3swift/Utils/EIP/EIP712/EIP712.swift @@ -84,64 +84,6 @@ public func eip712hash(domainSeparator: EIP712Hashable, message: EIP712Hashable) try eip712hash(domainSeparatorHash: domainSeparator.hash(), messageHash: message.hash()) } -public func eip712hash(_ eip712TypedData: EIP712TypedData) throws -> Data { - guard let chainId = eip712TypedData.domain["chainId"] as? Int64, - let verifyingContract = eip712TypedData.domain["verifyingContract"] as? String, - let verifyingContractAddress = EIP712.Address(verifyingContract) - else { - throw Web3Error.inputError(desc: "Failed to parse chainId or verifyingContract address. Domain object is \(eip712TypedData.domain).") - } - - let domainHash = try EIP712Domain(chainId: EIP712.UInt256(chainId), verifyingContract: verifyingContractAddress).hash() - guard let primaryTypeData = eip712TypedData.types[eip712TypedData.primaryType] else { - throw Web3Error.inputError(desc: "EIP712 hashing error. Given primary type name is not present amongst types. primaryType - \(eip712TypedData.primaryType); available types - \(eip712TypedData.types.values)") - } - - let messageHash = try hashEip712Message(eip712TypedData, - eip712TypedData.message, - messageTypeData: primaryTypeData) - return eip712hash(domainSeparatorHash: domainHash, messageHash: messageHash) -} - -func hashEip712Message(_ typedData: EIP712TypedData, _ message: [String: AnyObject], messageTypeData: [EIP712TypeProperty]) throws -> Data { - var messageData: [Data] = [] - for field in messageTypeData { - guard let fieldValue = message[field.name] else { - throw Web3Error.inputError(desc: "EIP712 message doesn't have field with name \(field.name).") - } - - if let customType = typedData.types[field.type] { - guard let objectAttribute = fieldValue as? [String: AnyObject] else { - throw Web3Error.processingError(desc: "Failed to hash EIP712 message. A property from 'message' field with custom type cannot be represented as object and thus encoded & hashed. Property name \(field.name); value \(String(describing: message[field.name])).") - } - try messageData.append(hashEip712Message(typedData, objectAttribute, messageTypeData: customType)) - } else { - let type = try ABITypeParser.parseTypeString(field.type) - var data: Data? - switch type { - case .dynamicBytes, .bytes: - if let bytes = fieldValue as? Data { - data = bytes.sha3(.keccak256) - } - case .string: - if let string = fieldValue as? String { - data = Data(string.bytes).sha3(.keccak256) - } - default: - data = ABIEncoder.encodeSingleType(type: type, value: fieldValue) - } - - if let data = data { - messageData.append(data) - } else { - throw Web3Error.processingError(desc: "Failed to encode property of EIP712 message. Property name \(field.name); value \(String(describing: message[field.name]))") - } - } - } - - return Data(messageData.flatMap { $0.bytes }).sha3(.keccak256) -} - public func eip712hash(domainSeparatorHash: Data, messageHash: Data) -> Data { (Data([UInt8(0x19), UInt8(0x01)]) + domainSeparatorHash + messageHash).sha3(.keccak256) } diff --git a/Sources/web3swift/Utils/EIP/EIP712/EIP712Parser.swift b/Sources/web3swift/Utils/EIP/EIP712/EIP712Parser.swift index 8e1370a75..fb5860ce5 100644 --- a/Sources/web3swift/Utils/EIP/EIP712/EIP712Parser.swift +++ b/Sources/web3swift/Utils/EIP/EIP712/EIP712Parser.swift @@ -233,4 +233,66 @@ public struct EIP712TypedData { } return type + "(" + parameters.joined(separator: ",") + ")" + encodedSubtypes.joined(separator: "") } + + /// Convenience function for ``encodeData(_:data:)`` that uses ``primaryType`` and ``message`` as values. + /// - Returns: encoded data based on ``primaryType`` and ``message``. + public func encodeData() throws -> Data { + try encodeData(primaryType, data: message) + } + + public func encodeData(_ type: String, data: [String : AnyObject]) throws -> Data { + // Adding typehash + var encTypes: [ABI.Element.ParameterType] = [.bytes(length: 32)] + var encValues: [Any] = [try typeHash(type)] + + guard let typeData = types[type] else { + throw Web3Error.processingError(desc: "EIP712. Attempting to encode data for type that doesn't exist in this payload. Given type: \(type). Available types: \(types.values).") + } + + // Add field contents + for field in typeData { + let value = data[field.name] + if field.type == "string" { + guard let value = value as? String else { + throw Web3Error.processingError(desc: "EIP712. Type metadata '\(field)' and actual value '\(String(describing: value))' type doesn't match. Cannot cast value to String.") + } + encTypes.append(.bytes(length: 32)) + encValues.append(value.sha3(.keccak256).addHexPrefix()) + } else if field.type == "bytes"{ + guard let value = value as? Data else { + throw Web3Error.processingError(desc: "EIP712. Type metadata '\(field)' and actual value '\(String(describing: value))' type doesn't match. Cannot cast value to Data.") + } + encTypes.append(.bytes(length: 32)) + encValues.append(value.sha3(.keccak256)) + } else if types[field.type] != nil { + guard let value = value as? [String : AnyObject] else { + throw Web3Error.processingError(desc: "EIP712. Custom type metadata '\(field)' and actual value '\(String(describing: value))' type doesn't match. Cannot cast value to [String : AnyObject].") + } + encTypes.append(.bytes(length: 32)) + encValues.append(try encodeData(field.type, data: value).sha3(.keccak256)) + } else { + encTypes.append(try ABITypeParser.parseTypeString(field.type)) + encValues.append(value as Any) + } + } + + guard let encodedData = ABIEncoder.encode(types: encTypes, values: encValues) else { + throw Web3Error.processingError(desc: "EIP712. ABIEncoder.encode failed with the following types and values: \(encTypes); \(encValues)") + } + return encodedData + } + + /// Convenience function for ``structHash(_:data:)`` that uses ``primaryType`` and ``message`` as values. + /// - Returns: SH# keccak256 hash of encoded data based on ``primaryType`` and ``message``. + public func structHash() throws -> Data { + try structHash(primaryType, data: message) + } + + public func structHash(_ type: String, data: [String : AnyObject]) throws -> Data { + try encodeData(type, data: data).sha3(.keccak256) + } + + public func signHash() throws -> Data { + try (Data.fromHex("0x1901")! + structHash("EIP712Domain", data: domain) + structHash()).sha3(.keccak256) + } } diff --git a/Sources/web3swift/Web3/Web3+Signing.swift b/Sources/web3swift/Web3/Web3+Signing.swift index d87c4f3dc..295d1a497 100755 --- a/Sources/web3swift/Web3/Web3+Signing.swift +++ b/Sources/web3swift/Web3/Web3+Signing.swift @@ -44,11 +44,12 @@ public struct Web3Signer { keystore: BIP32Keystore, account: EthereumAddress, password: String? = nil) throws -> Data { - let hash = try eip712hash(eip712TypedDataPayload) + let hash = try eip712TypedDataPayload.signHash() guard let signature = try Web3Signer.signPersonalMessage(hash, keystore: keystore, account: account, - password: password ?? "") + password: password ?? "", + useHash: false) else { throw Web3Error.dataError } @@ -67,7 +68,8 @@ public struct Web3Signer { guard let signature = try Web3Signer.signPersonalMessage(hash, keystore: keystore, account: account, - password: password ?? "") + password: password ?? "", + useHash: false) else { throw Web3Error.dataError } diff --git a/Tests/web3swiftTests/localTests/EIP712TypedDataPayloadTests.swift b/Tests/web3swiftTests/localTests/EIP712TypedDataPayloadTests.swift index d5c9cb11c..3c62db42f 100644 --- a/Tests/web3swiftTests/localTests/EIP712TypedDataPayloadTests.swift +++ b/Tests/web3swiftTests/localTests/EIP712TypedDataPayloadTests.swift @@ -7,8 +7,9 @@ import Foundation import XCTest import web3swift -import Web3Core +@testable import Web3Core +// TODO: take more tests from https://github.com/Mrtenz/eip-712/blob/master/src/eip-712.test.ts /// Tests based primarily on the following example https://eips.ethereum.org/assets/eip-712/Example.js class EIP712TypedDataPayloadTests: XCTestCase { @@ -221,4 +222,55 @@ class EIP712TypedDataPayloadTests: XCTestCase { try XCTAssertEqual(parsedEip712TypedData.typeHash("Person"), "0xb9d8c78acf9b987311de6c7b45bb6a9c8e1bf361fa7fd3467a2163f994c79500") try XCTAssertEqual(parsedEip712TypedData.typeHash("Mail"), "0xa0cedeb2dc280ba39b857546d74f5549c3a1d7bdc2dd96bf881f76108e23dac2") } + + func testEIP712EncodeData() throws { + let parsedEip712TypedData = try EIP712Parser.parse(testTypedDataPayload) + let encodedMessage = "a0cedeb2dc280ba39b857546d74f5549c3a1d7bdc2dd96bf881f76108e23dac2fc71e5fa27ff56c350aa531bc129ebdf613b772b6604664f5d8dbe21b85eb0c8cd54f074a4af31b4411ff6a60c9719dbd559c221c8ac3492d9d872b041d703d1b5aadf3154a261abdd9086fc627b61efca26ae5702701d05cd2305f7c52a2fc8" + XCTAssertEqual(try parsedEip712TypedData.encodeData().toHexString(), encodedMessage) + XCTAssertEqual(try parsedEip712TypedData.encodeData(parsedEip712TypedData.primaryType, data: parsedEip712TypedData.message).toHexString(), encodedMessage) + + XCTAssertEqual(try parsedEip712TypedData.encodeData("EIP712Domain", data: parsedEip712TypedData.domain).toHexString(), + "8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400fc70ef06638535b4881fafcac8287e210e3769ff1a8e91f1b95d6246e61e4d3c6c89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc60000000000000000000000000000000000000000000000000000000000000001000000000000000000000000cccccccccccccccccccccccccccccccccccccccc") + + XCTAssertEqual(try parsedEip712TypedData.encodeData("Person", data: parsedEip712TypedData.message["from"] as! [String : AnyObject]).toHexString(), + "b9d8c78acf9b987311de6c7b45bb6a9c8e1bf361fa7fd3467a2163f994c795008c1d2bd5348394761719da11ec67eedae9502d137e8940fee8ecd6f641ee1648000000000000000000000000cd2a3d9f938e13cd947ec05abc7fe734df8dd826") + + XCTAssertEqual(try parsedEip712TypedData.encodeData("Person", + data: ["wallet" : "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826", + "name" : "Cow"] as [String : AnyObject]).toHexString(), + "b9d8c78acf9b987311de6c7b45bb6a9c8e1bf361fa7fd3467a2163f994c795008c1d2bd5348394761719da11ec67eedae9502d137e8940fee8ecd6f641ee1648000000000000000000000000cd2a3d9f938e13cd947ec05abc7fe734df8dd826") + } + + func testEIP712StructHash() throws { + let parsedEip712TypedData = try EIP712Parser.parse(testTypedDataPayload) + XCTAssertEqual(try parsedEip712TypedData.structHash().toHexString(), "c52c0ee5d84264471806290a3f2c4cecfc5490626bf912d01f240d7a274b371e") + XCTAssertEqual(try parsedEip712TypedData.structHash("EIP712Domain", data: parsedEip712TypedData.domain).toHexString(), + "f2cee375fa42b42143804025fc449deafd50cc031ca257e0b194a650a912090f") + } + + func testEIP712SignHash() throws { + let parsedEip712TypedData = try EIP712Parser.parse(testTypedDataPayload) + XCTAssertEqual(try parsedEip712TypedData.signHash().toHexString(), "be609aee343fb3c4b28e1df9e632fca64fcfaede20f02e86244efddf30957bd2") + } + + func testEIP712Signing() throws { + let parsedEip712TypedData = try EIP712Parser.parse(testTypedDataPayload) + let privateKey = Data.fromHex("cow".sha3(.keccak256).addHexPrefix())! + let publicKey = Utilities.privateToPublic(privateKey)! + let address = Utilities.publicToAddress(publicKey)! + XCTAssertEqual(address, EthereumAddress("0xcd2a3d9f938e13cd947ec05abc7fe734df8dd826")); + + /// This signing doesn't use `"\u{19}Ethereum Signed Message:\n"`. As per EIP712 standard + /// the following format is used instead: + /// ``` + /// encode(domainSeparator : 𝔹²⁵⁶, message : 𝕊) = "\x19\x01" ‖ domainSeparator ‖ hashStruct(message) + /// ``` + /// + /// The output of ``EIP712TypedData.signHash`` is exactly that. + let (compressedSignature, _) = try SECP256K1.signForRecovery(hash: parsedEip712TypedData.signHash(), privateKey: privateKey) + let unmarshalledSignature = Utilities.unmarshalSignature(signatureData: compressedSignature!)! + XCTAssertEqual(unmarshalledSignature.v, 28) + XCTAssertEqual(unmarshalledSignature.r.toHexString(), "4355c47d63924e8a72e509b65029052eb6c299d53a04e167c5775fd466751c9d") + XCTAssertEqual(unmarshalledSignature.s.toHexString(), "07299936d304c153f6443dfa05f40ff007d72911b6f72307f996231605b91562") + } } From 07965a1a126192f7452645106a237a67ce213ee6 Mon Sep 17 00:00:00 2001 From: Jenea Vranceanu Date: Thu, 19 Oct 2023 00:10:14 +0300 Subject: [PATCH 26/62] fix: typo --- Sources/web3swift/Web3/Web3+Contract.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/web3swift/Web3/Web3+Contract.swift b/Sources/web3swift/Web3/Web3+Contract.swift index 7bf192710..1877bb65c 100755 --- a/Sources/web3swift/Web3/Web3+Contract.swift +++ b/Sources/web3swift/Web3/Web3+Contract.swift @@ -47,7 +47,7 @@ extension Web3 { // MARK: Writing Data flow // FIXME: Rewrite this to CodableTransaction - /// Deploys a constact instance using the previously provided ABI, some bytecode, constructor parameters and options. + /// Deploys a contract instance using the previously provided ABI, some bytecode, constructor parameters and options. /// If extraData is supplied it is appended to encoded bytecode and constructor parameters. /// /// Returns a "Transaction intermediate" object. From 5493dd0409a862071e9bc926d016d70ed4e93040 Mon Sep 17 00:00:00 2001 From: Jenea Vranceanu Date: Thu, 19 Oct 2023 00:20:11 +0300 Subject: [PATCH 27/62] chore(EIP712): new test + moved EIP712 reused data into one file; --- .../localTests/EIP712TestData.swift | 76 +++++++++++++++++ .../localTests/EIP712Tests.swift | 12 +++ .../EIP712TypedDataPayloadTests.swift | 81 ++----------------- 3 files changed, 95 insertions(+), 74 deletions(-) create mode 100644 Tests/web3swiftTests/localTests/EIP712TestData.swift diff --git a/Tests/web3swiftTests/localTests/EIP712TestData.swift b/Tests/web3swiftTests/localTests/EIP712TestData.swift new file mode 100644 index 000000000..52ab9a7dc --- /dev/null +++ b/Tests/web3swiftTests/localTests/EIP712TestData.swift @@ -0,0 +1,76 @@ +// +// EIP712TestData.swift +// +// Created by JeneaVranceanu on 19.10.2023. +// + +import Foundation + +class EIP712TestData { + static let testTypedDataPayload = """ + { + "types":{ + "EIP712Domain":[ + { + "name":"name", + "type":"string" + }, + { + "name":"version", + "type":"string" + }, + { + "name":"chainId", + "type":"uint256" + }, + { + "name":"verifyingContract", + "type":"address" + } + ], + "Person":[ + { + "name":"name", + "type":"string" + }, + { + "name":"wallet", + "type":"address" + } + ], + "Mail":[ + { + "name":"from", + "type":"Person" + }, + { + "name":"to", + "type":"Person" + }, + { + "name":"contents", + "type":"string" + } + ] + }, + "primaryType":"Mail", + "domain":{ + "name":"Ether Mail", + "version":"1", + "chainId":1, + "verifyingContract":"0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC" + }, + "message":{ + "from":{ + "name":"Cow", + "wallet":"0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826" + }, + "to":{ + "name":"Bob", + "wallet":"0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB" + }, + "contents":"Hello, Bob!" + } + } +""" +} diff --git a/Tests/web3swiftTests/localTests/EIP712Tests.swift b/Tests/web3swiftTests/localTests/EIP712Tests.swift index 908490e88..149c43221 100644 --- a/Tests/web3swiftTests/localTests/EIP712Tests.swift +++ b/Tests/web3swiftTests/localTests/EIP712Tests.swift @@ -105,4 +105,16 @@ class EIP712Tests: XCTestCase { chainId: chainId) XCTAssertEqual(signature.toHexString(), "9ee2aadf14739e1cafc3bc1a0b48457c12419d5b480a8ffa86eb7df538c82d0753ca2a6f8024dea576b383cbcbe5e2b181b087e489298674bf6512756cabc5b01b") } + + func testEIP712TypedDataSigning() throws { + let mnemonic = "normal dune pole key case cradle unfold require tornado mercy hospital buyer" + let keystore = try! BIP32Keystore(mnemonics: mnemonic, password: "", mnemonicsPassword: "")! + let account = keystore.addresses?[0] + let eip712TypedData = try EIP712Parser.parse(EIP712TestData.testTypedDataPayload) + let signature = try Web3Signer.signEIP712( + eip712TypedData, + keystore: keystore, + account: account!) + XCTAssertEqual(signature.toHexString(), "70d1f5d9eac7b6303683d0792ea8dc93369e3b79888c4e0b86121bec19f479ba4067cf7ac3f8208cbc60a706c4793c2c17e19637298bb31642e531619272b26e1b") + } } diff --git a/Tests/web3swiftTests/localTests/EIP712TypedDataPayloadTests.swift b/Tests/web3swiftTests/localTests/EIP712TypedDataPayloadTests.swift index 3c62db42f..7189af1a6 100644 --- a/Tests/web3swiftTests/localTests/EIP712TypedDataPayloadTests.swift +++ b/Tests/web3swiftTests/localTests/EIP712TypedDataPayloadTests.swift @@ -13,75 +13,8 @@ import web3swift /// Tests based primarily on the following example https://eips.ethereum.org/assets/eip-712/Example.js class EIP712TypedDataPayloadTests: XCTestCase { - let testTypedDataPayload = """ - { - "types":{ - "EIP712Domain":[ - { - "name":"name", - "type":"string" - }, - { - "name":"version", - "type":"string" - }, - { - "name":"chainId", - "type":"uint256" - }, - { - "name":"verifyingContract", - "type":"address" - } - ], - "Person":[ - { - "name":"name", - "type":"string" - }, - { - "name":"wallet", - "type":"address" - } - ], - "Mail":[ - { - "name":"from", - "type":"Person" - }, - { - "name":"to", - "type":"Person" - }, - { - "name":"contents", - "type":"string" - } - ] - }, - "primaryType":"Mail", - "domain":{ - "name":"Ether Mail", - "version":"1", - "chainId":1, - "verifyingContract":"0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC" - }, - "message":{ - "from":{ - "name":"Cow", - "wallet":"0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826" - }, - "to":{ - "name":"Bob", - "wallet":"0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB" - }, - "contents":"Hello, Bob!" - } - } - """ - func testEIP712Parser() throws { - let parsedEip712TypedData = try EIP712Parser.parse(testTypedDataPayload) + let parsedEip712TypedData = try EIP712Parser.parse(EIP712TestData.testTypedDataPayload) XCTAssertEqual(parsedEip712TypedData.types.count, 3) let eip712Domain = parsedEip712TypedData.types["EIP712Domain"] @@ -210,21 +143,21 @@ class EIP712TypedDataPayloadTests: XCTestCase { } func testEIP712EncodeType() throws { - let parsedEip712TypedData = try EIP712Parser.parse(testTypedDataPayload) + let parsedEip712TypedData = try EIP712Parser.parse(EIP712TestData.testTypedDataPayload) try XCTAssertEqual(parsedEip712TypedData.encodeType("EIP712Domain"), "EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)") try XCTAssertEqual(parsedEip712TypedData.encodeType("Person"), "Person(string name,address wallet)") try XCTAssertEqual(parsedEip712TypedData.encodeType("Mail"), "Mail(Person from,Person to,string contents)Person(string name,address wallet)") } func testEIP712TypeHash() throws { - let parsedEip712TypedData = try EIP712Parser.parse(testTypedDataPayload) + let parsedEip712TypedData = try EIP712Parser.parse(EIP712TestData.testTypedDataPayload) try XCTAssertEqual(parsedEip712TypedData.typeHash("EIP712Domain"), "0x8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f") try XCTAssertEqual(parsedEip712TypedData.typeHash("Person"), "0xb9d8c78acf9b987311de6c7b45bb6a9c8e1bf361fa7fd3467a2163f994c79500") try XCTAssertEqual(parsedEip712TypedData.typeHash("Mail"), "0xa0cedeb2dc280ba39b857546d74f5549c3a1d7bdc2dd96bf881f76108e23dac2") } func testEIP712EncodeData() throws { - let parsedEip712TypedData = try EIP712Parser.parse(testTypedDataPayload) + let parsedEip712TypedData = try EIP712Parser.parse(EIP712TestData.testTypedDataPayload) let encodedMessage = "a0cedeb2dc280ba39b857546d74f5549c3a1d7bdc2dd96bf881f76108e23dac2fc71e5fa27ff56c350aa531bc129ebdf613b772b6604664f5d8dbe21b85eb0c8cd54f074a4af31b4411ff6a60c9719dbd559c221c8ac3492d9d872b041d703d1b5aadf3154a261abdd9086fc627b61efca26ae5702701d05cd2305f7c52a2fc8" XCTAssertEqual(try parsedEip712TypedData.encodeData().toHexString(), encodedMessage) XCTAssertEqual(try parsedEip712TypedData.encodeData(parsedEip712TypedData.primaryType, data: parsedEip712TypedData.message).toHexString(), encodedMessage) @@ -242,19 +175,19 @@ class EIP712TypedDataPayloadTests: XCTestCase { } func testEIP712StructHash() throws { - let parsedEip712TypedData = try EIP712Parser.parse(testTypedDataPayload) + let parsedEip712TypedData = try EIP712Parser.parse(EIP712TestData.testTypedDataPayload) XCTAssertEqual(try parsedEip712TypedData.structHash().toHexString(), "c52c0ee5d84264471806290a3f2c4cecfc5490626bf912d01f240d7a274b371e") XCTAssertEqual(try parsedEip712TypedData.structHash("EIP712Domain", data: parsedEip712TypedData.domain).toHexString(), "f2cee375fa42b42143804025fc449deafd50cc031ca257e0b194a650a912090f") } func testEIP712SignHash() throws { - let parsedEip712TypedData = try EIP712Parser.parse(testTypedDataPayload) + let parsedEip712TypedData = try EIP712Parser.parse(EIP712TestData.testTypedDataPayload) XCTAssertEqual(try parsedEip712TypedData.signHash().toHexString(), "be609aee343fb3c4b28e1df9e632fca64fcfaede20f02e86244efddf30957bd2") } func testEIP712Signing() throws { - let parsedEip712TypedData = try EIP712Parser.parse(testTypedDataPayload) + let parsedEip712TypedData = try EIP712Parser.parse(EIP712TestData.testTypedDataPayload) let privateKey = Data.fromHex("cow".sha3(.keccak256).addHexPrefix())! let publicKey = Utilities.privateToPublic(privateKey)! let address = Utilities.publicToAddress(publicKey)! From 4669bfe91edc325ecd23accb16cebd75ca538896 Mon Sep 17 00:00:00 2001 From: Jenea Vranceanu Date: Thu, 19 Oct 2023 12:58:39 +0300 Subject: [PATCH 28/62] fix(EIP712): fixed values in tests since personal-message-hashing is not used for EIP712 --- Tests/web3swiftTests/localTests/EIP712Tests.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Tests/web3swiftTests/localTests/EIP712Tests.swift b/Tests/web3swiftTests/localTests/EIP712Tests.swift index 149c43221..3473dbf2b 100644 --- a/Tests/web3swiftTests/localTests/EIP712Tests.swift +++ b/Tests/web3swiftTests/localTests/EIP712Tests.swift @@ -52,7 +52,7 @@ class EIP712Tests: XCTestCase { account: account!, password: password, chainId: chainId) - XCTAssertEqual(signature.toHexString(), "c0567b120d3de6b3042ae3de1aa346e167454c675e1eaf40ea2f9de89e6a95c2783c1aa6c96aa1e0aaead4ae8901052fa9fd7abe4acb331adafd61610e93c3f01c") + XCTAssertEqual(signature.toHexString(), "39e48b17008344acd58c86fba540ce65a9a4dad048e0d4d10efced291e02174c7267c9749cd2c1f9738ba1267f6fb8caadd054497daa20e2eaaee6472e7fde4e1b") } func testWithChainId() throws { @@ -103,7 +103,7 @@ class EIP712Tests: XCTestCase { account: account!, password: password, chainId: chainId) - XCTAssertEqual(signature.toHexString(), "9ee2aadf14739e1cafc3bc1a0b48457c12419d5b480a8ffa86eb7df538c82d0753ca2a6f8024dea576b383cbcbe5e2b181b087e489298674bf6512756cabc5b01b") + XCTAssertEqual(signature.toHexString(), "e5ebc20f5794b756f01adb271db9e535df74751dfce4328b2f5bae4740d6e5ef392626b95ae0c0975a91b99033b079e6e0ccd41cb6fa70dd5f8833d78af4282f1c") } func testEIP712TypedDataSigning() throws { From b8e55b227598eebd88cd2b826365b00b403ce7d5 Mon Sep 17 00:00:00 2001 From: Jenea Vranceanu Date: Fri, 20 Oct 2023 00:37:21 +0300 Subject: [PATCH 29/62] chore(EIP712): example for EIP712Parser; --- Sources/web3swift/Utils/EIP/EIP712/EIP712.swift | 1 - .../web3swift/Utils/EIP/EIP712/EIP712Parser.swift | 14 +++++++++++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/Sources/web3swift/Utils/EIP/EIP712/EIP712.swift b/Sources/web3swift/Utils/EIP/EIP712/EIP712.swift index 245fab7e6..21458618b 100644 --- a/Sources/web3swift/Utils/EIP/EIP712/EIP712.swift +++ b/Sources/web3swift/Utils/EIP/EIP712/EIP712.swift @@ -63,7 +63,6 @@ public extension EIP712Hashable { case let boolean as Bool: result = ABIEncoder.encodeSingleType(type: .uint(bits: 8), value: boolean ? 1 : 0)! case let hashable as EIP712Hashable: - // TODO: should it be hashed here? result = try hashable.hash() default: /// Cast to `AnyObject` is required. Otherwise, `nil` value will fail this condition. diff --git a/Sources/web3swift/Utils/EIP/EIP712/EIP712Parser.swift b/Sources/web3swift/Utils/EIP/EIP712/EIP712Parser.swift index fb5860ce5..6078392d2 100644 --- a/Sources/web3swift/Utils/EIP/EIP712/EIP712Parser.swift +++ b/Sources/web3swift/Utils/EIP/EIP712/EIP712Parser.swift @@ -7,7 +7,7 @@ import Foundation import Web3Core -/// The only purpose of this class is to parse raw JSON and output an EIP712 hash. +/// The only purpose of this class is to parse raw JSON and output an EIP712 hash ready for signing. /// Example of a payload that is received via `eth_signTypedData` for signing: /// ``` /// { @@ -75,7 +75,19 @@ import Web3Core /// } /// } /// ``` +/// +/// Example use case: +/// ``` +/// let payload: String = ... // This is the payload received from eth_signTypedData +/// let eip712TypedData = try EIP712Parser.parse(payload) +/// let signature = try Web3Signer.signEIP712( +/// eip712TypedData, +/// keystore: keystore, +/// account: account, +/// password: password) +/// ``` public class EIP712Parser { + static func toData(_ json: String) throws -> Data { guard let json = json.data(using: .utf8) else { throw Web3Error.inputError(desc: "Failed to parse EIP712 payload. Given string is not valid UTF8 string. \(json)") From c01f6a164ee27b56f58cdfb74527f380b1cd8ca1 Mon Sep 17 00:00:00 2001 From: Jenea Vranceanu Date: Tue, 31 Oct 2023 10:23:50 +0200 Subject: [PATCH 30/62] fix: ParsingError and AbstractKeystoreError are now extensions of LocalizedError type; - updated ParsingError and AbstractKeystoreError with description; - minor refactoring; - added 2 more EIP712 tests; --- Sources/Web3Core/EthereumABI/ABIParsing.swift | 31 ++++- .../Web3Core/EthereumABI/ABITypeParser.swift | 2 +- .../KeystoreManager/AbstractKeystore.swift | 31 ++++- .../KeystoreManager/BIP32Keystore.swift | 115 +++++++++--------- Sources/Web3Core/KeystoreManager/BIP39.swift | 8 +- .../KeystoreManager/EthereumKeystoreV3.swift | 62 ++++------ .../KeystoreManager/KeystoreManager.swift | 2 +- .../Transaction/CodableTransaction.swift | 2 +- .../EIP712TypedDataPayloadTests.swift | 18 +++ 9 files changed, 163 insertions(+), 108 deletions(-) diff --git a/Sources/Web3Core/EthereumABI/ABIParsing.swift b/Sources/Web3Core/EthereumABI/ABIParsing.swift index c4e68aaef..3eaf5d006 100755 --- a/Sources/Web3Core/EthereumABI/ABIParsing.swift +++ b/Sources/Web3Core/EthereumABI/ABIParsing.swift @@ -7,9 +7,9 @@ import Foundation extension ABI { - public enum ParsingError: Swift.Error { + public enum ParsingError: LocalizedError { case invalidJsonFile - case elementTypeInvalid + case elementTypeInvalid(_ desc: String? = nil) case elementNameInvalid case functionInputInvalid case functionOutputInvalid @@ -17,6 +17,31 @@ extension ABI { case parameterTypeInvalid case parameterTypeNotFound case abiInvalid + + public var errorDescription: String? { + var errorMessage: [String?] + switch self { + case .invalidJsonFile: + errorMessage = ["invalidJsonFile"] + case .elementTypeInvalid(let desc): + errorMessage = ["elementTypeInvalid", desc] + case .elementNameInvalid: + errorMessage = ["elementNameInvalid"] + case .functionInputInvalid: + errorMessage = ["functionInputInvalid"] + case .functionOutputInvalid: + errorMessage = ["functionOutputInvalid"] + case .eventInputInvalid: + errorMessage = ["eventInputInvalid"] + case .parameterTypeInvalid: + errorMessage = ["parameterTypeInvalid"] + case .parameterTypeNotFound: + errorMessage = ["parameterTypeNotFound"] + case .abiInvalid: + errorMessage = ["abiInvalid"] + } + return errorMessage.compactMap { $0 }.joined(separator: " ") + } } enum TypeParsingExpressions { @@ -39,7 +64,7 @@ extension ABI.Record { public func parse() throws -> ABI.Element { let typeString = self.type ?? "function" guard let type = ABI.ElementType(rawValue: typeString) else { - throw ABI.ParsingError.elementTypeInvalid + throw ABI.ParsingError.elementTypeInvalid("Invalid ABI type \(typeString).") } return try parseToElement(from: self, type: type) } diff --git a/Sources/Web3Core/EthereumABI/ABITypeParser.swift b/Sources/Web3Core/EthereumABI/ABITypeParser.swift index 753c8788f..d12af4005 100755 --- a/Sources/Web3Core/EthereumABI/ABITypeParser.swift +++ b/Sources/Web3Core/EthereumABI/ABITypeParser.swift @@ -46,7 +46,7 @@ public struct ABITypeParser { public static func parseTypeString(_ string: String) throws -> ABI.Element.ParameterType { let (type, tail) = recursiveParseType(string) - guard let t = type, tail == nil else {throw ABI.ParsingError.elementTypeInvalid} + guard let t = type, tail == nil else { throw ABI.ParsingError.elementTypeInvalid("Invalid ABI type \(string).") } return t } diff --git a/Sources/Web3Core/KeystoreManager/AbstractKeystore.swift b/Sources/Web3Core/KeystoreManager/AbstractKeystore.swift index e8c515241..97b90ce88 100755 --- a/Sources/Web3Core/KeystoreManager/AbstractKeystore.swift +++ b/Sources/Web3Core/KeystoreManager/AbstractKeystore.swift @@ -11,11 +11,30 @@ public protocol AbstractKeystore { func UNSAFE_getPrivateKeyData(password: String, account: EthereumAddress) throws -> Data } -public enum AbstractKeystoreError: Error { - case noEntropyError - case keyDerivationError - case aesError - case invalidAccountError +public enum AbstractKeystoreError: LocalizedError { + case noEntropyError(_ additionalDescription: String? = nil) + case keyDerivationError(_ additionalDescription: String? = nil) + case aesError(_ additionalDescription: String? = nil) + case invalidAccountError(_ additionalDescription: String? = nil) case invalidPasswordError - case encryptionError(String) + case encryptionError(_ additionalDescription: String? = nil) + + public var errorDescription: String? { + var errorMessage: [String?] + switch self { + case .noEntropyError(let additionalDescription): + errorMessage = ["Entropy error (e.g. failed to generate a random array of bytes).", additionalDescription] + case .keyDerivationError(let additionalDescription): + errorMessage = ["Key derivation error.", additionalDescription] + case .aesError(let additionalDescription): + errorMessage = ["AES error.", additionalDescription] + case .invalidAccountError(let additionalDescription): + errorMessage = ["Invalid account error.", additionalDescription] + case .invalidPasswordError: + errorMessage = ["Invalid password error."] + case .encryptionError(let additionalDescription): + errorMessage = ["Encryption error.", additionalDescription] + } + return errorMessage.compactMap { $0 }.joined(separator: " ") + } } diff --git a/Sources/Web3Core/KeystoreManager/BIP32Keystore.swift b/Sources/Web3Core/KeystoreManager/BIP32Keystore.swift index 22b305431..1646f3631 100755 --- a/Sources/Web3Core/KeystoreManager/BIP32Keystore.swift +++ b/Sources/Web3Core/KeystoreManager/BIP32Keystore.swift @@ -40,22 +40,22 @@ public class BIP32Keystore: AbstractKeystore { } public func UNSAFE_getPrivateKeyData(password: String, account: EthereumAddress) throws -> Data { - if let key = addressStorage.path(by: account) { - guard let decryptedRootNode = try? self.getPrefixNodeData(password) else {throw AbstractKeystoreError.encryptionError("Failed to decrypt a keystore")} - guard let rootNode = HDNode(decryptedRootNode) else {throw AbstractKeystoreError.encryptionError("Failed to deserialize a root node")} - guard rootNode.depth == (self.rootPrefix.components(separatedBy: "/").count - 1) else {throw AbstractKeystoreError.encryptionError("Derivation depth mismatch")} - guard let index = UInt32(key.components(separatedBy: "/").last!) else { - throw AbstractKeystoreError.encryptionError("Derivation depth mismatch") + if let path = addressStorage.path(by: account) { + guard let decryptedRootNode = try? self.getPrefixNodeData(password) else { throw AbstractKeystoreError.encryptionError("BIP32Keystore. Failed to decrypt a keystore") } + guard let rootNode = HDNode(decryptedRootNode) else { throw AbstractKeystoreError.encryptionError("BIP32Keystore. Failed to deserialize a root node") } + guard rootNode.depth == (rootPrefix.components(separatedBy: "/").count - 1) else {throw AbstractKeystoreError.encryptionError("BIP32Keystore. Derivation depth mismatch")} + guard let index = UInt32(path.components(separatedBy: "/").last!) else { + throw AbstractKeystoreError.encryptionError("BIP32Keystore. Derivation depth mismatch. `path` doesn't have an index (UInt32) as the last path component: \(path).") } guard let keyNode = rootNode.derive(index: index, derivePrivateKey: true) else { - throw AbstractKeystoreError.encryptionError("Derivation failed") + throw AbstractKeystoreError.encryptionError("BIP32Keystore. Derivation from rootNode failed. derive(index: \(index), derivePrivateKey: true)") } guard let privateKey = keyNode.privateKey else { - throw AbstractKeystoreError.invalidAccountError + throw AbstractKeystoreError.invalidAccountError("BIP32Keystore. Derived node doesn't have private key. derive(index: \(index), derivePrivateKey: true)") } return privateKey } - throw AbstractKeystoreError.invalidAccountError + throw AbstractKeystoreError.invalidAccountError("BIP32Keystore. Failed to find path for given address \(account.address).") } // -------------- @@ -89,7 +89,7 @@ public class BIP32Keystore: AbstractKeystore { public convenience init?(mnemonics: String, password: String, mnemonicsPassword: String = "", language: BIP39Language = BIP39Language.english, prefixPath: String = HDNode.defaultPathMetamaskPrefix, aesMode: String = "aes-128-cbc") throws { guard var seed = BIP39.seedFromMmemonics(mnemonics, password: mnemonicsPassword, language: language) else { - throw AbstractKeystoreError.noEntropyError + throw AbstractKeystoreError.noEntropyError("BIP32Keystore. Failed to generate seed from given mnemonics, password and language.") } defer { Data.zero(&seed) @@ -99,7 +99,7 @@ public class BIP32Keystore: AbstractKeystore { public convenience init?(mnemonicsPhrase: [String], password: String, mnemonicsPassword: String = "", language: BIP39Language = .english, prefixPath: String = HDNode.defaultPathMetamaskPrefix, aesMode: String = "aes-128-cbc") throws { guard var seed = BIP39.seedFromMmemonics(mnemonicsPhrase, password: mnemonicsPassword, language: language) else { - throw AbstractKeystoreError.noEntropyError + throw AbstractKeystoreError.noEntropyError("BIP32Keystore. Failed to generate seed from given mnemonics, password and language.") } defer { Data.zero(&seed) @@ -111,11 +111,11 @@ public class BIP32Keystore: AbstractKeystore { addressStorage = PathAddressStorage() guard let rootNode = HDNode(seed: seed)?.derive(path: prefixPath, derivePrivateKey: true) else { return nil } self.rootPrefix = prefixPath - try createNewAccount(parentNode: rootNode, password: password) + try createNewAccount(parentNode: rootNode) guard let serializedRootNode = rootNode.serialize(serializePublic: false) else { - throw AbstractKeystoreError.keyDerivationError + throw AbstractKeystoreError.keyDerivationError("BIP32Keystore. Failed to serialize root node.") } - try encryptDataToStorage(password, data: serializedRootNode, aesMode: aesMode) + try encryptDataToStorage(password, serializedNodeData: serializedRootNode, aesMode: aesMode) } public func createNewChildAccount(password: String) throws { @@ -129,14 +129,14 @@ public class BIP32Keystore: AbstractKeystore { guard rootNode.depth == prefixPath.components(separatedBy: "/").count - 1 else { throw AbstractKeystoreError.encryptionError("Derivation depth mismatch") } - try createNewAccount(parentNode: rootNode, password: password) + try createNewAccount(parentNode: rootNode) guard let serializedRootNode = rootNode.serialize(serializePublic: false) else { - throw AbstractKeystoreError.keyDerivationError + throw AbstractKeystoreError.keyDerivationError("BIP32Keystore. Failed to serialize root node.") } - try encryptDataToStorage(password, data: serializedRootNode, aesMode: self.keystoreParams!.crypto.cipher) + try encryptDataToStorage(password, serializedNodeData: serializedRootNode, aesMode: self.keystoreParams!.crypto.cipher) } - func createNewAccount(parentNode: HDNode, password: String = "web3swift") throws { + func createNewAccount(parentNode: HDNode) throws { let maxIndex = addressStorage.paths .compactMap { $0.components(separatedBy: "/").last } .compactMap { UInt32($0) } @@ -151,10 +151,10 @@ public class BIP32Keystore: AbstractKeystore { } guard let newNode = parentNode.derive(index: newIndex, derivePrivateKey: true, hardened: false) else { - throw AbstractKeystoreError.keyDerivationError + throw AbstractKeystoreError.keyDerivationError("BIP32Keystore. Failed to derive a new node. Check given parent node.") } guard let newAddress = Utilities.publicToAddress(newNode.publicKey) else { - throw AbstractKeystoreError.keyDerivationError + throw AbstractKeystoreError.keyDerivationError("BIP32Keystore. Failed to derive a public address from the new derived node.") } let newPath = rootPrefix + "/" + String(newNode.index) addressStorage.add(address: newAddress, for: newPath) @@ -163,10 +163,10 @@ public class BIP32Keystore: AbstractKeystore { public func createNewCustomChildAccount(password: String, path: String) throws { guard let decryptedRootNode = try getPrefixNodeData(password), let keystoreParams else { - throw AbstractKeystoreError.encryptionError("Failed to decrypt a keystore") + throw AbstractKeystoreError.encryptionError("BIP32Keystore. Failed to decrypt the keystore. Check given password.") } guard let rootNode = HDNode(decryptedRootNode) else { - throw AbstractKeystoreError.encryptionError("Failed to deserialize a root node") + throw AbstractKeystoreError.encryptionError("BIP32Keystore. Failed to deserialize the root node.") } let prefixPath = rootPrefix @@ -176,29 +176,29 @@ public class BIP32Keystore: AbstractKeystore { if let upperIndex = (path.range(of: prefixPath)?.upperBound), upperIndex < path.endIndex { pathAppendix = String(path[path.index(after: upperIndex).. [EthereumAddress] { - guard let decryptedRootNode = try? getPrefixNodeData(password), + guard let decryptedRootNode = try getPrefixNodeData(password), let rootNode = HDNode(decryptedRootNode) else { - throw AbstractKeystoreError.encryptionError("Failed to decrypt a keystore") + throw AbstractKeystoreError.encryptionError("BIP32Keystore. Failed to decrypt a keystore. Check given password.") } return try [UInt](0.. Data? { guard let keystorePars = keystoreParams else { return nil diff --git a/Sources/Web3Core/KeystoreManager/BIP39.swift b/Sources/Web3Core/KeystoreManager/BIP39.swift index e9965ef5d..ce0b3ee28 100755 --- a/Sources/Web3Core/KeystoreManager/BIP39.swift +++ b/Sources/Web3Core/KeystoreManager/BIP39.swift @@ -95,11 +95,13 @@ public class BIP39 { } private static func entropyOf(size: Int) throws -> Data { + let isCorrectSize = size >= 128 && size <= 256 && size.isMultiple(of: 32) + let randomBytesCount = size / 8 guard - size >= 128 && size <= 256 && size.isMultiple(of: 32), - let entropy = Data.randomBytes(length: size/8) + isCorrectSize, + let entropy = Data.randomBytes(length: randomBytesCount) else { - throw AbstractKeystoreError.noEntropyError + throw AbstractKeystoreError.noEntropyError("BIP39. \(!isCorrectSize ? "Requested entropy of wrong bits size \(size)." : "Failed to generated \(randomBytesCount) of random bytes.")") } return entropy } diff --git a/Sources/Web3Core/KeystoreManager/EthereumKeystoreV3.swift b/Sources/Web3Core/KeystoreManager/EthereumKeystoreV3.swift index d2602637c..733721be6 100755 --- a/Sources/Web3Core/KeystoreManager/EthereumKeystoreV3.swift +++ b/Sources/Web3Core/KeystoreManager/EthereumKeystoreV3.swift @@ -23,13 +23,13 @@ public class EthereumKeystoreV3: AbstractKeystore { } public func UNSAFE_getPrivateKeyData(password: String, account: EthereumAddress) throws -> Data { - if self.addresses?.count == 1 && account == self.addresses?.last { - guard let privateKey = try? self.getKeyData(password) else { + if account == addresses?.last { + guard let privateKey = try? getKeyData(password) else { throw AbstractKeystoreError.invalidPasswordError } return privateKey } - throw AbstractKeystoreError.invalidAccountError + throw AbstractKeystoreError.invalidAccountError("EthereumKeystoreV3. Cannot get private key: keystore doesn't contain information about given address \(account.address).") } // Class @@ -77,7 +77,7 @@ public class EthereumKeystoreV3: AbstractKeystore { defer { Data.zero(&newPrivateKey) } - try encryptDataToStorage(password, keyData: newPrivateKey, aesMode: aesMode) + try encryptDataToStorage(password, privateKey: newPrivateKey, aesMode: aesMode) } public init?(privateKey: Data, password: String, aesMode: String = "aes-128-cbc") throws { @@ -87,53 +87,46 @@ public class EthereumKeystoreV3: AbstractKeystore { guard SECP256K1.verifyPrivateKey(privateKey: privateKey) else { return nil } - try encryptDataToStorage(password, keyData: privateKey, aesMode: aesMode) + try encryptDataToStorage(password, privateKey: privateKey, aesMode: aesMode) } - fileprivate func encryptDataToStorage(_ password: String, keyData: Data?, dkLen: Int = 32, N: Int = 4096, R: Int = 6, P: Int = 1, aesMode: String = "aes-128-cbc") throws { - if keyData == nil { - throw AbstractKeystoreError.encryptionError("Encryption without key data") + fileprivate func encryptDataToStorage(_ password: String, privateKey: Data, dkLen: Int = 32, N: Int = 4096, R: Int = 6, P: Int = 1, aesMode: String = "aes-128-cbc") throws { + if privateKey.count != 32 { + throw AbstractKeystoreError.encryptionError("EthereumKeystoreV3. Attempted encryption with private key of length != 32. Given private key length is \(privateKey.count).") } let saltLen = 32 guard let saltData = Data.randomBytes(length: saltLen) else { - throw AbstractKeystoreError.noEntropyError + throw AbstractKeystoreError.noEntropyError("EthereumKeystoreV3. Failed to generate random bytes: `Data.randomBytes(length: \(saltLen))`.") } guard let derivedKey = scrypt(password: password, salt: saltData, length: dkLen, N: N, R: R, P: P) else { - throw AbstractKeystoreError.keyDerivationError + throw AbstractKeystoreError.keyDerivationError("EthereumKeystoreV3. Scrypt function failed.") } let last16bytes = Data(derivedKey[(derivedKey.count - 16)...(derivedKey.count - 1)]) let encryptionKey = Data(derivedKey[0...15]) guard let IV = Data.randomBytes(length: 16) else { - throw AbstractKeystoreError.noEntropyError + throw AbstractKeystoreError.noEntropyError("EthereumKeystoreV3. Failed to generate random bytes: `Data.randomBytes(length: 16)`.") } - var aesCipher: AES? - switch aesMode { + var aesCipher: AES + switch aesMode.lowercased() { case "aes-128-cbc": - aesCipher = try? AES(key: encryptionKey.bytes, blockMode: CBC(iv: IV.bytes), padding: .noPadding) + aesCipher = try AES(key: encryptionKey.bytes, blockMode: CBC(iv: IV.bytes), padding: .noPadding) case "aes-128-ctr": - aesCipher = try? AES(key: encryptionKey.bytes, blockMode: CTR(iv: IV.bytes), padding: .noPadding) + aesCipher = try AES(key: encryptionKey.bytes, blockMode: CTR(iv: IV.bytes), padding: .noPadding) default: - aesCipher = nil + throw AbstractKeystoreError.aesError("EthereumKeystoreV3. AES error: given AES mode can be one of 'aes-128-cbc' or 'aes-128-ctr'. Instead '\(aesMode)' was given.") } - if aesCipher == nil { - throw AbstractKeystoreError.aesError - } - guard let encryptedKey = try aesCipher?.encrypt(keyData!.bytes) else { - throw AbstractKeystoreError.aesError - } - let encryptedKeyData = Data(encryptedKey) - var dataForMAC = Data() - dataForMAC.append(last16bytes) - dataForMAC.append(encryptedKeyData) + + let encryptedKeyData = Data(try aesCipher.encrypt(privateKey.bytes)) + let dataForMAC = last16bytes + encryptedKeyData let mac = dataForMAC.sha3(.keccak256) let kdfparams = KdfParamsV3(salt: saltData.toHexString(), dklen: dkLen, n: N, p: P, r: R, c: nil, prf: nil) let cipherparams = CipherParamsV3(iv: IV.toHexString()) let crypto = CryptoParamsV3(ciphertext: encryptedKeyData.toHexString(), cipher: aesMode, cipherparams: cipherparams, kdf: "scrypt", kdfparams: kdfparams, mac: mac.toHexString(), version: nil) - guard let pubKey = Utilities.privateToPublic(keyData!) else { - throw AbstractKeystoreError.keyDerivationError + guard let publicKey = Utilities.privateToPublic(privateKey) else { + throw AbstractKeystoreError.keyDerivationError("EthereumKeystoreV3. Failed to derive public key from given private key. `Utilities.privateToPublic(privateKey)` returned `nil`.") } - guard let addr = Utilities.publicToAddress(pubKey) else { - throw AbstractKeystoreError.keyDerivationError + guard let addr = Utilities.publicToAddress(publicKey) else { + throw AbstractKeystoreError.keyDerivationError("EthereumKeystoreV3. Failed to derive address from derived public key. `Utilities.publicToAddress(publicKey)` returned `nil`.") } self.address = addr let keystoreparams = KeystoreParamsV3(address: addr.address.lowercased(), crypto: crypto, id: UUID().uuidString.lowercased(), version: 3) @@ -141,14 +134,13 @@ public class EthereumKeystoreV3: AbstractKeystore { } public func regenerate(oldPassword: String, newPassword: String, dkLen: Int = 32, N: Int = 4096, R: Int = 6, P: Int = 1) throws { - var keyData = try self.getKeyData(oldPassword) - if keyData == nil { - throw AbstractKeystoreError.encryptionError("Failed to decrypt a keystore") + guard var privateKey = try getKeyData(oldPassword) else { + throw AbstractKeystoreError.encryptionError("EthereumKeystoreV3. Failed to decrypt a keystore") } defer { - Data.zero(&keyData!) + Data.zero(&privateKey) } - try self.encryptDataToStorage(newPassword, keyData: keyData!, aesMode: self.keystoreParams!.crypto.cipher) + try self.encryptDataToStorage(newPassword, privateKey: privateKey, aesMode: self.keystoreParams!.crypto.cipher) } fileprivate func getKeyData(_ password: String) throws -> Data? { diff --git a/Sources/Web3Core/KeystoreManager/KeystoreManager.swift b/Sources/Web3Core/KeystoreManager/KeystoreManager.swift index db2cfe22b..b0eedd077 100755 --- a/Sources/Web3Core/KeystoreManager/KeystoreManager.swift +++ b/Sources/Web3Core/KeystoreManager/KeystoreManager.swift @@ -43,7 +43,7 @@ public class KeystoreManager: AbstractKeystore { public func UNSAFE_getPrivateKeyData(password: String, account: EthereumAddress) throws -> Data { guard let keystore = walletForAddress(account) else { - throw AbstractKeystoreError.invalidAccountError + throw AbstractKeystoreError.invalidAccountError("KeystoreManager: no keystore/wallet found for given address. Address `\(account.address)`.") } return try keystore.UNSAFE_getPrivateKeyData(password: password, account: account) } diff --git a/Sources/Web3Core/Transaction/CodableTransaction.swift b/Sources/Web3Core/Transaction/CodableTransaction.swift index 806e2a36f..1246e2714 100644 --- a/Sources/Web3Core/Transaction/CodableTransaction.swift +++ b/Sources/Web3Core/Transaction/CodableTransaction.swift @@ -156,7 +156,7 @@ public struct CodableTransaction { let result = self.attemptSignature(privateKey: privateKey, useExtraEntropy: useExtraEntropy) if result { return } } - throw AbstractKeystoreError.invalidAccountError + throw AbstractKeystoreError.invalidAccountError("Failed to sign transaction with given private key.") } // actual signing algorithm implementation diff --git a/Tests/web3swiftTests/localTests/EIP712TypedDataPayloadTests.swift b/Tests/web3swiftTests/localTests/EIP712TypedDataPayloadTests.swift index 7189af1a6..6cf5854db 100644 --- a/Tests/web3swiftTests/localTests/EIP712TypedDataPayloadTests.swift +++ b/Tests/web3swiftTests/localTests/EIP712TypedDataPayloadTests.swift @@ -142,6 +142,24 @@ class EIP712TypedDataPayloadTests: XCTestCase { } } + func testEIP712ParserWithCustomTypeArrays() throws { + let problematicTypeExample = """ + {"types":{"EIP712Domain":[{"name":"name","type":"string"},{"name":"version","type":"string"},{"name":"chainId","type":"uint256"},{"name":"verifyingContract","type":"address"}],"OrderComponents":[{"name":"offerer","type":"address"},{"name":"zone","type":"address"},{"name":"offer","type":"OfferItem[]"},{"name":"consideration","type":"ConsiderationItem[]"},{"name":"orderType","type":"uint8"},{"name":"startTime","type":"uint256"},{"name":"endTime","type":"uint256"},{"name":"zoneHash","type":"bytes32"},{"name":"salt","type":"uint256"},{"name":"conduitKey","type":"bytes32"},{"name":"counter","type":"uint256"}],"OfferItem":[{"name":"itemType","type":"uint8"},{"name":"token","type":"address"},{"name":"identifierOrCriteria","type":"uint256"},{"name":"startAmount","type":"uint256"},{"name":"endAmount","type":"uint256"}],"ConsiderationItem":[{"name":"itemType","type":"uint8"},{"name":"token","type":"address"},{"name":"identifierOrCriteria","type":"uint256"},{"name":"startAmount","type":"uint256"},{"name":"endAmount","type":"uint256"},{"name":"recipient","type":"address"}]},"primaryType":"OrderComponents","domain":{"name":"Seaport","version":"1.5","chainId":"5","verifyingContract":"0x00000000000000ADc04C56Bf30aC9d3c0aAF14dC"},"message":{"offerer":"0xD0727E8a578DE9Dd19BcED635B1aa43576E638bC","offer":[{"itemType":"2","token":"0xE84a7676aAe742770A179dd7431073429a88c7B8","identifierOrCriteria":"44","startAmount":"1","endAmount":"1"}],"consideration":[{"itemType":"0","token":"0x0000000000000000000000000000000000000000","identifierOrCriteria":"0","startAmount":"950000000000000000","endAmount":"950000000000000000","recipient":"0xD0727E8a578DE9Dd19BcED635B1aa43576E638bC"},{"itemType":"0","token":"0x0000000000000000000000000000000000000000","identifierOrCriteria":"0","startAmount":"25000000000000000","endAmount":"25000000000000000","recipient":"0x0000a26b00c1F0DF003000390027140000fAa719"},{"itemType":"0","token":"0x0000000000000000000000000000000000000000","identifierOrCriteria":"0","startAmount":"25000000000000000","endAmount":"25000000000000000","recipient":"0xbDEf201FB5BE36579b6B66971d40A6e162b92B80"}],"startTime":"1698665491","endTime":"1701343891","orderType":"0","zone":"0x004C00500000aD104D7DBd00e3ae0A5C00560C00","zoneHash":"0x0000000000000000000000000000000000000000000000000000000000000000","salt":"24446860302761739304752683030156737591518664810215442929808784621098726351597","conduitKey":"0x0000007b02230091a7ed01230072f7006a004d60a8d4e71d599b8104250f0000","totalOriginalConsiderationItems":"3","counter":"0"}} + """ + XCTAssertNoThrow(try EIP712Parser.parse(problematicTypeExample)) + } + + func testEIP712SignHashWithCustomTypeArrays() throws { + let problematicTypeExample = """ + {"types":{"EIP712Domain":[{"name":"name","type":"string"},{"name":"version","type":"string"},{"name":"chainId","type":"uint256"},{"name":"verifyingContract","type":"address"}],"OrderComponents":[{"name":"offerer","type":"address"},{"name":"zone","type":"address"},{"name":"offer","type":"OfferItem[]"},{"name":"consideration","type":"ConsiderationItem[]"},{"name":"orderType","type":"uint8"},{"name":"startTime","type":"uint256"},{"name":"endTime","type":"uint256"},{"name":"zoneHash","type":"bytes32"},{"name":"salt","type":"uint256"},{"name":"conduitKey","type":"bytes32"},{"name":"counter","type":"uint256"}],"OfferItem":[{"name":"itemType","type":"uint8"},{"name":"token","type":"address"},{"name":"identifierOrCriteria","type":"uint256"},{"name":"startAmount","type":"uint256"},{"name":"endAmount","type":"uint256"}],"ConsiderationItem":[{"name":"itemType","type":"uint8"},{"name":"token","type":"address"},{"name":"identifierOrCriteria","type":"uint256"},{"name":"startAmount","type":"uint256"},{"name":"endAmount","type":"uint256"},{"name":"recipient","type":"address"}]},"primaryType":"OrderComponents","domain":{"name":"Seaport","version":"1.5","chainId":"5","verifyingContract":"0x00000000000000ADc04C56Bf30aC9d3c0aAF14dC"},"message":{"offerer":"0xD0727E8a578DE9Dd19BcED635B1aa43576E638bC","offer":[{"itemType":"2","token":"0xE84a7676aAe742770A179dd7431073429a88c7B8","identifierOrCriteria":"44","startAmount":"1","endAmount":"1"}],"consideration":[{"itemType":"0","token":"0x0000000000000000000000000000000000000000","identifierOrCriteria":"0","startAmount":"950000000000000000","endAmount":"950000000000000000","recipient":"0xD0727E8a578DE9Dd19BcED635B1aa43576E638bC"},{"itemType":"0","token":"0x0000000000000000000000000000000000000000","identifierOrCriteria":"0","startAmount":"25000000000000000","endAmount":"25000000000000000","recipient":"0x0000a26b00c1F0DF003000390027140000fAa719"},{"itemType":"0","token":"0x0000000000000000000000000000000000000000","identifierOrCriteria":"0","startAmount":"25000000000000000","endAmount":"25000000000000000","recipient":"0xbDEf201FB5BE36579b6B66971d40A6e162b92B80"}],"startTime":"1698665491","endTime":"1701343891","orderType":"0","zone":"0x004C00500000aD104D7DBd00e3ae0A5C00560C00","zoneHash":"0x0000000000000000000000000000000000000000000000000000000000000000","salt":"24446860302761739304752683030156737591518664810215442929808784621098726351597","conduitKey":"0x0000007b02230091a7ed01230072f7006a004d60a8d4e71d599b8104250f0000","totalOriginalConsiderationItems":"3","counter":"0"}} + """ + let eip712Payload = try EIP712Parser.parse(problematicTypeExample) + XCTAssertEqual(try eip712Payload.encodeType("OrderComponents"), "OrderComponents(address offerer,address zone,OfferItem[] offer,ConsiderationItem[] consideration,uint8 orderType,uint256 startTime,uint256 endTime,bytes32 zoneHash,uint256 salt,bytes32 conduitKey,uint256 counter)OfferItem(uint8 itemType,address token,uint256 identifierOrCriteria,uint256 startAmount,uint256 endAmount)ConsiderationItem(uint8 itemType,address token,uint256 identifierOrCriteria,uint256 startAmount,uint256 endAmount,address recipient)") + XCTAssertEqual(try eip712Payload.encodeType("OfferItem"), "OfferItem(uint8 itemType,address token,uint256 identifierOrCriteria,uint256 startAmount,uint256 endAmount)") + XCTAssertEqual(try eip712Payload.encodeType("ConsiderationItem"), "ConsiderationItem(uint8 itemType,address token,uint256 identifierOrCriteria,uint256 startAmount,uint256 endAmount,address recipient)") + XCTAssertNoThrow(try eip712Payload.signHash()) + } + func testEIP712EncodeType() throws { let parsedEip712TypedData = try EIP712Parser.parse(EIP712TestData.testTypedDataPayload) try XCTAssertEqual(parsedEip712TypedData.encodeType("EIP712Domain"), "EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)") From 41828fbab824ce545ff9cda358ba29e5efbebeeb Mon Sep 17 00:00:00 2001 From: Jenea Vranceanu Date: Fri, 3 Nov 2023 10:25:44 +0200 Subject: [PATCH 31/62] feat: support for eth_signTypedDataV4 payload parsing --- .../Utils/EIP/EIP712/EIP712Parser.swift | 151 ++++----- .../EIP712TypedDataPayloadTests.swift | 308 +++++++++++++----- 2 files changed, 287 insertions(+), 172 deletions(-) diff --git a/Sources/web3swift/Utils/EIP/EIP712/EIP712Parser.swift b/Sources/web3swift/Utils/EIP/EIP712/EIP712Parser.swift index 6078392d2..90426ce8f 100644 --- a/Sources/web3swift/Utils/EIP/EIP712/EIP712Parser.swift +++ b/Sources/web3swift/Utils/EIP/EIP712/EIP712Parser.swift @@ -123,10 +123,31 @@ public struct EIP712TypeProperty: Codable { public let name: String /// Property type. A type that's ABI encodable. public let type: String + /// Strips brackets (e.g. [] - denoting an array) and other characters augmenting the type. + /// If ``type`` is an array of then ``coreType`` will return the type of the array. + public let coreType: String + + public let isArray: Bool public init(name: String, type: String) { - self.name = name - self.type = type + self.name = name.trimmingCharacters(in: .whitespacesAndNewlines) + self.type = type.trimmingCharacters(in: .whitespacesAndNewlines) + + var _coreType = self.type + if _coreType.hasSuffix("[]") { + _coreType.removeLast(2) + isArray = true + } else { + isArray = false + } + self.coreType = _coreType + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + let name = try container.decode(String.self, forKey: .name) + let type = try container.decode(String.self, forKey: .type) + self.init(name: name, type: type) } } @@ -144,82 +165,9 @@ public struct EIP712TypedData { domain: [String : AnyObject], message: [String : AnyObject]) throws { self.types = types - self.primaryType = primaryType + self.primaryType = primaryType.trimmingCharacters(in: .whitespacesAndNewlines) self.domain = domain self.message = message - if let problematicType = hasCircularDependency() { - throw Web3Error.inputError(desc: "Created EIP712TypedData has a circular dependency amongst it's types. Cycle was first identified in '\(problematicType)'. Review it's uses in 'types'.") - } - } - - /// Checks for a circular dependency among the given types. - /// - /// If a circular dependency is detected, it returns the name of the type where the cycle was first identified. - /// Otherwise, it returns `nil`. - /// - /// - Returns: The type name where a circular dependency is detected, or `nil` if no circular dependency exists. - /// - Note: The function utilizes depth-first search to identify the circular dependencies. - func hasCircularDependency() -> String? { - - /// Generates an adjacency list for the given types, representing their dependencies. - /// - /// - Parameter types: A dictionary mapping type names to their property definitions. - /// - Returns: An adjacency list representing type dependencies. - func createAdjacencyList(types: [String: [EIP712TypeProperty]]) -> [String: [String]] { - var adjList: [String: [String]] = [:] - - for (typeName, fields) in types { - adjList[typeName] = [] - for field in fields { - if types.keys.contains(field.type) { - adjList[typeName]?.append(field.type) - } - } - } - - return adjList - } - - let adjList = createAdjacencyList(types: types) - - /// Depth-first search to check for circular dependencies. - /// - /// - Parameters: - /// - node: The current type being checked. - /// - visited: A dictionary keeping track of the visited types. - /// - stack: A dictionary used for checking the current path for cycles. - /// - /// - Returns: `true` if a cycle is detected from the current node, `false` otherwise. - func depthFirstSearch(node: String, visited: inout [String: Bool], stack: inout [String: Bool]) -> Bool { - visited[node] = true - stack[node] = true - - for neighbor in adjList[node] ?? [] { - if visited[neighbor] == nil { - if depthFirstSearch(node: neighbor, visited: &visited, stack: &stack) { - return true - } - } else if stack[neighbor] == true { - return true - } - } - - stack[node] = false - return false - } - - var visited: [String: Bool] = [:] - var stack: [String: Bool] = [:] - - for typeName in adjList.keys { - if visited[typeName] == nil { - if depthFirstSearch(node: typeName, visited: &visited, stack: &stack) { - return typeName - } - } - } - - return nil } public func encodeType(_ type: String) throws -> String { @@ -237,9 +185,11 @@ public struct EIP712TypedData { var typesCovered = typesCovered var encodedSubtypes: [String] = [] let parameters = try typeData.map { attributeType in - if let innerTypes = types[attributeType.type], !typesCovered.contains(attributeType.type) { - encodedSubtypes.append(try encodeType(attributeType.type, innerTypes)) - typesCovered.append(attributeType.type) + if let innerTypes = types[attributeType.coreType], !typesCovered.contains(attributeType.coreType) { + typesCovered.append(attributeType.coreType) + if attributeType.coreType != type { + encodedSubtypes.append(try encodeType(attributeType.coreType, innerTypes)) + } } return "\(attributeType.type) \(attributeType.name)" } @@ -261,9 +211,10 @@ public struct EIP712TypedData { throw Web3Error.processingError(desc: "EIP712. Attempting to encode data for type that doesn't exist in this payload. Given type: \(type). Available types: \(types.values).") } - // Add field contents - for field in typeData { - let value = data[field.name] + func encodeField(_ field: EIP712TypeProperty, + value: AnyObject?) throws -> (encTypes: [ABI.Element.ParameterType], encValues: [Any]) { + var encTypes: [ABI.Element.ParameterType] = [] + var encValues: [Any] = [] if field.type == "string" { guard let value = value as? String else { throw Web3Error.processingError(desc: "EIP712. Type metadata '\(field)' and actual value '\(String(describing: value))' type doesn't match. Cannot cast value to String.") @@ -276,16 +227,44 @@ public struct EIP712TypedData { } encTypes.append(.bytes(length: 32)) encValues.append(value.sha3(.keccak256)) - } else if types[field.type] != nil { - guard let value = value as? [String : AnyObject] else { - throw Web3Error.processingError(desc: "EIP712. Custom type metadata '\(field)' and actual value '\(String(describing: value))' type doesn't match. Cannot cast value to [String : AnyObject].") + } else if field.isArray { + guard let values = value as? [AnyObject] else { + throw Web3Error.processingError(desc: "EIP712. Custom type metadata '\(field)' and actual value '\(String(describing: value))' type doesn't match. Cannot cast value to [AnyObject].") } encTypes.append(.bytes(length: 32)) - encValues.append(try encodeData(field.type, data: value).sha3(.keccak256)) + let subField = EIP712TypeProperty(name: field.name, type: field.coreType) + var encodedSubTypes: [ABI.Element.ParameterType] = [] + var encodedSubValues: [Any] = [] + try values.forEach { value in + let encoded = try encodeField(subField, value: value) + encodedSubTypes.append(contentsOf: encoded.encTypes) + encodedSubValues.append(contentsOf: encoded.encValues) + } + + guard let encodedValue = ABIEncoder.encode(types: encodedSubTypes, values: encodedSubValues) else { + throw Web3Error.processingError(desc: "EIP712. Failed to encode an array of custom type. Field: '\(field)'; value: '\(String(describing: value))'.") + } + + encValues.append(encodedValue.sha3(.keccak256)) + } else if types[field.coreType] != nil { + encTypes.append(.bytes(length: 32)) + if let value = value as? [String : AnyObject] { + encValues.append(try encodeData(field.type, data: value).sha3(.keccak256)) + } else { + encValues.append(Data(count: 32)) + } } else { encTypes.append(try ABITypeParser.parseTypeString(field.type)) encValues.append(value as Any) } + return (encTypes, encValues) + } + + // Add field contents + for field in typeData { + let (_encTypes, _encValues) = try encodeField(field, value: data[field.name]) + encTypes.append(contentsOf: _encTypes) + encValues.append(contentsOf: _encValues) } guard let encodedData = ABIEncoder.encode(types: encTypes, values: encValues) else { diff --git a/Tests/web3swiftTests/localTests/EIP712TypedDataPayloadTests.swift b/Tests/web3swiftTests/localTests/EIP712TypedDataPayloadTests.swift index 6cf5854db..6786c1c58 100644 --- a/Tests/web3swiftTests/localTests/EIP712TypedDataPayloadTests.swift +++ b/Tests/web3swiftTests/localTests/EIP712TypedDataPayloadTests.swift @@ -57,91 +57,6 @@ class EIP712TypedDataPayloadTests: XCTestCase { XCTAssertEqual(parsedEip712TypedData.message["contents"] as? String, "Hello, Bob!") } - func testEIP712CircularDependency() throws { - let problematicTypeExample = """ - { - "types":{ - "EIP712Domain":[ - { - "name":"name", - "type":"string" - }, - { - "name":"version", - "type":"string" - }, - { - "name":"chainId", - "type":"uint256" - }, - { - "name":"verifyingContract", - "type":"address" - } - ], - "Person":[ - { - "name":"name", - "type":"string" - }, - { - "name":"wallet", - "type":"address" - }, - { - "name":"mail", - "type":"Mail" - } - ], - "Mail":[ - { - "name":"from", - "type":"Person" - }, - { - "name":"to", - "type":"Person" - }, - { - "name":"contents", - "type":"string" - } - ] - }, - "primaryType":"Mail", - "domain":{ - "name":"Ether Mail", - "version":"1", - "chainId":1, - "verifyingContract":"0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC" - }, - "message":{ - "from":{ - "name":"Cow", - "wallet":"0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826" - }, - "to":{ - "name":"Bob", - "wallet":"0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB" - }, - "contents":"Hello, Bob!" - } - } - """ - XCTAssertThrowsError(try EIP712Parser.parse(problematicTypeExample)) { error in - guard let error = error as? Web3Error else { - XCTFail("Thrown error is not Web3Error.") - return - } - - if case let .inputError(desc) = error { - XCTAssertTrue(desc.hasPrefix("Created EIP712TypedData has a circular dependency amongst it's types.")) - } else { - XCTFail("A different Web3Error is thrown. Something changed?") - } - } - } - func testEIP712ParserWithCustomTypeArrays() throws { let problematicTypeExample = """ {"types":{"EIP712Domain":[{"name":"name","type":"string"},{"name":"version","type":"string"},{"name":"chainId","type":"uint256"},{"name":"verifyingContract","type":"address"}],"OrderComponents":[{"name":"offerer","type":"address"},{"name":"zone","type":"address"},{"name":"offer","type":"OfferItem[]"},{"name":"consideration","type":"ConsiderationItem[]"},{"name":"orderType","type":"uint8"},{"name":"startTime","type":"uint256"},{"name":"endTime","type":"uint256"},{"name":"zoneHash","type":"bytes32"},{"name":"salt","type":"uint256"},{"name":"conduitKey","type":"bytes32"},{"name":"counter","type":"uint256"}],"OfferItem":[{"name":"itemType","type":"uint8"},{"name":"token","type":"address"},{"name":"identifierOrCriteria","type":"uint256"},{"name":"startAmount","type":"uint256"},{"name":"endAmount","type":"uint256"}],"ConsiderationItem":[{"name":"itemType","type":"uint8"},{"name":"token","type":"address"},{"name":"identifierOrCriteria","type":"uint256"},{"name":"startAmount","type":"uint256"},{"name":"endAmount","type":"uint256"},{"name":"recipient","type":"address"}]},"primaryType":"OrderComponents","domain":{"name":"Seaport","version":"1.5","chainId":"5","verifyingContract":"0x00000000000000ADc04C56Bf30aC9d3c0aAF14dC"},"message":{"offerer":"0xD0727E8a578DE9Dd19BcED635B1aa43576E638bC","offer":[{"itemType":"2","token":"0xE84a7676aAe742770A179dd7431073429a88c7B8","identifierOrCriteria":"44","startAmount":"1","endAmount":"1"}],"consideration":[{"itemType":"0","token":"0x0000000000000000000000000000000000000000","identifierOrCriteria":"0","startAmount":"950000000000000000","endAmount":"950000000000000000","recipient":"0xD0727E8a578DE9Dd19BcED635B1aa43576E638bC"},{"itemType":"0","token":"0x0000000000000000000000000000000000000000","identifierOrCriteria":"0","startAmount":"25000000000000000","endAmount":"25000000000000000","recipient":"0x0000a26b00c1F0DF003000390027140000fAa719"},{"itemType":"0","token":"0x0000000000000000000000000000000000000000","identifierOrCriteria":"0","startAmount":"25000000000000000","endAmount":"25000000000000000","recipient":"0xbDEf201FB5BE36579b6B66971d40A6e162b92B80"}],"startTime":"1698665491","endTime":"1701343891","orderType":"0","zone":"0x004C00500000aD104D7DBd00e3ae0A5C00560C00","zoneHash":"0x0000000000000000000000000000000000000000000000000000000000000000","salt":"24446860302761739304752683030156737591518664810215442929808784621098726351597","conduitKey":"0x0000007b02230091a7ed01230072f7006a004d60a8d4e71d599b8104250f0000","totalOriginalConsiderationItems":"3","counter":"0"}} @@ -214,7 +129,7 @@ class EIP712TypedDataPayloadTests: XCTestCase { /// This signing doesn't use `"\u{19}Ethereum Signed Message:\n"`. As per EIP712 standard /// the following format is used instead: /// ``` - /// encode(domainSeparator : 𝔹²⁵⁶, message : 𝕊) = "\x19\x01" ‖ domainSeparator ‖ hashStruct(message) + /// encode(domainSeparator : 𝔹²⁵⁶, message : 𝕊) = "\x19\x01" ‖ domainSeparator ‖ structHash(message) /// ``` /// /// The output of ``EIP712TypedData.signHash`` is exactly that. @@ -224,4 +139,225 @@ class EIP712TypedDataPayloadTests: XCTestCase { XCTAssertEqual(unmarshalledSignature.r.toHexString(), "4355c47d63924e8a72e509b65029052eb6c299d53a04e167c5775fd466751c9d") XCTAssertEqual(unmarshalledSignature.s.toHexString(), "07299936d304c153f6443dfa05f40ff007d72911b6f72307f996231605b91562") } + + func testEIP712SignedTypedDataV4() throws { + // Payload includes recursive types, arrays and empty fields + let rawPayload = """ + { + "types":{ + "EIP712Domain":[ + { + "name":"name", + "type":"string" + }, + { + "name":"version", + "type":"string" + }, + { + "name":"chainId", + "type":"uint256" + }, + { + "name":"verifyingContract", + "type":"address" + } + ], + "Person":[ + { + "name":"name", + "type":"string" + }, + { + "name":"wallets", + "type":"address[]" + } + ], + "Mail":[ + { + "name":"from", + "type":"Person" + }, + { + "name":"to", + "type":"Person[]" + }, + { + "name":"contents", + "type":"string" + } + ], + "Group":[ + { + "name":"name", + "type":"string" + }, + { + "name":"members", + "type":"Person[]" + } + ] + }, + "domain":{ + "name":"Ether Mail", + "version":"1", + "chainId":1, + "verifyingContract":"0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC" + }, + "primaryType":"Mail", + "message":{ + "from":{ + "name":"Cow", + "wallets":[ + "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826", + "0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF" + ] + }, + "to":[ + { + "name":"Bob", + "wallets":[ + "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB", + "0xB0BdaBea57B0BDABeA57b0bdABEA57b0BDabEa57", + "0xB0B0b0b0b0b0B000000000000000000000000000" + ] + } + ], + "contents":"Hello, Bob!" + } + } + """ + let parsedEip712TypedData = try EIP712Parser.parse(rawPayload) + XCTAssertEqual(try parsedEip712TypedData.encodeType("Group"), + "Group(string name,Person[] members)Person(string name,address[] wallets)") + XCTAssertEqual(try parsedEip712TypedData.encodeType("Person"), + "Person(string name,address[] wallets)") + XCTAssertEqual(try parsedEip712TypedData.typeHash("Person"), + "0xfabfe1ed996349fc6027709802be19d047da1aa5d6894ff5f6486d92db2e6860") + + XCTAssertEqual(try parsedEip712TypedData.encodeData("Person", data: parsedEip712TypedData.message["from"] as! [String : AnyObject]).toHexString(), + "fabfe1ed996349fc6027709802be19d047da1aa5d6894ff5f6486d92db2e68608c1d2bd5348394761719da11ec67eedae9502d137e8940fee8ecd6f641ee16488a8bfe642b9fc19c25ada5dadfd37487461dc81dd4b0778f262c163ed81b5e2a") + XCTAssertEqual(try parsedEip712TypedData.structHash("Person", data: parsedEip712TypedData.message["from"] as! [String : AnyObject]).toHexString(), + "9b4846dd48b866f0ac54d61b9b21a9e746f921cefa4ee94c4c0a1c49c774f67f") + + XCTAssertEqual(try parsedEip712TypedData.encodeData("Person", data: (parsedEip712TypedData.message["to"] as! [[String : AnyObject]])[0]).toHexString(), + "fabfe1ed996349fc6027709802be19d047da1aa5d6894ff5f6486d92db2e686028cac318a86c8a0a6a9156c2dba2c8c2363677ba0514ef616592d81557e679b6d2734f4c86cc3bd9cabf04c3097589d3165d95e4648fc72d943ed161f651ec6d") + XCTAssertEqual(try parsedEip712TypedData.structHash("Person", data: (parsedEip712TypedData.message["to"] as! [[String : AnyObject]])[0]).toHexString(), + "efa62530c7ae3a290f8a13a5fc20450bdb3a6af19d9d9d2542b5a94e631a9168") + + XCTAssertEqual(try parsedEip712TypedData.encodeType("Mail"), + "Mail(Person from,Person[] to,string contents)Person(string name,address[] wallets)") + XCTAssertEqual(try parsedEip712TypedData.typeHash("Mail"), + "0x4bd8a9a2b93427bb184aca81e24beb30ffa3c747e2a33d4225ec08bf12e2e753") + XCTAssertEqual(try parsedEip712TypedData.encodeData().toHexString(), + "4bd8a9a2b93427bb184aca81e24beb30ffa3c747e2a33d4225ec08bf12e2e7539b4846dd48b866f0ac54d61b9b21a9e746f921cefa4ee94c4c0a1c49c774f67fca322beec85be24e374d18d582a6f2997f75c54e7993ab5bc07404ce176ca7cdb5aadf3154a261abdd9086fc627b61efca26ae5702701d05cd2305f7c52a2fc8") + XCTAssertEqual(try parsedEip712TypedData.structHash().toHexString(), + "eb4221181ff3f1a83ea7313993ca9218496e424604ba9492bb4052c03d5c3df8") + XCTAssertEqual(try parsedEip712TypedData.structHash("EIP712Domain", data: parsedEip712TypedData.domain).toHexString(), + "f2cee375fa42b42143804025fc449deafd50cc031ca257e0b194a650a912090f") + XCTAssertEqual(try parsedEip712TypedData.signHash().toHexString(), + "a85c2e2b118698e88db68a8105b794a8cc7cec074e89ef991cb4f5f533819cc2") + + let privateKey = Data.fromHex("cow".sha3(.keccak256).addHexPrefix())! + let publicKey = Utilities.privateToPublic(privateKey)! + let address = Utilities.publicToAddress(publicKey)! + XCTAssertEqual(address, EthereumAddress("0xcd2a3d9f938e13cd947ec05abc7fe734df8dd826")); + let (compressedSignature, _) = try SECP256K1.signForRecovery(hash: parsedEip712TypedData.signHash(), privateKey: privateKey) + XCTAssertEqual(compressedSignature!.toHexString(), "65cbd956f2fae28a601bebc9b906cea0191744bd4c4247bcd27cd08f8eb6b71c78efdf7a31dc9abee78f492292721f362d296cf86b4538e07b51303b67f749061b") + } + + func testEIP712SignedTypedDataV4_differentPayload() throws { + let rawPayload = + """ + { + "types":{ + "EIP712Domain":[ + { + "name":"name", + "type":"string" + }, + { + "name":"version", + "type":"string" + }, + { + "name":"chainId", + "type":"uint256" + }, + { + "name":"verifyingContract", + "type":"address" + } + ], + "Person":[ + { + "name":"name", + "type":"string" + }, + { + "name":"mother", + "type":"Person" + }, + { + "name":"father", + "type":"Person" + } + ] + }, + "domain":{ + "name":"Family Tree", + "version":"1", + "chainId":1, + "verifyingContract":"0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC" + }, + "primaryType":"Person", + "message":{ + "name":"Jon", + "mother":{ + "name":"Lyanna", + "father":{ + "name":"Rickard" + } + }, + "father":{ + "name":"Rhaegar", + "father":{ + "name":"Aeris II" + } + } + } + } + """ + + let parsedEip712TypedData = try EIP712Parser.parse(rawPayload) + + XCTAssertEqual(try parsedEip712TypedData.encodeType("Person"), "Person(string name,Person mother,Person father)") + XCTAssertEqual(try parsedEip712TypedData.typeHash("Person"), "0x7c5c8e90cb92c8da53b893b24962513be98afcf1b57b00327ae4cc14e3a64116") + + XCTAssertEqual(try parsedEip712TypedData.encodeData("Person", data: parsedEip712TypedData.message["mother"] as! [String : AnyObject]).toHexString(), + "7c5c8e90cb92c8da53b893b24962513be98afcf1b57b00327ae4cc14e3a64116afe4142a2b3e7b0503b44951e6030e0e2c5000ef83c61857e2e6003e7aef8570000000000000000000000000000000000000000000000000000000000000000088f14be0dd46a8ec608ccbff6d3923a8b4e95cdfc9648f0db6d92a99a264cb36") + XCTAssertEqual(try parsedEip712TypedData.structHash("Person", data: parsedEip712TypedData.message["mother"] as! [String : AnyObject]).toHexString(), + "9ebcfbf94f349de50bcb1e3aa4f1eb38824457c99914fefda27dcf9f99f6178b") + + XCTAssertEqual(try parsedEip712TypedData.encodeData("Person", data: parsedEip712TypedData.message["father"] as! [String : AnyObject]).toHexString(), + "7c5c8e90cb92c8da53b893b24962513be98afcf1b57b00327ae4cc14e3a64116b2a7c7faba769181e578a391a6a6811a3e84080c6a3770a0bf8a856dfa79d333000000000000000000000000000000000000000000000000000000000000000002cc7460f2c9ff107904cff671ec6fee57ba3dd7decf999fe9fe056f3fd4d56e") + XCTAssertEqual(try parsedEip712TypedData.structHash("Person", data: parsedEip712TypedData.message["father"] as! [String : AnyObject]).toHexString(), + "b852e5abfeff916a30cb940c4e24c43cfb5aeb0fa8318bdb10dd2ed15c8c70d8") + + XCTAssertEqual(try parsedEip712TypedData.encodeData(parsedEip712TypedData.primaryType, data: parsedEip712TypedData.message).toHexString(), + "7c5c8e90cb92c8da53b893b24962513be98afcf1b57b00327ae4cc14e3a64116e8d55aa98b6b411f04dbcf9b23f29247bb0e335a6bc5368220032fdcb9e5927f9ebcfbf94f349de50bcb1e3aa4f1eb38824457c99914fefda27dcf9f99f6178bb852e5abfeff916a30cb940c4e24c43cfb5aeb0fa8318bdb10dd2ed15c8c70d8") + XCTAssertEqual(try parsedEip712TypedData.structHash(parsedEip712TypedData.primaryType, data: parsedEip712TypedData.message).toHexString(), + "fdc7b6d35bbd81f7fa78708604f57569a10edff2ca329c8011373f0667821a45") + XCTAssertEqual(try parsedEip712TypedData.structHash("EIP712Domain", data: parsedEip712TypedData.domain).toHexString(), + "facb2c1888f63a780c84c216bd9a81b516fc501a19bae1fc81d82df590bbdc60") + XCTAssertEqual(try parsedEip712TypedData.signHash().toHexString(), + "807773b9faa9879d4971b43856c4d60c2da15c6f8c062bd9d33afefb756de19c") + + let privateKey = Data.fromHex("dragon".sha3(.keccak256).addHexPrefix())! + let publicKey = Utilities.privateToPublic(privateKey)! + let address = Utilities.publicToAddress(publicKey)! + XCTAssertEqual(address, EthereumAddress("0x065a687103c9f6467380bee800ecd70b17f6b72f")); + let (compressedSignature, _) = try SECP256K1.signForRecovery(hash: parsedEip712TypedData.signHash(), privateKey: privateKey) + XCTAssertEqual(compressedSignature!.toHexString(), "f2ec61e636ff7bb3ac8bc2a4cc2c8b8f635dd1b2ec8094c963128b358e79c85c5ca6dd637ed7e80f0436fe8fce39c0e5f2082c9517fe677cc2917dcd6c84ba881c") + } } From c6da90fb865e1f6c9a7fb851fbfb554918f0edf3 Mon Sep 17 00:00:00 2001 From: Jenea Vranceanu Date: Fri, 3 Nov 2023 16:45:55 +0200 Subject: [PATCH 32/62] chore: make parsing errors more granular --- .../Utils/EIP/EIP712/EIP712Parser.swift | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/Sources/web3swift/Utils/EIP/EIP712/EIP712Parser.swift b/Sources/web3swift/Utils/EIP/EIP712/EIP712Parser.swift index 90426ce8f..04d87495d 100644 --- a/Sources/web3swift/Utils/EIP/EIP712/EIP712Parser.swift +++ b/Sources/web3swift/Utils/EIP/EIP712/EIP712Parser.swift @@ -102,14 +102,18 @@ public class EIP712Parser { public static func parse(_ rawJson: Data) throws -> EIP712TypedData { let decoder = JSONDecoder() let types = try decoder.decode(EIP712TypeArray.self, from: rawJson).types - guard let json = try rawJson.asJsonDictionary(), - let primaryType = json["primaryType"] as? String, - let domain = json["domain"] as? [String : AnyObject], - let message = json["message"] as? [String : AnyObject] - else { - throw Web3Error.inputError(desc: "EIP712Parser: cannot decode EIP712TypedData object. Failed to parse one of primaryType, domain or message fields. Is any field missing?") + guard let json = try rawJson.asJsonDictionary() else { + throw Web3Error.inputError(desc: "EIP712Parser. Cannot decode given JSON as it cannot be represented as a Dictionary. Is it valid JSON?") + } + guard let primaryType = json["primaryType"] as? String else { + throw Web3Error.inputError(desc: "EIP712Parser. Top-level string field 'primaryType' missing.") + } + guard let domain = json["domain"] as? [String : AnyObject] else { + throw Web3Error.inputError(desc: "EIP712Parser. Top-level object field 'domain' missing.") + } + guard let message = json["message"] as? [String : AnyObject] else { + throw Web3Error.inputError(desc: "EIP712Parser. Top-level object field 'message' missing.") } - return try EIP712TypedData(types: types, primaryType: primaryType, domain: domain, message: message) } } From e40c3f8e23139382953637dc1746038160e8e960 Mon Sep 17 00:00:00 2001 From: Jenea Vranceanu Date: Fri, 3 Nov 2023 16:46:40 +0200 Subject: [PATCH 33/62] fix: order custom subtypes (no primitives!) in alphabetical order when encoding --- Sources/web3swift/Utils/EIP/EIP712/EIP712Parser.swift | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/Sources/web3swift/Utils/EIP/EIP712/EIP712Parser.swift b/Sources/web3swift/Utils/EIP/EIP712/EIP712Parser.swift index 04d87495d..154b744d2 100644 --- a/Sources/web3swift/Utils/EIP/EIP712/EIP712Parser.swift +++ b/Sources/web3swift/Utils/EIP/EIP712/EIP712Parser.swift @@ -187,17 +187,21 @@ public struct EIP712TypedData { internal func encodeType(_ type: String, _ typeData: [EIP712TypeProperty], typesCovered: [String] = []) throws -> String { var typesCovered = typesCovered - var encodedSubtypes: [String] = [] + var encodedSubtypes: [String : String] = [:] let parameters = try typeData.map { attributeType in if let innerTypes = types[attributeType.coreType], !typesCovered.contains(attributeType.coreType) { typesCovered.append(attributeType.coreType) if attributeType.coreType != type { - encodedSubtypes.append(try encodeType(attributeType.coreType, innerTypes)) + encodedSubtypes[attributeType.coreType] = try encodeType(attributeType.coreType, innerTypes) } } return "\(attributeType.type) \(attributeType.name)" } - return type + "(" + parameters.joined(separator: ",") + ")" + encodedSubtypes.joined(separator: "") + return type + "(" + parameters.joined(separator: ",") + ")" + encodedSubtypes.sorted { lhs, rhs in + return lhs.key < rhs.key + } + .map { $0.value } + .joined(separator: "") } /// Convenience function for ``encodeData(_:data:)`` that uses ``primaryType`` and ``message`` as values. From 499008446fcf9eaecd43868f6333afd9bf4d46d7 Mon Sep 17 00:00:00 2001 From: Jenea Vranceanu Date: Fri, 3 Nov 2023 16:47:10 +0200 Subject: [PATCH 34/62] chore: added new test for Invalid Order Signature error --- .../EIP712TypedDataPayloadTests.swift | 99 +++++++++++++++++++ 1 file changed, 99 insertions(+) diff --git a/Tests/web3swiftTests/localTests/EIP712TypedDataPayloadTests.swift b/Tests/web3swiftTests/localTests/EIP712TypedDataPayloadTests.swift index 6786c1c58..89a2f5cd1 100644 --- a/Tests/web3swiftTests/localTests/EIP712TypedDataPayloadTests.swift +++ b/Tests/web3swiftTests/localTests/EIP712TypedDataPayloadTests.swift @@ -360,4 +360,103 @@ class EIP712TypedDataPayloadTests: XCTestCase { let (compressedSignature, _) = try SECP256K1.signForRecovery(hash: parsedEip712TypedData.signHash(), privateKey: privateKey) XCTAssertEqual(compressedSignature!.toHexString(), "f2ec61e636ff7bb3ac8bc2a4cc2c8b8f635dd1b2ec8094c963128b358e79c85c5ca6dd637ed7e80f0436fe8fce39c0e5f2082c9517fe677cc2917dcd6c84ba881c") } + + /// This test makes sure that custom types are alphabetically ordered when encoded + /// This test is built on thje following example: https://github.com/trustwallet/wallet-core/pull/2325/files + /// Link to the GitHub issue https://github.com/trustwallet/wallet-core/issues/2323 + /// > According to the description of the issues it fixes (see the link above): + /// > The type string is different from `metamask/eth-sig-util` + /// > `type: OrderComponents(...)OfferItem(...)ConsiderationItem(...)` + /// > `ConsiderationItem` should be in front of `OfferItem` + func testEIP712OpenseaInvalidOrderSignature() throws { + let rawPayload = """ + { + "types": { + "EIP712Domain": [ + { "name": "name", "type": "string" }, + { "name": "version", "type": "string" }, + { "name": "chainId", "type": "uint256" }, + { "name": "verifyingContract", "type": "address" } + ], + "OrderComponents": [ + { "name": "offerer", "type": "address" }, + { "name": "zone", "type": "address" }, + { "name": "offer", "type": "OfferItem[]" }, + { "name": "consideration", "type": "ConsiderationItem[]" }, + { "name": "orderType", "type": "uint8" }, + { "name": "startTime", "type": "uint256" }, + { "name": "endTime", "type": "uint256" }, + { "name": "zoneHash", "type": "bytes32" }, + { "name": "salt", "type": "uint256" }, + { "name": "conduitKey", "type": "bytes32" }, + { "name": "counter", "type": "uint256" } + ], + "OfferItem": [ + { "name": "itemType", "type": "uint8" }, + { "name": "token", "type": "address" }, + { "name": "identifierOrCriteria", "type": "uint256" }, + { "name": "startAmount", "type": "uint256" }, + { "name": "endAmount", "type": "uint256" } + ], + "ConsiderationItem": [ + { "name": "itemType", "type": "uint8" }, + { "name": "token", "type": "address" }, + { "name": "identifierOrCriteria", "type": "uint256" }, + { "name": "startAmount", "type": "uint256" }, + { "name": "endAmount", "type": "uint256" }, + { "name": "recipient", "type": "address" } + ] + }, + "primaryType": "OrderComponents", + "domain": { + "name": "Seaport", + "version": "1.1", + "chainId": "1", + "verifyingContract": "0x00000000006c3852cbEf3e08E8dF289169EdE581" + }, + "message": { + "offerer": "0x7d8bf18C7cE84b3E175b339c4Ca93aEd1dD166F1", + "offer": [ + { + "itemType": "2", + "token": "0x3F53082981815Ed8142384EDB1311025cA750Ef1", + "identifierOrCriteria": "134", + "startAmount": "1", + "endAmount": "1" + } + ], + "orderType": "2", + "consideration": [ + { + "itemType": "0", + "token": "0x0000000000000000000000000000000000000000", + "identifierOrCriteria": "0", + "startAmount": "975000000000000000", + "endAmount": "975000000000000000", + "recipient": "0x7d8bf18C7cE84b3E175b339c4Ca93aEd1dD166F1" + }, + { + "itemType": "0", + "token": "0x0000000000000000000000000000000000000000", + "identifierOrCriteria": "0", + "startAmount": "25000000000000000", + "endAmount": "25000000000000000", + "recipient": "0x8De9C5A032463C561423387a9648c5C7BCC5BC90" + } + ], + "startTime": "1655450129", + "endTime": "1658042129", + "zone": "0x004C00500000aD104D7DBd00e3ae0A5C00560C00", + "zoneHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "salt": "795459960395409", + "conduitKey": "0x0000007b02230091a7ed01230072f7006a004d60a8d4e71d599b8104250f0000", + "totalOriginalConsiderationItems": "2", + "counter": "0" + } + } + """ + + let parsedPayload = try EIP712Parser.parse(rawPayload) + try XCTAssertEqual(parsedPayload.signHash().toHexString(), "54140d99a864932cbc40fd8a2d1d1706c3923a79c183a3b151e929ac468064db") + } } From d69d594a2ab6aac258eed1fe1a4468dcae495fa8 Mon Sep 17 00:00:00 2001 From: Jenea Vranceanu Date: Fri, 3 Nov 2023 23:44:47 +0200 Subject: [PATCH 35/62] fix: updated test case to match the expected result --- .../localTests/EIP712TypedDataPayloadTests.swift | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Tests/web3swiftTests/localTests/EIP712TypedDataPayloadTests.swift b/Tests/web3swiftTests/localTests/EIP712TypedDataPayloadTests.swift index 89a2f5cd1..1a31b2b8d 100644 --- a/Tests/web3swiftTests/localTests/EIP712TypedDataPayloadTests.swift +++ b/Tests/web3swiftTests/localTests/EIP712TypedDataPayloadTests.swift @@ -69,7 +69,7 @@ class EIP712TypedDataPayloadTests: XCTestCase { {"types":{"EIP712Domain":[{"name":"name","type":"string"},{"name":"version","type":"string"},{"name":"chainId","type":"uint256"},{"name":"verifyingContract","type":"address"}],"OrderComponents":[{"name":"offerer","type":"address"},{"name":"zone","type":"address"},{"name":"offer","type":"OfferItem[]"},{"name":"consideration","type":"ConsiderationItem[]"},{"name":"orderType","type":"uint8"},{"name":"startTime","type":"uint256"},{"name":"endTime","type":"uint256"},{"name":"zoneHash","type":"bytes32"},{"name":"salt","type":"uint256"},{"name":"conduitKey","type":"bytes32"},{"name":"counter","type":"uint256"}],"OfferItem":[{"name":"itemType","type":"uint8"},{"name":"token","type":"address"},{"name":"identifierOrCriteria","type":"uint256"},{"name":"startAmount","type":"uint256"},{"name":"endAmount","type":"uint256"}],"ConsiderationItem":[{"name":"itemType","type":"uint8"},{"name":"token","type":"address"},{"name":"identifierOrCriteria","type":"uint256"},{"name":"startAmount","type":"uint256"},{"name":"endAmount","type":"uint256"},{"name":"recipient","type":"address"}]},"primaryType":"OrderComponents","domain":{"name":"Seaport","version":"1.5","chainId":"5","verifyingContract":"0x00000000000000ADc04C56Bf30aC9d3c0aAF14dC"},"message":{"offerer":"0xD0727E8a578DE9Dd19BcED635B1aa43576E638bC","offer":[{"itemType":"2","token":"0xE84a7676aAe742770A179dd7431073429a88c7B8","identifierOrCriteria":"44","startAmount":"1","endAmount":"1"}],"consideration":[{"itemType":"0","token":"0x0000000000000000000000000000000000000000","identifierOrCriteria":"0","startAmount":"950000000000000000","endAmount":"950000000000000000","recipient":"0xD0727E8a578DE9Dd19BcED635B1aa43576E638bC"},{"itemType":"0","token":"0x0000000000000000000000000000000000000000","identifierOrCriteria":"0","startAmount":"25000000000000000","endAmount":"25000000000000000","recipient":"0x0000a26b00c1F0DF003000390027140000fAa719"},{"itemType":"0","token":"0x0000000000000000000000000000000000000000","identifierOrCriteria":"0","startAmount":"25000000000000000","endAmount":"25000000000000000","recipient":"0xbDEf201FB5BE36579b6B66971d40A6e162b92B80"}],"startTime":"1698665491","endTime":"1701343891","orderType":"0","zone":"0x004C00500000aD104D7DBd00e3ae0A5C00560C00","zoneHash":"0x0000000000000000000000000000000000000000000000000000000000000000","salt":"24446860302761739304752683030156737591518664810215442929808784621098726351597","conduitKey":"0x0000007b02230091a7ed01230072f7006a004d60a8d4e71d599b8104250f0000","totalOriginalConsiderationItems":"3","counter":"0"}} """ let eip712Payload = try EIP712Parser.parse(problematicTypeExample) - XCTAssertEqual(try eip712Payload.encodeType("OrderComponents"), "OrderComponents(address offerer,address zone,OfferItem[] offer,ConsiderationItem[] consideration,uint8 orderType,uint256 startTime,uint256 endTime,bytes32 zoneHash,uint256 salt,bytes32 conduitKey,uint256 counter)OfferItem(uint8 itemType,address token,uint256 identifierOrCriteria,uint256 startAmount,uint256 endAmount)ConsiderationItem(uint8 itemType,address token,uint256 identifierOrCriteria,uint256 startAmount,uint256 endAmount,address recipient)") + XCTAssertEqual(try eip712Payload.encodeType("OrderComponents"), "OrderComponents(address offerer,address zone,OfferItem[] offer,ConsiderationItem[] consideration,uint8 orderType,uint256 startTime,uint256 endTime,bytes32 zoneHash,uint256 salt,bytes32 conduitKey,uint256 counter)ConsiderationItem(uint8 itemType,address token,uint256 identifierOrCriteria,uint256 startAmount,uint256 endAmount,address recipient)OfferItem(uint8 itemType,address token,uint256 identifierOrCriteria,uint256 startAmount,uint256 endAmount)") XCTAssertEqual(try eip712Payload.encodeType("OfferItem"), "OfferItem(uint8 itemType,address token,uint256 identifierOrCriteria,uint256 startAmount,uint256 endAmount)") XCTAssertEqual(try eip712Payload.encodeType("ConsiderationItem"), "ConsiderationItem(uint8 itemType,address token,uint256 identifierOrCriteria,uint256 startAmount,uint256 endAmount,address recipient)") XCTAssertNoThrow(try eip712Payload.signHash()) @@ -368,7 +368,9 @@ class EIP712TypedDataPayloadTests: XCTestCase { /// > The type string is different from `metamask/eth-sig-util` /// > `type: OrderComponents(...)OfferItem(...)ConsiderationItem(...)` /// > `ConsiderationItem` should be in front of `OfferItem` - func testEIP712OpenseaInvalidOrderSignature() throws { + /// + /// The `InvalidOrderSignature` error is thrown when hash created for signing is invalid, thus, resulting in invalid signature. + func testEIP712NoInvalidOrderSignature() throws { let rawPayload = """ { "types": { From 40a9a3d9cbf8aacc73219a0342565bef3bbb870d Mon Sep 17 00:00:00 2001 From: Jenea Vranceanu <36865532+JeneaVranceanu@users.noreply.github.com> Date: Fri, 3 Nov 2023 23:54:36 +0200 Subject: [PATCH 36/62] chore: updated error message for entropyOf --- Sources/Web3Core/KeystoreManager/BIP39.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/Web3Core/KeystoreManager/BIP39.swift b/Sources/Web3Core/KeystoreManager/BIP39.swift index ce0b3ee28..7e7314316 100755 --- a/Sources/Web3Core/KeystoreManager/BIP39.swift +++ b/Sources/Web3Core/KeystoreManager/BIP39.swift @@ -101,7 +101,7 @@ public class BIP39 { isCorrectSize, let entropy = Data.randomBytes(length: randomBytesCount) else { - throw AbstractKeystoreError.noEntropyError("BIP39. \(!isCorrectSize ? "Requested entropy of wrong bits size \(size)." : "Failed to generated \(randomBytesCount) of random bytes.")") + throw AbstractKeystoreError.noEntropyError("BIP39. \(!isCorrectSize ? "Requested entropy of wrong bits size: \(size). Expected: 128 <= size <= 256, size % 32 == 0." : "Failed to generate \(randomBytesCount) of random bytes.")") } return entropy } From cfd8ee948649c7d4ea84c87e751920d0a5917390 Mon Sep 17 00:00:00 2001 From: Jenea Vranceanu Date: Sat, 4 Nov 2023 00:01:59 +0200 Subject: [PATCH 37/62] chore: updated error messages for EIP712Parser --- .../Utils/EIP/EIP712/EIP712Parser.swift | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Sources/web3swift/Utils/EIP/EIP712/EIP712Parser.swift b/Sources/web3swift/Utils/EIP/EIP712/EIP712Parser.swift index 154b744d2..c95f2b6e0 100644 --- a/Sources/web3swift/Utils/EIP/EIP712/EIP712Parser.swift +++ b/Sources/web3swift/Utils/EIP/EIP712/EIP712Parser.swift @@ -90,7 +90,7 @@ public class EIP712Parser { static func toData(_ json: String) throws -> Data { guard let json = json.data(using: .utf8) else { - throw Web3Error.inputError(desc: "Failed to parse EIP712 payload. Given string is not valid UTF8 string. \(json)") + throw Web3Error.inputError(desc: "EIP712Parser. Failed to parse EIP712 payload. Given string is not valid UTF8 string. \(json)") } return json } @@ -176,7 +176,7 @@ public struct EIP712TypedData { public func encodeType(_ type: String) throws -> String { guard let typeData = types[type] else { - throw Web3Error.processingError(desc: "EIP712. Attempting to encode type that doesn't exist in this payload. Given type: \(type). Available types: \(types.values).") + throw Web3Error.processingError(desc: "EIP712Parser. Attempting to encode type that doesn't exist in this payload. Given type: \(type). Available types: \(types.values).") } return try encodeType(type, typeData) } @@ -216,7 +216,7 @@ public struct EIP712TypedData { var encValues: [Any] = [try typeHash(type)] guard let typeData = types[type] else { - throw Web3Error.processingError(desc: "EIP712. Attempting to encode data for type that doesn't exist in this payload. Given type: \(type). Available types: \(types.values).") + throw Web3Error.processingError(desc: "EIP712Parser. Attempting to encode data for type that doesn't exist in this payload. Given type: \(type). Available types: \(types.values).") } func encodeField(_ field: EIP712TypeProperty, @@ -225,19 +225,19 @@ public struct EIP712TypedData { var encValues: [Any] = [] if field.type == "string" { guard let value = value as? String else { - throw Web3Error.processingError(desc: "EIP712. Type metadata '\(field)' and actual value '\(String(describing: value))' type doesn't match. Cannot cast value to String.") + throw Web3Error.processingError(desc: "EIP712Parser. Type metadata '\(field)' and actual value '\(String(describing: value))' type doesn't match. Cannot cast value to String.") } encTypes.append(.bytes(length: 32)) encValues.append(value.sha3(.keccak256).addHexPrefix()) } else if field.type == "bytes"{ guard let value = value as? Data else { - throw Web3Error.processingError(desc: "EIP712. Type metadata '\(field)' and actual value '\(String(describing: value))' type doesn't match. Cannot cast value to Data.") + throw Web3Error.processingError(desc: "EIP712Parser. Type metadata '\(field)' and actual value '\(String(describing: value))' type doesn't match. Cannot cast value to Data.") } encTypes.append(.bytes(length: 32)) encValues.append(value.sha3(.keccak256)) } else if field.isArray { guard let values = value as? [AnyObject] else { - throw Web3Error.processingError(desc: "EIP712. Custom type metadata '\(field)' and actual value '\(String(describing: value))' type doesn't match. Cannot cast value to [AnyObject].") + throw Web3Error.processingError(desc: "EIP712Parser. Custom type metadata '\(field)' and actual value '\(String(describing: value))' type doesn't match. Cannot cast value to [AnyObject].") } encTypes.append(.bytes(length: 32)) let subField = EIP712TypeProperty(name: field.name, type: field.coreType) @@ -250,7 +250,7 @@ public struct EIP712TypedData { } guard let encodedValue = ABIEncoder.encode(types: encodedSubTypes, values: encodedSubValues) else { - throw Web3Error.processingError(desc: "EIP712. Failed to encode an array of custom type. Field: '\(field)'; value: '\(String(describing: value))'.") + throw Web3Error.processingError(desc: "EIP712Parser. Failed to encode an array of custom type. Field: '\(field)'; value: '\(String(describing: value))'.") } encValues.append(encodedValue.sha3(.keccak256)) @@ -276,7 +276,7 @@ public struct EIP712TypedData { } guard let encodedData = ABIEncoder.encode(types: encTypes, values: encValues) else { - throw Web3Error.processingError(desc: "EIP712. ABIEncoder.encode failed with the following types and values: \(encTypes); \(encValues)") + throw Web3Error.processingError(desc: "EIP712Parser. ABIEncoder.encode failed with the following types and values: \(encTypes); \(encValues)") } return encodedData } From 0b0b3fae4f9b16de708c98b3f6e25d77f34a3a86 Mon Sep 17 00:00:00 2001 From: Jenea Vranceanu Date: Sat, 4 Nov 2023 00:04:21 +0200 Subject: [PATCH 38/62] chore: error message fix --- Sources/web3swift/Utils/EIP/EIP712/EIP712Parser.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/web3swift/Utils/EIP/EIP712/EIP712Parser.swift b/Sources/web3swift/Utils/EIP/EIP712/EIP712Parser.swift index c95f2b6e0..42934c140 100644 --- a/Sources/web3swift/Utils/EIP/EIP712/EIP712Parser.swift +++ b/Sources/web3swift/Utils/EIP/EIP712/EIP712Parser.swift @@ -176,7 +176,7 @@ public struct EIP712TypedData { public func encodeType(_ type: String) throws -> String { guard let typeData = types[type] else { - throw Web3Error.processingError(desc: "EIP712Parser. Attempting to encode type that doesn't exist in this payload. Given type: \(type). Available types: \(types.values).") + throw Web3Error.processingError(desc: "EIP712Parser. Attempting to encode type that doesn't exist in this payload. Given type: \(type). Available types: \(types.keys).") } return try encodeType(type, typeData) } @@ -216,7 +216,7 @@ public struct EIP712TypedData { var encValues: [Any] = [try typeHash(type)] guard let typeData = types[type] else { - throw Web3Error.processingError(desc: "EIP712Parser. Attempting to encode data for type that doesn't exist in this payload. Given type: \(type). Available types: \(types.values).") + throw Web3Error.processingError(desc: "EIP712Parser. Attempting to encode data for type that doesn't exist in this payload. Given type: \(type). Available types: \(types.keys).") } func encodeField(_ field: EIP712TypeProperty, From 68d845700ebd56403fb09a6c88c429383cb5b533 Mon Sep 17 00:00:00 2001 From: Jenea Vranceanu <36865532+JeneaVranceanu@users.noreply.github.com> Date: Sun, 5 Nov 2023 01:23:33 +0200 Subject: [PATCH 39/62] chore: updated error message --- Sources/web3swift/Utils/EIP/EIP712/EIP712Parser.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/web3swift/Utils/EIP/EIP712/EIP712Parser.swift b/Sources/web3swift/Utils/EIP/EIP712/EIP712Parser.swift index 42934c140..68d9b3a95 100644 --- a/Sources/web3swift/Utils/EIP/EIP712/EIP712Parser.swift +++ b/Sources/web3swift/Utils/EIP/EIP712/EIP712Parser.swift @@ -90,7 +90,7 @@ public class EIP712Parser { static func toData(_ json: String) throws -> Data { guard let json = json.data(using: .utf8) else { - throw Web3Error.inputError(desc: "EIP712Parser. Failed to parse EIP712 payload. Given string is not valid UTF8 string. \(json)") + throw Web3Error.inputError(desc: "EIP712Parser. Failed to parse EIP712 payload. Given string is not valid UTF8 string.") } return json } From 111d33e62c461a30cbd08bcf0f54e9780a73320b Mon Sep 17 00:00:00 2001 From: Jenea Vranceanu <36865532+JeneaVranceanu@users.noreply.github.com> Date: Sun, 5 Nov 2023 01:26:58 +0200 Subject: [PATCH 40/62] chore: docs update --- Sources/web3swift/Utils/EIP/EIP712/EIP712Parser.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/web3swift/Utils/EIP/EIP712/EIP712Parser.swift b/Sources/web3swift/Utils/EIP/EIP712/EIP712Parser.swift index 68d9b3a95..2f7e77e6a 100644 --- a/Sources/web3swift/Utils/EIP/EIP712/EIP712Parser.swift +++ b/Sources/web3swift/Utils/EIP/EIP712/EIP712Parser.swift @@ -125,9 +125,9 @@ internal struct EIP712TypeArray: Codable { public struct EIP712TypeProperty: Codable { /// Property name. An arbitrary string. public let name: String - /// Property type. A type that's ABI encodable. + /// Property type. A type that's ABI encodable or a custom type from ``EIP712TypedData/types``. public let type: String - /// Strips brackets (e.g. [] - denoting an array) and other characters augmenting the type. + /// Stripped of brackets ([] - denoting an array). /// If ``type`` is an array of then ``coreType`` will return the type of the array. public let coreType: String From e77af0ab310ed73d6203c1f26a6124158c8e007d Mon Sep 17 00:00:00 2001 From: Jenea Vranceanu Date: Tue, 7 Nov 2023 10:48:32 +0200 Subject: [PATCH 41/62] fix: EIP712 - change type to AbstractKeystore from BIP32Keystore --- Sources/web3swift/Web3/Web3+Signing.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/web3swift/Web3/Web3+Signing.swift b/Sources/web3swift/Web3/Web3+Signing.swift index 295d1a497..e3d50cb64 100755 --- a/Sources/web3swift/Web3/Web3+Signing.swift +++ b/Sources/web3swift/Web3/Web3+Signing.swift @@ -41,7 +41,7 @@ public struct Web3Signer { } public static func signEIP712(_ eip712TypedDataPayload: EIP712TypedData, - keystore: BIP32Keystore, + keystore: AbstractKeystore, account: EthereumAddress, password: String? = nil) throws -> Data { let hash = try eip712TypedDataPayload.signHash() @@ -57,7 +57,7 @@ public struct Web3Signer { } public static func signEIP712(_ eip712Hashable: EIP712Hashable, - keystore: BIP32Keystore, + keystore: AbstractKeystore, verifyingContract: EthereumAddress, account: EthereumAddress, password: String? = nil, From bab86ca2258cabb7ee3cc4d6ff0a37ca6ce22dd5 Mon Sep 17 00:00:00 2001 From: "pharms.eth" <100330083+pharms-eth@users.noreply.github.com> Date: Tue, 7 Nov 2023 14:45:25 -0800 Subject: [PATCH 42/62] refactor: random bytes generation; data and string extensions update (#791) * Update HexDecodable+Extensions.swift * Update Data+Extension.swift * Update String+Extension.swift * chore: refactoring + documentation for Data.fromHex function * updates as per conversations * chore: updated docs for randomBytes + minor refactoring * chore: fixed typo in Data+Extension.swift * chore: lint issue fixed * add string spit test * Revert "add string spit test" github issue This reverts commit e8a5e2bf4e799d7bb35a0a3e280b00cd2da2ec0d. --------- Co-authored-by: Jenea Vranceanu Co-authored-by: Jenea Vranceanu <36865532+JeneaVranceanu@users.noreply.github.com> --- .../Utility/HexDecodable+Extensions.swift | 21 +++++----- Sources/Web3Core/Utility/Data+Extension.swift | 39 ++++++++++--------- .../Web3Core/Utility/String+Extension.swift | 20 ++++++++++ .../localTests/UncategorizedTests.swift | 11 ++++++ 4 files changed, 63 insertions(+), 28 deletions(-) diff --git a/Sources/Web3Core/EthereumNetwork/Utility/HexDecodable+Extensions.swift b/Sources/Web3Core/EthereumNetwork/Utility/HexDecodable+Extensions.swift index d64bb9a09..739442fca 100644 --- a/Sources/Web3Core/EthereumNetwork/Utility/HexDecodable+Extensions.swift +++ b/Sources/Web3Core/EthereumNetwork/Utility/HexDecodable+Extensions.swift @@ -17,16 +17,17 @@ extension BigInt: LiteralInitiableFromString { } extension BigUInt: LiteralInitiableFromString { } extension Data: LiteralInitiableFromString { + /// Converts hexadecimal string representation of some bytes into actual bytes. + /// Notes: + /// - empty string will return `nil`; + /// - empty hex string, meaning it's equal to `"0x"`, will return empty `Data` object. + /// - Parameter hex: bytes represented as string. + /// - Returns: optional raw bytes. public static func fromHex(_ hex: String) -> Data? { - let string = hex.lowercased().stripHexPrefix() - let array = [UInt8](hex: string) - if array.count == 0 { - if hex == "0x" || hex == "" { - return Data() - } else { - return nil - } - } - return Data(array) + let hex = hex.lowercased().trim() + guard !hex.isEmpty else { return nil } + guard hex != "0x" else { return Data() } + let bytes = [UInt8](hex: hex.stripHexPrefix()) + return bytes.isEmpty ? nil : Data(bytes) } } diff --git a/Sources/Web3Core/Utility/Data+Extension.swift b/Sources/Web3Core/Utility/Data+Extension.swift index 448728f1a..3a8185d0e 100755 --- a/Sources/Web3Core/Utility/Data+Extension.swift +++ b/Sources/Web3Core/Utility/Data+Extension.swift @@ -5,7 +5,8 @@ import Foundation -extension Data { +public extension Data { + init(fromArray values: [T]) { let values = values let ptrUB = values.withUnsafeBufferPointer { (ptr: UnsafeBufferPointer) in return ptr } @@ -33,32 +34,34 @@ extension Data { return difference == UInt8(0x00) } - public static func zero(_ data: inout Data) { + static func zero(_ data: inout Data) { let count = data.count data.withUnsafeMutableBytes { (body: UnsafeMutableRawBufferPointer) in body.baseAddress?.assumingMemoryBound(to: UInt8.self).initialize(repeating: 0, count: count) } } - public static func randomBytes(length: Int) -> Data? { - for _ in 0...1024 { - var data = Data(repeating: 0, count: length) - let result = data.withUnsafeMutableBytes { (body: UnsafeMutableRawBufferPointer) -> Int32? in - if let bodyAddress = body.baseAddress, body.count > 0 { - let pointer = bodyAddress.assumingMemoryBound(to: UInt8.self) - return SecRandomCopyBytes(kSecRandomDefault, length, pointer) - } else { - return nil - } - } - if let notNilResult = result, notNilResult == errSecSuccess { - return data - } + /** + Generates an array of random bytes of the specified length. + This function uses `SecRandomCopyBytes` to generate random bytes returning it as a `Data` object. + If an error occurs during random bytes generation, the function returns `nil`. + Error occurs only if `SecRandomCopyBytes` returns status that is not `errSecSuccess`. + See [all status codes](https://developer.apple.com/documentation/security/1542001-security_framework_result_codes) for possible error reasons. + Note: in v4 of web3swift this function will be deprecated and a new implementation will be provided that will throw occurred error. + - Parameter length: The number of random bytes to generate. + + - Returns: optional `Data` object containing the generated random bytes, or `nil` if an error occurred during generation. + */ + static func randomBytes(length: Int) -> Data? { + var entropyBytes = [UInt8](repeating: 0, count: length) + let status = SecRandomCopyBytes(kSecRandomDefault, entropyBytes.count, &entropyBytes) + guard status == errSecSuccess else { + return nil } - return nil + return Data(entropyBytes) } - public func bitsInRange(_ startingBit: Int, _ length: Int) -> UInt64? { // return max of 8 bytes for simplicity, non-public + func bitsInRange(_ startingBit: Int, _ length: Int) -> UInt64? { // return max of 8 bytes for simplicity, non-public if startingBit + length / 8 > self.count, length > 64, startingBit > 0, length >= 1 { return nil } let bytes = self[(startingBit/8) ..< (startingBit+length+7)/8] let padding = Data(repeating: 0, count: 8 - bytes.count) diff --git a/Sources/Web3Core/Utility/String+Extension.swift b/Sources/Web3Core/Utility/String+Extension.swift index dbe0a10ca..529fe2ff0 100755 --- a/Sources/Web3Core/Utility/String+Extension.swift +++ b/Sources/Web3Core/Utility/String+Extension.swift @@ -135,6 +135,26 @@ extension String { func trim() -> String { trimmingCharacters(in: .whitespacesAndNewlines) } + + /// Splits a string into groups of `every` n characters, grouping from left-to-right by default. If `backwards` is true, right-to-left. + public func split(every: Int, backwards: Bool = false) -> [String] { + var result = [String]() + + for i in stride(from: 0, to: self.count, by: every) { + switch backwards { + case true: + let endIndex = self.index(self.endIndex, offsetBy: -i) + let startIndex = self.index(endIndex, offsetBy: -every, limitedBy: self.startIndex) ?? self.startIndex + result.insert(String(self[startIndex.. Date: Tue, 21 Nov 2023 01:33:36 +0200 Subject: [PATCH 43/62] fix: parsing and encoding of "bytes" --- .../Utils/EIP/EIP712/EIP712Parser.swift | 17 ++- .../EIP712TypedDataPayloadTests.swift | 129 ++++++++++++++++++ 2 files changed, 141 insertions(+), 5 deletions(-) diff --git a/Sources/web3swift/Utils/EIP/EIP712/EIP712Parser.swift b/Sources/web3swift/Utils/EIP/EIP712/EIP712Parser.swift index 2f7e77e6a..ad0f80561 100644 --- a/Sources/web3swift/Utils/EIP/EIP712/EIP712Parser.swift +++ b/Sources/web3swift/Utils/EIP/EIP712/EIP712Parser.swift @@ -225,19 +225,26 @@ public struct EIP712TypedData { var encValues: [Any] = [] if field.type == "string" { guard let value = value as? String else { - throw Web3Error.processingError(desc: "EIP712Parser. Type metadata '\(field)' and actual value '\(String(describing: value))' type doesn't match. Cannot cast value to String.") + throw Web3Error.processingError(desc: "EIP712Parser. Type metadata of '\(field)' and actual value '\(String(describing: value))' type doesn't match. Cannot cast value to String. Parent object type: \(type).") } encTypes.append(.bytes(length: 32)) encValues.append(value.sha3(.keccak256).addHexPrefix()) } else if field.type == "bytes"{ - guard let value = value as? Data else { - throw Web3Error.processingError(desc: "EIP712Parser. Type metadata '\(field)' and actual value '\(String(describing: value))' type doesn't match. Cannot cast value to Data.") + let _value: Data? + if let value = value as? String, + let data = Data.fromHex(value) { + _value = data + } else { + _value = value as? Data + } + guard let value = _value else { + throw Web3Error.processingError(desc: "EIP712Parser. Type metadata '\(field)' and actual value '\(String(describing: value))' type doesn't match. Cannot cast/parse value to Data. Parent object type: \(type).") } encTypes.append(.bytes(length: 32)) encValues.append(value.sha3(.keccak256)) } else if field.isArray { guard let values = value as? [AnyObject] else { - throw Web3Error.processingError(desc: "EIP712Parser. Custom type metadata '\(field)' and actual value '\(String(describing: value))' type doesn't match. Cannot cast value to [AnyObject].") + throw Web3Error.processingError(desc: "EIP712Parser. Custom type metadata '\(field)' and actual value '\(String(describing: value))' type doesn't match. Cannot cast value to [AnyObject]. Parent object type: \(type)") } encTypes.append(.bytes(length: 32)) let subField = EIP712TypeProperty(name: field.name, type: field.coreType) @@ -250,7 +257,7 @@ public struct EIP712TypedData { } guard let encodedValue = ABIEncoder.encode(types: encodedSubTypes, values: encodedSubValues) else { - throw Web3Error.processingError(desc: "EIP712Parser. Failed to encode an array of custom type. Field: '\(field)'; value: '\(String(describing: value))'.") + throw Web3Error.processingError(desc: "EIP712Parser. Failed to encode an array of custom type. Field: '\(field)'; value: '\(String(describing: value))'. Parent object type: \(type)") } encValues.append(encodedValue.sha3(.keccak256)) diff --git a/Tests/web3swiftTests/localTests/EIP712TypedDataPayloadTests.swift b/Tests/web3swiftTests/localTests/EIP712TypedDataPayloadTests.swift index 1a31b2b8d..e188370f8 100644 --- a/Tests/web3swiftTests/localTests/EIP712TypedDataPayloadTests.swift +++ b/Tests/web3swiftTests/localTests/EIP712TypedDataPayloadTests.swift @@ -461,4 +461,133 @@ class EIP712TypedDataPayloadTests: XCTestCase { let parsedPayload = try EIP712Parser.parse(rawPayload) try XCTAssertEqual(parsedPayload.signHash().toHexString(), "54140d99a864932cbc40fd8a2d1d1706c3923a79c183a3b151e929ac468064db") } + + /// A test to check payload encoding, specifically parsing and encoding of fields with "bytes" type. + /// Given raw payload was failing with the following error: + /// ``` + /// EIP712Parser. + /// Type metadata 'EIP712TypeProperty(name: "data", type: "bytes", coreType: "bytes", isArray: false)' + /// and actual value + /// 'Optional(0x000000000000000000000000e84a7676aae742770a179dd7431073429a88c7b8000000000000000000000000000000000000000000000000000000000000002c)' + /// type doesn't match. + /// Cannot cast value to Data. + /// + /// ``` + func testEIP712BytesEncoding() throws { + let rawPayload = """ + { + "message":{ + "takeAsset":{ + "assetType":{ + "assetClass":"0xaaaebeba", + "data":"0x" + }, + "value":"2000000000000000000" + }, + "data":"0x000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000d6ffd79b52a587a0a9941a61f4e6cb0d386d54580000000000000000000000000000000000000000000000000000000000000064", + "dataType":"0x23d235ef", + "maker":"0xd0727e8a578de9dd19bced635b1aa43576e638bc", + "taker":"0x0000000000000000000000000000000000000000", + "salt":"0x8f9761e56ed73b34d0cb184a2c5530d86c355c63c1cde8db1e0d2557d93f10d7", + "end":1703058225, + "makeAsset":{ + "value":"1", + "assetType":{ + "data":"0x000000000000000000000000e84a7676aae742770a179dd7431073429a88c7b8000000000000000000000000000000000000000000000000000000000000002c", + "assetClass":"0x73ad2146" + } + }, + "start":0 + }, + "domain":{ + "verifyingContract":"0x02afbd43cad367fcb71305a2dfb9a3928218f0c1", + "version":"2", + "chainId":5, + "name":"Exchange" + }, + "primaryType":"Order", + "types":{ + "Order":[ + { + "type":"address", + "name":"maker" + }, + { + "type":"Asset", + "name":"makeAsset" + }, + { + "name":"taker", + "type":"address" + }, + { + "name":"takeAsset", + "type":"Asset" + }, + { + "name":"salt", + "type":"uint256" + }, + { + "name":"start", + "type":"uint256" + }, + { + "type":"uint256", + "name":"end" + }, + { + "type":"bytes4", + "name":"dataType" + }, + { + "type":"bytes", + "name":"data" + } + ], + "EIP712Domain":[ + { + "name":"name", + "type":"string" + }, + { + "type":"string", + "name":"version" + }, + { + "name":"chainId", + "type":"uint256" + }, + { + "name":"verifyingContract", + "type":"address" + } + ], + "Asset":[ + { + "name":"assetType", + "type":"AssetType" + }, + { + "type":"uint256", + "name":"value" + } + ], + "AssetType":[ + { + "type":"bytes4", + "name":"assetClass" + }, + { + "name":"data", + "type":"bytes" + } + ] + } + } + """ + + let parsedPayload = try EIP712Parser.parse(rawPayload) + try XCTAssertEqual(parsedPayload.signHash().toHexString(), "95625b9843950aa6cdd50c703e2bf0bdaa5ddeef9842d5839a81d927b7159637") + } } From bc6f6211d9ebbb36d759e0203db8e11cb2a52301 Mon Sep 17 00:00:00 2001 From: Jenea Vranceanu <36865532+JeneaVranceanu@users.noreply.github.com> Date: Sun, 26 Nov 2023 23:32:23 +0200 Subject: [PATCH 44/62] chore: error message update --- .../Web3Core/EthereumNetwork/Request/APIRequest+Methods.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/Web3Core/EthereumNetwork/Request/APIRequest+Methods.swift b/Sources/Web3Core/EthereumNetwork/Request/APIRequest+Methods.swift index 9c4e40880..31f832d58 100644 --- a/Sources/Web3Core/EthereumNetwork/Request/APIRequest+Methods.swift +++ b/Sources/Web3Core/EthereumNetwork/Request/APIRequest+Methods.swift @@ -15,7 +15,7 @@ func checkError(method: String, error: JsonRpcErrorObject.RpcError) throws -> St if let result = spelunkData(value: error) { return result.data } - throw Web3Error.nodeError(desc: "missing revert data in call exception; Transaction reverted without a reason string") + throw Web3Error.nodeError(desc: "Error data decoding failed: missing revert data in exception; Transaction reverted without a reason string.") } throw Web3Error.nodeError(desc: error.message) From 45c8ba53c2db45e95f69873f2871fc9ce0be4839 Mon Sep 17 00:00:00 2001 From: Jenea Vranceanu <36865532+JeneaVranceanu@users.noreply.github.com> Date: Sun, 26 Nov 2023 23:32:40 +0200 Subject: [PATCH 45/62] chore: code reordering --- .../EthereumNetwork/Request/APIRequest+Methods.swift | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Sources/Web3Core/EthereumNetwork/Request/APIRequest+Methods.swift b/Sources/Web3Core/EthereumNetwork/Request/APIRequest+Methods.swift index 31f832d58..336c6139d 100644 --- a/Sources/Web3Core/EthereumNetwork/Request/APIRequest+Methods.swift +++ b/Sources/Web3Core/EthereumNetwork/Request/APIRequest+Methods.swift @@ -22,6 +22,10 @@ func checkError(method: String, error: JsonRpcErrorObject.RpcError) throws -> St } func spelunkData(value: Any?) -> (message: String, data: String)? { + if (value == nil) { + return nil + } + func spelunkRpcError(_ message: String, data: String) -> (message: String, data: String)? { if message.contains("revert") && data.isHex { return (message, data) @@ -30,10 +34,6 @@ func spelunkData(value: Any?) -> (message: String, data: String)? { } } - if (value == nil) { - return nil - } - if let error = value as? JsonRpcErrorObject.RpcError { if let data = error.data as? String { return spelunkRpcError(error.message, data: data) From e542974dce36a7913e6efce35307804749de0fa1 Mon Sep 17 00:00:00 2001 From: Jenea Vranceanu <36865532+JeneaVranceanu@users.noreply.github.com> Date: Sun, 26 Nov 2023 23:32:55 +0200 Subject: [PATCH 46/62] chore: docs refactoring --- .../Web3Core/EthereumNetwork/Request/APIRequest+Methods.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/Web3Core/EthereumNetwork/Request/APIRequest+Methods.swift b/Sources/Web3Core/EthereumNetwork/Request/APIRequest+Methods.swift index 336c6139d..ca27bd3b2 100644 --- a/Sources/Web3Core/EthereumNetwork/Request/APIRequest+Methods.swift +++ b/Sources/Web3Core/EthereumNetwork/Request/APIRequest+Methods.swift @@ -105,7 +105,7 @@ extension APIRequest { return APIResponse(id: 2, result: result) } - /// This bit of code is purposed to work with literal types that comes in ``Response`` in hexString type. + /// This bit of code is purposed to work with literal types that come in ``Response`` in hexString type. /// Currently it's just `Data` and any kind of Integers `(U)Int`, `Big(U)Int`. if let LiteralType = Result.self as? LiteralInitiableFromString.Type { guard let responseAsString = try? JSONDecoder().decode(APIResponse.self, from: data) else { throw Web3Error.dataError } From f00a88fd34abf8aef247ee587d1f67a50a5a74a7 Mon Sep 17 00:00:00 2001 From: Jenea Vranceanu <36865532+JeneaVranceanu@users.noreply.github.com> Date: Sun, 26 Nov 2023 23:33:09 +0200 Subject: [PATCH 47/62] chore: docs refactoring --- .../Web3Core/EthereumNetwork/Request/APIRequest+Methods.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/Web3Core/EthereumNetwork/Request/APIRequest+Methods.swift b/Sources/Web3Core/EthereumNetwork/Request/APIRequest+Methods.swift index ca27bd3b2..3f068eb0e 100644 --- a/Sources/Web3Core/EthereumNetwork/Request/APIRequest+Methods.swift +++ b/Sources/Web3Core/EthereumNetwork/Request/APIRequest+Methods.swift @@ -110,7 +110,7 @@ extension APIRequest { if let LiteralType = Result.self as? LiteralInitiableFromString.Type { guard let responseAsString = try? JSONDecoder().decode(APIResponse.self, from: data) else { throw Web3Error.dataError } guard let literalValue = LiteralType.init(from: responseAsString.result) else { throw Web3Error.dataError } - /// `literalValue` conforms `LiteralInitiableFromString`, that conforming to an `APIResponseType` type, so it's never fails. + /// `literalValue` conforms `LiteralInitiableFromString` (which conforms to an `APIResponseType` type) so it never fails. guard let result = literalValue as? Result else { throw Web3Error.typeError } return APIResponse(id: responseAsString.id, jsonrpc: responseAsString.jsonrpc, result: result) } From 9dc97138f4b967b1beca6c5bd469431b358d38d7 Mon Sep 17 00:00:00 2001 From: Jenea Vranceanu <36865532+JeneaVranceanu@users.noreply.github.com> Date: Sun, 26 Nov 2023 23:33:25 +0200 Subject: [PATCH 48/62] chore: comment refactoring --- .../EthereumNetwork/Request/APIRequest+UtilityTypes.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/Web3Core/EthereumNetwork/Request/APIRequest+UtilityTypes.swift b/Sources/Web3Core/EthereumNetwork/Request/APIRequest+UtilityTypes.swift index 66df0a49b..1e40e53d5 100644 --- a/Sources/Web3Core/EthereumNetwork/Request/APIRequest+UtilityTypes.swift +++ b/Sources/Web3Core/EthereumNetwork/Request/APIRequest+UtilityTypes.swift @@ -46,8 +46,8 @@ struct RequestBody: Encodable { } public var encodedBody: Data { - // this is safe to force try this here - // Because request must failed to compile if it not conformable with `Encodable` protocol + // Safe to use force-try because request will fail to + // compile if it's not conforming to the `Encodable` protocol. return try! JSONEncoder().encode(self) } } From 62addff5b4507744db84f4a563e83e185b20365a Mon Sep 17 00:00:00 2001 From: Jenea Vranceanu <36865532+JeneaVranceanu@users.noreply.github.com> Date: Sun, 26 Nov 2023 23:34:00 +0200 Subject: [PATCH 49/62] chore: missing space added --- Sources/web3swift/Web3/Web3+Contract.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/web3swift/Web3/Web3+Contract.swift b/Sources/web3swift/Web3/Web3+Contract.swift index 6112b6e20..a99d51766 100755 --- a/Sources/web3swift/Web3/Web3+Contract.swift +++ b/Sources/web3swift/Web3/Web3+Contract.swift @@ -113,7 +113,7 @@ extension Web3 { return .init(transaction: transaction, web3: web3, contract: contract, method: method) } - /// Combines `createReadOperation`& `callContractMethod` + /// Combines `createReadOperation` & `callContractMethod` @discardableResult public func callStatic(_ method: String, parameters: [Any]) async throws -> [String: Any] { try await contract.callStatic(method, parameters: parameters, provider: web3.provider) From 71e890235041d46cdacb18bd62b5bfd5bf712ac7 Mon Sep 17 00:00:00 2001 From: Jenea Vranceanu <36865532+JeneaVranceanu@users.noreply.github.com> Date: Sun, 26 Nov 2023 23:36:50 +0200 Subject: [PATCH 50/62] chore: avoiding try? in tests (try is more preffered) --- Tests/web3swiftTests/localTests/ABIDecoderSliceTests.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/web3swiftTests/localTests/ABIDecoderSliceTests.swift b/Tests/web3swiftTests/localTests/ABIDecoderSliceTests.swift index 3e7f077d8..9165b4b4a 100644 --- a/Tests/web3swiftTests/localTests/ABIDecoderSliceTests.swift +++ b/Tests/web3swiftTests/localTests/ABIDecoderSliceTests.swift @@ -80,7 +80,7 @@ final class ABIDecoderSliceTests: XCTestCase { var resultArray = [BigUInt]() for i in 0..<2 { guard let data = returnData[i][1] as? Data, - let balance = try? erc20_balanceof.decodeReturnData(data)["0"] as? BigUInt else { + let balance = try erc20_balanceof.decodeReturnData(data)["0"] as? BigUInt else { resultArray.append(0) continue } From 3caddddf51775a8ae2ff6dc15cc1a7b0383c5329 Mon Sep 17 00:00:00 2001 From: Jenea Vranceanu Date: Sun, 26 Nov 2023 23:43:48 +0200 Subject: [PATCH 51/62] fix: isHex string extension function refactoring --- Sources/Web3Core/Utility/String+Extension.swift | 12 +++++++++++- .../localTests/ABIDecoderSliceTests.swift | 2 +- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/Sources/Web3Core/Utility/String+Extension.swift b/Sources/Web3Core/Utility/String+Extension.swift index e0e9b94ca..403e0fa62 100755 --- a/Sources/Web3Core/Utility/String+Extension.swift +++ b/Sources/Web3Core/Utility/String+Extension.swift @@ -137,7 +137,17 @@ extension String { } public var isHex: Bool { - stripHexPrefix().reduce(true, { $0 && $1.isHexDigit } ) + var _str = self.trim() + if _str.isEmpty { + return false + } + _str = _str.stripHexPrefix() + for char in _str { + if !char.isHexDigit { + return false + } + } + return true } } diff --git a/Tests/web3swiftTests/localTests/ABIDecoderSliceTests.swift b/Tests/web3swiftTests/localTests/ABIDecoderSliceTests.swift index 3e7f077d8..9165b4b4a 100644 --- a/Tests/web3swiftTests/localTests/ABIDecoderSliceTests.swift +++ b/Tests/web3swiftTests/localTests/ABIDecoderSliceTests.swift @@ -80,7 +80,7 @@ final class ABIDecoderSliceTests: XCTestCase { var resultArray = [BigUInt]() for i in 0..<2 { guard let data = returnData[i][1] as? Data, - let balance = try? erc20_balanceof.decodeReturnData(data)["0"] as? BigUInt else { + let balance = try erc20_balanceof.decodeReturnData(data)["0"] as? BigUInt else { resultArray.append(0) continue } From 01e1f6a4850cd176333d0c699db53cf1eca179a7 Mon Sep 17 00:00:00 2001 From: Jenea Vranceanu Date: Sun, 26 Nov 2023 23:44:35 +0200 Subject: [PATCH 52/62] chore: isHex string extension tests --- .../String+ExtensionTests.swift.swift | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 Tests/web3swiftTests/localTests/String+ExtensionTests.swift.swift diff --git a/Tests/web3swiftTests/localTests/String+ExtensionTests.swift.swift b/Tests/web3swiftTests/localTests/String+ExtensionTests.swift.swift new file mode 100644 index 000000000..00d7cadb2 --- /dev/null +++ b/Tests/web3swiftTests/localTests/String+ExtensionTests.swift.swift @@ -0,0 +1,31 @@ +// +// String+ExtensionTests.swift +// +// Created by JeneaVranceanu on 26.11.2023. +// + +import Foundation +import XCTest + +class StringExtensionsTest: XCTestCase { + + func testIsHex() throws { + XCTAssertTrue("0x".isHex) + XCTAssertTrue("0xF".isHex) + XCTAssertTrue("F".isHex) + XCTAssertTrue("0xFF".isHex) + XCTAssertTrue("0x0123456789abcdefABCDEF".isHex) + XCTAssertTrue("0123456789abcdefABCDEF".isHex) + XCTAssertTrue("0123456789abcdefABCDEF ".isHex) + XCTAssertTrue(" 0123456789abcdefABCDEF ".isHex) + XCTAssertTrue(" 0123456789abcdefABCDEF".isHex) + + XCTAssertFalse("".isHex) + XCTAssertFalse("-".isHex) + XCTAssertFalse("xyz".isHex) + XCTAssertFalse("0xCAFEQ".isHex) + XCTAssertFalse("R0123456789abcdefABCDEF ".isHex) + XCTAssertFalse(" R0123456789abcdefABCDEFT ".isHex) + } + +} From 0ba7039607cf2bcadda44dad9afe57fbd43585d0 Mon Sep 17 00:00:00 2001 From: Jenea Vranceanu Date: Sun, 26 Nov 2023 23:50:13 +0200 Subject: [PATCH 53/62] chore: error messages updated --- Sources/Web3Core/Contract/ContractProtocol.swift | 4 ++-- .../localTests/String+ExtensionTests.swift.swift | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Sources/Web3Core/Contract/ContractProtocol.swift b/Sources/Web3Core/Contract/ContractProtocol.swift index 65f254d3b..f54123554 100755 --- a/Sources/Web3Core/Contract/ContractProtocol.swift +++ b/Sources/Web3Core/Contract/ContractProtocol.swift @@ -349,7 +349,7 @@ extension DefaultContractProtocol { throw Web3Error.inputError(desc: "Make sure ABI you use contains error that can match signature: 0x\(selector.toHexString())") } default: - throw Web3Error.inputError(desc: "Invalid data count") + throw Web3Error.inputError(desc: "Given data has invalid bytes count.") } } @@ -388,7 +388,7 @@ extension DefaultContractProtocol { @discardableResult public func callStatic(_ method: String, parameters: [Any], provider: Web3Provider) async throws -> [String: Any] { guard let address = address else { - throw Web3Error.inputError(desc: "address field is missing") + throw Web3Error.inputError(desc: "RPC failed: contract is missing an address.") } guard let data = self.method(method, parameters: parameters, extraData: nil) else { throw Web3Error.dataError diff --git a/Tests/web3swiftTests/localTests/String+ExtensionTests.swift.swift b/Tests/web3swiftTests/localTests/String+ExtensionTests.swift.swift index 00d7cadb2..1a6686178 100644 --- a/Tests/web3swiftTests/localTests/String+ExtensionTests.swift.swift +++ b/Tests/web3swiftTests/localTests/String+ExtensionTests.swift.swift @@ -25,7 +25,7 @@ class StringExtensionsTest: XCTestCase { XCTAssertFalse("xyz".isHex) XCTAssertFalse("0xCAFEQ".isHex) XCTAssertFalse("R0123456789abcdefABCDEF ".isHex) - XCTAssertFalse(" R0123456789abcdefABCDEFT ".isHex) + XCTAssertFalse(" R0123456789abcdefABCDEF ".isHex) } } From 7e4fdc1ef5f515295dfeb9416a30208f6f96026d Mon Sep 17 00:00:00 2001 From: august Date: Mon, 27 Nov 2023 11:50:36 +0800 Subject: [PATCH 54/62] fix: code spell --- .../Web3Core/EthereumNetwork/Request/APIRequest+Methods.swift | 2 +- Sources/web3swift/Web3/Web3+Contract.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/Web3Core/EthereumNetwork/Request/APIRequest+Methods.swift b/Sources/Web3Core/EthereumNetwork/Request/APIRequest+Methods.swift index 3f068eb0e..6019266d5 100644 --- a/Sources/Web3Core/EthereumNetwork/Request/APIRequest+Methods.swift +++ b/Sources/Web3Core/EthereumNetwork/Request/APIRequest+Methods.swift @@ -25,7 +25,7 @@ func spelunkData(value: Any?) -> (message: String, data: String)? { if (value == nil) { return nil } - + func spelunkRpcError(_ message: String, data: String) -> (message: String, data: String)? { if message.contains("revert") && data.isHex { return (message, data) diff --git a/Sources/web3swift/Web3/Web3+Contract.swift b/Sources/web3swift/Web3/Web3+Contract.swift index a99d51766..de01b10ba 100755 --- a/Sources/web3swift/Web3/Web3+Contract.swift +++ b/Sources/web3swift/Web3/Web3+Contract.swift @@ -47,7 +47,7 @@ extension Web3 { // MARK: Writing Data flow // FIXME: Rewrite this to CodableTransaction - /// Deploys a constact instance using the previously provided ABI, some bytecode, constructor parameters and options. + /// Deploys a contract instance using the previously provided ABI, some bytecode, constructor parameters and options. /// If extraData is supplied it is appended to encoded bytecode and constructor parameters. /// /// Returns a "Transaction intermediate" object. From d4f6678f3a26f5401cd89e6d2655c29e3c63331d Mon Sep 17 00:00:00 2001 From: august Date: Mon, 27 Nov 2023 13:08:37 +0800 Subject: [PATCH 55/62] fix: testDecodeMulticallCopy --- Tests/web3swiftTests/localTests/ABIDecoderSliceTests.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/web3swiftTests/localTests/ABIDecoderSliceTests.swift b/Tests/web3swiftTests/localTests/ABIDecoderSliceTests.swift index 9165b4b4a..3e7f077d8 100644 --- a/Tests/web3swiftTests/localTests/ABIDecoderSliceTests.swift +++ b/Tests/web3swiftTests/localTests/ABIDecoderSliceTests.swift @@ -80,7 +80,7 @@ final class ABIDecoderSliceTests: XCTestCase { var resultArray = [BigUInt]() for i in 0..<2 { guard let data = returnData[i][1] as? Data, - let balance = try erc20_balanceof.decodeReturnData(data)["0"] as? BigUInt else { + let balance = try? erc20_balanceof.decodeReturnData(data)["0"] as? BigUInt else { resultArray.append(0) continue } From 56fc498e1bcccbbb5017c2b43b4f83b3260a2744 Mon Sep 17 00:00:00 2001 From: Jenea Vranceanu <36865532+JeneaVranceanu@users.noreply.github.com> Date: Tue, 9 Jan 2024 00:28:42 +0200 Subject: [PATCH 56/62] chore: Update Sources/Web3Core/EthereumNetwork/Request/APIRequest+Methods.swift --- .../Web3Core/EthereumNetwork/Request/APIRequest+Methods.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/Web3Core/EthereumNetwork/Request/APIRequest+Methods.swift b/Sources/Web3Core/EthereumNetwork/Request/APIRequest+Methods.swift index 6019266d5..95ae1320b 100644 --- a/Sources/Web3Core/EthereumNetwork/Request/APIRequest+Methods.swift +++ b/Sources/Web3Core/EthereumNetwork/Request/APIRequest+Methods.swift @@ -105,8 +105,8 @@ extension APIRequest { return APIResponse(id: 2, result: result) } - /// This bit of code is purposed to work with literal types that come in ``Response`` in hexString type. - /// Currently it's just `Data` and any kind of Integers `(U)Int`, `Big(U)Int`. + /// Checks if `Result` type can be initialized from HEX-encoded bytes. + /// If it can - we attempt initializing a value of `Result` type. if let LiteralType = Result.self as? LiteralInitiableFromString.Type { guard let responseAsString = try? JSONDecoder().decode(APIResponse.self, from: data) else { throw Web3Error.dataError } guard let literalValue = LiteralType.init(from: responseAsString.result) else { throw Web3Error.dataError } From 084a2cb9e9f416a279b1259c6f68e879c3c018e1 Mon Sep 17 00:00:00 2001 From: Jenea Vranceanu <36865532+JeneaVranceanu@users.noreply.github.com> Date: Tue, 9 Jan 2024 00:29:01 +0200 Subject: [PATCH 57/62] chore: Update Sources/Web3Core/EthereumNetwork/Request/APIRequest+Methods.swift --- .../Web3Core/EthereumNetwork/Request/APIRequest+Methods.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/Web3Core/EthereumNetwork/Request/APIRequest+Methods.swift b/Sources/Web3Core/EthereumNetwork/Request/APIRequest+Methods.swift index 95ae1320b..83773275d 100644 --- a/Sources/Web3Core/EthereumNetwork/Request/APIRequest+Methods.swift +++ b/Sources/Web3Core/EthereumNetwork/Request/APIRequest+Methods.swift @@ -88,8 +88,8 @@ extension APIRequest { return urlRequest } - public static func send(_ method: String, parameter: [Encodable], with provider: Web3Provider) async throws -> APIResponse { - let body = RequestBody(method: method, params: parameter) + public static func send(_ method: String, parameters: [Encodable], with provider: Web3Provider) async throws -> APIResponse { + let body = RequestBody(method: method, params: parameters) let uRLRequest = setupRequest(for: body, with: provider) let data: Data From e6ff9918a54045e683dfe057a8edab2887095086 Mon Sep 17 00:00:00 2001 From: Jenea Vranceanu Date: Tue, 9 Jan 2024 00:56:17 +0200 Subject: [PATCH 58/62] chore: added docs; new test for ABI.Element.Function; --- .../EthereumABI/ABIParameterTypes.swift | 38 ++++++++++++++++++- .../Request/APIRequest+Methods.swift | 4 +- .../web3swift/Web3/Web3+HttpProvider.swift | 2 +- .../localTests/ABIElementsTests.swift | 29 ++++++++++++++ 4 files changed, 68 insertions(+), 5 deletions(-) create mode 100644 Tests/web3swiftTests/localTests/ABIElementsTests.swift diff --git a/Sources/Web3Core/EthereumABI/ABIParameterTypes.swift b/Sources/Web3Core/EthereumABI/ABIParameterTypes.swift index 4acee18a9..7abd04ed1 100755 --- a/Sources/Web3Core/EthereumABI/ABIParameterTypes.swift +++ b/Sources/Web3Core/EthereumABI/ABIParameterTypes.swift @@ -168,45 +168,79 @@ extension ABI.Element.ParameterType: Equatable { } extension ABI.Element.Function { + /// String representation of a function, e.g. `transfer(address,uint256)`. public var signature: String { return "\(name ?? "")(\(inputs.map { $0.type.abiRepresentation }.joined(separator: ",")))" } + /// Function selector, e.g. `"cafe1234"`. Without hex prefix `0x`. + @available(*, deprecated, renamed: "selector", message: "Please, use 'selector' property instead.") public var methodString: String { + return selector + } + + /// Function selector, e.g. `"cafe1234"`. Without hex prefix `0x`. + public var selector: String { return String(signature.sha3(.keccak256).prefix(8)) } + /// Function selector (e.g. `0xcafe1234`) but as raw bytes. + @available(*, deprecated, renamed: "selectorEncoded", message: "Please, use 'selectorEncoded' property instead.") public var methodEncoding: Data { - return signature.data(using: .ascii)!.sha3(.keccak256)[0...3] + return selectorEncoded + } + + /// Function selector (e.g. `0xcafe1234`) but as raw bytes. + public var selectorEncoded: Data { + return Data.fromHex(selector)! } } // MARK: - Event topic extension ABI.Element.Event { + /// String representation of an event, e.g. `ContractCreated(address)`. public var signature: String { return "\(name)(\(inputs.map { $0.type.abiRepresentation }.joined(separator: ",")))" } + /// Hashed signature of an event, e.g. `0xcf78cf0d6f3d8371e1075c69c492ab4ec5d8cf23a1a239b6a51a1d00be7ca312`. public var topic: Data { return signature.data(using: .ascii)!.sha3(.keccak256) } } extension ABI.Element.EthError { + /// String representation of an error, e.g. `TrasferFailed(address)`. public var signature: String { return "\(name)(\(inputs.map { $0.type.abiRepresentation }.joined(separator: ",")))" } + /// Error selector, e.g. `"cafe1234"`. Without hex prefix `0x`. + @available(*, deprecated, renamed: "selector", message: "Please, use 'selector' property instead.") public var methodString: String { + return selector + } + + /// Error selector, e.g. `"cafe1234"`. Without hex prefix `0x`. + public var selector: String { return String(signature.sha3(.keccak256).prefix(8)) } + /// Error selector (e.g. `0xcafe1234`) but as raw bytes. + @available(*, deprecated, renamed: "selectorEncoded", message: "Please, use 'selectorEncoded' property instead.") public var methodEncoding: Data { - return signature.data(using: .ascii)!.sha3(.keccak256)[0...3] + return selectorEncoded + } + + /// Error selector (e.g. `0xcafe1234`) but as raw bytes. + public var selectorEncoded: Data { + return Data.fromHex(selector)! } } extension ABI.Element.ParameterType: ABIEncoding { + + /// Returns a valid solidity type like `address`, `uint128` or any other built-in type from Solidity. public var abiRepresentation: String { switch self { case .uint(let bits): diff --git a/Sources/Web3Core/EthereumNetwork/Request/APIRequest+Methods.swift b/Sources/Web3Core/EthereumNetwork/Request/APIRequest+Methods.swift index 83773275d..c25ee496d 100644 --- a/Sources/Web3Core/EthereumNetwork/Request/APIRequest+Methods.swift +++ b/Sources/Web3Core/EthereumNetwork/Request/APIRequest+Methods.swift @@ -76,7 +76,7 @@ func spelunkData(value: Any?) -> (message: String, data: String)? { extension APIRequest { public static func sendRequest(with provider: Web3Provider, for call: APIRequest) async throws -> APIResponse { - try await send(call.call, parameter: call.parameters, with: provider) + try await send(call.call, parameters: call.parameters, with: provider) } static func setupRequest(for body: RequestBody, with provider: Web3Provider) -> URLRequest { @@ -106,7 +106,7 @@ extension APIRequest { } /// Checks if `Result` type can be initialized from HEX-encoded bytes. - /// If it can - we attempt initializing a value of `Result` type. + /// If it can - we attempt initializing a value of `Result` type. if let LiteralType = Result.self as? LiteralInitiableFromString.Type { guard let responseAsString = try? JSONDecoder().decode(APIResponse.self, from: data) else { throw Web3Error.dataError } guard let literalValue = LiteralType.init(from: responseAsString.result) else { throw Web3Error.dataError } diff --git a/Sources/web3swift/Web3/Web3+HttpProvider.swift b/Sources/web3swift/Web3/Web3+HttpProvider.swift index b3b8539b2..1e75b242c 100755 --- a/Sources/web3swift/Web3/Web3+HttpProvider.swift +++ b/Sources/web3swift/Web3/Web3+HttpProvider.swift @@ -34,7 +34,7 @@ public class Web3HttpProvider: Web3Provider { network = net } else { /// chain id could be a hex string or an int value. - let response: String = try await APIRequest.send(APIRequest.getNetwork.call, parameter: [], with: self).result + let response: String = try await APIRequest.send(APIRequest.getNetwork.call, parameters: [], with: self).result let result: UInt if response.hasHexPrefix() { guard let num = BigUInt(response, radix: 16) else { diff --git a/Tests/web3swiftTests/localTests/ABIElementsTests.swift b/Tests/web3swiftTests/localTests/ABIElementsTests.swift new file mode 100644 index 000000000..a94fa3d9d --- /dev/null +++ b/Tests/web3swiftTests/localTests/ABIElementsTests.swift @@ -0,0 +1,29 @@ +// +// ABIElementsTests.swift +// +// +// Created by JeneaVranceanu on 09.01.2024. +// + +import Foundation +import XCTest +@testable import web3swift +@testable import Web3Core + +class ABIElementsTests: XCTestCase { + + func testABIElementFunction() { + let test1Function = ABI.Element.Function(name: "Test1", + inputs: [], + outputs: [], + constant: true, + payable: false) + + XCTAssertEqual(test1Function.name, "Test1") + XCTAssertEqual(test1Function.selector, String("Test1()".sha3(.keccak256).prefix(8))) + XCTAssertEqual(test1Function.selectorEncoded, + Data.fromHex("Test1()".sha3(.keccak256))?.prefix(4)) + + } + +} From 34cf1e8a3cbddda2e8ccfc9f141327ad6f09409e Mon Sep 17 00:00:00 2001 From: Yaroslav Date: Wed, 7 Feb 2024 23:52:40 +0100 Subject: [PATCH 59/62] Update README.md Unknown wallet deleted --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index d468f2b8b..8e16493f5 100755 --- a/README.md +++ b/README.md @@ -10,7 +10,6 @@ [![Platform](https://img.shields.io/cocoapods/p/web3swift?style=flat)](http://cocoapods.org/pods/web3swift) [![CocoaPods Compatible](https://img.shields.io/cocoapods/v/web3swift?style=flat)](http://cocoapods.org/pods/web3swift) [![License](https://img.shields.io/cocoapods/l/web3swift.svg?style=flat)](https://github.com/web3swift-team/web3swift/blob/master/LICENSE.md) -[![support](https://brianmacdonald.github.io/Ethonate/svg/eth-support-blue.svg)](https://brianmacdonald.github.io/Ethonate/address#0xe22b8979739d724343bd002f9f432f5990879901) [![Stackoverflow](https://img.shields.io/badge/stackoverflow-ask-blue.svg)](https://stackoverflow.com/questions/tagged/web3swift) --- From c90def520d99c5fa8d610decca1d6d271831ed0a Mon Sep 17 00:00:00 2001 From: Jenea Vranceanu Date: Mon, 1 Apr 2024 01:00:11 +0300 Subject: [PATCH 60/62] fix: BIP39 use given language to generate mnemonics; --- Sources/Web3Core/KeystoreManager/BIP39.swift | 4 ++-- Tests/web3swiftTests/localTests/BIP39Tests.swift | 16 ++++++++++++++++ 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/Sources/Web3Core/KeystoreManager/BIP39.swift b/Sources/Web3Core/KeystoreManager/BIP39.swift index 7e7314316..e655d1589 100755 --- a/Sources/Web3Core/KeystoreManager/BIP39.swift +++ b/Sources/Web3Core/KeystoreManager/BIP39.swift @@ -6,7 +6,7 @@ import Foundation import CryptoSwift -public enum BIP39Language { +public enum BIP39Language: CaseIterable { case english case chinese_simplified case chinese_traditional @@ -124,7 +124,7 @@ public class BIP39 { public static func generateMnemonicsFromEntropy(entropy: Data, language: BIP39Language = .english) -> String? { guard entropy.count >= 16, entropy.count & 4 == 0 else { return nil } let separator = language.separator - let wordList = generateMnemonicsFrom(entropy: entropy) + let wordList = generateMnemonicsFrom(entropy: entropy, language: language) return wordList.joined(separator: separator) } diff --git a/Tests/web3swiftTests/localTests/BIP39Tests.swift b/Tests/web3swiftTests/localTests/BIP39Tests.swift index e9ccf6410..443286808 100644 --- a/Tests/web3swiftTests/localTests/BIP39Tests.swift +++ b/Tests/web3swiftTests/localTests/BIP39Tests.swift @@ -11,6 +11,22 @@ import XCTest final class BIP39Tests: XCTestCase { + func testAllLanguageMnemonics() throws { + for language in BIP39Language.allCases { + guard let newMnemonics = try BIP39.generateMnemonics(bitsOfEntropy: 128, language: language) else { + XCTFail("Failed to generate BIP39 mnemonics phrase") + return + } + let wordsOfNewMnemonic = newMnemonics.split(separator: language.separator).map { String($0) } + for word in wordsOfNewMnemonic { + guard language.words.contains(word) else { + XCTFail("Given word is not contained in the list of words of selected language available for mnemonics generation: \(word); \(language)") + return + } + } + } + } + func testBIP39() throws { var entropy = Data.fromHex("00000000000000000000000000000000")! var phrase = BIP39.generateMnemonicsFromEntropy(entropy: entropy) From d99c0f67fe6002b8a165d8e24515bfb661fdef83 Mon Sep 17 00:00:00 2001 From: Jenea Vranceanu Date: Mon, 1 Apr 2024 22:07:18 +0300 Subject: [PATCH 61/62] fix: CI failure + new tests for BIP39 --- Sources/Web3Core/KeystoreManager/BIP39.swift | 5 ++ .../localTests/BIP39Tests.swift | 46 ++++++++++++------- 2 files changed, 35 insertions(+), 16 deletions(-) diff --git a/Sources/Web3Core/KeystoreManager/BIP39.swift b/Sources/Web3Core/KeystoreManager/BIP39.swift index e655d1589..7ea57d27f 100755 --- a/Sources/Web3Core/KeystoreManager/BIP39.swift +++ b/Sources/Web3Core/KeystoreManager/BIP39.swift @@ -36,7 +36,12 @@ public enum BIP39Language: CaseIterable { return spanishWords } } + public var separator: String { + return String(separatorCharacter) + } + + public var separatorCharacter: Character { switch self { case .japanese: return "\u{3000}" diff --git a/Tests/web3swiftTests/localTests/BIP39Tests.swift b/Tests/web3swiftTests/localTests/BIP39Tests.swift index 443286808..55b053c9d 100644 --- a/Tests/web3swiftTests/localTests/BIP39Tests.swift +++ b/Tests/web3swiftTests/localTests/BIP39Tests.swift @@ -11,22 +11,6 @@ import XCTest final class BIP39Tests: XCTestCase { - func testAllLanguageMnemonics() throws { - for language in BIP39Language.allCases { - guard let newMnemonics = try BIP39.generateMnemonics(bitsOfEntropy: 128, language: language) else { - XCTFail("Failed to generate BIP39 mnemonics phrase") - return - } - let wordsOfNewMnemonic = newMnemonics.split(separator: language.separator).map { String($0) } - for word in wordsOfNewMnemonic { - guard language.words.contains(word) else { - XCTFail("Given word is not contained in the list of words of selected language available for mnemonics generation: \(word); \(language)") - return - } - } - } - } - func testBIP39() throws { var entropy = Data.fromHex("00000000000000000000000000000000")! var phrase = BIP39.generateMnemonicsFromEntropy(entropy: entropy) @@ -167,6 +151,7 @@ final class BIP39Tests: XCTestCase { XCTAssert(keystore1?.addresses?.first == keystore2?.addresses?.first) } + /// It's expected for the entropy bits count to be [128, 256] and (bits mod 32) must return 0. func testWrongBitsOfEntropyMustThrow() throws { XCTAssertThrowsError(try BIP39.generateMnemonics(entropy: 127)) XCTAssertThrowsError(try BIP39.generateMnemonics(entropy: 255)) @@ -182,4 +167,33 @@ final class BIP39Tests: XCTestCase { XCTAssertFalse(try BIP39.generateMnemonics(entropy: 256).isEmpty) } + func testBip39CorrectWordsCount() throws { + XCTAssertEqual(try BIP39.generateMnemonics(entropy: 128).count, 12) + XCTAssertEqual(try BIP39.generateMnemonics(entropy: 160).count, 15) + XCTAssertEqual(try BIP39.generateMnemonics(entropy: 192).count, 18) + XCTAssertEqual(try BIP39.generateMnemonics(entropy: 224).count, 21) + XCTAssertEqual(try BIP39.generateMnemonics(entropy: 256).count, 24) + } + + func testAllLanguageMnemonics() throws { + for language in BIP39Language.allCases { + let mnemonicPhrase = try BIP39.generateMnemonics(entropy: 128, language: language) + for word in mnemonicPhrase { + guard language.words.contains(word) else { + XCTFail("Given word is not contained in the list of words of selected language available for mnemonics generation: \(word); \(language)") + return + } + } + } + } + + func testBip39MnemonicSeparatorUse() throws { + for language in BIP39Language.allCases { + guard let mnemonicPhrase = try BIP39.generateMnemonics(bitsOfEntropy: 128, language: language) else { + XCTFail("Failed to generate BIP39 mnemonics phrase with 128 bits of entropy using language: \(language)") + return + } + XCTAssertEqual(mnemonicPhrase.split(whereSeparator: { $0 == language.separatorCharacter }).count, 12) + } + } } From 6bdb9e698db68443941ba24a4101f290ae54981b Mon Sep 17 00:00:00 2001 From: ZhouMiao <41764730+zmgl@users.noreply.github.com> Date: Tue, 2 Apr 2024 20:13:18 +0800 Subject: [PATCH 62/62] chore: keeping history of original support Portuguese PR (#849) * Update web3swift.podspec * Update BIP39.swift --------- Co-authored-by: Mengsk