1
- import BigInt
2
1
import CryptoSwift
3
2
import Foundation
3
+ import BigInt
4
4
import Core
5
5
6
- // TODO: Refactor me
7
-
8
- struct EIP712Domain : EIP712DomainHashable {
9
- let chainId : EIP712 . UInt256 ?
10
- let verifyingContract : EIP712 . Address
11
- }
12
-
13
- protocol EIP712DomainHashable : EIP712Hashable { }
14
-
15
- public struct SafeTx : EIP712Hashable {
16
- let to : EIP712 . Address
17
- let value : EIP712 . UInt256
18
- let data : EIP712 . Bytes
19
- let operation : EIP712 . UInt8
20
- let safeTxGas : EIP712 . UInt256
21
- let baseGas : EIP712 . UInt256
22
- let gasPrice : EIP712 . UInt256
23
- let gasToken : EIP712 . Address
24
- let refundReceiver : EIP712 . Address
25
- let nonce : EIP712 . UInt256
26
-
27
- public init ( to: EIP712 . Address , value: EIP712 . UInt256 , data: EIP712 . Bytes , operation: EIP712 . UInt8 , safeTxGas: EIP712 . UInt256 , baseGas: EIP712 . UInt256 , gasPrice: EIP712 . UInt256 , gasToken: EIP712 . Address , refundReceiver: EIP712 . Address , nonce: EIP712 . UInt256 ) {
28
- self . to = to
29
- self . value = value
30
- self . data = data
31
- self . operation = operation
32
- self . safeTxGas = safeTxGas
33
- self . baseGas = baseGas
34
- self . gasPrice = gasPrice
35
- self . gasToken = gasToken
36
- self . refundReceiver = refundReceiver
37
- self . nonce = nonce
38
- }
39
-
40
- }
41
-
42
6
/// Protocol defines EIP712 struct encoding
43
7
public protocol EIP712Hashable {
44
8
var typehash : Data { get }
@@ -52,27 +16,90 @@ public class EIP712 {
52
16
public typealias Bytes = Data
53
17
}
54
18
19
+ public struct EIP712Domain : EIP712Hashable {
20
+ public let chainId : EIP712 . UInt256 ?
21
+ public let verifyingContract : EIP712 . Address
22
+ public init ( chainId: EIP712 . UInt256 ? , verifyingContract: EIP712 . Address ) {
23
+ self . chainId = chainId
24
+ self . verifyingContract = verifyingContract
25
+ }
26
+ }
27
+
55
28
public extension EIP712 . Address {
56
29
static var zero : Self {
57
30
EthereumAddress ( Data ( count: 20 ) ) !
58
31
}
59
32
}
60
33
34
+ // MARK: - Default implementation for EIP712Hashable
35
+ public extension EIP712Hashable {
36
+ var typehash : Data {
37
+ Data ( encodeType ( ) . bytes) . sha3 ( . keccak256)
38
+ }
39
+
40
+ func hash( ) throws -> Data {
41
+ typealias SolidityValue = ( value: Any , type: ABI . Element . ParameterType )
42
+ var parameters : [ Data ] = [ typehash]
43
+ for case let ( _, field) in Mirror ( reflecting: self ) . children {
44
+ let result : Data
45
+ switch field {
46
+ case let string as String :
47
+ result = Data ( string. bytes) . sha3 ( . keccak256)
48
+ case let data as EIP712 . Bytes :
49
+ result = data. sha3 ( . keccak256)
50
+ case is EIP712 . UInt8 :
51
+ result = ABIEncoder . encodeSingleType ( type: . uint( bits: 8 ) , value: field as AnyObject ) !
52
+ case is EIP712 . UInt256 :
53
+ result = ABIEncoder . encodeSingleType ( type: . uint( bits: 256 ) , value: field as AnyObject ) !
54
+ case is EIP712 . Address :
55
+ result = ABIEncoder . encodeSingleType ( type: . address, value: field as AnyObject ) !
56
+ case let hashable as EIP712Hashable :
57
+ result = try hashable. hash ( )
58
+ default :
59
+ if ( field as AnyObject ) is NSNull {
60
+ continue
61
+ } else {
62
+ preconditionFailure ( " Not solidity type " )
63
+ }
64
+ }
65
+ guard result. count == 32 else { preconditionFailure ( " ABI encode error " ) }
66
+ parameters. append ( result)
67
+ }
68
+ return Data ( parameters. flatMap { $0. bytes } ) . sha3 ( . keccak256)
69
+ }
70
+ }
71
+
72
+ public func eip712encode( domainSeparator: EIP712Hashable , message: EIP712Hashable ) throws -> Data {
73
+ let data = try Data ( [ UInt8 ( 0x19 ) , UInt8 ( 0x01 ) ] ) + domainSeparator. hash ( ) + message. hash ( )
74
+ return data. sha3 ( . keccak256)
75
+ }
76
+
77
+ // MARK: - Additional private and public extensions with support members
78
+
61
79
public extension EIP712Hashable {
62
- private var name : String {
80
+ func encodeType( ) -> String {
81
+ let dependencies = dependencies ( ) . map { $0. encodePrimaryType ( ) }
82
+ let selfPrimaryType = encodePrimaryType ( )
83
+
84
+ let result = Set ( dependencies) . filter { $0 != selfPrimaryType }
85
+ return selfPrimaryType + result. sorted ( ) . joined ( )
86
+ }
87
+ }
88
+
89
+ fileprivate extension EIP712Hashable {
90
+ var name : String {
63
91
let fullName = " \( Self . self) "
64
92
let name = fullName. components ( separatedBy: " . " ) . last ?? fullName
65
93
return name
66
94
}
67
95
68
- private func dependencies( ) -> [ EIP712Hashable ] {
69
- let dependencies = Mirror ( reflecting: self ) . children
96
+ func dependencies( ) -> [ EIP712Hashable ] {
97
+ Mirror ( reflecting: self ) . children
70
98
. compactMap { $0. value as? EIP712Hashable }
71
99
. flatMap { [ $0] + $0. dependencies ( ) }
72
- return dependencies
73
100
}
74
101
75
- private func encodePrimaryType( ) -> String {
102
+ func encodePrimaryType( ) -> String {
76
103
let parametrs : [ String ] = Mirror ( reflecting: self ) . children. compactMap { key, value in
77
104
guard let key = key else { return nil }
78
105
@@ -100,71 +127,62 @@ public extension EIP712Hashable {
100
127
}
101
128
return typeName + " " + key
102
129
}
103
- return self . name + " ( " + parametrs. joined ( separator: " , " ) + " ) "
104
- }
105
-
106
- func encodeType( ) -> String {
107
- let dependencies = self . dependencies ( ) . map { $0. encodePrimaryType ( ) }
108
- let selfPrimaryType = self . encodePrimaryType ( )
109
-
110
- let result = Set ( dependencies) . filter { $0 != selfPrimaryType }
111
- return selfPrimaryType + result. sorted ( ) . joined ( )
112
- }
113
-
114
- // MARK: - Default implementation
115
-
116
- var typehash : Data {
117
- keccak256 ( encodeType ( ) )
118
- }
119
-
120
- func hash( ) throws -> Data {
121
- typealias SolidityValue = ( value: Any , type: ABI . Element . ParameterType )
122
- var parametrs : [ Data ] = [ self . typehash]
123
- for case let ( _, field) in Mirror ( reflecting: self ) . children {
124
- let result : Data
125
- switch field {
126
- case let string as String :
127
- result = keccak256 ( string)
128
- case let data as EIP712 . Bytes :
129
- result = keccak256 ( data)
130
- case is EIP712 . UInt8 :
131
- result = ABIEncoder . encodeSingleType ( type: . uint( bits: 8 ) , value: field as AnyObject ) !
132
- case is EIP712 . UInt256 :
133
- result = ABIEncoder . encodeSingleType ( type: . uint( bits: 256 ) , value: field as AnyObject ) !
134
- case is EIP712 . Address :
135
- result = ABIEncoder . encodeSingleType ( type: . address, value: field as AnyObject ) !
136
- case let hashable as EIP712Hashable :
137
- result = try hashable. hash ( )
138
- default :
139
- if ( field as AnyObject ) is NSNull {
140
- continue
141
- } else {
142
- preconditionFailure ( " Not solidity type " )
143
- }
144
- }
145
- guard result. count == 32 else { preconditionFailure ( " ABI encode error " ) }
146
- parametrs. append ( result)
147
- }
148
- let encoded = parametrs. flatMap { $0. bytes }
149
- return keccak256 ( encoded)
130
+ return name + " ( " + parametrs. joined ( separator: " , " ) + " ) "
150
131
}
151
132
}
152
133
153
- // Encode functions
154
- func eip712encode( domainSeparator: EIP712Hashable , message: EIP712Hashable ) throws -> Data {
155
- let data = try Data ( [ UInt8 ( 0x19 ) , UInt8 ( 0x01 ) ] ) + domainSeparator. hash ( ) + message. hash ( )
156
- return keccak256 ( data)
157
- }
158
-
159
- // MARK: - keccak256
160
- private func keccak256( _ data: [ UInt8 ] ) -> Data {
161
- Data ( SHA3 ( variant: . keccak256) . calculate ( for: data) )
162
- }
134
+ // MARK: - Gnosis Safe Transaction model
135
+
136
+ /// Gnosis Safe Transaction.
137
+ /// https://docs.gnosis-safe.io/tutorials/tutorial_tx_service_initiate_sign
138
+ ///
139
+ /// Note for web3swift developers: **DO NOT CHANGE THE ORDER OF VARIABLES**.
140
+ ///
141
+ /// Changing the order will result in a different hash.
142
+ /// Order must match the implementation of hash calculation in
143
+ /// [`GnosisSafe.sol`](https://github.com/safe-global/safe-contracts/blob/main/contracts/GnosisSafe.sol#L126).
144
+ public struct GnosisSafeTx : EIP712Hashable {
145
+ /// Checksummed address
146
+ let to : EIP712 . Address
147
+ /// Value in wei
148
+ let value : EIP712 . UInt256
149
+ /// 0x prefixed hex string
150
+ let data : EIP712 . Bytes
151
+ /// `0` CALL, `1` DELEGATE_CALL
152
+ let operation : EIP712 . UInt8
153
+ /// Max gas to use in the transaction
154
+ let safeTxGas : EIP712 . UInt256
155
+ /// Gast costs not related to the transaction execution (signature check, refund payment...)
156
+ let baseGas : EIP712 . UInt256
157
+ /// Gas price used for the refund calculation
158
+ let gasPrice : EIP712 . UInt256
159
+ /// Token address, **must be checksummed**, (held by the Safe) to be used as a refund to the sender, if `null` is Ether
160
+ let gasToken : EIP712 . Address
161
+ /// Checksummed address of receiver of gas payment (or `null` if tx.origin)
162
+ let refundReceiver : EIP712 . Address
163
+ /// Nonce of the Safe, transaction cannot be executed until Safe's nonce is not equal to this nonce
164
+ let nonce : EIP712 . UInt256
163
165
164
- private func keccak256( _ string: String ) -> Data {
165
- keccak256 ( Array ( string. utf8) )
166
- }
166
+ public init ( to: EIP712 . Address ,
167
+ value: EIP712 . UInt256 ,
168
+ data: EIP712 . Bytes ,
169
+ operation: EIP712 . UInt8 ,
170
+ safeTxGas: EIP712 . UInt256 ,
171
+ baseGas: EIP712 . UInt256 ,
172
+ gasPrice: EIP712 . UInt256 ,
173
+ gasToken: EIP712 . Address ,
174
+ refundReceiver: EIP712 . Address ,
175
+ nonce: EIP712 . UInt256 ) {
176
+ self . to = to
177
+ self . value = value
178
+ self . data = data
179
+ self . operation = operation
180
+ self . safeTxGas = safeTxGas
181
+ self . baseGas = baseGas
182
+ self . gasPrice = gasPrice
183
+ self . gasToken = gasToken
184
+ self . refundReceiver = refundReceiver
185
+ self . nonce = nonce
186
+ }
167
187
168
- private func keccak256( _ data: Data ) -> Data {
169
- keccak256 ( data. bytes)
170
188
}
0 commit comments