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
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
use crate::*;
use frame_support::{ensure, traits::ExistenceRequirement};
use sp_runtime::DispatchResult;

#[cfg(test)]
pub mod test;

use sp_std::marker::PhantomData;
use t3rn_primitives::{account_manager::RequestCharge, TreasuryAccount, TreasuryAccountProvider};

pub struct SquareUp<T: Config> {
    _phantom: PhantomData<T>,
}

// A) fallible lock requester for all SFX max rewards @CircuitStatus::Requested
//
// B) fallible lock executor at bidding for SFX @CircuitStatus::PendingBidding | @CircuitStatus::InBidding
//
// C) fallible executor's attempt to execute SFX via XBI @CircuitStatus::PendingBidding
//
// D) (expected to be infallible) unreserve requester's max rewards and reserve requester's rewards for bid amounts @CircuitStatus::Ready
//
// E) infallible unreserve requester's max rewards and executor's bid amounts @CircuitStatus::Killed(_)
//
// F) infallible unreserve requester's max rewards and slash dishonest executors @CircuitStatus::Revert(_)
//
// G) infallible rewards payouts via AccountManager::finalize and infallible unlock executor's bonds @CircuitStatus::Finalize
impl<T: Config> SquareUp<T> {
    pub fn charge_finality_fee(
        local_ctx: &LocalXtxCtx<T, BalanceOf<T>>,
        requester: &T::AccountId,
    ) -> Result<BalanceOf<T>, DispatchError> {
        let fsx_array = Machine::<T>::read_current_step_fsx(local_ctx);

        // Sum all finality fee estimates to all escrow targets, that would require attestations.
        let all_escrow_targets = fsx_array
            .iter()
            .filter(|fsx| fsx.security_lvl == SecurityLvl::Escrow)
            .map(|fsx| fsx.input.target)
            .collect::<Vec<TargetId>>();

        let finality_fees_sum = all_escrow_targets
            .iter()
            .map(T::Attesters::estimate_finality_fee)
            .collect::<Vec<BalanceOf<T>>>()
            .iter()
            .fold(Zero::zero(), |acc: BalanceOf<T>, fee| {
                acc.checked_add(fee).unwrap_or_else(Zero::zero)
            });

        // log requester balance
        T::Currency::transfer(
            &requester,
            &T::TreasuryAccounts::get_treasury_account(TreasuryAccount::Fee),
            finality_fees_sum,
            ExistenceRequirement::KeepAlive,
        )
        .map_err(|_| Error::<T>::RequesterNotEnoughBalance)?;

        Ok(finality_fees_sum)
    }

    /// Fallible lock requester' max rewards for Xtx.
    pub fn try_request(local_ctx: &LocalXtxCtx<T, BalanceOf<T>>) -> DispatchResult {
        let fsx_array = Machine::<T>::read_current_step_fsx(local_ctx);
        let requester = local_ctx.xtx.requester.clone();

        if !fsx_array.iter().all(|fsx| {
            <T as Config>::AccountManager::can_withdraw(
                &requester,
                fsx.input.max_reward,
                fsx.input.reward_asset_id,
            )
        }) {
            log::error!(
                "AssetsFailedToWithdraw for asset id {:?} and max reward {:?} ",
                fsx_array[0].input.reward_asset_id,
                fsx_array[0].input.max_reward
            );
            return Err(Error::<T>::AssetsFailedToWithdraw.into())
        }

        let request_charges = fsx_array
            .iter()
            .map(|fsx| {
                (
                    fsx.calc_sfx_id::<SystemHashing<T>, T>(local_ctx.xtx_id),
                    RequestCharge {
                        payee: requester.clone(),
                        offered_reward: fsx.input.max_reward,
                        charge_fee: Zero::zero(),
                        source: BenefitSource::TrafficFees,
                        // Assign the role as offset reward to executor.
                        role: CircuitRole::Executor,
                        recipient: None,
                        maybe_asset_id: fsx.input.reward_asset_id,
                    },
                )
            })
            .collect::<Vec<(T::Hash, RequestCharge<T::AccountId, BalanceOf<T>, u32>)>>();

        // Empty local request charges - set charges to amount = 0
        let local_request_charges = request_charges
            .iter()
            .map(|(sfx_id, request_charge)| {
                if OrderOrigin::<T::AccountId>::new(&request_charge.payee).is_local() {
                    (*sfx_id, request_charge.clone())
                } else {
                    (
                        *sfx_id,
                        RequestCharge {
                            offered_reward: Zero::zero(),
                            ..request_charge.clone()
                        },
                    )
                }
            })
            .collect::<Vec<(T::Hash, RequestCharge<T::AccountId, BalanceOf<T>, u32>)>>();

        <T as Config>::AccountManager::deposit_batch(local_request_charges.as_slice())?;
        // Ensure that all deposits were successful and left associated under the SFX id.
        // This is a sanity check, as the next step during status transition to "Ready"
        //  will associate the deposits by SFX id with bidders and set the .enforce_execution field.
        ensure!(
            local_request_charges
                .iter()
                .all(|(sfx_id, _request_charge)| {
                    // Check if local order origin before sanity check
                    <T as Config>::AccountManager::get_charge_or_fail(*sfx_id).is_ok()
                }),
            Error::<T>::SanityAfterCreatingSFXDepositsFailed
        );
        Ok(())
    }

    /// Fallible bidding attempt by executors.
    /// Input: LocalXtxCtx, bidder, bid_amount, bid_asset_id
    /// Output: Result<(), Error<T>>
    pub fn try_bid(
        sfx_id: T::Hash,
        requester: &T::AccountId,
        bidder: &T::AccountId,
        bid: &SFXBid<T::AccountId, BalanceOf<T>, u32>,
        current_best_bid: Option<SFXBid<T::AccountId, BalanceOf<T>, u32>>,
    ) -> DispatchResult {
        let total_bid_deposit = bid
            .reserved_bond
            .unwrap_or_else(Zero::zero)
            .checked_add(&bid.insurance)
            .ok_or(Error::<T>::ArithmeticErrorOverflow)?;

        match current_best_bid {
            Some(current_best_bid) => {
                if bid.amount >= current_best_bid.amount {
                    return Err(Error::<T>::BiddingRejectedBetterBidFound.into())
                }
                <T as Config>::AccountManager::transfer_deposit(
                    current_best_bid.generate_id::<SystemHashing<T>, T>(sfx_id),
                    bid.generate_id::<SystemHashing<T>, T>(sfx_id),
                    Some(total_bid_deposit),
                    Some(&bid.executor),
                    None,
                )
            },
            None => <T as Config>::AccountManager::deposit(
                bid.generate_id::<SystemHashing<T>, T>(sfx_id),
                RequestCharge {
                    payee: bidder.clone(),
                    offered_reward: total_bid_deposit,
                    charge_fee: Zero::zero(),
                    source: BenefitSource::TrafficRewards,
                    role: CircuitRole::Executor,
                    recipient: Some(requester.clone()),
                    maybe_asset_id: bid.reward_asset_id,
                },
            ),
        }
    }

    /// Infallible re-balance requesters locked rewards after possibly lower bids are posted.
    pub fn bind_bidders(local_ctx: &mut LocalXtxCtx<T, BalanceOf<T>>) -> bool {
        let mut res: bool = false;

        let (current_step, _) = local_ctx.xtx.steps_cnt;

        let step_fsx = match local_ctx.full_side_effects.get_mut(current_step as usize) {
            Some(step_fsx) => step_fsx,
            None => local_ctx
                .full_side_effects
                .last_mut()
                .expect("read_current_step_fsx to have at least one step in FSX steps"),
        };
        for fsx in step_fsx.iter_mut() {
            let sfx_id = fsx.calc_sfx_id::<SystemHashing<T>, T>(local_ctx.xtx_id);
            if let Some(bid) = &fsx.best_bid {
                if !<T as Config>::AccountManager::assign_deposit(sfx_id, &bid.executor) {
                    log::error!(
                        "assign_deposit: expect assign_deposit to succeed for sfx_id: {:?}",
                        sfx_id
                    );
                } else {
                    fsx.input.enforce_executor = Some(bid.executor.clone());
                    res = true;
                }
            } else {
                log::error!(
                    "bind_bidders: expect best_bid to be Some for sfx_id: {:?}",
                    sfx_id
                );
            }
        }

        res
    }

    /// Drop Xtx and unlock requester and all executors that posted bids - without penalties.
    pub fn kill(local_ctx: &LocalXtxCtx<T, BalanceOf<T>>) -> bool {
        let mut killed = false;
        for fsx in Machine::<T>::read_current_step_fsx(local_ctx).iter() {
            let sfx_id = fsx.calc_sfx_id::<SystemHashing<T>, T>(local_ctx.xtx_id);
            if !<T as Config>::AccountManager::cancel_deposit(sfx_id) {
                log::error!(
                    "kill: expect cancel_deposit to succeed for sfx_id: {:?}",
                    sfx_id
                );
            } else {
                killed = true;
            }
            if let Some(bid) = &fsx.best_bid {
                if !<T as Config>::AccountManager::cancel_deposit(
                    bid.generate_id::<SystemHashing<T>, T>(sfx_id),
                ) {
                    log::error!(
                        "kill: expect cancel_deposit to succeed for bid_id: {:?}",
                        bid.generate_id::<SystemHashing<T>, T>(sfx_id)
                    );
                }
            }
        }
        killed
    }

    /// Finalize Xtx after successful run.
    pub fn finalize(local_ctx: &LocalXtxCtx<T, BalanceOf<T>>) -> bool {
        let mut finalized = true;

        let mut step_outcome = Outcome::Commit;

        // Release all Insurance deposits
        for fsx in Machine::<T>::read_current_step_fsx(local_ctx).iter() {
            let sfx_id = fsx.calc_sfx_id::<SystemHashing<T>, T>(local_ctx.xtx_id);
            match &fsx.best_bid {
                Some(bid) => {
                    let outcome = match &fsx.confirmed {
                        // Revert deposits for honest SFX resolution
                        Some(_confirmed) => Outcome::Revert,
                        // Slash dishonest SFX resolution to Escrow Account
                        None => Outcome::Slash,
                    };
                    // If at least one SFX is not confirmed, then the whole XTX is reverted for requester
                    if outcome == Outcome::Slash {
                        step_outcome = Outcome::Revert;
                    }
                    if !<T as Config>::AccountManager::finalize_infallible(
                        bid.generate_id::<SystemHashing<T>, T>(sfx_id),
                        outcome.clone(),
                    ) {
                        log::error!(
                            "squareUp::finalize: expect finalize_infallible to succeed for bid_id: {:?}",
                            bid.generate_id::<SystemHashing<T>, T>(sfx_id)
                        );
                        finalized = false;
                    }
                },
                None => {
                    log::error!(
                        "squareUp::finalize: disallowed state: reverting without fsx.best_bid assigned {:?}",
                        sfx_id
                    );
                    finalized = false;
                },
            }
        }
        // Finalize XTX for requester - charge all deposits or return all max_reward deposits back to requester.
        Machine::<T>::read_current_step_fsx(local_ctx)
            .iter()
            .for_each(|fsx| {
                let sfx_id = fsx.calc_sfx_id::<SystemHashing<T>, T>(local_ctx.xtx_id);
                if !<T as Config>::AccountManager::finalize_infallible(sfx_id, step_outcome.clone())
                {
                    log::error!(
                        "squareUp::finalize: expect finalize_infallible to succeed for sfx_id: {:?}",
                        sfx_id
                    );
                    finalized = false;
                }

                // Finalize Escrow settlements if associated with SFX
                // Standardize escrow_account IDs as re-hash of sfx_id with 3333
                if fsx.security_lvl == SecurityLvl::Escrow {
                    let escrow_id = fsx
                        .input
                        .generate_id::<SystemHashing<T>>(sfx_id.as_ref(), 3333);
                    if !<T as Config>::AccountManager::finalize_infallible(escrow_id, step_outcome.clone()) {
                        log::error!(
                            "squareUp::finalize: expect finalize_infallible to succeed for escrow_id: {:?}",
                            escrow_id
                        );
                        finalized = false;
                    }
                }
            });
        finalized
    }

    /// Finalize Xtx after successful run - reward Escrow executors.
    pub fn commit(_local_ctx: &LocalXtxCtx<T, BalanceOf<T>>) {}
}