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 }