Files
2026-08-09 09:23:07 -07:00

151 lines
6.2 KiB
Plaintext

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");
}
}