pragma cashscript ^0.13.0; //////////////////////////////////////////////////////////////////////////////// // PoolContract — single pool instance per payout epoch (M=3, K=2) // // Holds user deposits. Each deploy is one height-gated payout epoch. // Next month: redeploy with a new splitBlockheight, then K-of-M migrate. // //////////////////////////////////////////////////////////////////////////////// contract PoolContract( pubkey node0, pubkey node1, pubkey node2, bytes20 treasuryPkh, bytes share0, bytes share1, bytes share2, int minConsolidation, int splitBlockheight ) { ////////////////////////////////////////////////////////////////////////////// // Operation: consolidate (permissionless) // inputs: // 0..N PoolContract [BCH] (one or more pool UTXOs) // ? feePayer [BCH] (optional) // outputs: // 0..1 PoolContract [BCH] (self-replicating, each >= minConsolidation) ////////////////////////////////////////////////////////////////////////////// function consolidate() { require(tx.outputs.length <= 2, "consolidate: too many outputs"); require(tx.outputs.length >= 1, "consolidate: need at least 1 output"); int inSum = 0; for (int i = 0; i < tx.inputs.length; i = i + 1) { inSum = inSum + tx.inputs[i].value; } // Compare against the P2SH locking bytecode on this input (not redeem script). bytes contractLock = tx.inputs[this.activeInputIndex].lockingBytecode; int outSum = 0; for (int j = 0; j < tx.outputs.length; j = j + 1) { require( tx.outputs[j].lockingBytecode == contractLock, "consolidate: must self-replicate" ); require(tx.outputs[j].tokenCategory == 0x, "consolidate: pure BCH only"); require( tx.outputs[j].value >= minConsolidation, "consolidate: output below minConsolidation" ); outSum = outSum + tx.outputs[j].value; } require(outSum <= inSum, "consolidate: value not conserved"); } ////////////////////////////////////////////////////////////////////////////// // Operation: split (permissionless, blockheight-gated) // inputs: // 0 PoolContract [BCH] (consolidated pool UTXO — active input) // 1? feePayer [BCH] (optional companion fee input) // outputs: // 0 treasury [BCH] (20% + rounding remainder) // 1 ShareContract node0 [BCH] (equal share of 80%) // 2 ShareContract node1 [BCH] // 3 ShareContract node2 [BCH] ////////////////////////////////////////////////////////////////////////////// function split() { require(tx.outputs.length == 4, "split: expected 4 outputs"); require(tx.time >= splitBlockheight, "split: blockheight gate not met"); // Fee companion allowed; pool value taken from this active input only. require(tx.inputs.length <= 2, "split: at most 2 inputs"); int poolValue = tx.inputs[this.activeInputIndex].value; int treasuryBase = poolValue * 20 / 100; int remaining = poolValue - treasuryBase; int share = remaining / 3; int remainder = remaining - share * 3; int treasuryValue = treasuryBase + remainder; // Output 0: treasury require( tx.outputs[0].lockingBytecode == new LockingBytecodeP2PKH(treasuryPkh), "split: output 0 must be treasury" ); require(tx.outputs[0].tokenCategory == 0x, "split: treasury must be pure BCH"); require(tx.outputs[0].value == treasuryValue, "split: bad treasury value"); // Outputs 1..3: equal shares locked in ShareContracts require(tx.outputs[1].lockingBytecode == share0, "split: output 1 must be share0"); require(tx.outputs[1].tokenCategory == 0x, "split: share0 must be pure BCH"); require(tx.outputs[1].value == share, "split: bad share0 value"); require(tx.outputs[2].lockingBytecode == share1, "split: output 2 must be share1"); require(tx.outputs[2].tokenCategory == 0x, "split: share1 must be pure BCH"); require(tx.outputs[2].value == share, "split: bad share1 value"); require(tx.outputs[3].lockingBytecode == share2, "split: output 3 must be share2"); require(tx.outputs[3].tokenCategory == 0x, "split: share2 must be pure BCH"); require(tx.outputs[3].value == share, "split: bad share2 value"); // Exact poolValue apportionment (fee paid by companion input if present) require( treasuryValue + share + share + share == poolValue, "split: apportionment mismatch" ); } ////////////////////////////////////////////////////////////////////////////// // Operation: migrate (K-of-M) // inputs: // 0..N PoolContract [BCH] // ? feePayer [BCH] (optional) // outputs: // 0..1 next-epoch destination [BCH] (authorized by signatures) ////////////////////////////////////////////////////////////////////////////// function migrate(sig s0, sig s1, sig s2) { require(tx.outputs.length <= 2, "migrate: too many outputs"); require(tx.outputs.length >= 1, "migrate: need at least 1 output"); int validCount = 0; if (checkSig(s0, node0)) { validCount = validCount + 1; } if (checkSig(s1, node1)) { validCount = validCount + 1; } if (checkSig(s2, node2)) { validCount = validCount + 1; } require(validCount >= 2, "migrate: need at least 2-of-3 signatures"); int inSum = 0; for (int i = 0; i < tx.inputs.length; i = i + 1) { inSum = inSum + tx.inputs[i].value; } int outSum = 0; for (int j = 0; j < tx.outputs.length; j = j + 1) { require(tx.outputs[j].tokenCategory == 0x, "migrate: pure BCH only"); require(tx.outputs[j].value > 0, "migrate: zero output"); outSum = outSum + tx.outputs[j].value; } require(outSum <= inSum, "migrate: value not conserved"); } }