From 8380f5be1ab559568fbe430bbf91a2446124f50f Mon Sep 17 00:00:00 2001 From: Tomasz Slabon Date: Thu, 13 Jul 2023 13:00:17 +0200 Subject: [PATCH 1/5] Added additional system test scenario --- system-tests/test/deposit-redemption.test.ts | 276 ++++++++++++++++++- 1 file changed, 275 insertions(+), 1 deletion(-) diff --git a/system-tests/test/deposit-redemption.test.ts b/system-tests/test/deposit-redemption.test.ts index f72a13894..f2e5310e0 100644 --- a/system-tests/test/deposit-redemption.test.ts +++ b/system-tests/test/deposit-redemption.test.ts @@ -47,6 +47,7 @@ describe("System Test - Deposit and redemption", () => { let systemTestsContext: SystemTestsContext let electrumClient: ElectrumClient let bridgeAddress: string + let vaultAddress: string let maintainerBridgeHandle: EthereumBridge let depositorBridgeHandle: EthereumBridge let bank: Contract @@ -77,6 +78,7 @@ describe("System Test - Deposit and redemption", () => { ) bridgeAddress = deployedContracts.Bridge.address + vaultAddress = deployedContracts.TBTCVault.address maintainerBridgeHandle = new EthereumBridge({ address: bridgeAddress, @@ -103,7 +105,7 @@ describe("System Test - Deposit and redemption", () => { ) }) - context("when deposit is made and revealed", () => { + context("when deposit is made and revealed without a vault", () => { before("make and reveal deposit", async () => { deposit = generateDeposit( systemTestsContext.depositor.address, @@ -372,4 +374,276 @@ describe("System Test - Deposit and redemption", () => { }) }) }) + + context("when deposit is made and revealed with a vault", () => { + before("make and reveal deposit", async () => { + deposit = generateDeposit( + systemTestsContext.depositor.address, + depositAmount, + systemTestsContext.walletBitcoinKeyPair.publicKey.compressed, + vaultAddress, + ) + + console.log(` + Generated deposit data: + ${JSON.stringify(deposit)} + `) + ;({ depositUtxo } = await submitDepositTransaction( + deposit, + systemTestsContext.depositorBitcoinKeyPair.wif, + electrumClient, + true + )) + + console.log(` + Deposit made on BTC chain: + - Transaction hash: ${depositUtxo.transactionHash} + - Output index: ${depositUtxo.outputIndex} + `) + + // Since the reveal deposit logic does not perform SPV proof, we + // can reveal the deposit transaction immediately without waiting + // for confirmations. + await TBTC.revealDeposit( + depositUtxo, + deposit, + electrumClient, + depositorBridgeHandle, + deposit.vault, + ) + + console.log(` + Deposit revealed on Ethereum chain + `) + }) + + it("should broadcast the deposit transaction on the Bitcoin network", async () => { + expect( + (await electrumClient.getRawTransaction(depositUtxo.transactionHash)) + .transactionHex.length + ).to.be.greaterThan(0) + }) + + it("should reveal the deposit to the bridge", async () => { + const { revealedAt } = await TBTC.getRevealedDeposit( + depositUtxo, + maintainerBridgeHandle + ) + expect(revealedAt).to.be.greaterThan(0) + }) + + context("when deposit is swept and sweep proof submitted", () => { + before("sweep the deposit and submit sweep proof", async () => { + ;({ newMainUtxo: sweepUtxo } = await submitDepositSweepTransaction( + electrumClient, + depositSweepTxFee, + systemTestsContext.walletBitcoinKeyPair.wif, + true, + [depositUtxo], + [deposit] + )) + + console.log(` + Deposit swept on Bitcoin chain: + - Transaction hash: ${sweepUtxo.transactionHash} + `) + + // Unlike in the deposit transaction case, we must wait for the sweep + // transaction to have an enough number of confirmations. This is + // because the bridge performs the SPV proof of that transaction. + await waitTransactionConfirmed( + electrumClient, + sweepUtxo.transactionHash + ) + + await fakeRelayDifficulty( + relay, + electrumClient, + sweepUtxo.transactionHash + ) + + // TODO: Consider fetching the current wallet main UTXO and passing it + // here. This will allow running this test scenario multiple + // times for the same wallet. + await SpvMaintainer.submitDepositSweepProof( + sweepUtxo.transactionHash, + // This is the first sweep of the given wallet so there is no main UTXO. + { + // The function expects an unprefixed hash. + transactionHash: BitcoinTransactionHash.from(constants.HashZero), + outputIndex: 0, + value: BigNumber.from(0), + }, + maintainerBridgeHandle, + electrumClient + ) + + console.log(` + Deposit sweep proved on the bridge + `) + }) + + it("should broadcast the sweep transaction on the Bitcoin network", async () => { + expect( + (await electrumClient.getRawTransaction(sweepUtxo.transactionHash)) + .transactionHex.length + ).to.be.greaterThan(0) + }) + + it("should sweep the deposit on the bridge", async () => { + const { sweptAt } = await TBTC.getRevealedDeposit( + depositUtxo, + maintainerBridgeHandle + ) + expect(sweptAt).to.be.greaterThan(0) + }) + + it("should increase depositor's balance in the bank", async () => { + const { treasuryFee } = await TBTC.getRevealedDeposit( + depositUtxo, + maintainerBridgeHandle + ) + + const expectedBalance = depositAmount + .sub(treasuryFee) + .sub(depositSweepTxFee) + + const actualBalance = await bank.balanceOf( + systemTestsContext.depositor.address + ) + + expect(actualBalance).to.be.equal(expectedBalance) + }) + + context("when redemption is requested", () => { + let requestedAmount: BigNumber + let redeemerOutputScript: string + let redemptionRequest: RedemptionRequest + + before("request the redemption", async () => { + // Redeem the full depositor's balance. + requestedAmount = await bank.balanceOf( + systemTestsContext.depositor.address + ) + + // Allow the bridge to take the redeemed bank balance. + await bank + .connect(systemTestsContext.depositor) + .approveBalance(bridgeAddress, requestedAmount) + + // Request redemption to depositor's address. + redeemerOutputScript = `0014${computeHash160( + systemTestsContext.depositorBitcoinKeyPair.publicKey.compressed + )}` + + await depositorBridgeHandle.requestRedemption( + systemTestsContext.walletBitcoinKeyPair.publicKey.compressed, + sweepUtxo, + redeemerOutputScript, + requestedAmount, + ) + + console.log( + `Requested redemption of amount ${requestedAmount} to script ${redeemerOutputScript} on the bridge` + ) + + redemptionRequest = await TBTC.getRedemptionRequest( + systemTestsContext.walletBitcoinKeyPair.publicKey.compressed, + redeemerOutputScript, + "pending", + maintainerBridgeHandle + ) + }) + + it("should transfer depositor's bank balance to the Bridge", async () => { + expect( + await bank.balanceOf(systemTestsContext.depositor.address) + ).to.be.equal(0) + + expect(await bank.balanceOf(bridgeAddress)).to.be.equal( + requestedAmount + ) + }) + + it("should register the redemption request on the bridge", async () => { + expect(redemptionRequest.requestedAt).to.be.greaterThan(0) + expect(redemptionRequest.requestedAmount).to.be.equal(requestedAmount) + expect(redemptionRequest.redeemerOutputScript).to.be.equal( + redeemerOutputScript + ) + }) + + context( + "when redemption is made and redemption proof submitted", + () => { + let redemptionTxHash: BitcoinTransactionHash + + before( + "make the redemption and submit redemption proof", + async () => { + ;({ transactionHash: redemptionTxHash } = + await submitRedemptionTransaction( + electrumClient, + maintainerBridgeHandle, + systemTestsContext.walletBitcoinKeyPair.wif, + sweepUtxo, + [redemptionRequest.redeemerOutputScript], + true + )) + + console.log( + "Redemption made on Bitcoin chain:\n" + + `- Transaction hash: ${redemptionTxHash}` + ) + + await waitTransactionConfirmed(electrumClient, redemptionTxHash) + + await fakeRelayDifficulty( + relay, + electrumClient, + redemptionTxHash + ) + + await SpvMaintainer.submitRedemptionProof( + redemptionTxHash, + sweepUtxo, + systemTestsContext.walletBitcoinKeyPair.publicKey.compressed, + maintainerBridgeHandle, + electrumClient + ) + + console.log("Redemption proved on the bridge") + } + ) + + it("should broadcast the redemption transaction on the Bitcoin network", async () => { + expect( + (await electrumClient.getRawTransaction(redemptionTxHash)) + .transactionHex.length + ).to.be.greaterThan(0) + }) + + it("should close the redemption request on the bridge", async () => { + await expect( + TBTC.getRedemptionRequest( + systemTestsContext.walletBitcoinKeyPair.publicKey.compressed, + redemptionRequest.redeemerOutputScript, + "pending", + maintainerBridgeHandle + ) + ).to.be.rejectedWith( + "Provided redeemer output script and wallet public key do not identify a redemption request" + ) + }) + + it("should decrease Bridge's balance in the bank", async () => { + const actualBalance = await bank.balanceOf(bridgeAddress) + + expect(actualBalance).to.be.equal(0) + }) + } + ) + }) + }) + }) }) From 7aba3cf1f15c8d29d2a6668deafe9259a3b4f352 Mon Sep 17 00:00:00 2001 From: Tomasz Slabon Date: Fri, 14 Jul 2023 15:55:06 +0200 Subject: [PATCH 2/5] Request redemption via tBTC token --- system-tests/test/deposit-redemption.test.ts | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/system-tests/test/deposit-redemption.test.ts b/system-tests/test/deposit-redemption.test.ts index f2e5310e0..5d25820d5 100644 --- a/system-tests/test/deposit-redemption.test.ts +++ b/system-tests/test/deposit-redemption.test.ts @@ -21,6 +21,7 @@ import type { UnspentTransactionOutput } from "@keep-network/tbtc-v2.ts/dist/src import type { SystemTestsContext } from "./utils/context" import type { RedemptionRequest } from "@keep-network/tbtc-v2.ts/dist/src/redemption" import type { Deposit } from "@keep-network/tbtc-v2.ts/dist/src/deposit" +import { TBTCToken } from "@keep-network/tbtc-v2.ts/dist/src/ethereum" chai.use(chaiAsPromised) @@ -46,8 +47,10 @@ chai.use(chaiAsPromised) describe("System Test - Deposit and redemption", () => { let systemTestsContext: SystemTestsContext let electrumClient: ElectrumClient + let tbtcTokenAddress: string let bridgeAddress: string let vaultAddress: string + let tbtcTokenHandle: TBTCToken let maintainerBridgeHandle: EthereumBridge let depositorBridgeHandle: EthereumBridge let bank: Contract @@ -77,9 +80,15 @@ describe("System Test - Deposit and redemption", () => { ELECTRUM_RETRY_BACKOFF_STEP_MS ) + tbtcTokenAddress = deployedContracts.TBTC.address bridgeAddress = deployedContracts.Bridge.address vaultAddress = deployedContracts.TBTCVault.address + tbtcTokenHandle = new TBTCToken({ + address: tbtcTokenAddress, + signerOrProvider: depositor, + }) + maintainerBridgeHandle = new EthereumBridge({ address: bridgeAddress, signerOrProvider: maintainer, @@ -536,11 +545,12 @@ describe("System Test - Deposit and redemption", () => { systemTestsContext.depositorBitcoinKeyPair.publicKey.compressed )}` - await depositorBridgeHandle.requestRedemption( + await TBTC.requestRedemption( systemTestsContext.walletBitcoinKeyPair.publicKey.compressed, sweepUtxo, redeemerOutputScript, requestedAmount, + tbtcTokenHandle, ) console.log( From b55734cdcd58a691d1516bc6af35921da512998e Mon Sep 17 00:00:00 2001 From: Tomasz Slabon Date: Tue, 18 Jul 2023 12:23:10 +0200 Subject: [PATCH 3/5] Adjusted test scenario to account for the vault presence --- system-tests/test/deposit-redemption.test.ts | 56 ++++++++++++++------ 1 file changed, 41 insertions(+), 15 deletions(-) diff --git a/system-tests/test/deposit-redemption.test.ts b/system-tests/test/deposit-redemption.test.ts index 5d25820d5..17cdb3bd0 100644 --- a/system-tests/test/deposit-redemption.test.ts +++ b/system-tests/test/deposit-redemption.test.ts @@ -55,6 +55,7 @@ describe("System Test - Deposit and redemption", () => { let depositorBridgeHandle: EthereumBridge let bank: Contract let relay: Contract + let tbtc: Contract const depositAmount = BigNumber.from(2000000) const depositSweepTxFee = BigNumber.from(10000) @@ -63,6 +64,8 @@ describe("System Test - Deposit and redemption", () => { // Initial backoff step in milliseconds that will be increased exponentially for // subsequent Electrum retry attempts. const ELECTRUM_RETRY_BACKOFF_STEP_MS = 10000 // 10sec + // Multiplier to convert satoshi to TBTC token units. + const SATOSHI_MULTIPLIER: BigNumber = BigNumber.from("10000000000") let deposit: Deposit let depositUtxo: UnspentTransactionOutput @@ -112,6 +115,13 @@ describe("System Test - Deposit and redemption", () => { relayDeploymentInfo.abi, maintainer ) + + const tbtcDeploymentInfo = deployedContracts.TBTC + tbtc = new Contract( + tbtcDeploymentInfo.address, + tbtcDeploymentInfo.abi, + maintainer + ) }) context("when deposit is made and revealed without a vault", () => { @@ -484,7 +494,8 @@ describe("System Test - Deposit and redemption", () => { value: BigNumber.from(0), }, maintainerBridgeHandle, - electrumClient + electrumClient, + deposit.vault, ) console.log(` @@ -507,7 +518,7 @@ describe("System Test - Deposit and redemption", () => { expect(sweptAt).to.be.greaterThan(0) }) - it("should increase depositor's balance in the bank", async () => { + it("should increase vault's balance in the bank", async () => { const { treasuryFee } = await TBTC.getRevealedDeposit( depositUtxo, maintainerBridgeHandle @@ -518,27 +529,44 @@ describe("System Test - Deposit and redemption", () => { .sub(depositSweepTxFee) const actualBalance = await bank.balanceOf( - systemTestsContext.depositor.address + vaultAddress ) expect(actualBalance).to.be.equal(expectedBalance) }) + it("should mint TBTC tokens for the depositor", async () => { + const { treasuryFee } = await TBTC.getRevealedDeposit( + depositUtxo, + maintainerBridgeHandle + ) + + const balanceInSatoshis = depositAmount + .sub(treasuryFee) + .sub(depositSweepTxFee) + + const expectedTbtcBalance = balanceInSatoshis.mul(SATOSHI_MULTIPLIER) + + const actualBalance = tbtc.balanceOf( + systemTestsContext.depositor.address + ) + + expect(actualBalance).to.be.equal(expectedTbtcBalance) + }) + context("when redemption is requested", () => { let requestedAmount: BigNumber let redeemerOutputScript: string let redemptionRequest: RedemptionRequest before("request the redemption", async () => { - // Redeem the full depositor's balance. - requestedAmount = await bank.balanceOf( + // Redeem all of the depositor's TBTC tokens. + const tbtcBalanceOfDepositor = await tbtc.balanceOf( systemTestsContext.depositor.address ) - // Allow the bridge to take the redeemed bank balance. - await bank - .connect(systemTestsContext.depositor) - .approveBalance(bridgeAddress, requestedAmount) + // The depositor's balance converted to satoshis. + requestedAmount = tbtcBalanceOfDepositor.div(SATOSHI_MULTIPLIER) // Request redemption to depositor's address. redeemerOutputScript = `0014${computeHash160( @@ -549,12 +577,12 @@ describe("System Test - Deposit and redemption", () => { systemTestsContext.walletBitcoinKeyPair.publicKey.compressed, sweepUtxo, redeemerOutputScript, - requestedAmount, + tbtcBalanceOfDepositor, tbtcTokenHandle, ) console.log( - `Requested redemption of amount ${requestedAmount} to script ${redeemerOutputScript} on the bridge` + `Requested redemption of ${tbtcBalanceOfDepositor} TBTC tokens to script ${redeemerOutputScript} on the bridge` ) redemptionRequest = await TBTC.getRedemptionRequest( @@ -565,10 +593,8 @@ describe("System Test - Deposit and redemption", () => { ) }) - it("should transfer depositor's bank balance to the Bridge", async () => { - expect( - await bank.balanceOf(systemTestsContext.depositor.address) - ).to.be.equal(0) + it("should transfer vault's bank balance to the Bridge", async () => { + expect(await bank.balanceOf(vaultAddress)).to.be.equal(0) expect(await bank.balanceOf(bridgeAddress)).to.be.equal( requestedAmount From 580040f96fbae562b90af74a33bc6209434207a3 Mon Sep 17 00:00:00 2001 From: Tomasz Slabon Date: Tue, 18 Jul 2023 17:24:16 +0200 Subject: [PATCH 4/5] Added missing unit test checks --- system-tests/test/deposit-redemption.test.ts | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/system-tests/test/deposit-redemption.test.ts b/system-tests/test/deposit-redemption.test.ts index 17cdb3bd0..2ccbc5735 100644 --- a/system-tests/test/deposit-redemption.test.ts +++ b/system-tests/test/deposit-redemption.test.ts @@ -65,7 +65,7 @@ describe("System Test - Deposit and redemption", () => { // subsequent Electrum retry attempts. const ELECTRUM_RETRY_BACKOFF_STEP_MS = 10000 // 10sec // Multiplier to convert satoshi to TBTC token units. - const SATOSHI_MULTIPLIER: BigNumber = BigNumber.from("10000000000") + const SATOSHI_MULTIPLIER = BigNumber.from(10000000000) let deposit: Deposit let depositUtxo: UnspentTransactionOutput @@ -547,7 +547,7 @@ describe("System Test - Deposit and redemption", () => { const expectedTbtcBalance = balanceInSatoshis.mul(SATOSHI_MULTIPLIER) - const actualBalance = tbtc.balanceOf( + const actualBalance = await tbtc.balanceOf( systemTestsContext.depositor.address ) @@ -593,6 +593,14 @@ describe("System Test - Deposit and redemption", () => { ) }) + it("should unmint depositor's TBTC tokens", async () => { + const tbtcBalance = await tbtc.balanceOf( + systemTestsContext.depositor.address + ) + + expect(tbtcBalance).to.be.equal(0) + }) + it("should transfer vault's bank balance to the Bridge", async () => { expect(await bank.balanceOf(vaultAddress)).to.be.equal(0) From 4d7074138476783a8e2954fefcbc48152d791c0b Mon Sep 17 00:00:00 2001 From: Tomasz Slabon Date: Mon, 24 Jul 2023 12:17:44 +0200 Subject: [PATCH 5/5] Used main UTXO data from previous test case --- system-tests/test/deposit-redemption.test.ts | 24 ++++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/system-tests/test/deposit-redemption.test.ts b/system-tests/test/deposit-redemption.test.ts index 2ccbc5735..f40c0c6d0 100644 --- a/system-tests/test/deposit-redemption.test.ts +++ b/system-tests/test/deposit-redemption.test.ts @@ -70,6 +70,7 @@ describe("System Test - Deposit and redemption", () => { let deposit: Deposit let depositUtxo: UnspentTransactionOutput let sweepUtxo: UnspentTransactionOutput + let redemptionUtxo: UnspentTransactionOutput | undefined before(async () => { systemTestsContext = await setupSystemTestsContext() @@ -328,7 +329,7 @@ describe("System Test - Deposit and redemption", () => { before( "make the redemption and submit redemption proof", async () => { - ;({ transactionHash: redemptionTxHash } = + ;({ transactionHash: redemptionTxHash, newMainUtxo: redemptionUtxo } = await submitRedemptionTransaction( electrumClient, maintainerBridgeHandle, @@ -459,7 +460,8 @@ describe("System Test - Deposit and redemption", () => { systemTestsContext.walletBitcoinKeyPair.wif, true, [depositUtxo], - [deposit] + [deposit], + redemptionUtxo // The UTXO from the previous test became the new main UTXO. )) console.log(` @@ -481,18 +483,16 @@ describe("System Test - Deposit and redemption", () => { sweepUtxo.transactionHash ) - // TODO: Consider fetching the current wallet main UTXO and passing it - // here. This will allow running this test scenario multiple - // times for the same wallet. + // If the redemption transaction from the previous test created a new + // main UTXO, use it. Otherwise call it with a zero-filled main UTXO. + const mainUtxo = redemptionUtxo ? redemptionUtxo : { + transactionHash: BitcoinTransactionHash.from(constants.HashZero), + outputIndex: 0, + value: BigNumber.from(0), + }; await SpvMaintainer.submitDepositSweepProof( sweepUtxo.transactionHash, - // This is the first sweep of the given wallet so there is no main UTXO. - { - // The function expects an unprefixed hash. - transactionHash: BitcoinTransactionHash.from(constants.HashZero), - outputIndex: 0, - value: BigNumber.from(0), - }, + mainUtxo, maintainerBridgeHandle, electrumClient, deposit.vault,