Pular para o conteúdo
Escalibur Wallet
Auditoria

A conferência do plano

Kit/Sources/EscaliburChains/PlanIntentCheck.swift

O plano faz o que a tela pediu? Conferido pelo app antes de mostrar a revisão e de novo antes de assinar (auditoria 2, M1).

82 linhasSHA-256 ba4ac8f8e3385d47…Abrir no GitHub, no commit 015f5f5
1import EscaliburCore
2import Foundation
3
4/// O plano faz o que a tela pediu? Conferido pelo app antes de mostrar a revisao e de
5/// novo antes de assinar (auditoria 2, M1).
6///
7/// A revisao e montada pelo planejador da rede a partir das transacoes; esta conferencia
8/// amarra essa revisao ao pedido do dono: o ativo e o valor que saem, o minimo e o ativo
9/// que entram, e quem recebe. Falha fechada: movimento ausente e recusado.
10public enum PlanIntentCheck {
11 public enum Mismatch: Error, Equatable, Sendable {
12 case missingMovement
13 case wrongAsset
14 /// Sai mais (ou, num envio de valor exato, outro valor) do que o pedido.
15 case wrongAmount
16 case wrongBuyAsset
17 case belowMinimum
18 case wrongBeneficiary
19 }
20
21 /// Envio. Valor exato: sai exatamente `amount`. Enviar tudo (`amount` nil): sai no
22 /// maximo `ceiling`, o saldo que a tela mostrou.
23 public static func send(_ review: PlanReview, asset: Asset, amount: BigUInt?, ceiling: BigUInt, chain: Chain) throws {
24 guard let outgoing = review.outgoing else { throw Mismatch.missingMovement }
25 guard sameAsset(outgoing.assetID, asset, chain: chain) else { throw Mismatch.wrongAsset }
26 if let amount {
27 guard outgoing.amount == amount else { throw Mismatch.wrongAmount }
28 } else {
29 guard outgoing.amount <= ceiling else { throw Mismatch.wrongAmount }
30 }
31 }
32
33 /// Troca ou ordem limite: vende `sell`, nunca mais que `amountIn`; garante pelo
34 /// menos `minimumOut` de `buy`; e o que entra vai para `owner`.
35 public static func trade(
36 _ review: PlanReview, sell: Asset, amountIn: BigUInt, buy: Asset, minimumOut: BigUInt, owner: String, chain: Chain
37 ) throws {
38 guard let outgoing = review.outgoing, let incoming = review.incomingMinimum else { throw Mismatch.missingMovement }
39 guard sameAsset(outgoing.assetID, sell, chain: chain) else { throw Mismatch.wrongAsset }
40 guard outgoing.amount <= amountIn else { throw Mismatch.wrongAmount }
41 guard sameAsset(incoming.assetID, buy, chain: chain) else { throw Mismatch.wrongBuyAsset }
42 guard incoming.amount >= minimumOut else { throw Mismatch.belowMinimum }
43 guard Address.sameRecipient(review.beneficiary, owner, chain: chain) else { throw Mismatch.wrongBeneficiary }
44 }
45
46 /// Ponte: sai exatamente `amount` de `sell` na rede de origem; chega pelo menos
47 /// `minimumOut` de `receive` na rede de destino; e quem recebe e `owner`, a mesma
48 /// conta. Os ativos sao de redes diferentes: cada um e conferido na sua.
49 public static func bridge(
50 _ review: PlanReview, sell: Asset, amount: BigUInt, from: Chain, receive: Asset, minimumOut: BigUInt, to: Chain, owner: String
51 ) throws {
52 guard review.kind == .bridge, let outgoing = review.outgoing, let incoming = review.incomingMinimum else {
53 throw Mismatch.missingMovement
54 }
55 guard sameAsset(outgoing.assetID, sell, chain: from) else { throw Mismatch.wrongAsset }
56 guard outgoing.amount == amount else { throw Mismatch.wrongAmount }
57 guard sameAsset(incoming.assetID, receive, chain: to) else { throw Mismatch.wrongBuyAsset }
58 guard incoming.amount >= minimumOut else { throw Mismatch.belowMinimum }
59 guard Address.sameRecipient(review.beneficiary, owner, chain: to) else { throw Mismatch.wrongBeneficiary }
60 }
61
62 /// A tag, o memo ou o comentario do plano contra o digitado (auditoria 2, B7). A tag
63 /// do XRP Ledger e um numero: "0123" e 123 sao a mesma. Memo da Stellar, comentario
64 /// da TON e memo da Tron sao comparados exatamente: o motor da Stellar so escolhe
65 /// memo ID para digitos sem zero a esquerda, e "0123" de texto nunca vale o ID 123.
66 public static func sameTag(_ planned: String?, _ typed: String?, chain: Chain) -> Bool {
67 let a = planned.flatMap { $0.isEmpty ? nil : $0 }
68 let b = typed.flatMap { $0.isEmpty ? nil : $0 }
69 guard let a, let b else { return a == nil && b == nil }
70 if chain.destinationTag == .xrplTag {
71 guard let x = UInt32(a), let y = UInt32(b) else { return false }
72 return x == y
73 }
74 return a == b
75 }
76
77 /// O id do `TokenRegistry`. Na EVM o contrato pode vir em outra caixa (EIP-55), e
78 /// so la a comparacao ignora caixa: nas outras redes a caixa faz parte do endereco.
79 static func sameAsset(_ id: String, _ asset: Asset, chain: Chain) -> Bool {
80 chain.family == .evm ? id.lowercased() == asset.id.lowercased() : id == asset.id
81 }
82}