-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathAdversarialReentrancyConnectors.cdc
More file actions
230 lines (217 loc) · 11.3 KB
/
AdversarialReentrancyConnectors.cdc
File metadata and controls
230 lines (217 loc) · 11.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
import "FungibleToken"
import "FungibleTokenMetadataViews"
import "DeFiActionsUtils"
import "DeFiActions"
import "FlowALPv0"
import "FlowALPPositionResources"
import "FlowALPModels"
import "MOET"
import "FlowToken"
/// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
/// THIS IS A TESTING CONTRACT THAT SHOULD NOT BE USED IN PRODUCTION
/// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
///
/// AdversarialReentrancyConnectors
///
/// This contract holds malicious DeFi connectors which implement a re-entrancy attack.
/// When a user withdraws from their position, they can optionally pull from their configured top-up source to help fund the withdrawal.
/// This contract implements a malicious source which attempts to withdraw from the same position again
/// when it is asked to provide funds for the outer withdrawal.
/// If unaccounted for, this could allow an attacker to withdraw more than their available balance from the shared Pool reserve.
access(all) contract AdversarialReentrancyConnectors {
/// VaultSink
///
/// A DeFiActions connector that deposits tokens into a Vault
///
access(all) struct VaultSinkHacked : DeFiActions.Sink {
/// The Vault Type accepted by the Sink
access(all) let depositVaultType: Type
/// The maximum balance of the linked Vault, checked before executing a deposit
access(all) let maximumBalance: UFix64
/// An optional identifier allowing protocols to identify stacked connector operations by defining a protocol-
/// specific Identifier to associated connectors on construction
access(contract) var uniqueID: DeFiActions.UniqueIdentifier?
/// An unentitled Capability on the Vault to which deposits are distributed
access(self) let depositVault: Capability<&{FungibleToken.Vault}>
init(
max: UFix64?,
depositVault: Capability<&{FungibleToken.Vault}>,
uniqueID: DeFiActions.UniqueIdentifier?
) {
pre {
depositVault.check(): "Provided invalid Capability"
DeFiActionsUtils.definingContractIsFungibleToken(depositVault.borrow()!.getType()):
"The contract defining Vault \(depositVault.borrow()!.getType().identifier) does not conform to FungibleToken contract interface"
(max ?? UFix64.max) > 0.0:
"Maximum balance must be greater than 0.0 if provided"
}
self.maximumBalance = max ?? UFix64.max // assume no maximum if none provided
self.uniqueID = uniqueID
self.depositVaultType = depositVault.borrow()!.getType()
self.depositVault = depositVault
}
/// Returns a ComponentInfo struct containing information about this VaultSink and its inner DFA components
///
/// @return a ComponentInfo struct containing information about this component and a list of ComponentInfo for
/// each inner component in the stack.
///
access(all) fun getComponentInfo(): DeFiActions.ComponentInfo {
return DeFiActions.ComponentInfo(
type: self.getType(),
id: self.id(),
innerComponents: []
)
}
/// Returns a copy of the struct's UniqueIdentifier, used in extending a stack to identify another connector in
/// a DeFiActions stack. See DeFiActions.align() for more information.
///
/// @return a copy of the struct's UniqueIdentifier
///
access(contract) view fun copyID(): DeFiActions.UniqueIdentifier? {
return self.uniqueID
}
/// Sets the UniqueIdentifier of this component to the provided UniqueIdentifier, used in extending a stack to
/// identify another connector in a DeFiActions stack. See DeFiActions.align() for more information.
///
/// @param id: the UniqueIdentifier to set for this component
///
access(contract) fun setID(_ id: DeFiActions.UniqueIdentifier?) {
self.uniqueID = id
}
/// Returns the Vault type accepted by this Sink
access(all) view fun getSinkType(): Type {
return self.depositVaultType
}
/// Returns an estimate of how much of the associated Vault can be accepted by this Sink
access(all) fun minimumCapacity(): UFix64 {
if let vault = self.depositVault.borrow() {
return vault.balance < self.maximumBalance ? self.maximumBalance - vault.balance : 0.0
}
return 0.0
}
/// Deposits up to the Sink's capacity from the provided Vault
access(all) fun depositCapacity(from: auth(FungibleToken.Withdraw) &{FungibleToken.Vault}) {
let minimumCapacity = self.minimumCapacity()
if !self.depositVault.check() || minimumCapacity == 0.0 {
return
}
// deposit the lesser of the originating vault balance and minimum capacity
let capacity = minimumCapacity <= from.balance ? minimumCapacity : from.balance
self.depositVault.borrow()!.deposit(from: <-from.withdraw(amount: capacity))
}
}
access(all) resource LiveData {
/// Capability to the attacker's PositionManager for recursive withdrawal
access(all) var positionManagerCap: Capability<auth(FungibleToken.Withdraw, FlowALPModels.EPositionAdmin) &FlowALPPositionResources.PositionManager>?
/// Position ID for recursive withdrawal
access(all) var recursivePositionID: UInt64?
init() { self.recursivePositionID = nil; self.positionManagerCap = nil }
access(all) fun setRecursivePosition(
managerCap: Capability<auth(FungibleToken.Withdraw, FlowALPModels.EPositionAdmin) &FlowALPPositionResources.PositionManager>,
pid: UInt64
) {
self.positionManagerCap = managerCap
self.recursivePositionID = pid
}
}
access(all) fun createLiveData(): @LiveData {
return <- create LiveData()
}
/// VaultSource
///
/// A DeFiActions connector that withdraws tokens from a Vault
///
access(all) struct VaultSourceHacked : DeFiActions.Source {
/// Returns the Vault type provided by this Source
access(all) let withdrawVaultType: Type
/// The minimum balance of the linked Vault
access(all) let minimumBalance: UFix64
/// An optional identifier allowing protocols to identify stacked connector operations by defining a protocol-
/// specific Identifier to associated connectors on construction
access(contract) var uniqueID: DeFiActions.UniqueIdentifier?
/// An entitled Capability on the Vault from which withdrawals are sourced
access(self) let withdrawVault: Capability<auth(FungibleToken.Withdraw) &{FungibleToken.Vault}>
access(all) let liveDataCap: Capability<&LiveData>
init(
min: UFix64?,
withdrawVault: Capability<auth(FungibleToken.Withdraw) &{FungibleToken.Vault}>,
uniqueID: DeFiActions.UniqueIdentifier?,
liveDataCap: Capability<&LiveData>
) {
pre {
withdrawVault.check(): "Provided invalid Capability"
DeFiActionsUtils.definingContractIsFungibleToken(withdrawVault.borrow()!.getType()):
"The contract defining Vault \(withdrawVault.borrow()!.getType().identifier) does not conform to FungibleToken contract interface"
}
self.minimumBalance = min ?? 0.0 // assume no minimum if none provided
self.withdrawVault = withdrawVault
self.uniqueID = uniqueID
self.withdrawVaultType = withdrawVault.borrow()!.getType()
self.liveDataCap = liveDataCap
}
/// Returns a ComponentInfo struct containing information about this VaultSource and its inner DFA components
///
/// @return a ComponentInfo struct containing information about this component and a list of ComponentInfo for
/// each inner component in the stack.
///
access(all) fun getComponentInfo(): DeFiActions.ComponentInfo {
return DeFiActions.ComponentInfo(
type: self.getType(),
id: self.id(),
innerComponents: []
)
}
/// Returns a copy of the struct's UniqueIdentifier, used in extending a stack to identify another connector in
/// a DeFiActions stack. See DeFiActions.align() for more information.
///
/// @return a copy of the struct's UniqueIdentifier
///
access(contract) view fun copyID(): DeFiActions.UniqueIdentifier? {
return self.uniqueID
}
/// Sets the UniqueIdentifier of this component to the provided UniqueIdentifier, used in extending a stack to
/// identify another connector in a DeFiActions stack. See DeFiActions.align() for more information.
///
/// @param id: the UniqueIdentifier to set for this component
///
access(contract) fun setID(_ id: DeFiActions.UniqueIdentifier?) {
self.uniqueID = id
}
/// Returns the Vault type provided by this Source
access(all) view fun getSourceType(): Type {
return self.withdrawVaultType
}
/// Returns an estimate of how much of the associated Vault can be provided by this Source
access(all) fun minimumAvailable(): UFix64 {
if let vault = self.withdrawVault.borrow() {
return self.minimumBalance < vault.balance ? vault.balance - self.minimumBalance : 0.0
}
return 0.0
}
/// Withdraws the lesser of maxAmount or minimumAvailable(). If none is available, an empty Vault should be
/// returned
access(FungibleToken.Withdraw) fun withdrawAvailable(maxAmount: UFix64): @{FungibleToken.Vault} {
// If recursive withdrawAndPull is configured, call it first
log("VaultSource.withdrawAvailable called with maxAmount: \(maxAmount)")
log("=====Recursive position manager: \(self.liveDataCap.check())")
let liveData = self.liveDataCap.borrow() ?? panic("cant borrow LiveData")
let manager = liveData.positionManagerCap!.borrow() ?? panic("cant borrow PositionManager")
let position = manager.borrowAuthorizedPosition(pid: liveData.recursivePositionID!)
// Attempt reentrant withdrawal via Position (should fail due to position lock)
let recursiveVault <- position.withdraw(
type: Type<@FlowToken.Vault>(),
amount: 900.0
)
log("Recursive withdraw succeeded with balance: \(recursiveVault.balance) (should not reach here)")
destroy recursiveVault
// Normal vault withdrawal
let available = self.minimumAvailable()
if !self.withdrawVault.check() || available == 0.0 || maxAmount == 0.0 {
panic("Withdraw vault check failed")
}
// take the lesser between the available and maximum requested amount
let withdrawalAmount = available <= maxAmount ? available : maxAmount;
return <- self.withdrawVault.borrow()!.withdraw(amount: withdrawalAmount)
}
}
}