mirror of
https://github.com/Permissionless-Software-Foundation/psffpp-payments.git
synced 2026-09-21 16:52:04 -07:00
151 lines
6.2 KiB
Plaintext
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");
|
|
}
|
|
}
|