diff --git a/settings/Devnet.toml b/settings/Devnet.toml index 6c67353..3fdfb10 100644 --- a/settings/Devnet.toml +++ b/settings/Devnet.toml @@ -102,8 +102,8 @@ bitcoin_controller_automining_disabled = true # postgres_password = "postgres" # postgres_database = "postgres" # bitcoin_node_image_url = "quay.io/hirosystems/bitcoind:devnet-v2" -# stacks_node_image_url = "quay.io/hirosystems/stacks-node:v3-beta2" -# stacks_node_env_vars = ["STACKS_LOG_DEBUG=1"] +stacks_node_image_url = "quay.io/hirosystems/stacks-node:devnet-v3-beta3" +stacks_node_env_vars = ["STACKS_LOG_DEBUG=1"] # stacks_api_image_url = "hirosystems/stacks-blockchain-api:latest" # stacks_explorer_image_url = "hirosystems/explorer:latest" # bitcoin_explorer_image_url = "quay.io/hirosystems/bitcoin-explorer:devnet" @@ -117,7 +117,7 @@ bitcoin_controller_automining_disabled = true # subnet_node_p2p_port = 30444 # subnet_events_ingestion_port = 30445 # subnet_node_events_observers = ["host.docker.internal:8002"] -# epoch_2_00 = 100 -# epoch_2_05 = 102 -# epoch_2_1 = 106 -# pox_2_activation = 108 +epoch_2_00 = 100 +epoch_2_05 = 101 +epoch_2_1 = 103 +pox_2_activation = 110 diff --git a/tests/integration/constants.ts b/tests/integration/constants.ts index 16a45a3..003e612 100644 --- a/tests/integration/constants.ts +++ b/tests/integration/constants.ts @@ -42,4 +42,10 @@ export namespace Accounts { secretKey: "d655b2523bcd65e34889725c73064feb17ceb796831c0e111ba1a552b0f31b3901", }; + export const FAUCET = { + stxAddress: "STNHKEPYEPJ8ET55ZZ0M5A34J0R3N5FM2CMMMAZ6", + btcAddress: "mjSrB3wS4xab3kYqFktwBzfTdPg367ZJ2d", + secretKey: + "de433bdfa14ec43aa1098d5be594c8ffb20a31485ff9de2923b2689471c401b801", + }; } diff --git a/tests/integration/helpers.ts b/tests/integration/helpers.ts index cc7c5b8..37ffc30 100644 --- a/tests/integration/helpers.ts +++ b/tests/integration/helpers.ts @@ -17,7 +17,7 @@ interface EpochTimeline { pox_2_activation: number; } -const DEFAULT_EPOCH_TIMELINE = { +export const DEFAULT_EPOCH_TIMELINE = { epoch_2_0: Constants.DEVNET_DEFAULT_EPOCH_2_0, epoch_2_05: Constants.DEVNET_DEFAULT_EPOCH_2_05, epoch_2_1: Constants.DEVNET_DEFAULT_EPOCH_2_1, diff --git a/tests/integration/pox/helpers.ts b/tests/integration/pox/helpers.ts index bd8801b..805a863 100644 --- a/tests/integration/pox/helpers.ts +++ b/tests/integration/pox/helpers.ts @@ -17,8 +17,23 @@ import { uintCV, } from "@stacks/transactions"; import { decodeBtcAddress } from "@stacks/stacking"; -import { toBytes } from "@stacks/common"; +import { + toBytes, + asciiToBytes, + bigIntToBytes, + intToBytes, + concatBytes, + intToBigInt, +} from "@stacks/common"; +import { principalCV } from "@stacks/transactions/dist/clarity/types/principalCV"; const fetch = require("node-fetch"); +const Script = require("bitcore-lib/lib/script"); +const Opcode = require("bitcore-lib/lib/opcode"); +const Networks = require("bitcore-lib/lib/networks"); +const Transaction = require("bitcore-lib/lib/transaction"); +const PrivateKey = require("bitcore-lib/lib/privatekey"); +const Signature = require("bitcore-lib/lib/crypto/signature"); +const { Output, Input } = require("bitcore-lib/lib/transaction"); interface Account { stxAddress: string; @@ -176,3 +191,531 @@ export const broadcastStackSTX = async ( const result = await broadcastTransaction(tx, network); return result; }; + +export const broadcastStackSTXThroughBitcoin = async ( + orchestrator: DevnetNetworkOrchestrator, + bitcoinRpcUrl: string, + bitcoinRpcUsername: string, + bitcoinRpcPassword: string, + indexedBitcoinWallet: Account, + amountToStacks: number, + cycles: number +) => { + // Steps: + // - Retrieve a UTXO + // - Craft and broadcast a `PreOp` transaction where: + // - Output 1 is a PreOp signal + // - Output 2 is a legacy address that will be converted to the Stacks Address + // - Craft and broadcast the actual Stacks Op transaction where + // - Input 1 is the Output 2 of the `PreOp` transaction + // - Output 1 the actual Op OP_RETURN + let secretKey = new PrivateKey( + indexedBitcoinWallet.secretKey.slice(0, 64), + Networks.testnet + ); + console.log(indexedBitcoinWallet.btcAddress); + let basicAuthorization = + "Basic " + btoa(`${bitcoinRpcUsername}:${bitcoinRpcPassword}`); + console.log(`---> ${bitcoinRpcUrl}`); + let response = await fetch(bitcoinRpcUrl, { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: basicAuthorization, + }, + body: JSON.stringify({ + id: 0, + method: `listunspent`, + params: [1, 9999999, [indexedBitcoinWallet.btcAddress]], + }), + }); + let json = await response.json(); + let unspentOutputs = json.result; + + let typicalSize = 600; + let txFee = 10 * typicalSize; + let totalRequired = txFee; + let selectedUtxosIndices: number[] = []; + let cumulatedAmount = 0; + let i = 0; + + for (let utxo of unspentOutputs) { + cumulatedAmount += utxo.amount * 100_000_000; + selectedUtxosIndices.push(i); + if (cumulatedAmount >= totalRequired) { + break; + } + i++; + } + if (cumulatedAmount < totalRequired) { + return { + message: "Funding unsufficient", + unspentOutputs: unspentOutputs, + statusCode: 404, + }; + } + + selectedUtxosIndices.reverse(); + let preTransaction = new Transaction(); + preTransaction.setVersion(1); + let selectedUnspentOutput: any[] = []; + for (let index of selectedUtxosIndices) { + let unspentOutput = unspentOutputs[index]; + + unspentOutputs.splice(index, 1); + let input = Input.fromObject({ + prevTxId: unspentOutput.txid, + script: Script.empty(), + outputIndex: unspentOutput.vout, + output: new Output({ + satoshis: parseInt(unspentOutput.amount), + script: Buffer.from(unspentOutput.scriptPubKey, "hex"), + }), + }); + preTransaction.addInput(new Input.PublicKeyHash(input)); + selectedUnspentOutput.push(unspentOutput); + } + + /* + Wire format: + 0 2 3 + |------|--| + magic op + */ + let magicBytes = asciiToBytes("id"); + let opCodeByte = asciiToBytes("p"); + let messageBytes = concatBytes(magicBytes, opCodeByte); + console.log(`${messageBytes}`); + let unwrapOutput = new Output({ + satoshis: 0, + script: new Script() + .add(Opcode.map.OP_RETURN) + .add(Opcode.map.OP_PUSHDATA1) + .add(Buffer.from(messageBytes)) + }); + preTransaction.outputs.push(unwrapOutput); + + let principal = principalCV(indexedBitcoinWallet.stxAddress); + console.log(principal.address.hash160) + let changeOutput = new Output({ + satoshis: cumulatedAmount - txFee, + script: new Script() + .add(Opcode.map.OP_DUP) + .add(Opcode.map.OP_HASH160) + .add(Buffer.from(principal.address.hash160, "hex")) + .add(Opcode.map.OP_EQUALVERIFY) + .add(Opcode.map.OP_CHECKSIG), + }); + preTransaction.outputs.push(changeOutput); + + preTransaction.sign(secretKey, Signature.SIGHASH_ALL, "ecdsa"); + let preTx = preTransaction.serialize(true); + + console.log(`${preTx}`) + + response = await fetch(bitcoinRpcUrl, { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: basicAuthorization, + }, + body: JSON.stringify({ + id: 0, + method: `sendrawtransaction`, + params: [preTx], + }), + }); + json = await response.json(); + let preTxid = json.result; + + console.log(`PreOp: ${preTxid}`); + + // let chainUpdate = await orchestrator.waitForNextBitcoinBlock(); + // chainUpdate.new_blocks[0].transactions; + + let transaction = new Transaction(); + transaction.setVersion(1); + + let input = Input.fromObject({ + prevTxId: preTxid, + script: Script.empty(), + outputIndex: 1, + output: changeOutput, + }); + transaction.addInput(new Input.PublicKeyHash(input)); + /* + Wire format: + 0 2 3 19 20 + |------|--|-----------------------------|---------| + magic op uSTX to lock (u128) cycles (u8) + */ + opCodeByte = asciiToBytes("x"); + let amountBytes = bigIntToBytes(intToBigInt(amountToStacks, false)); + let cyclesBytes = intToBytes(cycles, false, 1); + messageBytes = concatBytes(magicBytes, opCodeByte, amountBytes, cyclesBytes); + unwrapOutput = new Output({ + satoshis: 0, + script: new Script() + .add(Opcode.map.OP_RETURN) + .add(Opcode.map.OP_PUSHDATA1) + .add(Buffer.from(messageBytes)) + }); + transaction.outputs.push(unwrapOutput); + changeOutput = new Output({ + satoshis: cumulatedAmount - txFee - txFee, + script: new Script() + .add(Opcode.map.OP_DUP) + .add(Opcode.map.OP_HASH160) + .add(Buffer.from(principal.address.hash160, "hex")) + .add(Opcode.map.OP_EQUALVERIFY) + .add(Opcode.map.OP_CHECKSIG), + }); + transaction.outputs.push(changeOutput); + + transaction.sign(secretKey, Signature.SIGHASH_ALL, "ecdsa"); + let tx = transaction.serialize(true); + response = await fetch(bitcoinRpcUrl, { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: basicAuthorization, + }, + body: JSON.stringify({ + id: 0, + method: `sendrawtransaction`, + params: [tx], + }), + }); + json = await response.json(); + console.log(json); + let txid = json.result; + console.log(txid); +}; + +export const broadcastDelegatedStackSTXThroughBitcoin = async ( + orchestrator: DevnetNetworkOrchestrator, + bitcoinRpcUrl: string, + bitcoinRpcUsername: string, + bitcoinRpcPassword: string, + indexedBitcoinWallet: Account, + amountToStacks: number, + burnBlockHeight: number, + delegateWallet: Account, +) => { + // Steps: + // - Retrieve a UTXO + // - Craft and broadcast a `PreOp` transaction where: + // - Output 1 is a PreOp signal + // - Output 2 is a legacy address that will be converted to the Stacks Address + // - Craft and broadcast the actual Stacks Op transaction where + // - Input 1 is the Output 2 of the `PreOp` transaction + // - Output 1 the actual Op OP_RETURN + let secretKey = new PrivateKey( + indexedBitcoinWallet.secretKey.slice(0, 64), + Networks.testnet + ); + console.log(indexedBitcoinWallet.btcAddress); + let basicAuthorization = + "Basic " + btoa(`${bitcoinRpcUsername}:${bitcoinRpcPassword}`); + console.log(`---> ${bitcoinRpcUrl}`); + let response = await fetch(bitcoinRpcUrl, { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: basicAuthorization, + }, + body: JSON.stringify({ + id: 0, + method: `listunspent`, + params: [1, 9999999, [indexedBitcoinWallet.btcAddress]], + }), + }); + let json = await response.json(); + let unspentOutputs = json.result; + + let typicalSize = 600; + let txFee = 10 * typicalSize; + let totalRequired = txFee; + let selectedUtxosIndices: number[] = []; + let cumulatedAmount = 0; + let i = 0; + + for (let utxo of unspentOutputs) { + cumulatedAmount += utxo.amount * 100_000_000; + selectedUtxosIndices.push(i); + if (cumulatedAmount >= totalRequired) { + break; + } + i++; + } + if (cumulatedAmount < totalRequired) { + return { + message: "Funding unsufficient", + unspentOutputs: unspentOutputs, + statusCode: 404, + }; + } + + selectedUtxosIndices.reverse(); + let preTransaction = new Transaction(); + preTransaction.setVersion(1); + let selectedUnspentOutput: any[] = []; + for (let index of selectedUtxosIndices) { + let unspentOutput = unspentOutputs[index]; + + unspentOutputs.splice(index, 1); + let input = Input.fromObject({ + prevTxId: unspentOutput.txid, + script: Script.empty(), + outputIndex: unspentOutput.vout, + output: new Output({ + satoshis: parseInt(unspentOutput.amount), + script: Buffer.from(unspentOutput.scriptPubKey, "hex"), + }), + }); + preTransaction.addInput(new Input.PublicKeyHash(input)); + selectedUnspentOutput.push(unspentOutput); + } + + /* + Wire format: + 0 2 3 + |------|--| + magic op + */ + let magicBytes = asciiToBytes("id"); + let opCodeByte = asciiToBytes("p"); + let messageBytes = concatBytes(magicBytes, opCodeByte); + console.log(`${messageBytes}`); + let unwrapOutput = new Output({ + satoshis: 0, + script: new Script() + .add(Opcode.map.OP_RETURN) + .add(Opcode.map.OP_PUSHDATA1) + .add(Buffer.from(messageBytes)) + }); + preTransaction.outputs.push(unwrapOutput); + + let rewardPrincipal = principalCV(delegateWallet.stxAddress); + let principal = principalCV(indexedBitcoinWallet.stxAddress); + console.log(principal.address.hash160) + let changeOutput = new Output({ + satoshis: cumulatedAmount - txFee, + script: new Script() + .add(Opcode.map.OP_DUP) + .add(Opcode.map.OP_HASH160) + .add(Buffer.from(principal.address.hash160, "hex")) + .add(Opcode.map.OP_EQUALVERIFY) + .add(Opcode.map.OP_CHECKSIG), + }); + preTransaction.outputs.push(changeOutput); + + preTransaction.sign(secretKey, Signature.SIGHASH_ALL, "ecdsa"); + let preTx = preTransaction.serialize(true); + + console.log(`${preTx}`) + + response = await fetch(bitcoinRpcUrl, { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: basicAuthorization, + }, + body: JSON.stringify({ + id: 0, + method: `sendrawtransaction`, + params: [preTx], + }), + }); + json = await response.json(); + let preTxid = json.result; + + console.log(`PreOp: ${preTxid}`); + + // let chainUpdate = await orchestrator.waitForNextBitcoinBlock(); + // chainUpdate.new_blocks[0].transactions; + + let transaction = new Transaction(); + transaction.setVersion(1); + + let input = Input.fromObject({ + prevTxId: preTxid, + script: Script.empty(), + outputIndex: 1, + output: changeOutput, + }); + transaction.addInput(new Input.PublicKeyHash(input)); + /* + Wire format: + 0 2 3 19 24 33 + |------|--|------------------|--------|--------------| + magic op delegated ustx ^ until burn height + reward addr output index + */ + opCodeByte = asciiToBytes("#"); + let amountBytes = bigIntToBytes(intToBigInt(amountToStacks, false)); + let rewardAddressOutputIndex = intToBytes(0, false, 4); + let burnHeight = intToBytes(burnBlockHeight, false, 8); + messageBytes = concatBytes(magicBytes, opCodeByte, amountBytes, rewardAddressOutputIndex, burnHeight); + unwrapOutput = new Output({ + satoshis: 0, + script: new Script() + .add(Opcode.map.OP_RETURN) + .add(Opcode.map.OP_PUSHDATA1) + .add(Buffer.from(messageBytes)) + }); + transaction.outputs.push(unwrapOutput); + let dust = 10000; + let rewardOutput = new Output({ + satoshis: dust, + script: new Script() + .add(Opcode.map.OP_DUP) + .add(Opcode.map.OP_HASH160) + .add(Buffer.from(rewardPrincipal.address.hash160, "hex")) + .add(Opcode.map.OP_EQUALVERIFY) + .add(Opcode.map.OP_CHECKSIG), + }); + transaction.outputs.push(rewardOutput); + + changeOutput = new Output({ + satoshis: cumulatedAmount - dust - txFee - txFee, + script: new Script() + .add(Opcode.map.OP_DUP) + .add(Opcode.map.OP_HASH160) + .add(Buffer.from(principal.address.hash160, "hex")) + .add(Opcode.map.OP_EQUALVERIFY) + .add(Opcode.map.OP_CHECKSIG), + }); + transaction.outputs.push(changeOutput); + + transaction.sign(secretKey, Signature.SIGHASH_ALL, "ecdsa"); + let tx = transaction.serialize(true); + response = await fetch(bitcoinRpcUrl, { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: basicAuthorization, + }, + body: JSON.stringify({ + id: 0, + method: `sendrawtransaction`, + params: [tx], + }), + }); + json = await response.json(); + console.log(json); + let txid = json.result; + console.log(txid); +}; + + +// export const broadcastStackSTXThroughBitcoin = async (bitcoinRpcUrl: string, bitcoinRpcUsername: string, bitcoinRpcPassword: string, indexedBitcoinWallet: Account, amountToStacks: number, cycles: number) => { + +// let response = await fetch(bitcoinRpcUrl, { +// method: "POST", +// headers: { +// "Content-Type": "application/json", +// "Authorization": "Basic " + btoa(`${bitcoinRpcUsername}:${bitcoinRpcPassword}`), +// }, +// body: JSON.stringify({ +// id: 0, +// method: `listunspent`, +// params: [1, 9999999, [indexedBitcoinWallet.btcAddress]], +// }), +// }); +// let json = await response.json(); +// let unspentOutputs = json.result; + +// // let recipientAddress = principalCV(transfer.recipient); + +// let typicalSize = 600; +// let txFee = 10 * typicalSize; +// let totalRequired = parseInt(transfer.amount) + txFee; +// let selectedUtxosIndices: number[] = []; +// let cumulatedAmount = 0; +// let i = 0; +// for (let utxo of unspentOutputs) { +// cumulatedAmount += utxo.amount * 100_000_000; +// selectedUtxosIndices.push(i); +// if (cumulatedAmount >= totalRequired) { +// break; +// } +// i++; +// } +// if (cumulatedAmount < totalRequired) { +// return { +// message: "Funding unsufficient", +// unspentOutputs: unspentOutputs, +// statusCode: 404, +// }; +// } + +// selectedUtxosIndices.reverse(); +// let transaction = new Transaction(); +// transaction.setVersion(1); +// let selectedUnspentOutput: any[] = []; +// for (let index of selectedUtxosIndices) { +// let unspentOutput = unspentOutputs[index]; + +// unspentOutputs.splice(index, 1); +// let input = Input.fromObject({ +// prevTxId: unspentOutput.txid, +// script: Script.empty(), +// outputIndex: unspentOutput.vout, +// output: new Output({ +// satoshis: parseInt(transfer.amount), +// script: Buffer.from(unspentOutput.scriptPubKey, "hex"), +// }), +// }); +// transaction.addInput(new Input.PublicKeyHash(input)); +// selectedUnspentOutput.push(unspentOutput); +// } + +// let unwrapOutput = new Output({ +// satoshis: parseInt(transfer.amount), +// script: new Script() +// .add(Opcode.map.OP_DUP) +// .add(Opcode.map.OP_HASH160) +// .add(Buffer.from(recipientAddress.address.hash160, "hex")) +// .add(Opcode.map.OP_EQUALVERIFY) +// .add(Opcode.map.OP_CHECKSIG), +// }); + +// transaction.outputs.push(unwrapOutput); + +// let changeOutput = new Output({ +// satoshis: cumulatedAmount - parseInt(transfer.amount) - txFee, +// script: new Script() +// .add(Opcode.map.OP_DUP) +// .add(Opcode.map.OP_HASH160) +// .add(Buffer.from(authorityAddress.address.hash160, "hex")) +// .add(Opcode.map.OP_EQUALVERIFY) +// .add(Opcode.map.OP_CHECKSIG), +// }); + +// transaction.outputs.push(changeOutput); + +// let secretKey = new PrivateKey( +// process.env.AUTHORITY_SECRET_KEY!.slice(0, 64), +// Networks.testnet, +// ); + +// transaction.sign(secretKey, Signature.SIGHASH_ALL, "ecdsa"); +// let tx = transaction.serialize(true); + +// response = await fetch(BITCOIN_NODE_URL, { +// method: "POST", +// headers: { +// "Content-Type": "application/json", +// "Authorization": "Basic " + btoa("devnet:devnet"), +// }, +// body: JSON.stringify({ +// id: 0, +// method: `sendrawtransaction`, +// params: [tx], +// }), +// }); +// json = await response.json(); +// let txid = json.result; + +// } diff --git a/tests/integration/pox/stacking/epoch_2_1_delegated_stacks_stx_via_bitcoin.spec.ts b/tests/integration/pox/stacking/epoch_2_1_delegated_stacks_stx_via_bitcoin.spec.ts new file mode 100644 index 0000000..7695ebd --- /dev/null +++ b/tests/integration/pox/stacking/epoch_2_1_delegated_stacks_stx_via_bitcoin.spec.ts @@ -0,0 +1,108 @@ +import { + buildDevnetNetworkOrchestrator, + getBitcoinBlockHeight, + getNetworkIdFromEnv, + DEFAULT_EPOCH_TIMELINE, +} from "../../helpers"; +import { + broadcastStackSTX, + waitForNextPreparePhase, + waitForNextRewardPhase, + getPoxInfo, + waitForRewardCycleId, + broadcastDelegatedStackSTXThroughBitcoin, +} from "../helpers"; +import { Accounts } from "../../constants"; +import { StacksTestnet } from "@stacks/network"; +import { DevnetNetworkOrchestrator } from "@hirosystems/stacks-devnet-js"; + +describe("testing stacking under epoch 2.1", () => { + let orchestrator: DevnetNetworkOrchestrator; + let timeline = { + epoch_2_0: 100, + epoch_2_05: 101, + epoch_2_1: 103, + pox_2_activation: 110, + }; + + beforeAll(() => { + orchestrator = buildDevnetNetworkOrchestrator(getNetworkIdFromEnv(), timeline, true); + orchestrator.start(); + }); + + afterAll(() => { + orchestrator.terminate(); + }); + + it("submitting stacks-stx through pox-1 contract during epoch 2.0 should succeed", async () => { + const network = new StacksTestnet({ url: orchestrator.getStacksNodeUrl() }); + + // Wait for Stacks genesis block + await orchestrator.waitForNextStacksBlock(); + + // Wait for block N-2 where N is the height of the next prepare phase + let blockHeight = DEFAULT_EPOCH_TIMELINE.pox_2_activation + 1; + let chainUpdate = + await orchestrator.waitForStacksBlockAnchoredOnBitcoinBlockOfHeight( + blockHeight + ); + + // Broadcast some STX stacking orders through BTC + let stx = await broadcastDelegatedStackSTXThroughBitcoin( + orchestrator, + 'http://localhost:18443', + "devnet", + "devnet", + Accounts.FAUCET, + 90_000_000_000_000, + 170, + Accounts.WALLET_3, + ); + + // let fee = 1000; + // let cycles = 1; + // let response = await broadcastStackSTX( + // 2, + // network, + // 25_000_000_000_000, + // Accounts.WALLET_1, + // blockHeight, + // cycles, + // fee, + // 0 + // ); + // expect(response.error).toBeUndefined(); + + // response = await broadcastStackSTX( + // 2, + // network, + // 50_000_000_000_000, + // Accounts.WALLET_2, + // blockHeight, + // cycles, + // fee, + // 0 + // ); + // expect(response.error).toBeUndefined(); + + // response = await broadcastStackSTX( + // 2, + // network, + // 75_000_000_000_000, + // Accounts.WALLET_3, + // blockHeight, + // cycles, + // fee, + // 0 + // ); + // expect(response.error).toBeUndefined(); + + // // Wait for block N+1 where N is the height of the next reward phase + // chainUpdate = await waitForNextRewardPhase(network, orchestrator, 1); + // let poxInfo = await getPoxInfo(network); + + // // Assert + // expect(poxInfo.contract_id).toBe("ST000000000000000000002AMW42H.pox-2"); + // expect(poxInfo.current_cycle.is_pox_active).toBe(true); + }); +}); diff --git a/tests/integration/pox/stacking/epoch_2_1_stacks_stx_via_bitcoin.spec.ts b/tests/integration/pox/stacking/epoch_2_1_stacks_stx_via_bitcoin.spec.ts new file mode 100644 index 0000000..50dbd29 --- /dev/null +++ b/tests/integration/pox/stacking/epoch_2_1_stacks_stx_via_bitcoin.spec.ts @@ -0,0 +1,101 @@ +import { + buildDevnetNetworkOrchestrator, + getBitcoinBlockHeight, + getNetworkIdFromEnv, + DEFAULT_EPOCH_TIMELINE, +} from "../../helpers"; +import { + broadcastStackSTX, + waitForNextPreparePhase, + waitForNextRewardPhase, + getPoxInfo, + waitForRewardCycleId, + broadcastStackSTXThroughBitcoin, +} from "../helpers"; +import { Accounts } from "../../constants"; +import { StacksTestnet } from "@stacks/network"; +import { DevnetNetworkOrchestrator } from "@hirosystems/stacks-devnet-js"; + +describe("testing stacking under epoch 2.1", () => { + let orchestrator: DevnetNetworkOrchestrator; + + beforeAll(() => { + orchestrator = buildDevnetNetworkOrchestrator(getNetworkIdFromEnv(), DEFAULT_EPOCH_TIMELINE, true); + orchestrator.start(); + }); + + afterAll(() => { + orchestrator.terminate(); + }); + + it("submitting stacks-stx through pox-1 contract during epoch 2.0 should succeed", async () => { + const network = new StacksTestnet({ url: orchestrator.getStacksNodeUrl() }); + + // Wait for Stacks genesis block + await orchestrator.waitForNextStacksBlock(); + + // Wait for block N-2 where N is the height of the next prepare phase + let blockHeight = DEFAULT_EPOCH_TIMELINE.pox_2_activation + 1; + let chainUpdate = + await orchestrator.waitForStacksBlockAnchoredOnBitcoinBlockOfHeight( + blockHeight + ); + + // Broadcast some STX stacking orders through BTC + let stx = await broadcastStackSTXThroughBitcoin( + orchestrator, + 'http://localhost:18443', + "devnet", + "devnet", + Accounts.FAUCET, + 90_000_000_000_000, + 6 + ); + + // let fee = 1000; + // let cycles = 1; + // let response = await broadcastStackSTX( + // 2, + // network, + // 25_000_000_000_000, + // Accounts.WALLET_1, + // blockHeight, + // cycles, + // fee, + // 0 + // ); + // expect(response.error).toBeUndefined(); + + // response = await broadcastStackSTX( + // 2, + // network, + // 50_000_000_000_000, + // Accounts.WALLET_2, + // blockHeight, + // cycles, + // fee, + // 0 + // ); + // expect(response.error).toBeUndefined(); + + // response = await broadcastStackSTX( + // 2, + // network, + // 75_000_000_000_000, + // Accounts.WALLET_3, + // blockHeight, + // cycles, + // fee, + // 0 + // ); + // expect(response.error).toBeUndefined(); + + // // Wait for block N+1 where N is the height of the next reward phase + // chainUpdate = await waitForNextRewardPhase(network, orchestrator, 1); + // let poxInfo = await getPoxInfo(network); + + // // Assert + // expect(poxInfo.contract_id).toBe("ST000000000000000000002AMW42H.pox-2"); + // expect(poxInfo.current_cycle.is_pox_active).toBe(true); + }); +});