1 module tagion.script.execute;
2 import std.algorithm;
3 import std.array;
4 import std.format;
5 import std.range;
6 import tagion.hibon.Document;
7 import tagion.hibon.HiBONRecord : getType, isRecord;
8 import tagion.logger.Logger;
9 import tagion.script.Currency;
10 import tagion.script.ScriptException;
11 import tagion.script.TagionCurrency;
12 import tagion.script.common;
13 
14 @safe
15 struct ContractProduct {
16     immutable(CollectedSignedContract*) contract;
17     Document[] outputs;
18     this(immutable(CollectedSignedContract)* contract, const(Document)[] outputs) @trusted immutable {
19         this.contract = contract;
20         this.outputs = cast(immutable) outputs;
21     }
22 }
23 
24 import tagion.dart.Recorder;
25 
26 @safe
27 struct CollectedSignedContract {
28     immutable(SignedContract)* sign_contract;
29     const(Document)[] inputs;
30     const(Document)[] reads;
31     //mixin HiBONRecord;
32 }
33 
34 @safe
35 interface CheckContract {
36     const(TagionCurrency) calcFees(immutable(CollectedSignedContract)* exec_contract, in TagionCurrency amount, in GasUse gas_use);
37     bool validAmount(immutable(CollectedSignedContract)* exec_contract,
38             in TagionCurrency input_amount,
39             in TagionCurrency output_amount,
40             in GasUse use);
41 }
42 
43 @safe
44 struct GasUse {
45     size_t gas;
46     size_t storage;
47 }
48 
49 import tagion.utils.Result;
50 
51 alias ContractProductResult = Result!(immutable(ContractProduct)*, Exception);
52 
53 @safe
54 class StdCheckContract : CheckContract {
55     TagionCurrency storage_fees; /// Fees per bytes
56     TagionCurrency gas_price; /// Fees per TVM instruction
57 
58     TagionCurrency calcFees(in GasUse use) pure {
59         return use.gas * gas_price + use.storage * storage_fees;
60     }
61 
62     const(TagionCurrency) calcFees(
63             immutable(CollectedSignedContract)* exec_contract,
64             in TagionCurrency amount,
65             in GasUse use) {
66         return calcFees(use);
67     }
68 
69     bool validAmount(immutable(CollectedSignedContract)* exec_contract,
70             in TagionCurrency input_amount,
71             in TagionCurrency output_amount,
72             in GasUse use) {
73         const gas_cost = calcFees(exec_contract, output_amount, use);
74         return input_amount >= output_amount + gas_cost;
75     }
76 
77 }
78 
79 @safe
80 struct ContractExecution {
81     static StdCheckContract check_contract;
82     enum pay_gas = 1000;
83     ContractProductResult opCall(immutable(CollectedSignedContract)* exec_contract) nothrow {
84         const script_doc = exec_contract.sign_contract.contract.script;
85         try {
86             if (isRecord!PayScript(script_doc)) {
87                 return ContractProductResult(pay(exec_contract));
88             }
89             return ContractProductResult(format("Illegal corrected contract %s", script_doc.getType));
90         }
91         catch (Exception e) {
92             return ContractProductResult(e);
93         }
94     }
95 
96     static TagionCurrency _billFees(const size_t number_of_input_bytes, const size_t number_of_output_bytes) {
97         const output_fee = check_contract.calcFees(GasUse(pay_gas, number_of_output_bytes));
98         const input_fee = check_contract.calcFees(GasUse(0, number_of_input_bytes));
99         return output_fee - input_fee;
100     }
101 
102     static TagionCurrency billFees(R1, R2)(R1 inputs, R2 outputs, const size_t extra)
103             if (isInputRange!R1 && isInputRange!R2 &&
104                 is(ElementType!R1 : const(Document)) && is(ElementType!R2 : const(Document))) {
105         return _billFees(
106                 inputs.map!(doc => doc.full_size).sum,
107                 outputs.map!(doc => doc.full_size).sum + extra);
108 
109     }
110 
111     immutable(ContractProduct)* pay(immutable(CollectedSignedContract)* exec_contract) {
112         import std.exception;
113 
114         const input_amount = exec_contract.inputs
115             .map!(doc => TagionBill(doc).value)
116             .totalAmount;
117 
118         const pay_script = PayScript(exec_contract.sign_contract.contract.script);
119         const output_amount = pay_script.outputs
120             .map!(bill => bill.value)
121             .tee!(value => check(value > 0.TGN, "Output with 0 TGN not allowed"))
122             .totalAmount;
123 
124         const output_docs = pay_script.outputs.map!(v => v.toDoc).array;
125         const result = new immutable(ContractProduct)(
126                 exec_contract,
127                 output_docs);
128 
129         const bill_fees = billFees(exec_contract.inputs, output_docs, 0);
130         check(input_amount >= (output_amount + bill_fees), "Invalid amount");
131         return result;
132     }
133 }
134 
135 static this() {
136     ContractExecution.check_contract = new StdCheckContract;
137     ContractExecution.check_contract.storage_fees = 1.TGN;
138     ContractExecution.check_contract.gas_price = 0.1.TGN;
139 }