From 26ac7e5be5a5269fd7c10a639c5ca0b3db761fbe Mon Sep 17 00:00:00 2001 From: kennycud Date: Thu, 24 Aug 2023 15:47:45 -0700 Subject: [PATCH] consolidated shared functionality into ACCTTests.java --- .../org/qortal/test/crosschain/ACCTTests.java | 790 ++++++++++++++++++ .../bitcoinv1/BitcoinACCTv1Tests.java | 699 +--------------- .../bitcoinv3/BitcoinACCTv3Tests.java | 775 +---------------- .../digibytev3/DigibyteACCTv3Tests.java | 775 +---------------- .../dogecoinv3/DogecoinACCTv3Tests.java | 775 +---------------- .../litecoinv1/LitecoinACCTv1Tests.java | 774 +---------------- .../litecoinv3/LitecoinACCTv3Tests.java | 777 +---------------- .../piratechainv3/PirateChainACCTv3Tests.java | 777 +---------------- .../ravencoinv3/RavencoinACCTv3Tests.java | 775 +---------------- 9 files changed, 1058 insertions(+), 5859 deletions(-) create mode 100644 src/test/java/org/qortal/test/crosschain/ACCTTests.java diff --git a/src/test/java/org/qortal/test/crosschain/ACCTTests.java b/src/test/java/org/qortal/test/crosschain/ACCTTests.java new file mode 100644 index 00000000..6af27a96 --- /dev/null +++ b/src/test/java/org/qortal/test/crosschain/ACCTTests.java @@ -0,0 +1,790 @@ +package org.qortal.test.crosschain; + +import com.google.common.hash.HashCode; +import com.google.common.primitives.Bytes; +import org.junit.Before; +import org.junit.Test; +import org.qortal.account.Account; +import org.qortal.account.PrivateKeyAccount; +import org.qortal.asset.Asset; +import org.qortal.block.Block; +import org.qortal.crosschain.ACCT; +import org.qortal.crosschain.AcctMode; +import org.qortal.crypto.Crypto; +import org.qortal.data.at.ATData; +import org.qortal.data.at.ATStateData; +import org.qortal.data.crosschain.CrossChainTradeData; +import org.qortal.data.transaction.BaseTransactionData; +import org.qortal.data.transaction.DeployAtTransactionData; +import org.qortal.data.transaction.MessageTransactionData; +import org.qortal.data.transaction.TransactionData; +import org.qortal.group.Group; +import org.qortal.repository.DataException; +import org.qortal.repository.Repository; +import org.qortal.repository.RepositoryManager; +import org.qortal.test.common.BlockUtils; +import org.qortal.test.common.Common; +import org.qortal.test.common.TransactionUtils; +import org.qortal.transaction.DeployAtTransaction; +import org.qortal.transaction.MessageTransaction; +import org.qortal.utils.Amounts; + +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneOffset; +import java.time.format.DateTimeFormatter; +import java.time.format.FormatStyle; +import java.util.Arrays; +import java.util.List; +import java.util.Random; +import java.util.function.Function; + +import static org.junit.Assert.*; + +public abstract class ACCTTests extends Common { + + public static final byte[] secretA = "This string is exactly 32 bytes!".getBytes(); + public static final byte[] hashOfSecretA = Crypto.hash160(secretA); // daf59884b4d1aec8c1b17102530909ee43c0151a + public static final int tradeTimeout = 20; // blocks + public static final long redeemAmount = 80_40200000L; + public static final long fundingAmount = 123_45600000L; + public static final long foreignAmount = 864200L; // 0.00864200 foreign units + + protected static final Random RANDOM = new Random(); + + protected abstract byte[] getPublicKey(); + + protected abstract byte[] buildQortalAT(String address, byte[] publicKey, long redeemAmount, long foreignAmount, int tradeTimeout); + + protected abstract ACCT getInstance(); + + protected abstract int calcRefundTimeout(long partnersOfferMessageTransactionTimestamp, int lockTimeA); + + protected abstract byte[] buildTradeMessage(String address, byte[] publicKey, byte[] hashOfSecretA, int lockTimeA, int refundTimeout); + + protected abstract byte[] buildRedeemMessage(byte[] secretA, String address); + + protected abstract byte[] getCodeBytesHash(); + + protected abstract String getSymbol(); + + protected abstract String getName(); + + @Before + public void beforeTest() throws DataException { + Common.useDefaultSettings(); + } + + @Test + public void testCompile() { + PrivateKeyAccount tradeAccount = createTradeAccount(null); + + byte[] creationBytes = buildQortalAT(tradeAccount.getAddress(), getPublicKey(), redeemAmount, foreignAmount, tradeTimeout); + assertNotNull(creationBytes); + + System.out.println("AT creation bytes: " + HashCode.fromBytes(creationBytes).toString()); + } + + + @Test + public void testDeploy() throws DataException { + try (final Repository repository = RepositoryManager.getRepository()) { + PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe"); + PrivateKeyAccount tradeAccount = createTradeAccount(repository); + + PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert"); + + long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT); + long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT); + + DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress()); + + long expectedBalance = deployersInitialBalance - fundingAmount - deployAtTransaction.getTransactionData().getFee(); + long actualBalance = deployer.getConfirmedBalance(Asset.QORT); + + assertEquals("Deployer's post-deployment balance incorrect", expectedBalance, actualBalance); + + expectedBalance = fundingAmount; + actualBalance = deployAtTransaction.getATAccount().getConfirmedBalance(Asset.QORT); + + assertEquals("AT's post-deployment balance incorrect", expectedBalance, actualBalance); + + expectedBalance = partnersInitialBalance; + actualBalance = partner.getConfirmedBalance(Asset.QORT); + + assertEquals("Partner's post-deployment balance incorrect", expectedBalance, actualBalance); + + // Test orphaning + BlockUtils.orphanLastBlock(repository); + + expectedBalance = deployersInitialBalance; + actualBalance = deployer.getConfirmedBalance(Asset.QORT); + + assertEquals("Deployer's post-orphan/pre-deployment balance incorrect", expectedBalance, actualBalance); + + expectedBalance = 0; + actualBalance = deployAtTransaction.getATAccount().getConfirmedBalance(Asset.QORT); + + assertEquals("AT's post-orphan/pre-deployment balance incorrect", expectedBalance, actualBalance); + + expectedBalance = partnersInitialBalance; + actualBalance = partner.getConfirmedBalance(Asset.QORT); + + assertEquals("Partner's post-orphan/pre-deployment balance incorrect", expectedBalance, actualBalance); + } + } + + @SuppressWarnings("unused") + @Test + public void testOfferCancel() throws DataException { + try (final Repository repository = RepositoryManager.getRepository()) { + PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe"); + PrivateKeyAccount tradeAccount = createTradeAccount(repository); + + PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert"); + + long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT); + long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT); + + DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress()); + Account at = deployAtTransaction.getATAccount(); + String atAddress = at.getAddress(); + + long deployAtFee = deployAtTransaction.getTransactionData().getFee(); + long deployersPostDeploymentBalance = deployersInitialBalance - fundingAmount - deployAtFee; + + // Send creator's address to AT, instead of typical partner's address + byte[] messageData = getInstance().buildCancelMessage(deployer.getAddress()); + MessageTransaction messageTransaction = sendMessage(repository, deployer, messageData, atAddress); + long messageFee = messageTransaction.getTransactionData().getFee(); + + // AT should process 'cancel' message in next block + BlockUtils.mintBlock(repository); + + describeAt(repository, atAddress); + + // Check AT is finished + ATData atData = repository.getATRepository().fromATAddress(atAddress); + assertTrue(atData.getIsFinished()); + + // AT should be in CANCELLED mode + CrossChainTradeData tradeData = getInstance().populateTradeData(repository, atData); + assertEquals(AcctMode.CANCELLED, tradeData.mode); + + // Check balances + long expectedMinimumBalance = deployersPostDeploymentBalance; + long expectedMaximumBalance = deployersInitialBalance - deployAtFee - messageFee; + + long actualBalance = deployer.getConfirmedBalance(Asset.QORT); + + assertTrue(String.format("Deployer's balance %s should be above minimum %s", actualBalance, expectedMinimumBalance), actualBalance > expectedMinimumBalance); + assertTrue(String.format("Deployer's balance %s should be below maximum %s", actualBalance, expectedMaximumBalance), actualBalance < expectedMaximumBalance); + + // Test orphaning + BlockUtils.orphanLastBlock(repository); + + // Check balances + long expectedBalance = deployersPostDeploymentBalance - messageFee; + actualBalance = deployer.getConfirmedBalance(Asset.QORT); + + assertEquals("Deployer's post-orphan/pre-refund balance incorrect", expectedBalance, actualBalance); + } + } + + @SuppressWarnings("unused") + @Test + public void testOfferCancelInvalidLength() throws DataException { + try (final Repository repository = RepositoryManager.getRepository()) { + PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe"); + PrivateKeyAccount tradeAccount = createTradeAccount(repository); + + PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert"); + + long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT); + long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT); + + DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress()); + Account at = deployAtTransaction.getATAccount(); + String atAddress = at.getAddress(); + + long deployAtFee = deployAtTransaction.getTransactionData().getFee(); + long deployersPostDeploymentBalance = deployersInitialBalance - fundingAmount - deployAtFee; + + // Instead of sending creator's address to AT, send too-short/invalid message + byte[] messageData = new byte[7]; + RANDOM.nextBytes(messageData); + MessageTransaction messageTransaction = sendMessage(repository, deployer, messageData, atAddress); + long messageFee = messageTransaction.getTransactionData().getFee(); + + // AT should process 'cancel' message in next block + // As message is too short, it will be padded to 32bytes but cancel code doesn't care about message content, so should be ok + BlockUtils.mintBlock(repository); + + describeAt(repository, atAddress); + + // Check AT is finished + ATData atData = repository.getATRepository().fromATAddress(atAddress); + assertTrue(atData.getIsFinished()); + + // AT should be in CANCELLED mode + CrossChainTradeData tradeData = getInstance().populateTradeData(repository, atData); + assertEquals(AcctMode.CANCELLED, tradeData.mode); + } + } + + @SuppressWarnings("unused") + @Test + public void testTradingInfoProcessing() throws DataException { + try (final Repository repository = RepositoryManager.getRepository()) { + PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe"); + PrivateKeyAccount tradeAccount = createTradeAccount(repository); + + PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert"); + + long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT); + long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT); + + DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress()); + Account at = deployAtTransaction.getATAccount(); + String atAddress = at.getAddress(); + + long partnersOfferMessageTransactionTimestamp = System.currentTimeMillis(); + int lockTimeA = calcTestLockTimeA(partnersOfferMessageTransactionTimestamp); + int refundTimeout = calcRefundTimeout(partnersOfferMessageTransactionTimestamp, lockTimeA); + + // Send trade info to AT + byte[] messageData = buildTradeMessage(partner.getAddress(), getPublicKey(), hashOfSecretA, lockTimeA, refundTimeout); + MessageTransaction messageTransaction = sendMessage(repository, tradeAccount, messageData, atAddress); + + Block postDeploymentBlock = BlockUtils.mintBlock(repository); + int postDeploymentBlockHeight = postDeploymentBlock.getBlockData().getHeight(); + + long deployAtFee = deployAtTransaction.getTransactionData().getFee(); + long deployersPostDeploymentBalance = deployersInitialBalance - fundingAmount - deployAtFee; + + describeAt(repository, atAddress); + + ATData atData = repository.getATRepository().fromATAddress(atAddress); + CrossChainTradeData tradeData = getInstance().populateTradeData(repository, atData); + + // AT should be in TRADE mode + assertEquals(AcctMode.TRADING, tradeData.mode); + + // Check hashOfSecretA was extracted correctly + assertTrue(Arrays.equals(hashOfSecretA, tradeData.hashOfSecretA)); + + // Check trade partner Qortal address was extracted correctly + assertEquals(partner.getAddress(), tradeData.qortalPartnerAddress); + + // Check trade partner's Foreign Coin PKH was extracted correctly + assertTrue(Arrays.equals(getPublicKey(), tradeData.partnerForeignPKH)); + + // Test orphaning + BlockUtils.orphanToBlock(repository, postDeploymentBlockHeight); + + // Check balances + long expectedBalance = deployersPostDeploymentBalance; + long actualBalance = deployer.getConfirmedBalance(Asset.QORT); + + assertEquals("Deployer's post-orphan/pre-refund balance incorrect", expectedBalance, actualBalance); + } + } + + // TEST SENDING TRADING INFO BUT NOT FROM AT CREATOR (SHOULD BE IGNORED) + @SuppressWarnings("unused") + @Test + public void testIncorrectTradeSender() throws DataException { + try (final Repository repository = RepositoryManager.getRepository()) { + PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe"); + PrivateKeyAccount tradeAccount = createTradeAccount(repository); + + PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert"); + + PrivateKeyAccount bystander = Common.getTestAccount(repository, "bob"); + + long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT); + long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT); + + DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress()); + Account at = deployAtTransaction.getATAccount(); + String atAddress = at.getAddress(); + + long partnersOfferMessageTransactionTimestamp = System.currentTimeMillis(); + int lockTimeA = calcTestLockTimeA(partnersOfferMessageTransactionTimestamp); + int refundTimeout = calcRefundTimeout(partnersOfferMessageTransactionTimestamp, lockTimeA); + + // Send trade info to AT BUT NOT FROM AT CREATOR + byte[] messageData = buildTradeMessage(partner.getAddress(), getPublicKey(), hashOfSecretA, lockTimeA, refundTimeout); + MessageTransaction messageTransaction = sendMessage(repository, bystander, messageData, atAddress); + + BlockUtils.mintBlock(repository); + + long expectedBalance = partnersInitialBalance; + long actualBalance = partner.getConfirmedBalance(Asset.QORT); + + assertEquals("Partner's post-initial-payout balance incorrect", expectedBalance, actualBalance); + + describeAt(repository, atAddress); + + ATData atData = repository.getATRepository().fromATAddress(atAddress); + CrossChainTradeData tradeData = getInstance().populateTradeData(repository, atData); + + // AT should still be in OFFER mode + assertEquals(AcctMode.OFFERING, tradeData.mode); + } + } + + @SuppressWarnings("unused") + @Test + public void testAutomaticTradeRefund() throws DataException { + try (final Repository repository = RepositoryManager.getRepository()) { + PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe"); + PrivateKeyAccount tradeAccount = createTradeAccount(repository); + + PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert"); + + long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT); + long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT); + + DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress()); + Account at = deployAtTransaction.getATAccount(); + String atAddress = at.getAddress(); + + long partnersOfferMessageTransactionTimestamp = System.currentTimeMillis(); + int lockTimeA = calcTestLockTimeA(partnersOfferMessageTransactionTimestamp); + int refundTimeout = calcRefundTimeout(partnersOfferMessageTransactionTimestamp, lockTimeA); + + // Send trade info to AT + byte[] messageData = buildTradeMessage(partner.getAddress(), getPublicKey(), hashOfSecretA, lockTimeA, refundTimeout); + MessageTransaction messageTransaction = sendMessage(repository, tradeAccount, messageData, atAddress); + + Block postDeploymentBlock = BlockUtils.mintBlock(repository); + int postDeploymentBlockHeight = postDeploymentBlock.getBlockData().getHeight(); + + // Check refund + long deployAtFee = deployAtTransaction.getTransactionData().getFee(); + long deployersPostDeploymentBalance = deployersInitialBalance - fundingAmount - deployAtFee; + + checkTradeRefund(repository, deployer, deployersInitialBalance, deployAtFee); + + describeAt(repository, atAddress); + + // Check AT is finished + ATData atData = repository.getATRepository().fromATAddress(atAddress); + assertTrue(atData.getIsFinished()); + + // AT should be in REFUNDED mode + CrossChainTradeData tradeData = getInstance().populateTradeData(repository, atData); + assertEquals(AcctMode.REFUNDED, tradeData.mode); + + // Test orphaning + BlockUtils.orphanToBlock(repository, postDeploymentBlockHeight); + + // Check balances + long expectedBalance = deployersPostDeploymentBalance; + long actualBalance = deployer.getConfirmedBalance(Asset.QORT); + + assertEquals("Deployer's post-orphan/pre-refund balance incorrect", expectedBalance, actualBalance); + } + } + + @SuppressWarnings("unused") + @Test + public void testCorrectSecretCorrectSender() throws DataException { + try (final Repository repository = RepositoryManager.getRepository()) { + PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe"); + PrivateKeyAccount tradeAccount = createTradeAccount(repository); + + PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert"); + + long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT); + long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT); + + DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress()); + Account at = deployAtTransaction.getATAccount(); + String atAddress = at.getAddress(); + + long partnersOfferMessageTransactionTimestamp = System.currentTimeMillis(); + int lockTimeA = calcTestLockTimeA(partnersOfferMessageTransactionTimestamp); + int refundTimeout = calcRefundTimeout(partnersOfferMessageTransactionTimestamp, lockTimeA); + + // Send trade info to AT + byte[] messageData = buildTradeMessage(partner.getAddress(), getPublicKey(), hashOfSecretA, lockTimeA, refundTimeout); + MessageTransaction messageTransaction = sendMessage(repository, tradeAccount, messageData, atAddress); + + // Give AT time to process message + BlockUtils.mintBlock(repository); + + // Send correct secret to AT, from correct account + messageData = buildRedeemMessage(secretA, partner.getAddress()); + messageTransaction = sendMessage(repository, partner, messageData, atAddress); + + // AT should send funds in the next block + ATStateData preRedeemAtStateData = repository.getATRepository().getLatestATState(atAddress); + BlockUtils.mintBlock(repository); + + describeAt(repository, atAddress); + + // Check AT is finished + ATData atData = repository.getATRepository().fromATAddress(atAddress); + assertTrue(atData.getIsFinished()); + + // AT should be in REDEEMED mode + CrossChainTradeData tradeData = getInstance().populateTradeData(repository, atData); + assertEquals(AcctMode.REDEEMED, tradeData.mode); + + // Check balances + long expectedBalance = partnersInitialBalance - messageTransaction.getTransactionData().getFee() + redeemAmount; + long actualBalance = partner.getConfirmedBalance(Asset.QORT); + + assertEquals("Partner's post-redeem balance incorrect", expectedBalance, actualBalance); + + // Orphan redeem + BlockUtils.orphanLastBlock(repository); + + // Check balances + expectedBalance = partnersInitialBalance - messageTransaction.getTransactionData().getFee(); + actualBalance = partner.getConfirmedBalance(Asset.QORT); + + assertEquals("Partner's post-orphan/pre-redeem balance incorrect", expectedBalance, actualBalance); + + // Check AT state + ATStateData postOrphanAtStateData = repository.getATRepository().getLatestATState(atAddress); + + assertTrue("AT states mismatch", Arrays.equals(preRedeemAtStateData.getStateData(), postOrphanAtStateData.getStateData())); + } + } + + @SuppressWarnings("unused") + @Test + public void testCorrectSecretIncorrectSender() throws DataException { + try (final Repository repository = RepositoryManager.getRepository()) { + PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe"); + PrivateKeyAccount tradeAccount = createTradeAccount(repository); + + PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert"); + + PrivateKeyAccount bystander = Common.getTestAccount(repository, "bob"); + + long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT); + long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT); + + DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress()); + long deployAtFee = deployAtTransaction.getTransactionData().getFee(); + + Account at = deployAtTransaction.getATAccount(); + String atAddress = at.getAddress(); + + long partnersOfferMessageTransactionTimestamp = System.currentTimeMillis(); + int lockTimeA = calcTestLockTimeA(partnersOfferMessageTransactionTimestamp); + int refundTimeout = calcRefundTimeout(partnersOfferMessageTransactionTimestamp, lockTimeA); + + // Send trade info to AT + byte[] messageData = buildTradeMessage(partner.getAddress(), getPublicKey(), hashOfSecretA, lockTimeA, refundTimeout); + MessageTransaction messageTransaction = sendMessage(repository, tradeAccount, messageData, atAddress); + + // Give AT time to process message + BlockUtils.mintBlock(repository); + + // Send correct secret to AT, but from wrong account + messageData = buildRedeemMessage(secretA, partner.getAddress()); + messageTransaction = sendMessage(repository, bystander, messageData, atAddress); + + // AT should NOT send funds in the next block + ATStateData preRedeemAtStateData = repository.getATRepository().getLatestATState(atAddress); + BlockUtils.mintBlock(repository); + + describeAt(repository, atAddress); + + // Check AT is NOT finished + ATData atData = repository.getATRepository().fromATAddress(atAddress); + assertFalse(atData.getIsFinished()); + + // AT should still be in TRADE mode + CrossChainTradeData tradeData = getInstance().populateTradeData(repository, atData); + assertEquals(AcctMode.TRADING, tradeData.mode); + + // Check balances + long expectedBalance = partnersInitialBalance; + long actualBalance = partner.getConfirmedBalance(Asset.QORT); + + assertEquals("Partner's balance incorrect", expectedBalance, actualBalance); + + // Check eventual refund + checkTradeRefund(repository, deployer, deployersInitialBalance, deployAtFee); + } + } + + @SuppressWarnings("unused") + @Test + public void testIncorrectSecretCorrectSender() throws DataException { + try (final Repository repository = RepositoryManager.getRepository()) { + PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe"); + PrivateKeyAccount tradeAccount = createTradeAccount(repository); + + PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert"); + + long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT); + long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT); + + DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress()); + long deployAtFee = deployAtTransaction.getTransactionData().getFee(); + + Account at = deployAtTransaction.getATAccount(); + String atAddress = at.getAddress(); + + long partnersOfferMessageTransactionTimestamp = System.currentTimeMillis(); + int lockTimeA = calcTestLockTimeA(partnersOfferMessageTransactionTimestamp); + int refundTimeout = calcRefundTimeout(partnersOfferMessageTransactionTimestamp, lockTimeA); + + // Send trade info to AT + byte[] messageData = buildTradeMessage(partner.getAddress(), getPublicKey(), hashOfSecretA, lockTimeA, refundTimeout); + MessageTransaction messageTransaction = sendMessage(repository, tradeAccount, messageData, atAddress); + + // Give AT time to process message + BlockUtils.mintBlock(repository); + + // Send incorrect secret to AT, from correct account + byte[] wrongSecret = new byte[32]; + RANDOM.nextBytes(wrongSecret); + messageData = buildRedeemMessage(wrongSecret, partner.getAddress()); + messageTransaction = sendMessage(repository, partner, messageData, atAddress); + + // AT should NOT send funds in the next block + ATStateData preRedeemAtStateData = repository.getATRepository().getLatestATState(atAddress); + BlockUtils.mintBlock(repository); + + describeAt(repository, atAddress); + + // Check AT is NOT finished + ATData atData = repository.getATRepository().fromATAddress(atAddress); + assertFalse(atData.getIsFinished()); + + // AT should still be in TRADE mode + CrossChainTradeData tradeData = getInstance().populateTradeData(repository, atData); + assertEquals(AcctMode.TRADING, tradeData.mode); + + long expectedBalance = partnersInitialBalance - messageTransaction.getTransactionData().getFee(); + long actualBalance = partner.getConfirmedBalance(Asset.QORT); + + assertEquals("Partner's balance incorrect", expectedBalance, actualBalance); + + // Check eventual refund + checkTradeRefund(repository, deployer, deployersInitialBalance, deployAtFee); + } + } + + @SuppressWarnings("unused") + @Test + public void testCorrectSecretCorrectSenderInvalidMessageLength() throws DataException { + try (final Repository repository = RepositoryManager.getRepository()) { + PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe"); + PrivateKeyAccount tradeAccount = createTradeAccount(repository); + + PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert"); + + long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT); + long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT); + + DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress()); + Account at = deployAtTransaction.getATAccount(); + String atAddress = at.getAddress(); + + long partnersOfferMessageTransactionTimestamp = System.currentTimeMillis(); + int lockTimeA = calcTestLockTimeA(partnersOfferMessageTransactionTimestamp); + int refundTimeout = calcRefundTimeout(partnersOfferMessageTransactionTimestamp, lockTimeA); + + // Send trade info to AT + byte[] messageData = buildTradeMessage(partner.getAddress(), getPublicKey(), hashOfSecretA, lockTimeA, refundTimeout); + MessageTransaction messageTransaction = sendMessage(repository, tradeAccount, messageData, atAddress); + + // Give AT time to process message + BlockUtils.mintBlock(repository); + + // Send correct secret to AT, from correct account, but missing receive address, hence incorrect length + messageData = Bytes.concat(secretA); + messageTransaction = sendMessage(repository, partner, messageData, atAddress); + + // AT should NOT send funds in the next block + ATStateData preRedeemAtStateData = repository.getATRepository().getLatestATState(atAddress); + BlockUtils.mintBlock(repository); + + describeAt(repository, atAddress); + + // Check AT is NOT finished + ATData atData = repository.getATRepository().fromATAddress(atAddress); + assertFalse(atData.getIsFinished()); + + // AT should be in TRADING mode + CrossChainTradeData tradeData = getInstance().populateTradeData(repository, atData); + assertEquals(AcctMode.TRADING, tradeData.mode); + } + } + + @SuppressWarnings("unused") + @Test + public void testDescribeDeployed() throws DataException { + try (final Repository repository = RepositoryManager.getRepository()) { + PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe"); + PrivateKeyAccount tradeAccount = createTradeAccount(repository); + + PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert"); + + long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT); + long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT); + + DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress()); + + List executableAts = repository.getATRepository().getAllExecutableATs(); + + for (ATData atData : executableAts) { + String atAddress = atData.getATAddress(); + byte[] codeBytes = atData.getCodeBytes(); + byte[] codeHash = Crypto.digest(codeBytes); + + System.out.println(String.format("%s: code length: %d byte%s, code hash: %s", + atAddress, + codeBytes.length, + (codeBytes.length != 1 ? "s": ""), + HashCode.fromBytes(codeHash))); + + // Not one of ours? + if (!Arrays.equals(codeHash, getCodeBytesHash())) + continue; + + describeAt(repository, atAddress); + } + } + } + + protected int calcTestLockTimeA(long messageTimestamp) { + return (int) (messageTimestamp / 1000L + tradeTimeout * 60); + } + + protected DeployAtTransaction doDeploy(Repository repository, PrivateKeyAccount deployer, String tradeAddress) throws DataException { + byte[] creationBytes = buildQortalAT(tradeAddress, getPublicKey(), redeemAmount, foreignAmount, tradeTimeout); + + long txTimestamp = System.currentTimeMillis(); + byte[] lastReference = deployer.getLastReference(); + + if (lastReference == null) { + System.err.println(String.format("Qortal account %s has no last reference", deployer.getAddress())); + System.exit(2); + } + + Long fee = null; + String name = "QORT-" + getSymbol() + " cross-chain trade"; + String description = String.format("Qortal-" + getName() + " cross-chain trade"); + String atType = "ACCT"; + String tags = "QORT-" + getSymbol() + " ACCT"; + + BaseTransactionData baseTransactionData = new BaseTransactionData(txTimestamp, Group.NO_GROUP, lastReference, deployer.getPublicKey(), fee, null); + TransactionData deployAtTransactionData = new DeployAtTransactionData(baseTransactionData, name, description, atType, tags, creationBytes, fundingAmount, Asset.QORT); + + DeployAtTransaction deployAtTransaction = new DeployAtTransaction(repository, deployAtTransactionData); + + fee = deployAtTransaction.calcRecommendedFee(); + deployAtTransactionData.setFee(fee); + + TransactionUtils.signAndMint(repository, deployAtTransactionData, deployer); + + return deployAtTransaction; + } + + protected MessageTransaction sendMessage(Repository repository, PrivateKeyAccount sender, byte[] data, String recipient) throws DataException { + long txTimestamp = System.currentTimeMillis(); + byte[] lastReference = sender.getLastReference(); + + if (lastReference == null) { + System.err.println(String.format("Qortal account %s has no last reference", sender.getAddress())); + System.exit(2); + } + + Long fee = null; + int version = 4; + int nonce = 0; + long amount = 0; + Long assetId = null; // because amount is zero + + BaseTransactionData baseTransactionData = new BaseTransactionData(txTimestamp, Group.NO_GROUP, lastReference, sender.getPublicKey(), fee, null); + TransactionData messageTransactionData = new MessageTransactionData(baseTransactionData, version, nonce, recipient, amount, assetId, data, false, false); + + MessageTransaction messageTransaction = new MessageTransaction(repository, messageTransactionData); + + fee = messageTransaction.calcRecommendedFee(); + messageTransactionData.setFee(fee); + + TransactionUtils.signAndMint(repository, messageTransactionData, sender); + + return messageTransaction; + } + + protected void checkTradeRefund(Repository repository, Account deployer, long deployersInitialBalance, long deployAtFee) throws DataException { + long deployersPostDeploymentBalance = deployersInitialBalance - fundingAmount - deployAtFee; + int refundTimeout = tradeTimeout / 2 + 1; // close enough + + // AT should automatically refund deployer after 'refundTimeout' blocks + for (int blockCount = 0; blockCount <= refundTimeout; ++blockCount) + BlockUtils.mintBlock(repository); + + // We don't bother to exactly calculate QORT spent running AT for several blocks, but we do know the expected range + long expectedMinimumBalance = deployersPostDeploymentBalance; + long expectedMaximumBalance = deployersInitialBalance - deployAtFee; + + long actualBalance = deployer.getConfirmedBalance(Asset.QORT); + + assertTrue(String.format("Deployer's balance %s should be above minimum %s", actualBalance, expectedMinimumBalance), actualBalance > expectedMinimumBalance); + assertTrue(String.format("Deployer's balance %s should be below maximum %s", actualBalance, expectedMaximumBalance), actualBalance < expectedMaximumBalance); + } + + protected void describeAt(Repository repository, String atAddress) throws DataException { + ATData atData = repository.getATRepository().fromATAddress(atAddress); + CrossChainTradeData tradeData = getInstance().populateTradeData(repository, atData); + + Function epochMilliFormatter = (timestamp) -> LocalDateTime.ofInstant(Instant.ofEpochMilli(timestamp), ZoneOffset.UTC).format(DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM)); + int currentBlockHeight = repository.getBlockRepository().getBlockchainHeight(); + + System.out.print(String.format("%s:\n" + + "\tmode: %s\n" + + "\tcreator: %s,\n" + + "\tcreation timestamp: %s,\n" + + "\tcurrent balance: %s QORT,\n" + + "\tis finished: %b,\n" + + "\tredeem payout: %s QORT,\n" + + "\texpected " + getName() + ": %s " + getSymbol() + ",\n" + + "\tcurrent block height: %d,\n", + tradeData.qortalAtAddress, + tradeData.mode, + tradeData.qortalCreator, + epochMilliFormatter.apply(tradeData.creationTimestamp), + Amounts.prettyAmount(tradeData.qortBalance), + atData.getIsFinished(), + Amounts.prettyAmount(tradeData.qortAmount), + Amounts.prettyAmount(tradeData.expectedForeignAmount), + currentBlockHeight)); + + describeRefundAt(tradeData, epochMilliFormatter); + } + + protected void describeRefundAt(CrossChainTradeData tradeData, Function epochMilliFormatter) { + if (tradeData.mode != AcctMode.OFFERING && tradeData.mode != AcctMode.CANCELLED) { + System.out.println(String.format("\trefund timeout: %d minutes,\n" + + "\trefund height: block %d,\n" + + "\tHASH160 of secret-A: %s,\n" + + "\t" + getName() + " P2SH-A nLockTime: %d (%s),\n" + + "\ttrade partner: %s\n" + + "\tpartner's receiving address: %s", + tradeData.refundTimeout, + tradeData.tradeRefundHeight, + HashCode.fromBytes(tradeData.hashOfSecretA).toString().substring(0, 40), + tradeData.lockTimeA, epochMilliFormatter.apply(tradeData.lockTimeA * 1000L), + tradeData.qortalPartnerAddress, + tradeData.qortalPartnerReceivingAddress)); + } + } + + protected PrivateKeyAccount createTradeAccount(Repository repository) { + // We actually use a known test account with funds to avoid PoW compute + return Common.getTestAccount(repository, "alice"); + } +} \ No newline at end of file diff --git a/src/test/java/org/qortal/test/crosschain/bitcoinv1/BitcoinACCTv1Tests.java b/src/test/java/org/qortal/test/crosschain/bitcoinv1/BitcoinACCTv1Tests.java index 4487e874..cc33eb43 100644 --- a/src/test/java/org/qortal/test/crosschain/bitcoinv1/BitcoinACCTv1Tests.java +++ b/src/test/java/org/qortal/test/crosschain/bitcoinv1/BitcoinACCTv1Tests.java @@ -2,507 +2,89 @@ package org.qortal.test.crosschain.bitcoinv1; import static org.junit.Assert.*; -import java.time.Instant; -import java.time.LocalDateTime; -import java.time.ZoneOffset; -import java.time.format.DateTimeFormatter; -import java.time.format.FormatStyle; -import java.util.Arrays; -import java.util.List; -import java.util.Random; import java.util.function.Function; -import org.junit.Before; import org.junit.Test; import org.qortal.account.Account; import org.qortal.account.PrivateKeyAccount; import org.qortal.asset.Asset; -import org.qortal.block.Block; +import org.qortal.crosschain.ACCT; import org.qortal.crosschain.BitcoinACCTv1; import org.qortal.crosschain.AcctMode; import org.qortal.crypto.Crypto; import org.qortal.data.at.ATData; import org.qortal.data.at.ATStateData; import org.qortal.data.crosschain.CrossChainTradeData; -import org.qortal.data.transaction.BaseTransactionData; -import org.qortal.data.transaction.DeployAtTransactionData; -import org.qortal.data.transaction.MessageTransactionData; -import org.qortal.data.transaction.TransactionData; -import org.qortal.group.Group; import org.qortal.repository.DataException; import org.qortal.repository.Repository; import org.qortal.repository.RepositoryManager; import org.qortal.test.common.BlockUtils; import org.qortal.test.common.Common; -import org.qortal.test.common.TransactionUtils; +import org.qortal.test.crosschain.ACCTTests; import org.qortal.transaction.DeployAtTransaction; import org.qortal.transaction.MessageTransaction; -import org.qortal.utils.Amounts; import com.google.common.hash.HashCode; -import com.google.common.primitives.Bytes; -public class BitcoinACCTv1Tests extends Common { +public class BitcoinACCTv1Tests extends ACCTTests { - public static final byte[] secretA = "This string is exactly 32 bytes!".getBytes(); - public static final byte[] hashOfSecretA = Crypto.hash160(secretA); // daf59884b4d1aec8c1b17102530909ee43c0151a public static final byte[] secretB = "This string is roughly 32 bytes?".getBytes(); public static final byte[] hashOfSecretB = Crypto.hash160(secretB); // 31f0dd71decf59bbc8ef0661f4030479255cfa58 public static final byte[] bitcoinPublicKeyHash = HashCode.fromString("bb00bb11bb22bb33bb44bb55bb66bb77bb88bb99").asBytes(); - public static final int tradeTimeout = 20; // blocks - public static final long redeemAmount = 80_40200000L; - public static final long fundingAmount = 123_45600000L; - public static final long bitcoinAmount = 864200L; // 0.00864200 BTC - private static final Random RANDOM = new Random(); + private static final String SYMBOL = "BTC"; - @Before - public void beforeTest() throws DataException { - Common.useDefaultSettings(); + private static final String NAME = "Bitcoin"; + + @Override + protected byte[] getPublicKey() { + return bitcoinPublicKeyHash; } - @Test - public void testCompile() { - PrivateKeyAccount tradeAccount = createTradeAccount(null); - - byte[] creationBytes = BitcoinACCTv1.buildQortalAT(tradeAccount.getAddress(), bitcoinPublicKeyHash, hashOfSecretB, redeemAmount, bitcoinAmount, tradeTimeout); - assertNotNull(creationBytes); - - System.out.println("AT creation bytes: " + HashCode.fromBytes(creationBytes).toString()); + @Override + protected byte[] buildQortalAT(String address, byte[] publicKey, long redeemAmount, long foreignAmount, int tradeTimeout) { + return BitcoinACCTv1.buildQortalAT(address,publicKey, hashOfSecretB, redeemAmount, foreignAmount,tradeTimeout); } - @Test - public void testDeploy() throws DataException { - try (final Repository repository = RepositoryManager.getRepository()) { - PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe"); - PrivateKeyAccount tradeAccount = createTradeAccount(repository); + @Override + protected ACCT getInstance() { + return BitcoinACCTv1.getInstance(); + } - PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert"); + @Override + protected int calcRefundTimeout(long partnersOfferMessageTransactionTimestamp, int lockTimeA) { + return BitcoinACCTv1.calcLockTimeB(partnersOfferMessageTransactionTimestamp, lockTimeA); + } - long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT); - long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT); + @Override + protected byte[] buildTradeMessage(String address, byte[] publicKey, byte[] hashOfSecretA, int lockTimeA, int refundTimeout) { + return BitcoinACCTv1.buildTradeMessage(address, publicKey, hashOfSecretA, lockTimeA, refundTimeout); + } - DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress()); + @Override + protected byte[] buildRedeemMessage(byte[] secretA, String address) { + return BitcoinACCTv1.buildRedeemMessage(secretA,secretB,address); + } - long expectedBalance = deployersInitialBalance - fundingAmount - deployAtTransaction.getTransactionData().getFee(); - long actualBalance = deployer.getConfirmedBalance(Asset.QORT); + @Override + protected byte[] getCodeBytesHash() { + return BitcoinACCTv1.CODE_BYTES_HASH; + } - assertEquals("Deployer's post-deployment balance incorrect", expectedBalance, actualBalance); + @Override + protected String getSymbol() { + return SYMBOL; + } - expectedBalance = fundingAmount; - actualBalance = deployAtTransaction.getATAccount().getConfirmedBalance(Asset.QORT); - - assertEquals("AT's post-deployment balance incorrect", expectedBalance, actualBalance); - - expectedBalance = partnersInitialBalance; - actualBalance = partner.getConfirmedBalance(Asset.QORT); - - assertEquals("Partner's post-deployment balance incorrect", expectedBalance, actualBalance); - - // Test orphaning - BlockUtils.orphanLastBlock(repository); - - expectedBalance = deployersInitialBalance; - actualBalance = deployer.getConfirmedBalance(Asset.QORT); - - assertEquals("Deployer's post-orphan/pre-deployment balance incorrect", expectedBalance, actualBalance); - - expectedBalance = 0; - actualBalance = deployAtTransaction.getATAccount().getConfirmedBalance(Asset.QORT); - - assertEquals("AT's post-orphan/pre-deployment balance incorrect", expectedBalance, actualBalance); - - expectedBalance = partnersInitialBalance; - actualBalance = partner.getConfirmedBalance(Asset.QORT); - - assertEquals("Partner's post-orphan/pre-deployment balance incorrect", expectedBalance, actualBalance); - } + @Override + protected String getName() { + return NAME; } @SuppressWarnings("unused") + @Override @Test - public void testOfferCancel() throws DataException { - try (final Repository repository = RepositoryManager.getRepository()) { - PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe"); - PrivateKeyAccount tradeAccount = createTradeAccount(repository); - - PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert"); - - long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT); - long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT); - - DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress()); - Account at = deployAtTransaction.getATAccount(); - String atAddress = at.getAddress(); - - long deployAtFee = deployAtTransaction.getTransactionData().getFee(); - long deployersPostDeploymentBalance = deployersInitialBalance - fundingAmount - deployAtFee; - - // Send creator's address to AT, instead of typical partner's address - byte[] messageData = BitcoinACCTv1.getInstance().buildCancelMessage(deployer.getAddress()); - MessageTransaction messageTransaction = sendMessage(repository, deployer, messageData, atAddress); - long messageFee = messageTransaction.getTransactionData().getFee(); - - // AT should process 'cancel' message in next block - BlockUtils.mintBlock(repository); - - describeAt(repository, atAddress); - - // Check AT is finished - ATData atData = repository.getATRepository().fromATAddress(atAddress); - assertTrue(atData.getIsFinished()); - - // AT should be in CANCELLED mode - CrossChainTradeData tradeData = BitcoinACCTv1.getInstance().populateTradeData(repository, atData); - assertEquals(AcctMode.CANCELLED, tradeData.mode); - - // Check balances - long expectedMinimumBalance = deployersPostDeploymentBalance; - long expectedMaximumBalance = deployersInitialBalance - deployAtFee - messageFee; - - long actualBalance = deployer.getConfirmedBalance(Asset.QORT); - - assertTrue(String.format("Deployer's balance %s should be above minimum %s", actualBalance, expectedMinimumBalance), actualBalance > expectedMinimumBalance); - assertTrue(String.format("Deployer's balance %s should be below maximum %s", actualBalance, expectedMaximumBalance), actualBalance < expectedMaximumBalance); - - // Test orphaning - BlockUtils.orphanLastBlock(repository); - - // Check balances - long expectedBalance = deployersPostDeploymentBalance - messageFee; - actualBalance = deployer.getConfirmedBalance(Asset.QORT); - - assertEquals("Deployer's post-orphan/pre-refund balance incorrect", expectedBalance, actualBalance); - } - } - - @SuppressWarnings("unused") - @Test - public void testOfferCancelInvalidLength() throws DataException { - try (final Repository repository = RepositoryManager.getRepository()) { - PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe"); - PrivateKeyAccount tradeAccount = createTradeAccount(repository); - - PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert"); - - long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT); - long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT); - - DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress()); - Account at = deployAtTransaction.getATAccount(); - String atAddress = at.getAddress(); - - long deployAtFee = deployAtTransaction.getTransactionData().getFee(); - long deployersPostDeploymentBalance = deployersInitialBalance - fundingAmount - deployAtFee; - - // Instead of sending creator's address to AT, send too-short/invalid message - byte[] messageData = new byte[7]; - RANDOM.nextBytes(messageData); - MessageTransaction messageTransaction = sendMessage(repository, deployer, messageData, atAddress); - long messageFee = messageTransaction.getTransactionData().getFee(); - - // AT should process 'cancel' message in next block - // As message is too short, it will be padded to 32bytes but cancel code doesn't care about message content, so should be ok - BlockUtils.mintBlock(repository); - - describeAt(repository, atAddress); - - // Check AT is finished - ATData atData = repository.getATRepository().fromATAddress(atAddress); - assertTrue(atData.getIsFinished()); - - // AT should be in CANCELLED mode - CrossChainTradeData tradeData = BitcoinACCTv1.getInstance().populateTradeData(repository, atData); - assertEquals(AcctMode.CANCELLED, tradeData.mode); - } - } - - @SuppressWarnings("unused") - @Test - public void testTradingInfoProcessing() throws DataException { - try (final Repository repository = RepositoryManager.getRepository()) { - PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe"); - PrivateKeyAccount tradeAccount = createTradeAccount(repository); - - PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert"); - - long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT); - long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT); - - DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress()); - Account at = deployAtTransaction.getATAccount(); - String atAddress = at.getAddress(); - - long partnersOfferMessageTransactionTimestamp = System.currentTimeMillis(); - int lockTimeA = calcTestLockTimeA(partnersOfferMessageTransactionTimestamp); - int lockTimeB = BitcoinACCTv1.calcLockTimeB(partnersOfferMessageTransactionTimestamp, lockTimeA); - - // Send trade info to AT - byte[] messageData = BitcoinACCTv1.buildTradeMessage(partner.getAddress(), bitcoinPublicKeyHash, hashOfSecretA, lockTimeA, lockTimeB); - MessageTransaction messageTransaction = sendMessage(repository, tradeAccount, messageData, atAddress); - - Block postDeploymentBlock = BlockUtils.mintBlock(repository); - int postDeploymentBlockHeight = postDeploymentBlock.getBlockData().getHeight(); - - long deployAtFee = deployAtTransaction.getTransactionData().getFee(); - long deployersPostDeploymentBalance = deployersInitialBalance - fundingAmount - deployAtFee; - - describeAt(repository, atAddress); - - ATData atData = repository.getATRepository().fromATAddress(atAddress); - CrossChainTradeData tradeData = BitcoinACCTv1.getInstance().populateTradeData(repository, atData); - - // AT should be in TRADE mode - assertEquals(AcctMode.TRADING, tradeData.mode); - - // Check hashOfSecretA was extracted correctly - assertTrue(Arrays.equals(hashOfSecretA, tradeData.hashOfSecretA)); - - // Check trade partner Qortal address was extracted correctly - assertEquals(partner.getAddress(), tradeData.qortalPartnerAddress); - - // Check trade partner's Bitcoin PKH was extracted correctly - assertTrue(Arrays.equals(bitcoinPublicKeyHash, tradeData.partnerForeignPKH)); - - // Test orphaning - BlockUtils.orphanToBlock(repository, postDeploymentBlockHeight); - - // Check balances - long expectedBalance = deployersPostDeploymentBalance; - long actualBalance = deployer.getConfirmedBalance(Asset.QORT); - - assertEquals("Deployer's post-orphan/pre-refund balance incorrect", expectedBalance, actualBalance); - } - } - - // TEST SENDING TRADING INFO BUT NOT FROM AT CREATOR (SHOULD BE IGNORED) - @SuppressWarnings("unused") - @Test - public void testIncorrectTradeSender() throws DataException { - try (final Repository repository = RepositoryManager.getRepository()) { - PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe"); - PrivateKeyAccount tradeAccount = createTradeAccount(repository); - - PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert"); - - PrivateKeyAccount bystander = Common.getTestAccount(repository, "bob"); - - long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT); - long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT); - - DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress()); - Account at = deployAtTransaction.getATAccount(); - String atAddress = at.getAddress(); - - long partnersOfferMessageTransactionTimestamp = System.currentTimeMillis(); - int lockTimeA = calcTestLockTimeA(partnersOfferMessageTransactionTimestamp); - int lockTimeB = BitcoinACCTv1.calcLockTimeB(partnersOfferMessageTransactionTimestamp, lockTimeA); - - // Send trade info to AT BUT NOT FROM AT CREATOR - byte[] messageData = BitcoinACCTv1.buildTradeMessage(partner.getAddress(), bitcoinPublicKeyHash, hashOfSecretA, lockTimeA, lockTimeB); - MessageTransaction messageTransaction = sendMessage(repository, bystander, messageData, atAddress); - - BlockUtils.mintBlock(repository); - - long expectedBalance = partnersInitialBalance; - long actualBalance = partner.getConfirmedBalance(Asset.QORT); - - assertEquals("Partner's post-initial-payout balance incorrect", expectedBalance, actualBalance); - - describeAt(repository, atAddress); - - ATData atData = repository.getATRepository().fromATAddress(atAddress); - CrossChainTradeData tradeData = BitcoinACCTv1.getInstance().populateTradeData(repository, atData); - - // AT should still be in OFFER mode - assertEquals(AcctMode.OFFERING, tradeData.mode); - } - } - - @SuppressWarnings("unused") - @Test - public void testAutomaticTradeRefund() throws DataException { - try (final Repository repository = RepositoryManager.getRepository()) { - PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe"); - PrivateKeyAccount tradeAccount = createTradeAccount(repository); - - PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert"); - - long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT); - long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT); - - DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress()); - Account at = deployAtTransaction.getATAccount(); - String atAddress = at.getAddress(); - - long partnersOfferMessageTransactionTimestamp = System.currentTimeMillis(); - int lockTimeA = calcTestLockTimeA(partnersOfferMessageTransactionTimestamp); - int lockTimeB = BitcoinACCTv1.calcLockTimeB(partnersOfferMessageTransactionTimestamp, lockTimeA); - - // Send trade info to AT - byte[] messageData = BitcoinACCTv1.buildTradeMessage(partner.getAddress(), bitcoinPublicKeyHash, hashOfSecretA, lockTimeA, lockTimeB); - MessageTransaction messageTransaction = sendMessage(repository, tradeAccount, messageData, atAddress); - - Block postDeploymentBlock = BlockUtils.mintBlock(repository); - int postDeploymentBlockHeight = postDeploymentBlock.getBlockData().getHeight(); - - // Check refund - long deployAtFee = deployAtTransaction.getTransactionData().getFee(); - long deployersPostDeploymentBalance = deployersInitialBalance - fundingAmount - deployAtFee; - - checkTradeRefund(repository, deployer, deployersInitialBalance, deployAtFee); - - describeAt(repository, atAddress); - - // Check AT is finished - ATData atData = repository.getATRepository().fromATAddress(atAddress); - assertTrue(atData.getIsFinished()); - - // AT should be in REFUNDED mode - CrossChainTradeData tradeData = BitcoinACCTv1.getInstance().populateTradeData(repository, atData); - assertEquals(AcctMode.REFUNDED, tradeData.mode); - - // Test orphaning - BlockUtils.orphanToBlock(repository, postDeploymentBlockHeight); - - // Check balances - long expectedBalance = deployersPostDeploymentBalance; - long actualBalance = deployer.getConfirmedBalance(Asset.QORT); - - assertEquals("Deployer's post-orphan/pre-refund balance incorrect", expectedBalance, actualBalance); - } - } - - @SuppressWarnings("unused") - @Test - public void testCorrectSecretsCorrectSender() throws DataException { - try (final Repository repository = RepositoryManager.getRepository()) { - PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe"); - PrivateKeyAccount tradeAccount = createTradeAccount(repository); - - PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert"); - - long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT); - long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT); - - DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress()); - Account at = deployAtTransaction.getATAccount(); - String atAddress = at.getAddress(); - - long partnersOfferMessageTransactionTimestamp = System.currentTimeMillis(); - int lockTimeA = calcTestLockTimeA(partnersOfferMessageTransactionTimestamp); - int lockTimeB = BitcoinACCTv1.calcLockTimeB(partnersOfferMessageTransactionTimestamp, lockTimeA); - - // Send trade info to AT - byte[] messageData = BitcoinACCTv1.buildTradeMessage(partner.getAddress(), bitcoinPublicKeyHash, hashOfSecretA, lockTimeA, lockTimeB); - MessageTransaction messageTransaction = sendMessage(repository, tradeAccount, messageData, atAddress); - - // Give AT time to process message - BlockUtils.mintBlock(repository); - - // Send correct secrets to AT, from correct account - messageData = BitcoinACCTv1.buildRedeemMessage(secretA, secretB, partner.getAddress()); - messageTransaction = sendMessage(repository, partner, messageData, atAddress); - - // AT should send funds in the next block - ATStateData preRedeemAtStateData = repository.getATRepository().getLatestATState(atAddress); - BlockUtils.mintBlock(repository); - - describeAt(repository, atAddress); - - // Check AT is finished - ATData atData = repository.getATRepository().fromATAddress(atAddress); - assertTrue(atData.getIsFinished()); - - // AT should be in REDEEMED mode - CrossChainTradeData tradeData = BitcoinACCTv1.getInstance().populateTradeData(repository, atData); - assertEquals(AcctMode.REDEEMED, tradeData.mode); - - // Check balances - long expectedBalance = partnersInitialBalance - messageTransaction.getTransactionData().getFee() + redeemAmount; - long actualBalance = partner.getConfirmedBalance(Asset.QORT); - - assertEquals("Partner's post-redeem balance incorrect", expectedBalance, actualBalance); - - // Orphan redeem - BlockUtils.orphanLastBlock(repository); - - // Check balances - expectedBalance = partnersInitialBalance - messageTransaction.getTransactionData().getFee(); - actualBalance = partner.getConfirmedBalance(Asset.QORT); - - assertEquals("Partner's post-orphan/pre-redeem balance incorrect", expectedBalance, actualBalance); - - // Check AT state - ATStateData postOrphanAtStateData = repository.getATRepository().getLatestATState(atAddress); - - assertTrue("AT states mismatch", Arrays.equals(preRedeemAtStateData.getStateData(), postOrphanAtStateData.getStateData())); - } - } - - @SuppressWarnings("unused") - @Test - public void testCorrectSecretsIncorrectSender() throws DataException { - try (final Repository repository = RepositoryManager.getRepository()) { - PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe"); - PrivateKeyAccount tradeAccount = createTradeAccount(repository); - - PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert"); - - PrivateKeyAccount bystander = Common.getTestAccount(repository, "bob"); - - long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT); - long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT); - - DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress()); - long deployAtFee = deployAtTransaction.getTransactionData().getFee(); - - Account at = deployAtTransaction.getATAccount(); - String atAddress = at.getAddress(); - - long partnersOfferMessageTransactionTimestamp = System.currentTimeMillis(); - int lockTimeA = calcTestLockTimeA(partnersOfferMessageTransactionTimestamp); - int lockTimeB = BitcoinACCTv1.calcLockTimeB(partnersOfferMessageTransactionTimestamp, lockTimeA); - - // Send trade info to AT - byte[] messageData = BitcoinACCTv1.buildTradeMessage(partner.getAddress(), bitcoinPublicKeyHash, hashOfSecretA, lockTimeA, lockTimeB); - MessageTransaction messageTransaction = sendMessage(repository, tradeAccount, messageData, atAddress); - - // Give AT time to process message - BlockUtils.mintBlock(repository); - - // Send correct secrets to AT, but from wrong account - messageData = BitcoinACCTv1.buildRedeemMessage(secretA, secretB, partner.getAddress()); - messageTransaction = sendMessage(repository, bystander, messageData, atAddress); - - // AT should NOT send funds in the next block - ATStateData preRedeemAtStateData = repository.getATRepository().getLatestATState(atAddress); - BlockUtils.mintBlock(repository); - - describeAt(repository, atAddress); - - // Check AT is NOT finished - ATData atData = repository.getATRepository().fromATAddress(atAddress); - assertFalse(atData.getIsFinished()); - - // AT should still be in TRADE mode - CrossChainTradeData tradeData = BitcoinACCTv1.getInstance().populateTradeData(repository, atData); - assertEquals(AcctMode.TRADING, tradeData.mode); - - // Check balances - long expectedBalance = partnersInitialBalance; - long actualBalance = partner.getConfirmedBalance(Asset.QORT); - - assertEquals("Partner's balance incorrect", expectedBalance, actualBalance); - - // Check eventual refund - checkTradeRefund(repository, deployer, deployersInitialBalance, deployAtFee); - } - } - - @SuppressWarnings("unused") - @Test - public void testIncorrectSecretsCorrectSender() throws DataException { + public void testIncorrectSecretCorrectSender() throws DataException { try (final Repository repository = RepositoryManager.getRepository()) { PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe"); PrivateKeyAccount tradeAccount = createTradeAccount(repository); @@ -582,197 +164,8 @@ public class BitcoinACCTv1Tests extends Common { } } - @SuppressWarnings("unused") - @Test - public void testCorrectSecretsCorrectSenderInvalidMessageLength() throws DataException { - try (final Repository repository = RepositoryManager.getRepository()) { - PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe"); - PrivateKeyAccount tradeAccount = createTradeAccount(repository); - - PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert"); - - long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT); - long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT); - - DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress()); - Account at = deployAtTransaction.getATAccount(); - String atAddress = at.getAddress(); - - long partnersOfferMessageTransactionTimestamp = System.currentTimeMillis(); - int lockTimeA = calcTestLockTimeA(partnersOfferMessageTransactionTimestamp); - int lockTimeB = BitcoinACCTv1.calcLockTimeB(partnersOfferMessageTransactionTimestamp, lockTimeA); - - // Send trade info to AT - byte[] messageData = BitcoinACCTv1.buildTradeMessage(partner.getAddress(), bitcoinPublicKeyHash, hashOfSecretA, lockTimeA, lockTimeB); - MessageTransaction messageTransaction = sendMessage(repository, tradeAccount, messageData, atAddress); - - // Give AT time to process message - BlockUtils.mintBlock(repository); - - // Send correct secrets to AT, from correct account, but missing receive address, hence incorrect length - messageData = Bytes.concat(secretA, secretB); - messageTransaction = sendMessage(repository, partner, messageData, atAddress); - - // AT should NOT send funds in the next block - ATStateData preRedeemAtStateData = repository.getATRepository().getLatestATState(atAddress); - BlockUtils.mintBlock(repository); - - describeAt(repository, atAddress); - - // Check AT is NOT finished - ATData atData = repository.getATRepository().fromATAddress(atAddress); - assertFalse(atData.getIsFinished()); - - // AT should be in TRADING mode - CrossChainTradeData tradeData = BitcoinACCTv1.getInstance().populateTradeData(repository, atData); - assertEquals(AcctMode.TRADING, tradeData.mode); - } - } - - @SuppressWarnings("unused") - @Test - public void testDescribeDeployed() throws DataException { - try (final Repository repository = RepositoryManager.getRepository()) { - PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe"); - PrivateKeyAccount tradeAccount = createTradeAccount(repository); - - PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert"); - - long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT); - long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT); - - DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress()); - - List executableAts = repository.getATRepository().getAllExecutableATs(); - - for (ATData atData : executableAts) { - String atAddress = atData.getATAddress(); - byte[] codeBytes = atData.getCodeBytes(); - byte[] codeHash = Crypto.digest(codeBytes); - - System.out.println(String.format("%s: code length: %d byte%s, code hash: %s", - atAddress, - codeBytes.length, - (codeBytes.length != 1 ? "s": ""), - HashCode.fromBytes(codeHash))); - - // Not one of ours? - if (!Arrays.equals(codeHash, BitcoinACCTv1.CODE_BYTES_HASH)) - continue; - - describeAt(repository, atAddress); - } - } - } - - private int calcTestLockTimeA(long messageTimestamp) { - return (int) (messageTimestamp / 1000L + tradeTimeout * 60); - } - - private DeployAtTransaction doDeploy(Repository repository, PrivateKeyAccount deployer, String tradeAddress) throws DataException { - byte[] creationBytes = BitcoinACCTv1.buildQortalAT(tradeAddress, bitcoinPublicKeyHash, hashOfSecretB, redeemAmount, bitcoinAmount, tradeTimeout); - - long txTimestamp = System.currentTimeMillis(); - byte[] lastReference = deployer.getLastReference(); - - if (lastReference == null) { - System.err.println(String.format("Qortal account %s has no last reference", deployer.getAddress())); - System.exit(2); - } - - Long fee = null; - String name = "QORT-BTC cross-chain trade"; - String description = String.format("Qortal-Bitcoin cross-chain trade"); - String atType = "ACCT"; - String tags = "QORT-BTC ACCT"; - - BaseTransactionData baseTransactionData = new BaseTransactionData(txTimestamp, Group.NO_GROUP, lastReference, deployer.getPublicKey(), fee, null); - TransactionData deployAtTransactionData = new DeployAtTransactionData(baseTransactionData, name, description, atType, tags, creationBytes, fundingAmount, Asset.QORT); - - DeployAtTransaction deployAtTransaction = new DeployAtTransaction(repository, deployAtTransactionData); - - fee = deployAtTransaction.calcRecommendedFee(); - deployAtTransactionData.setFee(fee); - - TransactionUtils.signAndMint(repository, deployAtTransactionData, deployer); - - return deployAtTransaction; - } - - private MessageTransaction sendMessage(Repository repository, PrivateKeyAccount sender, byte[] data, String recipient) throws DataException { - long txTimestamp = System.currentTimeMillis(); - byte[] lastReference = sender.getLastReference(); - - if (lastReference == null) { - System.err.println(String.format("Qortal account %s has no last reference", sender.getAddress())); - System.exit(2); - } - - Long fee = null; - int version = 4; - int nonce = 0; - long amount = 0; - Long assetId = null; // because amount is zero - - BaseTransactionData baseTransactionData = new BaseTransactionData(txTimestamp, Group.NO_GROUP, lastReference, sender.getPublicKey(), fee, null); - TransactionData messageTransactionData = new MessageTransactionData(baseTransactionData, version, nonce, recipient, amount, assetId, data, false, false); - - MessageTransaction messageTransaction = new MessageTransaction(repository, messageTransactionData); - - fee = messageTransaction.calcRecommendedFee(); - messageTransactionData.setFee(fee); - - TransactionUtils.signAndMint(repository, messageTransactionData, sender); - - return messageTransaction; - } - - private void checkTradeRefund(Repository repository, Account deployer, long deployersInitialBalance, long deployAtFee) throws DataException { - long deployersPostDeploymentBalance = deployersInitialBalance - fundingAmount - deployAtFee; - int refundTimeout = tradeTimeout * 3 / 4 + 1; // close enough - - // AT should automatically refund deployer after 'refundTimeout' blocks - for (int blockCount = 0; blockCount <= refundTimeout; ++blockCount) - BlockUtils.mintBlock(repository); - - // We don't bother to exactly calculate QORT spent running AT for several blocks, but we do know the expected range - long expectedMinimumBalance = deployersPostDeploymentBalance; - long expectedMaximumBalance = deployersInitialBalance - deployAtFee; - - long actualBalance = deployer.getConfirmedBalance(Asset.QORT); - - assertTrue(String.format("Deployer's balance %s should be above minimum %s", actualBalance, expectedMinimumBalance), actualBalance > expectedMinimumBalance); - assertTrue(String.format("Deployer's balance %s should be below maximum %s", actualBalance, expectedMaximumBalance), actualBalance < expectedMaximumBalance); - } - - private void describeAt(Repository repository, String atAddress) throws DataException { - ATData atData = repository.getATRepository().fromATAddress(atAddress); - CrossChainTradeData tradeData = BitcoinACCTv1.getInstance().populateTradeData(repository, atData); - - Function epochMilliFormatter = (timestamp) -> LocalDateTime.ofInstant(Instant.ofEpochMilli(timestamp), ZoneOffset.UTC).format(DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM)); - int currentBlockHeight = repository.getBlockRepository().getBlockchainHeight(); - - System.out.print(String.format("%s:\n" - + "\tmode: %s\n" - + "\tcreator: %s,\n" - + "\tcreation timestamp: %s,\n" - + "\tcurrent balance: %s QORT,\n" - + "\tis finished: %b,\n" - + "\tHASH160 of secret-B: %s,\n" - + "\tredeem payout: %s QORT,\n" - + "\texpected bitcoin: %s BTC,\n" - + "\tcurrent block height: %d,\n", - tradeData.qortalAtAddress, - tradeData.mode, - tradeData.qortalCreator, - epochMilliFormatter.apply(tradeData.creationTimestamp), - Amounts.prettyAmount(tradeData.qortBalance), - atData.getIsFinished(), - HashCode.fromBytes(tradeData.hashOfSecretB).toString().substring(0, 40), - Amounts.prettyAmount(tradeData.qortAmount), - Amounts.prettyAmount(tradeData.expectedForeignAmount), - currentBlockHeight)); - + @Override + protected void describeRefundAt(CrossChainTradeData tradeData, Function epochMilliFormatter) { if (tradeData.mode != AcctMode.OFFERING && tradeData.mode != AcctMode.CANCELLED) { System.out.println(String.format("\trefund height: block %d,\n" + "\tHASH160 of secret-A: %s,\n" @@ -786,10 +179,4 @@ public class BitcoinACCTv1Tests extends Common { tradeData.qortalPartnerAddress)); } } - - private PrivateKeyAccount createTradeAccount(Repository repository) { - // We actually use a known test account with funds to avoid PoW compute - return Common.getTestAccount(repository, "alice"); - } - } diff --git a/src/test/java/org/qortal/test/crosschain/bitcoinv3/BitcoinACCTv3Tests.java b/src/test/java/org/qortal/test/crosschain/bitcoinv3/BitcoinACCTv3Tests.java index 01345727..5e0048bf 100644 --- a/src/test/java/org/qortal/test/crosschain/bitcoinv3/BitcoinACCTv3Tests.java +++ b/src/test/java/org/qortal/test/crosschain/bitcoinv3/BitcoinACCTv3Tests.java @@ -1,769 +1,58 @@ package org.qortal.test.crosschain.bitcoinv3; import com.google.common.hash.HashCode; -import com.google.common.primitives.Bytes; -import org.junit.Before; -import org.junit.Test; -import org.qortal.account.Account; -import org.qortal.account.PrivateKeyAccount; -import org.qortal.asset.Asset; -import org.qortal.block.Block; -import org.qortal.crosschain.AcctMode; +import org.qortal.crosschain.ACCT; import org.qortal.crosschain.BitcoinACCTv3; -import org.qortal.crypto.Crypto; -import org.qortal.data.at.ATData; -import org.qortal.data.at.ATStateData; -import org.qortal.data.crosschain.CrossChainTradeData; -import org.qortal.data.transaction.BaseTransactionData; -import org.qortal.data.transaction.DeployAtTransactionData; -import org.qortal.data.transaction.MessageTransactionData; -import org.qortal.data.transaction.TransactionData; -import org.qortal.group.Group; -import org.qortal.repository.DataException; -import org.qortal.repository.Repository; -import org.qortal.repository.RepositoryManager; -import org.qortal.test.common.BlockUtils; -import org.qortal.test.common.Common; -import org.qortal.test.common.TransactionUtils; -import org.qortal.transaction.DeployAtTransaction; -import org.qortal.transaction.MessageTransaction; -import org.qortal.utils.Amounts; +import org.qortal.test.crosschain.ACCTTests; -import java.time.Instant; -import java.time.LocalDateTime; -import java.time.ZoneOffset; -import java.time.format.DateTimeFormatter; -import java.time.format.FormatStyle; -import java.util.Arrays; -import java.util.List; -import java.util.Random; -import java.util.function.Function; +public class BitcoinACCTv3Tests extends ACCTTests { -import static org.junit.Assert.*; - -public class BitcoinACCTv3Tests extends Common { - - public static final byte[] secretA = "This string is exactly 32 bytes!".getBytes(); - public static final byte[] hashOfSecretA = Crypto.hash160(secretA); // daf59884b4d1aec8c1b17102530909ee43c0151a public static final byte[] bitcoinPublicKeyHash = HashCode.fromString("bb00bb11bb22bb33bb44bb55bb66bb77bb88bb99").asBytes(); - public static final int tradeTimeout = 20; // blocks - public static final long redeemAmount = 80_40200000L; - public static final long fundingAmount = 123_45600000L; - public static final long bitcoinAmount = 864200L; // 0.00864200 BTC + private static final String SYMBOL = "BTC"; + private static final String NAME = "Bitcoin"; - private static final Random RANDOM = new Random(); - - @Before - public void beforeTest() throws DataException { - Common.useDefaultSettings(); + @Override + protected byte[] getPublicKey() { + return bitcoinPublicKeyHash; } - @Test - public void testCompile() { - PrivateKeyAccount tradeAccount = createTradeAccount(null); - - byte[] creationBytes = BitcoinACCTv3.buildQortalAT(tradeAccount.getAddress(), bitcoinPublicKeyHash, redeemAmount, bitcoinAmount, tradeTimeout); - assertNotNull(creationBytes); - - System.out.println("AT creation bytes: " + HashCode.fromBytes(creationBytes).toString()); + @Override + protected byte[] buildQortalAT(String address, byte[] publicKey, long redeemAmount, long foreignAmount, int tradeTimeout) { + return BitcoinACCTv3.buildQortalAT(address, publicKey, redeemAmount, foreignAmount, tradeTimeout); } - @Test - public void testDeploy() throws DataException { - try (final Repository repository = RepositoryManager.getRepository()) { - PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe"); - PrivateKeyAccount tradeAccount = createTradeAccount(repository); - - PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert"); - - long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT); - long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT); - - DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress()); - - long expectedBalance = deployersInitialBalance - fundingAmount - deployAtTransaction.getTransactionData().getFee(); - long actualBalance = deployer.getConfirmedBalance(Asset.QORT); - - assertEquals("Deployer's post-deployment balance incorrect", expectedBalance, actualBalance); - - expectedBalance = fundingAmount; - actualBalance = deployAtTransaction.getATAccount().getConfirmedBalance(Asset.QORT); - - assertEquals("AT's post-deployment balance incorrect", expectedBalance, actualBalance); - - expectedBalance = partnersInitialBalance; - actualBalance = partner.getConfirmedBalance(Asset.QORT); - - assertEquals("Partner's post-deployment balance incorrect", expectedBalance, actualBalance); - - // Test orphaning - BlockUtils.orphanLastBlock(repository); - - expectedBalance = deployersInitialBalance; - actualBalance = deployer.getConfirmedBalance(Asset.QORT); - - assertEquals("Deployer's post-orphan/pre-deployment balance incorrect", expectedBalance, actualBalance); - - expectedBalance = 0; - actualBalance = deployAtTransaction.getATAccount().getConfirmedBalance(Asset.QORT); - - assertEquals("AT's post-orphan/pre-deployment balance incorrect", expectedBalance, actualBalance); - - expectedBalance = partnersInitialBalance; - actualBalance = partner.getConfirmedBalance(Asset.QORT); - - assertEquals("Partner's post-orphan/pre-deployment balance incorrect", expectedBalance, actualBalance); - } + @Override + protected ACCT getInstance() { + return BitcoinACCTv3.getInstance(); } - @SuppressWarnings("unused") - @Test - public void testOfferCancel() throws DataException { - try (final Repository repository = RepositoryManager.getRepository()) { - PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe"); - PrivateKeyAccount tradeAccount = createTradeAccount(repository); - - PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert"); - - long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT); - long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT); - - DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress()); - Account at = deployAtTransaction.getATAccount(); - String atAddress = at.getAddress(); - - long deployAtFee = deployAtTransaction.getTransactionData().getFee(); - long deployersPostDeploymentBalance = deployersInitialBalance - fundingAmount - deployAtFee; - - // Send creator's address to AT, instead of typical partner's address - byte[] messageData = BitcoinACCTv3.getInstance().buildCancelMessage(deployer.getAddress()); - MessageTransaction messageTransaction = sendMessage(repository, deployer, messageData, atAddress); - long messageFee = messageTransaction.getTransactionData().getFee(); - - // AT should process 'cancel' message in next block - BlockUtils.mintBlock(repository); - - describeAt(repository, atAddress); - - // Check AT is finished - ATData atData = repository.getATRepository().fromATAddress(atAddress); - assertTrue(atData.getIsFinished()); - - // AT should be in CANCELLED mode - CrossChainTradeData tradeData = BitcoinACCTv3.getInstance().populateTradeData(repository, atData); - assertEquals(AcctMode.CANCELLED, tradeData.mode); - - // Check balances - long expectedMinimumBalance = deployersPostDeploymentBalance; - long expectedMaximumBalance = deployersInitialBalance - deployAtFee - messageFee; - - long actualBalance = deployer.getConfirmedBalance(Asset.QORT); - - assertTrue(String.format("Deployer's balance %s should be above minimum %s", actualBalance, expectedMinimumBalance), actualBalance > expectedMinimumBalance); - assertTrue(String.format("Deployer's balance %s should be below maximum %s", actualBalance, expectedMaximumBalance), actualBalance < expectedMaximumBalance); - - // Test orphaning - BlockUtils.orphanLastBlock(repository); - - // Check balances - long expectedBalance = deployersPostDeploymentBalance - messageFee; - actualBalance = deployer.getConfirmedBalance(Asset.QORT); - - assertEquals("Deployer's post-orphan/pre-refund balance incorrect", expectedBalance, actualBalance); - } + @Override + protected int calcRefundTimeout(long partnersOfferMessageTransactionTimestamp, int lockTimeA) { + return BitcoinACCTv3.calcRefundTimeout(partnersOfferMessageTransactionTimestamp, lockTimeA); } - @SuppressWarnings("unused") - @Test - public void testOfferCancelInvalidLength() throws DataException { - try (final Repository repository = RepositoryManager.getRepository()) { - PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe"); - PrivateKeyAccount tradeAccount = createTradeAccount(repository); - - PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert"); - - long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT); - long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT); - - DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress()); - Account at = deployAtTransaction.getATAccount(); - String atAddress = at.getAddress(); - - long deployAtFee = deployAtTransaction.getTransactionData().getFee(); - long deployersPostDeploymentBalance = deployersInitialBalance - fundingAmount - deployAtFee; - - // Instead of sending creator's address to AT, send too-short/invalid message - byte[] messageData = new byte[7]; - RANDOM.nextBytes(messageData); - MessageTransaction messageTransaction = sendMessage(repository, deployer, messageData, atAddress); - long messageFee = messageTransaction.getTransactionData().getFee(); - - // AT should process 'cancel' message in next block - // As message is too short, it will be padded to 32bytes but cancel code doesn't care about message content, so should be ok - BlockUtils.mintBlock(repository); - - describeAt(repository, atAddress); - - // Check AT is finished - ATData atData = repository.getATRepository().fromATAddress(atAddress); - assertTrue(atData.getIsFinished()); - - // AT should be in CANCELLED mode - CrossChainTradeData tradeData = BitcoinACCTv3.getInstance().populateTradeData(repository, atData); - assertEquals(AcctMode.CANCELLED, tradeData.mode); - } + @Override + protected byte[] buildTradeMessage(String address, byte[] publicKey, byte[] hashOfSecretA, int lockTimeA, int refundTimeout) { + return BitcoinACCTv3.buildTradeMessage(address, publicKey, hashOfSecretA, lockTimeA, refundTimeout); } - @SuppressWarnings("unused") - @Test - public void testTradingInfoProcessing() throws DataException { - try (final Repository repository = RepositoryManager.getRepository()) { - PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe"); - PrivateKeyAccount tradeAccount = createTradeAccount(repository); - - PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert"); - - long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT); - long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT); - - DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress()); - Account at = deployAtTransaction.getATAccount(); - String atAddress = at.getAddress(); - - long partnersOfferMessageTransactionTimestamp = System.currentTimeMillis(); - int lockTimeA = calcTestLockTimeA(partnersOfferMessageTransactionTimestamp); - int refundTimeout = BitcoinACCTv3.calcRefundTimeout(partnersOfferMessageTransactionTimestamp, lockTimeA); - - // Send trade info to AT - byte[] messageData = BitcoinACCTv3.buildTradeMessage(partner.getAddress(), bitcoinPublicKeyHash, hashOfSecretA, lockTimeA, refundTimeout); - MessageTransaction messageTransaction = sendMessage(repository, tradeAccount, messageData, atAddress); - - Block postDeploymentBlock = BlockUtils.mintBlock(repository); - int postDeploymentBlockHeight = postDeploymentBlock.getBlockData().getHeight(); - - long deployAtFee = deployAtTransaction.getTransactionData().getFee(); - long deployersPostDeploymentBalance = deployersInitialBalance - fundingAmount - deployAtFee; - - describeAt(repository, atAddress); - - ATData atData = repository.getATRepository().fromATAddress(atAddress); - CrossChainTradeData tradeData = BitcoinACCTv3.getInstance().populateTradeData(repository, atData); - - // AT should be in TRADE mode - assertEquals(AcctMode.TRADING, tradeData.mode); - - // Check hashOfSecretA was extracted correctly - assertTrue(Arrays.equals(hashOfSecretA, tradeData.hashOfSecretA)); - - // Check trade partner Qortal address was extracted correctly - assertEquals(partner.getAddress(), tradeData.qortalPartnerAddress); - - // Check trade partner's Bitcoin PKH was extracted correctly - assertTrue(Arrays.equals(bitcoinPublicKeyHash, tradeData.partnerForeignPKH)); - - // Test orphaning - BlockUtils.orphanToBlock(repository, postDeploymentBlockHeight); - - // Check balances - long expectedBalance = deployersPostDeploymentBalance; - long actualBalance = deployer.getConfirmedBalance(Asset.QORT); - - assertEquals("Deployer's post-orphan/pre-refund balance incorrect", expectedBalance, actualBalance); - } + @Override + protected byte[] buildRedeemMessage(byte[] secretA, String address) { + return BitcoinACCTv3.buildRedeemMessage(secretA, address); } - // TEST SENDING TRADING INFO BUT NOT FROM AT CREATOR (SHOULD BE IGNORED) - @SuppressWarnings("unused") - @Test - public void testIncorrectTradeSender() throws DataException { - try (final Repository repository = RepositoryManager.getRepository()) { - PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe"); - PrivateKeyAccount tradeAccount = createTradeAccount(repository); - - PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert"); - - PrivateKeyAccount bystander = Common.getTestAccount(repository, "bob"); - - long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT); - long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT); - - DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress()); - Account at = deployAtTransaction.getATAccount(); - String atAddress = at.getAddress(); - - long partnersOfferMessageTransactionTimestamp = System.currentTimeMillis(); - int lockTimeA = calcTestLockTimeA(partnersOfferMessageTransactionTimestamp); - int refundTimeout = BitcoinACCTv3.calcRefundTimeout(partnersOfferMessageTransactionTimestamp, lockTimeA); - - // Send trade info to AT BUT NOT FROM AT CREATOR - byte[] messageData = BitcoinACCTv3.buildTradeMessage(partner.getAddress(), bitcoinPublicKeyHash, hashOfSecretA, lockTimeA, refundTimeout); - MessageTransaction messageTransaction = sendMessage(repository, bystander, messageData, atAddress); - - BlockUtils.mintBlock(repository); - - long expectedBalance = partnersInitialBalance; - long actualBalance = partner.getConfirmedBalance(Asset.QORT); - - assertEquals("Partner's post-initial-payout balance incorrect", expectedBalance, actualBalance); - - describeAt(repository, atAddress); - - ATData atData = repository.getATRepository().fromATAddress(atAddress); - CrossChainTradeData tradeData = BitcoinACCTv3.getInstance().populateTradeData(repository, atData); - - // AT should still be in OFFER mode - assertEquals(AcctMode.OFFERING, tradeData.mode); - } + @Override + protected byte[] getCodeBytesHash() { + return BitcoinACCTv3.CODE_BYTES_HASH; } - @SuppressWarnings("unused") - @Test - public void testAutomaticTradeRefund() throws DataException { - try (final Repository repository = RepositoryManager.getRepository()) { - PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe"); - PrivateKeyAccount tradeAccount = createTradeAccount(repository); - - PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert"); - - long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT); - long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT); - - DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress()); - Account at = deployAtTransaction.getATAccount(); - String atAddress = at.getAddress(); - - long partnersOfferMessageTransactionTimestamp = System.currentTimeMillis(); - int lockTimeA = calcTestLockTimeA(partnersOfferMessageTransactionTimestamp); - int refundTimeout = BitcoinACCTv3.calcRefundTimeout(partnersOfferMessageTransactionTimestamp, lockTimeA); - - // Send trade info to AT - byte[] messageData = BitcoinACCTv3.buildTradeMessage(partner.getAddress(), bitcoinPublicKeyHash, hashOfSecretA, lockTimeA, refundTimeout); - MessageTransaction messageTransaction = sendMessage(repository, tradeAccount, messageData, atAddress); - - Block postDeploymentBlock = BlockUtils.mintBlock(repository); - int postDeploymentBlockHeight = postDeploymentBlock.getBlockData().getHeight(); - - // Check refund - long deployAtFee = deployAtTransaction.getTransactionData().getFee(); - long deployersPostDeploymentBalance = deployersInitialBalance - fundingAmount - deployAtFee; - - checkTradeRefund(repository, deployer, deployersInitialBalance, deployAtFee); - - describeAt(repository, atAddress); - - // Check AT is finished - ATData atData = repository.getATRepository().fromATAddress(atAddress); - assertTrue(atData.getIsFinished()); - - // AT should be in REFUNDED mode - CrossChainTradeData tradeData = BitcoinACCTv3.getInstance().populateTradeData(repository, atData); - assertEquals(AcctMode.REFUNDED, tradeData.mode); - - // Test orphaning - BlockUtils.orphanToBlock(repository, postDeploymentBlockHeight); - - // Check balances - long expectedBalance = deployersPostDeploymentBalance; - long actualBalance = deployer.getConfirmedBalance(Asset.QORT); - - assertEquals("Deployer's post-orphan/pre-refund balance incorrect", expectedBalance, actualBalance); - } + @Override + protected String getSymbol() { + return SYMBOL; } - @SuppressWarnings("unused") - @Test - public void testCorrectSecretCorrectSender() throws DataException { - try (final Repository repository = RepositoryManager.getRepository()) { - PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe"); - PrivateKeyAccount tradeAccount = createTradeAccount(repository); - - PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert"); - - long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT); - long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT); - - DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress()); - Account at = deployAtTransaction.getATAccount(); - String atAddress = at.getAddress(); - - long partnersOfferMessageTransactionTimestamp = System.currentTimeMillis(); - int lockTimeA = calcTestLockTimeA(partnersOfferMessageTransactionTimestamp); - int refundTimeout = BitcoinACCTv3.calcRefundTimeout(partnersOfferMessageTransactionTimestamp, lockTimeA); - - // Send trade info to AT - byte[] messageData = BitcoinACCTv3.buildTradeMessage(partner.getAddress(), bitcoinPublicKeyHash, hashOfSecretA, lockTimeA, refundTimeout); - MessageTransaction messageTransaction = sendMessage(repository, tradeAccount, messageData, atAddress); - - // Give AT time to process message - BlockUtils.mintBlock(repository); - - // Send correct secret to AT, from correct account - messageData = BitcoinACCTv3.buildRedeemMessage(secretA, partner.getAddress()); - messageTransaction = sendMessage(repository, partner, messageData, atAddress); - - // AT should send funds in the next block - ATStateData preRedeemAtStateData = repository.getATRepository().getLatestATState(atAddress); - BlockUtils.mintBlock(repository); - - describeAt(repository, atAddress); - - // Check AT is finished - ATData atData = repository.getATRepository().fromATAddress(atAddress); - assertTrue(atData.getIsFinished()); - - // AT should be in REDEEMED mode - CrossChainTradeData tradeData = BitcoinACCTv3.getInstance().populateTradeData(repository, atData); - assertEquals(AcctMode.REDEEMED, tradeData.mode); - - // Check balances - long expectedBalance = partnersInitialBalance - messageTransaction.getTransactionData().getFee() + redeemAmount; - long actualBalance = partner.getConfirmedBalance(Asset.QORT); - - assertEquals("Partner's post-redeem balance incorrect", expectedBalance, actualBalance); - - // Orphan redeem - BlockUtils.orphanLastBlock(repository); - - // Check balances - expectedBalance = partnersInitialBalance - messageTransaction.getTransactionData().getFee(); - actualBalance = partner.getConfirmedBalance(Asset.QORT); - - assertEquals("Partner's post-orphan/pre-redeem balance incorrect", expectedBalance, actualBalance); - - // Check AT state - ATStateData postOrphanAtStateData = repository.getATRepository().getLatestATState(atAddress); - - assertTrue("AT states mismatch", Arrays.equals(preRedeemAtStateData.getStateData(), postOrphanAtStateData.getStateData())); - } + @Override + protected String getName() { + return NAME; } - - @SuppressWarnings("unused") - @Test - public void testCorrectSecretIncorrectSender() throws DataException { - try (final Repository repository = RepositoryManager.getRepository()) { - PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe"); - PrivateKeyAccount tradeAccount = createTradeAccount(repository); - - PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert"); - - PrivateKeyAccount bystander = Common.getTestAccount(repository, "bob"); - - long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT); - long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT); - - DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress()); - long deployAtFee = deployAtTransaction.getTransactionData().getFee(); - - Account at = deployAtTransaction.getATAccount(); - String atAddress = at.getAddress(); - - long partnersOfferMessageTransactionTimestamp = System.currentTimeMillis(); - int lockTimeA = calcTestLockTimeA(partnersOfferMessageTransactionTimestamp); - int refundTimeout = BitcoinACCTv3.calcRefundTimeout(partnersOfferMessageTransactionTimestamp, lockTimeA); - - // Send trade info to AT - byte[] messageData = BitcoinACCTv3.buildTradeMessage(partner.getAddress(), bitcoinPublicKeyHash, hashOfSecretA, lockTimeA, refundTimeout); - MessageTransaction messageTransaction = sendMessage(repository, tradeAccount, messageData, atAddress); - - // Give AT time to process message - BlockUtils.mintBlock(repository); - - // Send correct secret to AT, but from wrong account - messageData = BitcoinACCTv3.buildRedeemMessage(secretA, partner.getAddress()); - messageTransaction = sendMessage(repository, bystander, messageData, atAddress); - - // AT should NOT send funds in the next block - ATStateData preRedeemAtStateData = repository.getATRepository().getLatestATState(atAddress); - BlockUtils.mintBlock(repository); - - describeAt(repository, atAddress); - - // Check AT is NOT finished - ATData atData = repository.getATRepository().fromATAddress(atAddress); - assertFalse(atData.getIsFinished()); - - // AT should still be in TRADE mode - CrossChainTradeData tradeData = BitcoinACCTv3.getInstance().populateTradeData(repository, atData); - assertEquals(AcctMode.TRADING, tradeData.mode); - - // Check balances - long expectedBalance = partnersInitialBalance; - long actualBalance = partner.getConfirmedBalance(Asset.QORT); - - assertEquals("Partner's balance incorrect", expectedBalance, actualBalance); - - // Check eventual refund - checkTradeRefund(repository, deployer, deployersInitialBalance, deployAtFee); - } - } - - @SuppressWarnings("unused") - @Test - public void testIncorrectSecretCorrectSender() throws DataException { - try (final Repository repository = RepositoryManager.getRepository()) { - PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe"); - PrivateKeyAccount tradeAccount = createTradeAccount(repository); - - PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert"); - - long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT); - long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT); - - DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress()); - long deployAtFee = deployAtTransaction.getTransactionData().getFee(); - - Account at = deployAtTransaction.getATAccount(); - String atAddress = at.getAddress(); - - long partnersOfferMessageTransactionTimestamp = System.currentTimeMillis(); - int lockTimeA = calcTestLockTimeA(partnersOfferMessageTransactionTimestamp); - int refundTimeout = BitcoinACCTv3.calcRefundTimeout(partnersOfferMessageTransactionTimestamp, lockTimeA); - - // Send trade info to AT - byte[] messageData = BitcoinACCTv3.buildTradeMessage(partner.getAddress(), bitcoinPublicKeyHash, hashOfSecretA, lockTimeA, refundTimeout); - MessageTransaction messageTransaction = sendMessage(repository, tradeAccount, messageData, atAddress); - - // Give AT time to process message - BlockUtils.mintBlock(repository); - - // Send incorrect secret to AT, from correct account - byte[] wrongSecret = new byte[32]; - RANDOM.nextBytes(wrongSecret); - messageData = BitcoinACCTv3.buildRedeemMessage(wrongSecret, partner.getAddress()); - messageTransaction = sendMessage(repository, partner, messageData, atAddress); - - // AT should NOT send funds in the next block - ATStateData preRedeemAtStateData = repository.getATRepository().getLatestATState(atAddress); - BlockUtils.mintBlock(repository); - - describeAt(repository, atAddress); - - // Check AT is NOT finished - ATData atData = repository.getATRepository().fromATAddress(atAddress); - assertFalse(atData.getIsFinished()); - - // AT should still be in TRADE mode - CrossChainTradeData tradeData = BitcoinACCTv3.getInstance().populateTradeData(repository, atData); - assertEquals(AcctMode.TRADING, tradeData.mode); - - long expectedBalance = partnersInitialBalance - messageTransaction.getTransactionData().getFee(); - long actualBalance = partner.getConfirmedBalance(Asset.QORT); - - assertEquals("Partner's balance incorrect", expectedBalance, actualBalance); - - // Check eventual refund - checkTradeRefund(repository, deployer, deployersInitialBalance, deployAtFee); - } - } - - @SuppressWarnings("unused") - @Test - public void testCorrectSecretCorrectSenderInvalidMessageLength() throws DataException { - try (final Repository repository = RepositoryManager.getRepository()) { - PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe"); - PrivateKeyAccount tradeAccount = createTradeAccount(repository); - - PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert"); - - long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT); - long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT); - - DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress()); - Account at = deployAtTransaction.getATAccount(); - String atAddress = at.getAddress(); - - long partnersOfferMessageTransactionTimestamp = System.currentTimeMillis(); - int lockTimeA = calcTestLockTimeA(partnersOfferMessageTransactionTimestamp); - int refundTimeout = BitcoinACCTv3.calcRefundTimeout(partnersOfferMessageTransactionTimestamp, lockTimeA); - - // Send trade info to AT - byte[] messageData = BitcoinACCTv3.buildTradeMessage(partner.getAddress(), bitcoinPublicKeyHash, hashOfSecretA, lockTimeA, refundTimeout); - MessageTransaction messageTransaction = sendMessage(repository, tradeAccount, messageData, atAddress); - - // Give AT time to process message - BlockUtils.mintBlock(repository); - - // Send correct secret to AT, from correct account, but missing receive address, hence incorrect length - messageData = Bytes.concat(secretA); - messageTransaction = sendMessage(repository, partner, messageData, atAddress); - - // AT should NOT send funds in the next block - ATStateData preRedeemAtStateData = repository.getATRepository().getLatestATState(atAddress); - BlockUtils.mintBlock(repository); - - describeAt(repository, atAddress); - - // Check AT is NOT finished - ATData atData = repository.getATRepository().fromATAddress(atAddress); - assertFalse(atData.getIsFinished()); - - // AT should be in TRADING mode - CrossChainTradeData tradeData = BitcoinACCTv3.getInstance().populateTradeData(repository, atData); - assertEquals(AcctMode.TRADING, tradeData.mode); - } - } - - @SuppressWarnings("unused") - @Test - public void testDescribeDeployed() throws DataException { - try (final Repository repository = RepositoryManager.getRepository()) { - PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe"); - PrivateKeyAccount tradeAccount = createTradeAccount(repository); - - PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert"); - - long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT); - long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT); - - DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress()); - - List executableAts = repository.getATRepository().getAllExecutableATs(); - - for (ATData atData : executableAts) { - String atAddress = atData.getATAddress(); - byte[] codeBytes = atData.getCodeBytes(); - byte[] codeHash = Crypto.digest(codeBytes); - - System.out.println(String.format("%s: code length: %d byte%s, code hash: %s", - atAddress, - codeBytes.length, - (codeBytes.length != 1 ? "s": ""), - HashCode.fromBytes(codeHash))); - - // Not one of ours? - if (!Arrays.equals(codeHash, BitcoinACCTv3.CODE_BYTES_HASH)) - continue; - - describeAt(repository, atAddress); - } - } - } - - private int calcTestLockTimeA(long messageTimestamp) { - return (int) (messageTimestamp / 1000L + tradeTimeout * 60); - } - - private DeployAtTransaction doDeploy(Repository repository, PrivateKeyAccount deployer, String tradeAddress) throws DataException { - byte[] creationBytes = BitcoinACCTv3.buildQortalAT(tradeAddress, bitcoinPublicKeyHash, redeemAmount, bitcoinAmount, tradeTimeout); - - long txTimestamp = System.currentTimeMillis(); - byte[] lastReference = deployer.getLastReference(); - - if (lastReference == null) { - System.err.println(String.format("Qortal account %s has no last reference", deployer.getAddress())); - System.exit(2); - } - - Long fee = null; - String name = "QORT-BTC cross-chain trade"; - String description = String.format("Qortal-Bitcoin cross-chain trade"); - String atType = "ACCT"; - String tags = "QORT-BTC ACCT"; - - BaseTransactionData baseTransactionData = new BaseTransactionData(txTimestamp, Group.NO_GROUP, lastReference, deployer.getPublicKey(), fee, null); - TransactionData deployAtTransactionData = new DeployAtTransactionData(baseTransactionData, name, description, atType, tags, creationBytes, fundingAmount, Asset.QORT); - - DeployAtTransaction deployAtTransaction = new DeployAtTransaction(repository, deployAtTransactionData); - - fee = deployAtTransaction.calcRecommendedFee(); - deployAtTransactionData.setFee(fee); - - TransactionUtils.signAndMint(repository, deployAtTransactionData, deployer); - - return deployAtTransaction; - } - - private MessageTransaction sendMessage(Repository repository, PrivateKeyAccount sender, byte[] data, String recipient) throws DataException { - long txTimestamp = System.currentTimeMillis(); - byte[] lastReference = sender.getLastReference(); - - if (lastReference == null) { - System.err.println(String.format("Qortal account %s has no last reference", sender.getAddress())); - System.exit(2); - } - - Long fee = null; - int version = 4; - int nonce = 0; - long amount = 0; - Long assetId = null; // because amount is zero - - BaseTransactionData baseTransactionData = new BaseTransactionData(txTimestamp, Group.NO_GROUP, lastReference, sender.getPublicKey(), fee, null); - TransactionData messageTransactionData = new MessageTransactionData(baseTransactionData, version, nonce, recipient, amount, assetId, data, false, false); - - MessageTransaction messageTransaction = new MessageTransaction(repository, messageTransactionData); - - fee = messageTransaction.calcRecommendedFee(); - messageTransactionData.setFee(fee); - - TransactionUtils.signAndMint(repository, messageTransactionData, sender); - - return messageTransaction; - } - - private void checkTradeRefund(Repository repository, Account deployer, long deployersInitialBalance, long deployAtFee) throws DataException { - long deployersPostDeploymentBalance = deployersInitialBalance - fundingAmount - deployAtFee; - int refundTimeout = tradeTimeout / 2 + 1; // close enough - - // AT should automatically refund deployer after 'refundTimeout' blocks - for (int blockCount = 0; blockCount <= refundTimeout; ++blockCount) - BlockUtils.mintBlock(repository); - - // We don't bother to exactly calculate QORT spent running AT for several blocks, but we do know the expected range - long expectedMinimumBalance = deployersPostDeploymentBalance; - long expectedMaximumBalance = deployersInitialBalance - deployAtFee; - - long actualBalance = deployer.getConfirmedBalance(Asset.QORT); - - assertTrue(String.format("Deployer's balance %s should be above minimum %s", actualBalance, expectedMinimumBalance), actualBalance > expectedMinimumBalance); - assertTrue(String.format("Deployer's balance %s should be below maximum %s", actualBalance, expectedMaximumBalance), actualBalance < expectedMaximumBalance); - } - - private void describeAt(Repository repository, String atAddress) throws DataException { - ATData atData = repository.getATRepository().fromATAddress(atAddress); - CrossChainTradeData tradeData = BitcoinACCTv3.getInstance().populateTradeData(repository, atData); - - Function epochMilliFormatter = (timestamp) -> LocalDateTime.ofInstant(Instant.ofEpochMilli(timestamp), ZoneOffset.UTC).format(DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM)); - int currentBlockHeight = repository.getBlockRepository().getBlockchainHeight(); - - System.out.print(String.format("%s:\n" - + "\tmode: %s\n" - + "\tcreator: %s,\n" - + "\tcreation timestamp: %s,\n" - + "\tcurrent balance: %s QORT,\n" - + "\tis finished: %b,\n" - + "\tredeem payout: %s QORT,\n" - + "\texpected Bitcoin: %s BTC,\n" - + "\tcurrent block height: %d,\n", - tradeData.qortalAtAddress, - tradeData.mode, - tradeData.qortalCreator, - epochMilliFormatter.apply(tradeData.creationTimestamp), - Amounts.prettyAmount(tradeData.qortBalance), - atData.getIsFinished(), - Amounts.prettyAmount(tradeData.qortAmount), - Amounts.prettyAmount(tradeData.expectedForeignAmount), - currentBlockHeight)); - - if (tradeData.mode != AcctMode.OFFERING && tradeData.mode != AcctMode.CANCELLED) { - System.out.println(String.format("\trefund timeout: %d minutes,\n" - + "\trefund height: block %d,\n" - + "\tHASH160 of secret-A: %s,\n" - + "\tBitcoin P2SH-A nLockTime: %d (%s),\n" - + "\ttrade partner: %s\n" - + "\tpartner's receiving address: %s", - tradeData.refundTimeout, - tradeData.tradeRefundHeight, - HashCode.fromBytes(tradeData.hashOfSecretA).toString().substring(0, 40), - tradeData.lockTimeA, epochMilliFormatter.apply(tradeData.lockTimeA * 1000L), - tradeData.qortalPartnerAddress, - tradeData.qortalPartnerReceivingAddress)); - } - } - - private PrivateKeyAccount createTradeAccount(Repository repository) { - // We actually use a known test account with funds to avoid PoW compute - return Common.getTestAccount(repository, "alice"); - } - } diff --git a/src/test/java/org/qortal/test/crosschain/digibytev3/DigibyteACCTv3Tests.java b/src/test/java/org/qortal/test/crosschain/digibytev3/DigibyteACCTv3Tests.java index d13aba4c..01ead678 100644 --- a/src/test/java/org/qortal/test/crosschain/digibytev3/DigibyteACCTv3Tests.java +++ b/src/test/java/org/qortal/test/crosschain/digibytev3/DigibyteACCTv3Tests.java @@ -1,769 +1,58 @@ package org.qortal.test.crosschain.digibytev3; import com.google.common.hash.HashCode; -import com.google.common.primitives.Bytes; -import org.junit.Before; -import org.junit.Test; -import org.qortal.account.Account; -import org.qortal.account.PrivateKeyAccount; -import org.qortal.asset.Asset; -import org.qortal.block.Block; -import org.qortal.crosschain.AcctMode; +import org.qortal.crosschain.ACCT; import org.qortal.crosschain.DigibyteACCTv3; -import org.qortal.crypto.Crypto; -import org.qortal.data.at.ATData; -import org.qortal.data.at.ATStateData; -import org.qortal.data.crosschain.CrossChainTradeData; -import org.qortal.data.transaction.BaseTransactionData; -import org.qortal.data.transaction.DeployAtTransactionData; -import org.qortal.data.transaction.MessageTransactionData; -import org.qortal.data.transaction.TransactionData; -import org.qortal.group.Group; -import org.qortal.repository.DataException; -import org.qortal.repository.Repository; -import org.qortal.repository.RepositoryManager; -import org.qortal.test.common.BlockUtils; -import org.qortal.test.common.Common; -import org.qortal.test.common.TransactionUtils; -import org.qortal.transaction.DeployAtTransaction; -import org.qortal.transaction.MessageTransaction; -import org.qortal.utils.Amounts; +import org.qortal.test.crosschain.ACCTTests; -import java.time.Instant; -import java.time.LocalDateTime; -import java.time.ZoneOffset; -import java.time.format.DateTimeFormatter; -import java.time.format.FormatStyle; -import java.util.Arrays; -import java.util.List; -import java.util.Random; -import java.util.function.Function; +public class DigibyteACCTv3Tests extends ACCTTests { -import static org.junit.Assert.*; - -public class DigibyteACCTv3Tests extends Common { - - public static final byte[] secretA = "This string is exactly 32 bytes!".getBytes(); - public static final byte[] hashOfSecretA = Crypto.hash160(secretA); // daf59884b4d1aec8c1b17102530909ee43c0151a public static final byte[] digibytePublicKeyHash = HashCode.fromString("bb00bb11bb22bb33bb44bb55bb66bb77bb88bb99").asBytes(); - public static final int tradeTimeout = 20; // blocks - public static final long redeemAmount = 80_40200000L; - public static final long fundingAmount = 123_45600000L; - public static final long digibyteAmount = 864200L; // 0.00864200 DGB + private static final String SYMBOL = "DGB"; + private static final String NAME = "DigiByte"; - private static final Random RANDOM = new Random(); - - @Before - public void beforeTest() throws DataException { - Common.useDefaultSettings(); + @Override + protected byte[] getPublicKey() { + return digibytePublicKeyHash; } - @Test - public void testCompile() { - PrivateKeyAccount tradeAccount = createTradeAccount(null); - - byte[] creationBytes = DigibyteACCTv3.buildQortalAT(tradeAccount.getAddress(), digibytePublicKeyHash, redeemAmount, digibyteAmount, tradeTimeout); - assertNotNull(creationBytes); - - System.out.println("AT creation bytes: " + HashCode.fromBytes(creationBytes).toString()); + @Override + protected byte[] buildQortalAT(String address, byte[] publicKey, long redeemAmount, long foreignAmount, int tradeTimeout) { + return DigibyteACCTv3.buildQortalAT(address, publicKey, redeemAmount, foreignAmount, tradeTimeout); } - @Test - public void testDeploy() throws DataException { - try (final Repository repository = RepositoryManager.getRepository()) { - PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe"); - PrivateKeyAccount tradeAccount = createTradeAccount(repository); - - PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert"); - - long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT); - long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT); - - DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress()); - - long expectedBalance = deployersInitialBalance - fundingAmount - deployAtTransaction.getTransactionData().getFee(); - long actualBalance = deployer.getConfirmedBalance(Asset.QORT); - - assertEquals("Deployer's post-deployment balance incorrect", expectedBalance, actualBalance); - - expectedBalance = fundingAmount; - actualBalance = deployAtTransaction.getATAccount().getConfirmedBalance(Asset.QORT); - - assertEquals("AT's post-deployment balance incorrect", expectedBalance, actualBalance); - - expectedBalance = partnersInitialBalance; - actualBalance = partner.getConfirmedBalance(Asset.QORT); - - assertEquals("Partner's post-deployment balance incorrect", expectedBalance, actualBalance); - - // Test orphaning - BlockUtils.orphanLastBlock(repository); - - expectedBalance = deployersInitialBalance; - actualBalance = deployer.getConfirmedBalance(Asset.QORT); - - assertEquals("Deployer's post-orphan/pre-deployment balance incorrect", expectedBalance, actualBalance); - - expectedBalance = 0; - actualBalance = deployAtTransaction.getATAccount().getConfirmedBalance(Asset.QORT); - - assertEquals("AT's post-orphan/pre-deployment balance incorrect", expectedBalance, actualBalance); - - expectedBalance = partnersInitialBalance; - actualBalance = partner.getConfirmedBalance(Asset.QORT); - - assertEquals("Partner's post-orphan/pre-deployment balance incorrect", expectedBalance, actualBalance); - } + @Override + protected ACCT getInstance() { + return DigibyteACCTv3.getInstance(); } - @SuppressWarnings("unused") - @Test - public void testOfferCancel() throws DataException { - try (final Repository repository = RepositoryManager.getRepository()) { - PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe"); - PrivateKeyAccount tradeAccount = createTradeAccount(repository); - - PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert"); - - long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT); - long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT); - - DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress()); - Account at = deployAtTransaction.getATAccount(); - String atAddress = at.getAddress(); - - long deployAtFee = deployAtTransaction.getTransactionData().getFee(); - long deployersPostDeploymentBalance = deployersInitialBalance - fundingAmount - deployAtFee; - - // Send creator's address to AT, instead of typical partner's address - byte[] messageData = DigibyteACCTv3.getInstance().buildCancelMessage(deployer.getAddress()); - MessageTransaction messageTransaction = sendMessage(repository, deployer, messageData, atAddress); - long messageFee = messageTransaction.getTransactionData().getFee(); - - // AT should process 'cancel' message in next block - BlockUtils.mintBlock(repository); - - describeAt(repository, atAddress); - - // Check AT is finished - ATData atData = repository.getATRepository().fromATAddress(atAddress); - assertTrue(atData.getIsFinished()); - - // AT should be in CANCELLED mode - CrossChainTradeData tradeData = DigibyteACCTv3.getInstance().populateTradeData(repository, atData); - assertEquals(AcctMode.CANCELLED, tradeData.mode); - - // Check balances - long expectedMinimumBalance = deployersPostDeploymentBalance; - long expectedMaximumBalance = deployersInitialBalance - deployAtFee - messageFee; - - long actualBalance = deployer.getConfirmedBalance(Asset.QORT); - - assertTrue(String.format("Deployer's balance %s should be above minimum %s", actualBalance, expectedMinimumBalance), actualBalance > expectedMinimumBalance); - assertTrue(String.format("Deployer's balance %s should be below maximum %s", actualBalance, expectedMaximumBalance), actualBalance < expectedMaximumBalance); - - // Test orphaning - BlockUtils.orphanLastBlock(repository); - - // Check balances - long expectedBalance = deployersPostDeploymentBalance - messageFee; - actualBalance = deployer.getConfirmedBalance(Asset.QORT); - - assertEquals("Deployer's post-orphan/pre-refund balance incorrect", expectedBalance, actualBalance); - } + @Override + protected int calcRefundTimeout(long partnersOfferMessageTransactionTimestamp, int lockTimeA) { + return DigibyteACCTv3.calcRefundTimeout(partnersOfferMessageTransactionTimestamp, lockTimeA); } - @SuppressWarnings("unused") - @Test - public void testOfferCancelInvalidLength() throws DataException { - try (final Repository repository = RepositoryManager.getRepository()) { - PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe"); - PrivateKeyAccount tradeAccount = createTradeAccount(repository); - - PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert"); - - long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT); - long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT); - - DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress()); - Account at = deployAtTransaction.getATAccount(); - String atAddress = at.getAddress(); - - long deployAtFee = deployAtTransaction.getTransactionData().getFee(); - long deployersPostDeploymentBalance = deployersInitialBalance - fundingAmount - deployAtFee; - - // Instead of sending creator's address to AT, send too-short/invalid message - byte[] messageData = new byte[7]; - RANDOM.nextBytes(messageData); - MessageTransaction messageTransaction = sendMessage(repository, deployer, messageData, atAddress); - long messageFee = messageTransaction.getTransactionData().getFee(); - - // AT should process 'cancel' message in next block - // As message is too short, it will be padded to 32bytes but cancel code doesn't care about message content, so should be ok - BlockUtils.mintBlock(repository); - - describeAt(repository, atAddress); - - // Check AT is finished - ATData atData = repository.getATRepository().fromATAddress(atAddress); - assertTrue(atData.getIsFinished()); - - // AT should be in CANCELLED mode - CrossChainTradeData tradeData = DigibyteACCTv3.getInstance().populateTradeData(repository, atData); - assertEquals(AcctMode.CANCELLED, tradeData.mode); - } + @Override + protected byte[] buildTradeMessage(String address, byte[] publicKey, byte[] hashOfSecretA, int lockTimeA, int refundTimeout) { + return DigibyteACCTv3.buildTradeMessage(address, publicKey, hashOfSecretA, lockTimeA, refundTimeout); } - @SuppressWarnings("unused") - @Test - public void testTradingInfoProcessing() throws DataException { - try (final Repository repository = RepositoryManager.getRepository()) { - PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe"); - PrivateKeyAccount tradeAccount = createTradeAccount(repository); - - PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert"); - - long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT); - long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT); - - DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress()); - Account at = deployAtTransaction.getATAccount(); - String atAddress = at.getAddress(); - - long partnersOfferMessageTransactionTimestamp = System.currentTimeMillis(); - int lockTimeA = calcTestLockTimeA(partnersOfferMessageTransactionTimestamp); - int refundTimeout = DigibyteACCTv3.calcRefundTimeout(partnersOfferMessageTransactionTimestamp, lockTimeA); - - // Send trade info to AT - byte[] messageData = DigibyteACCTv3.buildTradeMessage(partner.getAddress(), digibytePublicKeyHash, hashOfSecretA, lockTimeA, refundTimeout); - MessageTransaction messageTransaction = sendMessage(repository, tradeAccount, messageData, atAddress); - - Block postDeploymentBlock = BlockUtils.mintBlock(repository); - int postDeploymentBlockHeight = postDeploymentBlock.getBlockData().getHeight(); - - long deployAtFee = deployAtTransaction.getTransactionData().getFee(); - long deployersPostDeploymentBalance = deployersInitialBalance - fundingAmount - deployAtFee; - - describeAt(repository, atAddress); - - ATData atData = repository.getATRepository().fromATAddress(atAddress); - CrossChainTradeData tradeData = DigibyteACCTv3.getInstance().populateTradeData(repository, atData); - - // AT should be in TRADE mode - assertEquals(AcctMode.TRADING, tradeData.mode); - - // Check hashOfSecretA was extracted correctly - assertTrue(Arrays.equals(hashOfSecretA, tradeData.hashOfSecretA)); - - // Check trade partner Qortal address was extracted correctly - assertEquals(partner.getAddress(), tradeData.qortalPartnerAddress); - - // Check trade partner's digibyte PKH was extracted correctly - assertTrue(Arrays.equals(digibytePublicKeyHash, tradeData.partnerForeignPKH)); - - // Test orphaning - BlockUtils.orphanToBlock(repository, postDeploymentBlockHeight); - - // Check balances - long expectedBalance = deployersPostDeploymentBalance; - long actualBalance = deployer.getConfirmedBalance(Asset.QORT); - - assertEquals("Deployer's post-orphan/pre-refund balance incorrect", expectedBalance, actualBalance); - } + @Override + protected byte[] buildRedeemMessage(byte[] secretA, String address) { + return DigibyteACCTv3.buildRedeemMessage(secretA, address); } - // TEST SENDING TRADING INFO BUT NOT FROM AT CREATOR (SHOULD BE IGNORED) - @SuppressWarnings("unused") - @Test - public void testIncorrectTradeSender() throws DataException { - try (final Repository repository = RepositoryManager.getRepository()) { - PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe"); - PrivateKeyAccount tradeAccount = createTradeAccount(repository); - - PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert"); - - PrivateKeyAccount bystander = Common.getTestAccount(repository, "bob"); - - long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT); - long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT); - - DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress()); - Account at = deployAtTransaction.getATAccount(); - String atAddress = at.getAddress(); - - long partnersOfferMessageTransactionTimestamp = System.currentTimeMillis(); - int lockTimeA = calcTestLockTimeA(partnersOfferMessageTransactionTimestamp); - int refundTimeout = DigibyteACCTv3.calcRefundTimeout(partnersOfferMessageTransactionTimestamp, lockTimeA); - - // Send trade info to AT BUT NOT FROM AT CREATOR - byte[] messageData = DigibyteACCTv3.buildTradeMessage(partner.getAddress(), digibytePublicKeyHash, hashOfSecretA, lockTimeA, refundTimeout); - MessageTransaction messageTransaction = sendMessage(repository, bystander, messageData, atAddress); - - BlockUtils.mintBlock(repository); - - long expectedBalance = partnersInitialBalance; - long actualBalance = partner.getConfirmedBalance(Asset.QORT); - - assertEquals("Partner's post-initial-payout balance incorrect", expectedBalance, actualBalance); - - describeAt(repository, atAddress); - - ATData atData = repository.getATRepository().fromATAddress(atAddress); - CrossChainTradeData tradeData = DigibyteACCTv3.getInstance().populateTradeData(repository, atData); - - // AT should still be in OFFER mode - assertEquals(AcctMode.OFFERING, tradeData.mode); - } + @Override + protected byte[] getCodeBytesHash() { + return DigibyteACCTv3.CODE_BYTES_HASH; } - @SuppressWarnings("unused") - @Test - public void testAutomaticTradeRefund() throws DataException { - try (final Repository repository = RepositoryManager.getRepository()) { - PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe"); - PrivateKeyAccount tradeAccount = createTradeAccount(repository); - - PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert"); - - long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT); - long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT); - - DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress()); - Account at = deployAtTransaction.getATAccount(); - String atAddress = at.getAddress(); - - long partnersOfferMessageTransactionTimestamp = System.currentTimeMillis(); - int lockTimeA = calcTestLockTimeA(partnersOfferMessageTransactionTimestamp); - int refundTimeout = DigibyteACCTv3.calcRefundTimeout(partnersOfferMessageTransactionTimestamp, lockTimeA); - - // Send trade info to AT - byte[] messageData = DigibyteACCTv3.buildTradeMessage(partner.getAddress(), digibytePublicKeyHash, hashOfSecretA, lockTimeA, refundTimeout); - MessageTransaction messageTransaction = sendMessage(repository, tradeAccount, messageData, atAddress); - - Block postDeploymentBlock = BlockUtils.mintBlock(repository); - int postDeploymentBlockHeight = postDeploymentBlock.getBlockData().getHeight(); - - // Check refund - long deployAtFee = deployAtTransaction.getTransactionData().getFee(); - long deployersPostDeploymentBalance = deployersInitialBalance - fundingAmount - deployAtFee; - - checkTradeRefund(repository, deployer, deployersInitialBalance, deployAtFee); - - describeAt(repository, atAddress); - - // Check AT is finished - ATData atData = repository.getATRepository().fromATAddress(atAddress); - assertTrue(atData.getIsFinished()); - - // AT should be in REFUNDED mode - CrossChainTradeData tradeData = DigibyteACCTv3.getInstance().populateTradeData(repository, atData); - assertEquals(AcctMode.REFUNDED, tradeData.mode); - - // Test orphaning - BlockUtils.orphanToBlock(repository, postDeploymentBlockHeight); - - // Check balances - long expectedBalance = deployersPostDeploymentBalance; - long actualBalance = deployer.getConfirmedBalance(Asset.QORT); - - assertEquals("Deployer's post-orphan/pre-refund balance incorrect", expectedBalance, actualBalance); - } + @Override + protected String getSymbol() { + return SYMBOL; } - @SuppressWarnings("unused") - @Test - public void testCorrectSecretCorrectSender() throws DataException { - try (final Repository repository = RepositoryManager.getRepository()) { - PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe"); - PrivateKeyAccount tradeAccount = createTradeAccount(repository); - - PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert"); - - long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT); - long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT); - - DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress()); - Account at = deployAtTransaction.getATAccount(); - String atAddress = at.getAddress(); - - long partnersOfferMessageTransactionTimestamp = System.currentTimeMillis(); - int lockTimeA = calcTestLockTimeA(partnersOfferMessageTransactionTimestamp); - int refundTimeout = DigibyteACCTv3.calcRefundTimeout(partnersOfferMessageTransactionTimestamp, lockTimeA); - - // Send trade info to AT - byte[] messageData = DigibyteACCTv3.buildTradeMessage(partner.getAddress(), digibytePublicKeyHash, hashOfSecretA, lockTimeA, refundTimeout); - MessageTransaction messageTransaction = sendMessage(repository, tradeAccount, messageData, atAddress); - - // Give AT time to process message - BlockUtils.mintBlock(repository); - - // Send correct secret to AT, from correct account - messageData = DigibyteACCTv3.buildRedeemMessage(secretA, partner.getAddress()); - messageTransaction = sendMessage(repository, partner, messageData, atAddress); - - // AT should send funds in the next block - ATStateData preRedeemAtStateData = repository.getATRepository().getLatestATState(atAddress); - BlockUtils.mintBlock(repository); - - describeAt(repository, atAddress); - - // Check AT is finished - ATData atData = repository.getATRepository().fromATAddress(atAddress); - assertTrue(atData.getIsFinished()); - - // AT should be in REDEEMED mode - CrossChainTradeData tradeData = DigibyteACCTv3.getInstance().populateTradeData(repository, atData); - assertEquals(AcctMode.REDEEMED, tradeData.mode); - - // Check balances - long expectedBalance = partnersInitialBalance - messageTransaction.getTransactionData().getFee() + redeemAmount; - long actualBalance = partner.getConfirmedBalance(Asset.QORT); - - assertEquals("Partner's post-redeem balance incorrect", expectedBalance, actualBalance); - - // Orphan redeem - BlockUtils.orphanLastBlock(repository); - - // Check balances - expectedBalance = partnersInitialBalance - messageTransaction.getTransactionData().getFee(); - actualBalance = partner.getConfirmedBalance(Asset.QORT); - - assertEquals("Partner's post-orphan/pre-redeem balance incorrect", expectedBalance, actualBalance); - - // Check AT state - ATStateData postOrphanAtStateData = repository.getATRepository().getLatestATState(atAddress); - - assertTrue("AT states mismatch", Arrays.equals(preRedeemAtStateData.getStateData(), postOrphanAtStateData.getStateData())); - } + @Override + protected String getName() { + return NAME; } - - @SuppressWarnings("unused") - @Test - public void testCorrectSecretIncorrectSender() throws DataException { - try (final Repository repository = RepositoryManager.getRepository()) { - PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe"); - PrivateKeyAccount tradeAccount = createTradeAccount(repository); - - PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert"); - - PrivateKeyAccount bystander = Common.getTestAccount(repository, "bob"); - - long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT); - long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT); - - DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress()); - long deployAtFee = deployAtTransaction.getTransactionData().getFee(); - - Account at = deployAtTransaction.getATAccount(); - String atAddress = at.getAddress(); - - long partnersOfferMessageTransactionTimestamp = System.currentTimeMillis(); - int lockTimeA = calcTestLockTimeA(partnersOfferMessageTransactionTimestamp); - int refundTimeout = DigibyteACCTv3.calcRefundTimeout(partnersOfferMessageTransactionTimestamp, lockTimeA); - - // Send trade info to AT - byte[] messageData = DigibyteACCTv3.buildTradeMessage(partner.getAddress(), digibytePublicKeyHash, hashOfSecretA, lockTimeA, refundTimeout); - MessageTransaction messageTransaction = sendMessage(repository, tradeAccount, messageData, atAddress); - - // Give AT time to process message - BlockUtils.mintBlock(repository); - - // Send correct secret to AT, but from wrong account - messageData = DigibyteACCTv3.buildRedeemMessage(secretA, partner.getAddress()); - messageTransaction = sendMessage(repository, bystander, messageData, atAddress); - - // AT should NOT send funds in the next block - ATStateData preRedeemAtStateData = repository.getATRepository().getLatestATState(atAddress); - BlockUtils.mintBlock(repository); - - describeAt(repository, atAddress); - - // Check AT is NOT finished - ATData atData = repository.getATRepository().fromATAddress(atAddress); - assertFalse(atData.getIsFinished()); - - // AT should still be in TRADE mode - CrossChainTradeData tradeData = DigibyteACCTv3.getInstance().populateTradeData(repository, atData); - assertEquals(AcctMode.TRADING, tradeData.mode); - - // Check balances - long expectedBalance = partnersInitialBalance; - long actualBalance = partner.getConfirmedBalance(Asset.QORT); - - assertEquals("Partner's balance incorrect", expectedBalance, actualBalance); - - // Check eventual refund - checkTradeRefund(repository, deployer, deployersInitialBalance, deployAtFee); - } - } - - @SuppressWarnings("unused") - @Test - public void testIncorrectSecretCorrectSender() throws DataException { - try (final Repository repository = RepositoryManager.getRepository()) { - PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe"); - PrivateKeyAccount tradeAccount = createTradeAccount(repository); - - PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert"); - - long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT); - long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT); - - DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress()); - long deployAtFee = deployAtTransaction.getTransactionData().getFee(); - - Account at = deployAtTransaction.getATAccount(); - String atAddress = at.getAddress(); - - long partnersOfferMessageTransactionTimestamp = System.currentTimeMillis(); - int lockTimeA = calcTestLockTimeA(partnersOfferMessageTransactionTimestamp); - int refundTimeout = DigibyteACCTv3.calcRefundTimeout(partnersOfferMessageTransactionTimestamp, lockTimeA); - - // Send trade info to AT - byte[] messageData = DigibyteACCTv3.buildTradeMessage(partner.getAddress(), digibytePublicKeyHash, hashOfSecretA, lockTimeA, refundTimeout); - MessageTransaction messageTransaction = sendMessage(repository, tradeAccount, messageData, atAddress); - - // Give AT time to process message - BlockUtils.mintBlock(repository); - - // Send incorrect secret to AT, from correct account - byte[] wrongSecret = new byte[32]; - RANDOM.nextBytes(wrongSecret); - messageData = DigibyteACCTv3.buildRedeemMessage(wrongSecret, partner.getAddress()); - messageTransaction = sendMessage(repository, partner, messageData, atAddress); - - // AT should NOT send funds in the next block - ATStateData preRedeemAtStateData = repository.getATRepository().getLatestATState(atAddress); - BlockUtils.mintBlock(repository); - - describeAt(repository, atAddress); - - // Check AT is NOT finished - ATData atData = repository.getATRepository().fromATAddress(atAddress); - assertFalse(atData.getIsFinished()); - - // AT should still be in TRADE mode - CrossChainTradeData tradeData = DigibyteACCTv3.getInstance().populateTradeData(repository, atData); - assertEquals(AcctMode.TRADING, tradeData.mode); - - long expectedBalance = partnersInitialBalance - messageTransaction.getTransactionData().getFee(); - long actualBalance = partner.getConfirmedBalance(Asset.QORT); - - assertEquals("Partner's balance incorrect", expectedBalance, actualBalance); - - // Check eventual refund - checkTradeRefund(repository, deployer, deployersInitialBalance, deployAtFee); - } - } - - @SuppressWarnings("unused") - @Test - public void testCorrectSecretCorrectSenderInvalidMessageLength() throws DataException { - try (final Repository repository = RepositoryManager.getRepository()) { - PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe"); - PrivateKeyAccount tradeAccount = createTradeAccount(repository); - - PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert"); - - long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT); - long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT); - - DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress()); - Account at = deployAtTransaction.getATAccount(); - String atAddress = at.getAddress(); - - long partnersOfferMessageTransactionTimestamp = System.currentTimeMillis(); - int lockTimeA = calcTestLockTimeA(partnersOfferMessageTransactionTimestamp); - int refundTimeout = DigibyteACCTv3.calcRefundTimeout(partnersOfferMessageTransactionTimestamp, lockTimeA); - - // Send trade info to AT - byte[] messageData = DigibyteACCTv3.buildTradeMessage(partner.getAddress(), digibytePublicKeyHash, hashOfSecretA, lockTimeA, refundTimeout); - MessageTransaction messageTransaction = sendMessage(repository, tradeAccount, messageData, atAddress); - - // Give AT time to process message - BlockUtils.mintBlock(repository); - - // Send correct secret to AT, from correct account, but missing receive address, hence incorrect length - messageData = Bytes.concat(secretA); - messageTransaction = sendMessage(repository, partner, messageData, atAddress); - - // AT should NOT send funds in the next block - ATStateData preRedeemAtStateData = repository.getATRepository().getLatestATState(atAddress); - BlockUtils.mintBlock(repository); - - describeAt(repository, atAddress); - - // Check AT is NOT finished - ATData atData = repository.getATRepository().fromATAddress(atAddress); - assertFalse(atData.getIsFinished()); - - // AT should be in TRADING mode - CrossChainTradeData tradeData = DigibyteACCTv3.getInstance().populateTradeData(repository, atData); - assertEquals(AcctMode.TRADING, tradeData.mode); - } - } - - @SuppressWarnings("unused") - @Test - public void testDescribeDeployed() throws DataException { - try (final Repository repository = RepositoryManager.getRepository()) { - PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe"); - PrivateKeyAccount tradeAccount = createTradeAccount(repository); - - PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert"); - - long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT); - long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT); - - DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress()); - - List executableAts = repository.getATRepository().getAllExecutableATs(); - - for (ATData atData : executableAts) { - String atAddress = atData.getATAddress(); - byte[] codeBytes = atData.getCodeBytes(); - byte[] codeHash = Crypto.digest(codeBytes); - - System.out.println(String.format("%s: code length: %d byte%s, code hash: %s", - atAddress, - codeBytes.length, - (codeBytes.length != 1 ? "s": ""), - HashCode.fromBytes(codeHash))); - - // Not one of ours? - if (!Arrays.equals(codeHash, DigibyteACCTv3.CODE_BYTES_HASH)) - continue; - - describeAt(repository, atAddress); - } - } - } - - private int calcTestLockTimeA(long messageTimestamp) { - return (int) (messageTimestamp / 1000L + tradeTimeout * 60); - } - - private DeployAtTransaction doDeploy(Repository repository, PrivateKeyAccount deployer, String tradeAddress) throws DataException { - byte[] creationBytes = DigibyteACCTv3.buildQortalAT(tradeAddress, digibytePublicKeyHash, redeemAmount, digibyteAmount, tradeTimeout); - - long txTimestamp = System.currentTimeMillis(); - byte[] lastReference = deployer.getLastReference(); - - if (lastReference == null) { - System.err.println(String.format("Qortal account %s has no last reference", deployer.getAddress())); - System.exit(2); - } - - Long fee = null; - String name = "QORT-DGB cross-chain trade"; - String description = String.format("Qortal-Digibyte cross-chain trade"); - String atType = "ACCT"; - String tags = "QORT-DGB ACCT"; - - BaseTransactionData baseTransactionData = new BaseTransactionData(txTimestamp, Group.NO_GROUP, lastReference, deployer.getPublicKey(), fee, null); - TransactionData deployAtTransactionData = new DeployAtTransactionData(baseTransactionData, name, description, atType, tags, creationBytes, fundingAmount, Asset.QORT); - - DeployAtTransaction deployAtTransaction = new DeployAtTransaction(repository, deployAtTransactionData); - - fee = deployAtTransaction.calcRecommendedFee(); - deployAtTransactionData.setFee(fee); - - TransactionUtils.signAndMint(repository, deployAtTransactionData, deployer); - - return deployAtTransaction; - } - - private MessageTransaction sendMessage(Repository repository, PrivateKeyAccount sender, byte[] data, String recipient) throws DataException { - long txTimestamp = System.currentTimeMillis(); - byte[] lastReference = sender.getLastReference(); - - if (lastReference == null) { - System.err.println(String.format("Qortal account %s has no last reference", sender.getAddress())); - System.exit(2); - } - - Long fee = null; - int version = 4; - int nonce = 0; - long amount = 0; - Long assetId = null; // because amount is zero - - BaseTransactionData baseTransactionData = new BaseTransactionData(txTimestamp, Group.NO_GROUP, lastReference, sender.getPublicKey(), fee, null); - TransactionData messageTransactionData = new MessageTransactionData(baseTransactionData, version, nonce, recipient, amount, assetId, data, false, false); - - MessageTransaction messageTransaction = new MessageTransaction(repository, messageTransactionData); - - fee = messageTransaction.calcRecommendedFee(); - messageTransactionData.setFee(fee); - - TransactionUtils.signAndMint(repository, messageTransactionData, sender); - - return messageTransaction; - } - - private void checkTradeRefund(Repository repository, Account deployer, long deployersInitialBalance, long deployAtFee) throws DataException { - long deployersPostDeploymentBalance = deployersInitialBalance - fundingAmount - deployAtFee; - int refundTimeout = tradeTimeout / 2 + 1; // close enough - - // AT should automatically refund deployer after 'refundTimeout' blocks - for (int blockCount = 0; blockCount <= refundTimeout; ++blockCount) - BlockUtils.mintBlock(repository); - - // We don't bother to exactly calculate QORT spent running AT for several blocks, but we do know the expected range - long expectedMinimumBalance = deployersPostDeploymentBalance; - long expectedMaximumBalance = deployersInitialBalance - deployAtFee; - - long actualBalance = deployer.getConfirmedBalance(Asset.QORT); - - assertTrue(String.format("Deployer's balance %s should be above minimum %s", actualBalance, expectedMinimumBalance), actualBalance > expectedMinimumBalance); - assertTrue(String.format("Deployer's balance %s should be below maximum %s", actualBalance, expectedMaximumBalance), actualBalance < expectedMaximumBalance); - } - - private void describeAt(Repository repository, String atAddress) throws DataException { - ATData atData = repository.getATRepository().fromATAddress(atAddress); - CrossChainTradeData tradeData = DigibyteACCTv3.getInstance().populateTradeData(repository, atData); - - Function epochMilliFormatter = (timestamp) -> LocalDateTime.ofInstant(Instant.ofEpochMilli(timestamp), ZoneOffset.UTC).format(DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM)); - int currentBlockHeight = repository.getBlockRepository().getBlockchainHeight(); - - System.out.print(String.format("%s:\n" - + "\tmode: %s\n" - + "\tcreator: %s,\n" - + "\tcreation timestamp: %s,\n" - + "\tcurrent balance: %s QORT,\n" - + "\tis finished: %b,\n" - + "\tredeem payout: %s QORT,\n" - + "\texpected digibyte: %s DGB,\n" - + "\tcurrent block height: %d,\n", - tradeData.qortalAtAddress, - tradeData.mode, - tradeData.qortalCreator, - epochMilliFormatter.apply(tradeData.creationTimestamp), - Amounts.prettyAmount(tradeData.qortBalance), - atData.getIsFinished(), - Amounts.prettyAmount(tradeData.qortAmount), - Amounts.prettyAmount(tradeData.expectedForeignAmount), - currentBlockHeight)); - - if (tradeData.mode != AcctMode.OFFERING && tradeData.mode != AcctMode.CANCELLED) { - System.out.println(String.format("\trefund timeout: %d minutes,\n" - + "\trefund height: block %d,\n" - + "\tHASH160 of secret-A: %s,\n" - + "\tDigibyte P2SH-A nLockTime: %d (%s),\n" - + "\ttrade partner: %s\n" - + "\tpartner's receiving address: %s", - tradeData.refundTimeout, - tradeData.tradeRefundHeight, - HashCode.fromBytes(tradeData.hashOfSecretA).toString().substring(0, 40), - tradeData.lockTimeA, epochMilliFormatter.apply(tradeData.lockTimeA * 1000L), - tradeData.qortalPartnerAddress, - tradeData.qortalPartnerReceivingAddress)); - } - } - - private PrivateKeyAccount createTradeAccount(Repository repository) { - // We actually use a known test account with funds to avoid PoW compute - return Common.getTestAccount(repository, "alice"); - } - } diff --git a/src/test/java/org/qortal/test/crosschain/dogecoinv3/DogecoinACCTv3Tests.java b/src/test/java/org/qortal/test/crosschain/dogecoinv3/DogecoinACCTv3Tests.java index 7056e433..551173f7 100644 --- a/src/test/java/org/qortal/test/crosschain/dogecoinv3/DogecoinACCTv3Tests.java +++ b/src/test/java/org/qortal/test/crosschain/dogecoinv3/DogecoinACCTv3Tests.java @@ -1,769 +1,58 @@ package org.qortal.test.crosschain.dogecoinv3; import com.google.common.hash.HashCode; -import com.google.common.primitives.Bytes; -import org.junit.Before; -import org.junit.Test; -import org.qortal.account.Account; -import org.qortal.account.PrivateKeyAccount; -import org.qortal.asset.Asset; -import org.qortal.block.Block; -import org.qortal.crosschain.AcctMode; +import org.qortal.crosschain.ACCT; import org.qortal.crosschain.DogecoinACCTv3; -import org.qortal.crypto.Crypto; -import org.qortal.data.at.ATData; -import org.qortal.data.at.ATStateData; -import org.qortal.data.crosschain.CrossChainTradeData; -import org.qortal.data.transaction.BaseTransactionData; -import org.qortal.data.transaction.DeployAtTransactionData; -import org.qortal.data.transaction.MessageTransactionData; -import org.qortal.data.transaction.TransactionData; -import org.qortal.group.Group; -import org.qortal.repository.DataException; -import org.qortal.repository.Repository; -import org.qortal.repository.RepositoryManager; -import org.qortal.test.common.BlockUtils; -import org.qortal.test.common.Common; -import org.qortal.test.common.TransactionUtils; -import org.qortal.transaction.DeployAtTransaction; -import org.qortal.transaction.MessageTransaction; -import org.qortal.utils.Amounts; +import org.qortal.test.crosschain.ACCTTests; -import java.time.Instant; -import java.time.LocalDateTime; -import java.time.ZoneOffset; -import java.time.format.DateTimeFormatter; -import java.time.format.FormatStyle; -import java.util.Arrays; -import java.util.List; -import java.util.Random; -import java.util.function.Function; +public class DogecoinACCTv3Tests extends ACCTTests { -import static org.junit.Assert.*; - -public class DogecoinACCTv3Tests extends Common { - - public static final byte[] secretA = "This string is exactly 32 bytes!".getBytes(); - public static final byte[] hashOfSecretA = Crypto.hash160(secretA); // daf59884b4d1aec8c1b17102530909ee43c0151a public static final byte[] dogecoinPublicKeyHash = HashCode.fromString("bb00bb11bb22bb33bb44bb55bb66bb77bb88bb99").asBytes(); - public static final int tradeTimeout = 20; // blocks - public static final long redeemAmount = 80_40200000L; - public static final long fundingAmount = 123_45600000L; - public static final long dogecoinAmount = 864200L; // 0.00864200 DOGE + private static final String SYMBOL = "DOGE"; + private static final String NAME = "Dogecoin"; - private static final Random RANDOM = new Random(); - - @Before - public void beforeTest() throws DataException { - Common.useDefaultSettings(); + @Override + protected byte[] getPublicKey() { + return dogecoinPublicKeyHash; } - @Test - public void testCompile() { - PrivateKeyAccount tradeAccount = createTradeAccount(null); - - byte[] creationBytes = DogecoinACCTv3.buildQortalAT(tradeAccount.getAddress(), dogecoinPublicKeyHash, redeemAmount, dogecoinAmount, tradeTimeout); - assertNotNull(creationBytes); - - System.out.println("AT creation bytes: " + HashCode.fromBytes(creationBytes).toString()); + @Override + protected byte[] buildQortalAT(String address, byte[] publicKey, long redeemAmount, long foreignAmount, int tradeTimeout) { + return DogecoinACCTv3.buildQortalAT(address, publicKey, redeemAmount, foreignAmount, tradeTimeout); } - @Test - public void testDeploy() throws DataException { - try (final Repository repository = RepositoryManager.getRepository()) { - PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe"); - PrivateKeyAccount tradeAccount = createTradeAccount(repository); - - PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert"); - - long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT); - long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT); - - DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress()); - - long expectedBalance = deployersInitialBalance - fundingAmount - deployAtTransaction.getTransactionData().getFee(); - long actualBalance = deployer.getConfirmedBalance(Asset.QORT); - - assertEquals("Deployer's post-deployment balance incorrect", expectedBalance, actualBalance); - - expectedBalance = fundingAmount; - actualBalance = deployAtTransaction.getATAccount().getConfirmedBalance(Asset.QORT); - - assertEquals("AT's post-deployment balance incorrect", expectedBalance, actualBalance); - - expectedBalance = partnersInitialBalance; - actualBalance = partner.getConfirmedBalance(Asset.QORT); - - assertEquals("Partner's post-deployment balance incorrect", expectedBalance, actualBalance); - - // Test orphaning - BlockUtils.orphanLastBlock(repository); - - expectedBalance = deployersInitialBalance; - actualBalance = deployer.getConfirmedBalance(Asset.QORT); - - assertEquals("Deployer's post-orphan/pre-deployment balance incorrect", expectedBalance, actualBalance); - - expectedBalance = 0; - actualBalance = deployAtTransaction.getATAccount().getConfirmedBalance(Asset.QORT); - - assertEquals("AT's post-orphan/pre-deployment balance incorrect", expectedBalance, actualBalance); - - expectedBalance = partnersInitialBalance; - actualBalance = partner.getConfirmedBalance(Asset.QORT); - - assertEquals("Partner's post-orphan/pre-deployment balance incorrect", expectedBalance, actualBalance); - } + @Override + protected ACCT getInstance() { + return DogecoinACCTv3.getInstance(); } - @SuppressWarnings("unused") - @Test - public void testOfferCancel() throws DataException { - try (final Repository repository = RepositoryManager.getRepository()) { - PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe"); - PrivateKeyAccount tradeAccount = createTradeAccount(repository); - - PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert"); - - long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT); - long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT); - - DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress()); - Account at = deployAtTransaction.getATAccount(); - String atAddress = at.getAddress(); - - long deployAtFee = deployAtTransaction.getTransactionData().getFee(); - long deployersPostDeploymentBalance = deployersInitialBalance - fundingAmount - deployAtFee; - - // Send creator's address to AT, instead of typical partner's address - byte[] messageData = DogecoinACCTv3.getInstance().buildCancelMessage(deployer.getAddress()); - MessageTransaction messageTransaction = sendMessage(repository, deployer, messageData, atAddress); - long messageFee = messageTransaction.getTransactionData().getFee(); - - // AT should process 'cancel' message in next block - BlockUtils.mintBlock(repository); - - describeAt(repository, atAddress); - - // Check AT is finished - ATData atData = repository.getATRepository().fromATAddress(atAddress); - assertTrue(atData.getIsFinished()); - - // AT should be in CANCELLED mode - CrossChainTradeData tradeData = DogecoinACCTv3.getInstance().populateTradeData(repository, atData); - assertEquals(AcctMode.CANCELLED, tradeData.mode); - - // Check balances - long expectedMinimumBalance = deployersPostDeploymentBalance; - long expectedMaximumBalance = deployersInitialBalance - deployAtFee - messageFee; - - long actualBalance = deployer.getConfirmedBalance(Asset.QORT); - - assertTrue(String.format("Deployer's balance %s should be above minimum %s", actualBalance, expectedMinimumBalance), actualBalance > expectedMinimumBalance); - assertTrue(String.format("Deployer's balance %s should be below maximum %s", actualBalance, expectedMaximumBalance), actualBalance < expectedMaximumBalance); - - // Test orphaning - BlockUtils.orphanLastBlock(repository); - - // Check balances - long expectedBalance = deployersPostDeploymentBalance - messageFee; - actualBalance = deployer.getConfirmedBalance(Asset.QORT); - - assertEquals("Deployer's post-orphan/pre-refund balance incorrect", expectedBalance, actualBalance); - } + @Override + protected int calcRefundTimeout(long partnersOfferMessageTransactionTimestamp, int lockTimeA) { + return DogecoinACCTv3.calcRefundTimeout(partnersOfferMessageTransactionTimestamp, lockTimeA); } - @SuppressWarnings("unused") - @Test - public void testOfferCancelInvalidLength() throws DataException { - try (final Repository repository = RepositoryManager.getRepository()) { - PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe"); - PrivateKeyAccount tradeAccount = createTradeAccount(repository); - - PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert"); - - long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT); - long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT); - - DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress()); - Account at = deployAtTransaction.getATAccount(); - String atAddress = at.getAddress(); - - long deployAtFee = deployAtTransaction.getTransactionData().getFee(); - long deployersPostDeploymentBalance = deployersInitialBalance - fundingAmount - deployAtFee; - - // Instead of sending creator's address to AT, send too-short/invalid message - byte[] messageData = new byte[7]; - RANDOM.nextBytes(messageData); - MessageTransaction messageTransaction = sendMessage(repository, deployer, messageData, atAddress); - long messageFee = messageTransaction.getTransactionData().getFee(); - - // AT should process 'cancel' message in next block - // As message is too short, it will be padded to 32bytes but cancel code doesn't care about message content, so should be ok - BlockUtils.mintBlock(repository); - - describeAt(repository, atAddress); - - // Check AT is finished - ATData atData = repository.getATRepository().fromATAddress(atAddress); - assertTrue(atData.getIsFinished()); - - // AT should be in CANCELLED mode - CrossChainTradeData tradeData = DogecoinACCTv3.getInstance().populateTradeData(repository, atData); - assertEquals(AcctMode.CANCELLED, tradeData.mode); - } + @Override + protected byte[] buildTradeMessage(String address, byte[] publicKey, byte[] hashOfSecretA, int lockTimeA, int refundTimeout) { + return DogecoinACCTv3.buildTradeMessage(address, publicKey, hashOfSecretA, lockTimeA, refundTimeout); } - @SuppressWarnings("unused") - @Test - public void testTradingInfoProcessing() throws DataException { - try (final Repository repository = RepositoryManager.getRepository()) { - PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe"); - PrivateKeyAccount tradeAccount = createTradeAccount(repository); - - PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert"); - - long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT); - long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT); - - DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress()); - Account at = deployAtTransaction.getATAccount(); - String atAddress = at.getAddress(); - - long partnersOfferMessageTransactionTimestamp = System.currentTimeMillis(); - int lockTimeA = calcTestLockTimeA(partnersOfferMessageTransactionTimestamp); - int refundTimeout = DogecoinACCTv3.calcRefundTimeout(partnersOfferMessageTransactionTimestamp, lockTimeA); - - // Send trade info to AT - byte[] messageData = DogecoinACCTv3.buildTradeMessage(partner.getAddress(), dogecoinPublicKeyHash, hashOfSecretA, lockTimeA, refundTimeout); - MessageTransaction messageTransaction = sendMessage(repository, tradeAccount, messageData, atAddress); - - Block postDeploymentBlock = BlockUtils.mintBlock(repository); - int postDeploymentBlockHeight = postDeploymentBlock.getBlockData().getHeight(); - - long deployAtFee = deployAtTransaction.getTransactionData().getFee(); - long deployersPostDeploymentBalance = deployersInitialBalance - fundingAmount - deployAtFee; - - describeAt(repository, atAddress); - - ATData atData = repository.getATRepository().fromATAddress(atAddress); - CrossChainTradeData tradeData = DogecoinACCTv3.getInstance().populateTradeData(repository, atData); - - // AT should be in TRADE mode - assertEquals(AcctMode.TRADING, tradeData.mode); - - // Check hashOfSecretA was extracted correctly - assertTrue(Arrays.equals(hashOfSecretA, tradeData.hashOfSecretA)); - - // Check trade partner Qortal address was extracted correctly - assertEquals(partner.getAddress(), tradeData.qortalPartnerAddress); - - // Check trade partner's dogecoin PKH was extracted correctly - assertTrue(Arrays.equals(dogecoinPublicKeyHash, tradeData.partnerForeignPKH)); - - // Test orphaning - BlockUtils.orphanToBlock(repository, postDeploymentBlockHeight); - - // Check balances - long expectedBalance = deployersPostDeploymentBalance; - long actualBalance = deployer.getConfirmedBalance(Asset.QORT); - - assertEquals("Deployer's post-orphan/pre-refund balance incorrect", expectedBalance, actualBalance); - } + @Override + protected byte[] buildRedeemMessage(byte[] secretA, String address) { + return DogecoinACCTv3.buildRedeemMessage(secretA, address); } - // TEST SENDING TRADING INFO BUT NOT FROM AT CREATOR (SHOULD BE IGNORED) - @SuppressWarnings("unused") - @Test - public void testIncorrectTradeSender() throws DataException { - try (final Repository repository = RepositoryManager.getRepository()) { - PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe"); - PrivateKeyAccount tradeAccount = createTradeAccount(repository); - - PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert"); - - PrivateKeyAccount bystander = Common.getTestAccount(repository, "bob"); - - long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT); - long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT); - - DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress()); - Account at = deployAtTransaction.getATAccount(); - String atAddress = at.getAddress(); - - long partnersOfferMessageTransactionTimestamp = System.currentTimeMillis(); - int lockTimeA = calcTestLockTimeA(partnersOfferMessageTransactionTimestamp); - int refundTimeout = DogecoinACCTv3.calcRefundTimeout(partnersOfferMessageTransactionTimestamp, lockTimeA); - - // Send trade info to AT BUT NOT FROM AT CREATOR - byte[] messageData = DogecoinACCTv3.buildTradeMessage(partner.getAddress(), dogecoinPublicKeyHash, hashOfSecretA, lockTimeA, refundTimeout); - MessageTransaction messageTransaction = sendMessage(repository, bystander, messageData, atAddress); - - BlockUtils.mintBlock(repository); - - long expectedBalance = partnersInitialBalance; - long actualBalance = partner.getConfirmedBalance(Asset.QORT); - - assertEquals("Partner's post-initial-payout balance incorrect", expectedBalance, actualBalance); - - describeAt(repository, atAddress); - - ATData atData = repository.getATRepository().fromATAddress(atAddress); - CrossChainTradeData tradeData = DogecoinACCTv3.getInstance().populateTradeData(repository, atData); - - // AT should still be in OFFER mode - assertEquals(AcctMode.OFFERING, tradeData.mode); - } + @Override + protected byte[] getCodeBytesHash() { + return DogecoinACCTv3.CODE_BYTES_HASH; } - @SuppressWarnings("unused") - @Test - public void testAutomaticTradeRefund() throws DataException { - try (final Repository repository = RepositoryManager.getRepository()) { - PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe"); - PrivateKeyAccount tradeAccount = createTradeAccount(repository); - - PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert"); - - long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT); - long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT); - - DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress()); - Account at = deployAtTransaction.getATAccount(); - String atAddress = at.getAddress(); - - long partnersOfferMessageTransactionTimestamp = System.currentTimeMillis(); - int lockTimeA = calcTestLockTimeA(partnersOfferMessageTransactionTimestamp); - int refundTimeout = DogecoinACCTv3.calcRefundTimeout(partnersOfferMessageTransactionTimestamp, lockTimeA); - - // Send trade info to AT - byte[] messageData = DogecoinACCTv3.buildTradeMessage(partner.getAddress(), dogecoinPublicKeyHash, hashOfSecretA, lockTimeA, refundTimeout); - MessageTransaction messageTransaction = sendMessage(repository, tradeAccount, messageData, atAddress); - - Block postDeploymentBlock = BlockUtils.mintBlock(repository); - int postDeploymentBlockHeight = postDeploymentBlock.getBlockData().getHeight(); - - // Check refund - long deployAtFee = deployAtTransaction.getTransactionData().getFee(); - long deployersPostDeploymentBalance = deployersInitialBalance - fundingAmount - deployAtFee; - - checkTradeRefund(repository, deployer, deployersInitialBalance, deployAtFee); - - describeAt(repository, atAddress); - - // Check AT is finished - ATData atData = repository.getATRepository().fromATAddress(atAddress); - assertTrue(atData.getIsFinished()); - - // AT should be in REFUNDED mode - CrossChainTradeData tradeData = DogecoinACCTv3.getInstance().populateTradeData(repository, atData); - assertEquals(AcctMode.REFUNDED, tradeData.mode); - - // Test orphaning - BlockUtils.orphanToBlock(repository, postDeploymentBlockHeight); - - // Check balances - long expectedBalance = deployersPostDeploymentBalance; - long actualBalance = deployer.getConfirmedBalance(Asset.QORT); - - assertEquals("Deployer's post-orphan/pre-refund balance incorrect", expectedBalance, actualBalance); - } + @Override + protected String getSymbol() { + return SYMBOL; } - @SuppressWarnings("unused") - @Test - public void testCorrectSecretCorrectSender() throws DataException { - try (final Repository repository = RepositoryManager.getRepository()) { - PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe"); - PrivateKeyAccount tradeAccount = createTradeAccount(repository); - - PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert"); - - long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT); - long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT); - - DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress()); - Account at = deployAtTransaction.getATAccount(); - String atAddress = at.getAddress(); - - long partnersOfferMessageTransactionTimestamp = System.currentTimeMillis(); - int lockTimeA = calcTestLockTimeA(partnersOfferMessageTransactionTimestamp); - int refundTimeout = DogecoinACCTv3.calcRefundTimeout(partnersOfferMessageTransactionTimestamp, lockTimeA); - - // Send trade info to AT - byte[] messageData = DogecoinACCTv3.buildTradeMessage(partner.getAddress(), dogecoinPublicKeyHash, hashOfSecretA, lockTimeA, refundTimeout); - MessageTransaction messageTransaction = sendMessage(repository, tradeAccount, messageData, atAddress); - - // Give AT time to process message - BlockUtils.mintBlock(repository); - - // Send correct secret to AT, from correct account - messageData = DogecoinACCTv3.buildRedeemMessage(secretA, partner.getAddress()); - messageTransaction = sendMessage(repository, partner, messageData, atAddress); - - // AT should send funds in the next block - ATStateData preRedeemAtStateData = repository.getATRepository().getLatestATState(atAddress); - BlockUtils.mintBlock(repository); - - describeAt(repository, atAddress); - - // Check AT is finished - ATData atData = repository.getATRepository().fromATAddress(atAddress); - assertTrue(atData.getIsFinished()); - - // AT should be in REDEEMED mode - CrossChainTradeData tradeData = DogecoinACCTv3.getInstance().populateTradeData(repository, atData); - assertEquals(AcctMode.REDEEMED, tradeData.mode); - - // Check balances - long expectedBalance = partnersInitialBalance - messageTransaction.getTransactionData().getFee() + redeemAmount; - long actualBalance = partner.getConfirmedBalance(Asset.QORT); - - assertEquals("Partner's post-redeem balance incorrect", expectedBalance, actualBalance); - - // Orphan redeem - BlockUtils.orphanLastBlock(repository); - - // Check balances - expectedBalance = partnersInitialBalance - messageTransaction.getTransactionData().getFee(); - actualBalance = partner.getConfirmedBalance(Asset.QORT); - - assertEquals("Partner's post-orphan/pre-redeem balance incorrect", expectedBalance, actualBalance); - - // Check AT state - ATStateData postOrphanAtStateData = repository.getATRepository().getLatestATState(atAddress); - - assertTrue("AT states mismatch", Arrays.equals(preRedeemAtStateData.getStateData(), postOrphanAtStateData.getStateData())); - } + @Override + protected String getName() { + return NAME; } - - @SuppressWarnings("unused") - @Test - public void testCorrectSecretIncorrectSender() throws DataException { - try (final Repository repository = RepositoryManager.getRepository()) { - PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe"); - PrivateKeyAccount tradeAccount = createTradeAccount(repository); - - PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert"); - - PrivateKeyAccount bystander = Common.getTestAccount(repository, "bob"); - - long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT); - long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT); - - DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress()); - long deployAtFee = deployAtTransaction.getTransactionData().getFee(); - - Account at = deployAtTransaction.getATAccount(); - String atAddress = at.getAddress(); - - long partnersOfferMessageTransactionTimestamp = System.currentTimeMillis(); - int lockTimeA = calcTestLockTimeA(partnersOfferMessageTransactionTimestamp); - int refundTimeout = DogecoinACCTv3.calcRefundTimeout(partnersOfferMessageTransactionTimestamp, lockTimeA); - - // Send trade info to AT - byte[] messageData = DogecoinACCTv3.buildTradeMessage(partner.getAddress(), dogecoinPublicKeyHash, hashOfSecretA, lockTimeA, refundTimeout); - MessageTransaction messageTransaction = sendMessage(repository, tradeAccount, messageData, atAddress); - - // Give AT time to process message - BlockUtils.mintBlock(repository); - - // Send correct secret to AT, but from wrong account - messageData = DogecoinACCTv3.buildRedeemMessage(secretA, partner.getAddress()); - messageTransaction = sendMessage(repository, bystander, messageData, atAddress); - - // AT should NOT send funds in the next block - ATStateData preRedeemAtStateData = repository.getATRepository().getLatestATState(atAddress); - BlockUtils.mintBlock(repository); - - describeAt(repository, atAddress); - - // Check AT is NOT finished - ATData atData = repository.getATRepository().fromATAddress(atAddress); - assertFalse(atData.getIsFinished()); - - // AT should still be in TRADE mode - CrossChainTradeData tradeData = DogecoinACCTv3.getInstance().populateTradeData(repository, atData); - assertEquals(AcctMode.TRADING, tradeData.mode); - - // Check balances - long expectedBalance = partnersInitialBalance; - long actualBalance = partner.getConfirmedBalance(Asset.QORT); - - assertEquals("Partner's balance incorrect", expectedBalance, actualBalance); - - // Check eventual refund - checkTradeRefund(repository, deployer, deployersInitialBalance, deployAtFee); - } - } - - @SuppressWarnings("unused") - @Test - public void testIncorrectSecretCorrectSender() throws DataException { - try (final Repository repository = RepositoryManager.getRepository()) { - PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe"); - PrivateKeyAccount tradeAccount = createTradeAccount(repository); - - PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert"); - - long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT); - long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT); - - DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress()); - long deployAtFee = deployAtTransaction.getTransactionData().getFee(); - - Account at = deployAtTransaction.getATAccount(); - String atAddress = at.getAddress(); - - long partnersOfferMessageTransactionTimestamp = System.currentTimeMillis(); - int lockTimeA = calcTestLockTimeA(partnersOfferMessageTransactionTimestamp); - int refundTimeout = DogecoinACCTv3.calcRefundTimeout(partnersOfferMessageTransactionTimestamp, lockTimeA); - - // Send trade info to AT - byte[] messageData = DogecoinACCTv3.buildTradeMessage(partner.getAddress(), dogecoinPublicKeyHash, hashOfSecretA, lockTimeA, refundTimeout); - MessageTransaction messageTransaction = sendMessage(repository, tradeAccount, messageData, atAddress); - - // Give AT time to process message - BlockUtils.mintBlock(repository); - - // Send incorrect secret to AT, from correct account - byte[] wrongSecret = new byte[32]; - RANDOM.nextBytes(wrongSecret); - messageData = DogecoinACCTv3.buildRedeemMessage(wrongSecret, partner.getAddress()); - messageTransaction = sendMessage(repository, partner, messageData, atAddress); - - // AT should NOT send funds in the next block - ATStateData preRedeemAtStateData = repository.getATRepository().getLatestATState(atAddress); - BlockUtils.mintBlock(repository); - - describeAt(repository, atAddress); - - // Check AT is NOT finished - ATData atData = repository.getATRepository().fromATAddress(atAddress); - assertFalse(atData.getIsFinished()); - - // AT should still be in TRADE mode - CrossChainTradeData tradeData = DogecoinACCTv3.getInstance().populateTradeData(repository, atData); - assertEquals(AcctMode.TRADING, tradeData.mode); - - long expectedBalance = partnersInitialBalance - messageTransaction.getTransactionData().getFee(); - long actualBalance = partner.getConfirmedBalance(Asset.QORT); - - assertEquals("Partner's balance incorrect", expectedBalance, actualBalance); - - // Check eventual refund - checkTradeRefund(repository, deployer, deployersInitialBalance, deployAtFee); - } - } - - @SuppressWarnings("unused") - @Test - public void testCorrectSecretCorrectSenderInvalidMessageLength() throws DataException { - try (final Repository repository = RepositoryManager.getRepository()) { - PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe"); - PrivateKeyAccount tradeAccount = createTradeAccount(repository); - - PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert"); - - long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT); - long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT); - - DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress()); - Account at = deployAtTransaction.getATAccount(); - String atAddress = at.getAddress(); - - long partnersOfferMessageTransactionTimestamp = System.currentTimeMillis(); - int lockTimeA = calcTestLockTimeA(partnersOfferMessageTransactionTimestamp); - int refundTimeout = DogecoinACCTv3.calcRefundTimeout(partnersOfferMessageTransactionTimestamp, lockTimeA); - - // Send trade info to AT - byte[] messageData = DogecoinACCTv3.buildTradeMessage(partner.getAddress(), dogecoinPublicKeyHash, hashOfSecretA, lockTimeA, refundTimeout); - MessageTransaction messageTransaction = sendMessage(repository, tradeAccount, messageData, atAddress); - - // Give AT time to process message - BlockUtils.mintBlock(repository); - - // Send correct secret to AT, from correct account, but missing receive address, hence incorrect length - messageData = Bytes.concat(secretA); - messageTransaction = sendMessage(repository, partner, messageData, atAddress); - - // AT should NOT send funds in the next block - ATStateData preRedeemAtStateData = repository.getATRepository().getLatestATState(atAddress); - BlockUtils.mintBlock(repository); - - describeAt(repository, atAddress); - - // Check AT is NOT finished - ATData atData = repository.getATRepository().fromATAddress(atAddress); - assertFalse(atData.getIsFinished()); - - // AT should be in TRADING mode - CrossChainTradeData tradeData = DogecoinACCTv3.getInstance().populateTradeData(repository, atData); - assertEquals(AcctMode.TRADING, tradeData.mode); - } - } - - @SuppressWarnings("unused") - @Test - public void testDescribeDeployed() throws DataException { - try (final Repository repository = RepositoryManager.getRepository()) { - PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe"); - PrivateKeyAccount tradeAccount = createTradeAccount(repository); - - PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert"); - - long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT); - long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT); - - DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress()); - - List executableAts = repository.getATRepository().getAllExecutableATs(); - - for (ATData atData : executableAts) { - String atAddress = atData.getATAddress(); - byte[] codeBytes = atData.getCodeBytes(); - byte[] codeHash = Crypto.digest(codeBytes); - - System.out.println(String.format("%s: code length: %d byte%s, code hash: %s", - atAddress, - codeBytes.length, - (codeBytes.length != 1 ? "s": ""), - HashCode.fromBytes(codeHash))); - - // Not one of ours? - if (!Arrays.equals(codeHash, DogecoinACCTv3.CODE_BYTES_HASH)) - continue; - - describeAt(repository, atAddress); - } - } - } - - private int calcTestLockTimeA(long messageTimestamp) { - return (int) (messageTimestamp / 1000L + tradeTimeout * 60); - } - - private DeployAtTransaction doDeploy(Repository repository, PrivateKeyAccount deployer, String tradeAddress) throws DataException { - byte[] creationBytes = DogecoinACCTv3.buildQortalAT(tradeAddress, dogecoinPublicKeyHash, redeemAmount, dogecoinAmount, tradeTimeout); - - long txTimestamp = System.currentTimeMillis(); - byte[] lastReference = deployer.getLastReference(); - - if (lastReference == null) { - System.err.println(String.format("Qortal account %s has no last reference", deployer.getAddress())); - System.exit(2); - } - - Long fee = null; - String name = "QORT-DOGE cross-chain trade"; - String description = String.format("Qortal-Dogecoin cross-chain trade"); - String atType = "ACCT"; - String tags = "QORT-DOGE ACCT"; - - BaseTransactionData baseTransactionData = new BaseTransactionData(txTimestamp, Group.NO_GROUP, lastReference, deployer.getPublicKey(), fee, null); - TransactionData deployAtTransactionData = new DeployAtTransactionData(baseTransactionData, name, description, atType, tags, creationBytes, fundingAmount, Asset.QORT); - - DeployAtTransaction deployAtTransaction = new DeployAtTransaction(repository, deployAtTransactionData); - - fee = deployAtTransaction.calcRecommendedFee(); - deployAtTransactionData.setFee(fee); - - TransactionUtils.signAndMint(repository, deployAtTransactionData, deployer); - - return deployAtTransaction; - } - - private MessageTransaction sendMessage(Repository repository, PrivateKeyAccount sender, byte[] data, String recipient) throws DataException { - long txTimestamp = System.currentTimeMillis(); - byte[] lastReference = sender.getLastReference(); - - if (lastReference == null) { - System.err.println(String.format("Qortal account %s has no last reference", sender.getAddress())); - System.exit(2); - } - - Long fee = null; - int version = 4; - int nonce = 0; - long amount = 0; - Long assetId = null; // because amount is zero - - BaseTransactionData baseTransactionData = new BaseTransactionData(txTimestamp, Group.NO_GROUP, lastReference, sender.getPublicKey(), fee, null); - TransactionData messageTransactionData = new MessageTransactionData(baseTransactionData, version, nonce, recipient, amount, assetId, data, false, false); - - MessageTransaction messageTransaction = new MessageTransaction(repository, messageTransactionData); - - fee = messageTransaction.calcRecommendedFee(); - messageTransactionData.setFee(fee); - - TransactionUtils.signAndMint(repository, messageTransactionData, sender); - - return messageTransaction; - } - - private void checkTradeRefund(Repository repository, Account deployer, long deployersInitialBalance, long deployAtFee) throws DataException { - long deployersPostDeploymentBalance = deployersInitialBalance - fundingAmount - deployAtFee; - int refundTimeout = tradeTimeout / 2 + 1; // close enough - - // AT should automatically refund deployer after 'refundTimeout' blocks - for (int blockCount = 0; blockCount <= refundTimeout; ++blockCount) - BlockUtils.mintBlock(repository); - - // We don't bother to exactly calculate QORT spent running AT for several blocks, but we do know the expected range - long expectedMinimumBalance = deployersPostDeploymentBalance; - long expectedMaximumBalance = deployersInitialBalance - deployAtFee; - - long actualBalance = deployer.getConfirmedBalance(Asset.QORT); - - assertTrue(String.format("Deployer's balance %s should be above minimum %s", actualBalance, expectedMinimumBalance), actualBalance > expectedMinimumBalance); - assertTrue(String.format("Deployer's balance %s should be below maximum %s", actualBalance, expectedMaximumBalance), actualBalance < expectedMaximumBalance); - } - - private void describeAt(Repository repository, String atAddress) throws DataException { - ATData atData = repository.getATRepository().fromATAddress(atAddress); - CrossChainTradeData tradeData = DogecoinACCTv3.getInstance().populateTradeData(repository, atData); - - Function epochMilliFormatter = (timestamp) -> LocalDateTime.ofInstant(Instant.ofEpochMilli(timestamp), ZoneOffset.UTC).format(DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM)); - int currentBlockHeight = repository.getBlockRepository().getBlockchainHeight(); - - System.out.print(String.format("%s:\n" - + "\tmode: %s\n" - + "\tcreator: %s,\n" - + "\tcreation timestamp: %s,\n" - + "\tcurrent balance: %s QORT,\n" - + "\tis finished: %b,\n" - + "\tredeem payout: %s QORT,\n" - + "\texpected dogecoin: %s DOGE,\n" - + "\tcurrent block height: %d,\n", - tradeData.qortalAtAddress, - tradeData.mode, - tradeData.qortalCreator, - epochMilliFormatter.apply(tradeData.creationTimestamp), - Amounts.prettyAmount(tradeData.qortBalance), - atData.getIsFinished(), - Amounts.prettyAmount(tradeData.qortAmount), - Amounts.prettyAmount(tradeData.expectedForeignAmount), - currentBlockHeight)); - - if (tradeData.mode != AcctMode.OFFERING && tradeData.mode != AcctMode.CANCELLED) { - System.out.println(String.format("\trefund timeout: %d minutes,\n" - + "\trefund height: block %d,\n" - + "\tHASH160 of secret-A: %s,\n" - + "\tDogecoin P2SH-A nLockTime: %d (%s),\n" - + "\ttrade partner: %s\n" - + "\tpartner's receiving address: %s", - tradeData.refundTimeout, - tradeData.tradeRefundHeight, - HashCode.fromBytes(tradeData.hashOfSecretA).toString().substring(0, 40), - tradeData.lockTimeA, epochMilliFormatter.apply(tradeData.lockTimeA * 1000L), - tradeData.qortalPartnerAddress, - tradeData.qortalPartnerReceivingAddress)); - } - } - - private PrivateKeyAccount createTradeAccount(Repository repository) { - // We actually use a known test account with funds to avoid PoW compute - return Common.getTestAccount(repository, "alice"); - } - } diff --git a/src/test/java/org/qortal/test/crosschain/litecoinv1/LitecoinACCTv1Tests.java b/src/test/java/org/qortal/test/crosschain/litecoinv1/LitecoinACCTv1Tests.java index 609ff5f3..91a450d0 100644 --- a/src/test/java/org/qortal/test/crosschain/litecoinv1/LitecoinACCTv1Tests.java +++ b/src/test/java/org/qortal/test/crosschain/litecoinv1/LitecoinACCTv1Tests.java @@ -1,770 +1,60 @@ package org.qortal.test.crosschain.litecoinv1; -import static org.junit.Assert.*; - -import java.time.Instant; -import java.time.LocalDateTime; -import java.time.ZoneOffset; -import java.time.format.DateTimeFormatter; -import java.time.format.FormatStyle; -import java.util.Arrays; -import java.util.List; -import java.util.Random; -import java.util.function.Function; - -import org.junit.Before; -import org.junit.Test; -import org.qortal.account.Account; -import org.qortal.account.PrivateKeyAccount; -import org.qortal.asset.Asset; -import org.qortal.block.Block; +import org.qortal.crosschain.ACCT; import org.qortal.crosschain.LitecoinACCTv1; -import org.qortal.crosschain.AcctMode; -import org.qortal.crypto.Crypto; -import org.qortal.data.at.ATData; -import org.qortal.data.at.ATStateData; -import org.qortal.data.crosschain.CrossChainTradeData; -import org.qortal.data.transaction.BaseTransactionData; -import org.qortal.data.transaction.DeployAtTransactionData; -import org.qortal.data.transaction.MessageTransactionData; -import org.qortal.data.transaction.TransactionData; -import org.qortal.group.Group; -import org.qortal.repository.DataException; -import org.qortal.repository.Repository; -import org.qortal.repository.RepositoryManager; -import org.qortal.test.common.BlockUtils; -import org.qortal.test.common.Common; -import org.qortal.test.common.TransactionUtils; -import org.qortal.transaction.DeployAtTransaction; -import org.qortal.transaction.MessageTransaction; -import org.qortal.utils.Amounts; +import org.qortal.test.crosschain.ACCTTests; import com.google.common.hash.HashCode; -import com.google.common.primitives.Bytes; -public class LitecoinACCTv1Tests extends Common { +public class LitecoinACCTv1Tests extends ACCTTests { - public static final byte[] secretA = "This string is exactly 32 bytes!".getBytes(); - public static final byte[] hashOfSecretA = Crypto.hash160(secretA); // daf59884b4d1aec8c1b17102530909ee43c0151a public static final byte[] litecoinPublicKeyHash = HashCode.fromString("bb00bb11bb22bb33bb44bb55bb66bb77bb88bb99").asBytes(); - public static final int tradeTimeout = 20; // blocks - public static final long redeemAmount = 80_40200000L; - public static final long fundingAmount = 123_45600000L; - public static final long litecoinAmount = 864200L; // 0.00864200 LTC + private static final String SYMBOL = "LTC"; - private static final Random RANDOM = new Random(); + private static final String NAME = "Litecoin"; - @Before - public void beforeTest() throws DataException { - Common.useDefaultSettings(); + @Override + protected byte[] getPublicKey() { + return litecoinPublicKeyHash; } - @Test - public void testCompile() { - PrivateKeyAccount tradeAccount = createTradeAccount(null); - - byte[] creationBytes = LitecoinACCTv1.buildQortalAT(tradeAccount.getAddress(), litecoinPublicKeyHash, redeemAmount, litecoinAmount, tradeTimeout); - assertNotNull(creationBytes); - - System.out.println("AT creation bytes: " + HashCode.fromBytes(creationBytes).toString()); + @Override + protected byte[] buildQortalAT(String address, byte[] publicKey, long redeemAmount, long foreignAmount, int tradeTimeout) { + return LitecoinACCTv1.buildQortalAT(address, publicKey, redeemAmount, foreignAmount, tradeTimeout); } - @Test - public void testDeploy() throws DataException { - try (final Repository repository = RepositoryManager.getRepository()) { - PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe"); - PrivateKeyAccount tradeAccount = createTradeAccount(repository); - - PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert"); - - long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT); - long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT); - - DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress()); - - long expectedBalance = deployersInitialBalance - fundingAmount - deployAtTransaction.getTransactionData().getFee(); - long actualBalance = deployer.getConfirmedBalance(Asset.QORT); - - assertEquals("Deployer's post-deployment balance incorrect", expectedBalance, actualBalance); - - expectedBalance = fundingAmount; - actualBalance = deployAtTransaction.getATAccount().getConfirmedBalance(Asset.QORT); - - assertEquals("AT's post-deployment balance incorrect", expectedBalance, actualBalance); - - expectedBalance = partnersInitialBalance; - actualBalance = partner.getConfirmedBalance(Asset.QORT); - - assertEquals("Partner's post-deployment balance incorrect", expectedBalance, actualBalance); - - // Test orphaning - BlockUtils.orphanLastBlock(repository); - - expectedBalance = deployersInitialBalance; - actualBalance = deployer.getConfirmedBalance(Asset.QORT); - - assertEquals("Deployer's post-orphan/pre-deployment balance incorrect", expectedBalance, actualBalance); - - expectedBalance = 0; - actualBalance = deployAtTransaction.getATAccount().getConfirmedBalance(Asset.QORT); - - assertEquals("AT's post-orphan/pre-deployment balance incorrect", expectedBalance, actualBalance); - - expectedBalance = partnersInitialBalance; - actualBalance = partner.getConfirmedBalance(Asset.QORT); - - assertEquals("Partner's post-orphan/pre-deployment balance incorrect", expectedBalance, actualBalance); - } + @Override + protected ACCT getInstance() { + return LitecoinACCTv1.getInstance(); } - @SuppressWarnings("unused") - @Test - public void testOfferCancel() throws DataException { - try (final Repository repository = RepositoryManager.getRepository()) { - PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe"); - PrivateKeyAccount tradeAccount = createTradeAccount(repository); - - PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert"); - - long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT); - long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT); - - DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress()); - Account at = deployAtTransaction.getATAccount(); - String atAddress = at.getAddress(); - - long deployAtFee = deployAtTransaction.getTransactionData().getFee(); - long deployersPostDeploymentBalance = deployersInitialBalance - fundingAmount - deployAtFee; - - // Send creator's address to AT, instead of typical partner's address - byte[] messageData = LitecoinACCTv1.getInstance().buildCancelMessage(deployer.getAddress()); - MessageTransaction messageTransaction = sendMessage(repository, deployer, messageData, atAddress); - long messageFee = messageTransaction.getTransactionData().getFee(); - - // AT should process 'cancel' message in next block - BlockUtils.mintBlock(repository); - - describeAt(repository, atAddress); - - // Check AT is finished - ATData atData = repository.getATRepository().fromATAddress(atAddress); - assertTrue(atData.getIsFinished()); - - // AT should be in CANCELLED mode - CrossChainTradeData tradeData = LitecoinACCTv1.getInstance().populateTradeData(repository, atData); - assertEquals(AcctMode.CANCELLED, tradeData.mode); - - // Check balances - long expectedMinimumBalance = deployersPostDeploymentBalance; - long expectedMaximumBalance = deployersInitialBalance - deployAtFee - messageFee; - - long actualBalance = deployer.getConfirmedBalance(Asset.QORT); - - assertTrue(String.format("Deployer's balance %s should be above minimum %s", actualBalance, expectedMinimumBalance), actualBalance > expectedMinimumBalance); - assertTrue(String.format("Deployer's balance %s should be below maximum %s", actualBalance, expectedMaximumBalance), actualBalance < expectedMaximumBalance); - - // Test orphaning - BlockUtils.orphanLastBlock(repository); - - // Check balances - long expectedBalance = deployersPostDeploymentBalance - messageFee; - actualBalance = deployer.getConfirmedBalance(Asset.QORT); - - assertEquals("Deployer's post-orphan/pre-refund balance incorrect", expectedBalance, actualBalance); - } + @Override + protected int calcRefundTimeout(long partnersOfferMessageTransactionTimestamp, int lockTimeA) { + return LitecoinACCTv1.calcRefundTimeout(partnersOfferMessageTransactionTimestamp, lockTimeA); } - @SuppressWarnings("unused") - @Test - public void testOfferCancelInvalidLength() throws DataException { - try (final Repository repository = RepositoryManager.getRepository()) { - PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe"); - PrivateKeyAccount tradeAccount = createTradeAccount(repository); - - PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert"); - - long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT); - long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT); - - DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress()); - Account at = deployAtTransaction.getATAccount(); - String atAddress = at.getAddress(); - - long deployAtFee = deployAtTransaction.getTransactionData().getFee(); - long deployersPostDeploymentBalance = deployersInitialBalance - fundingAmount - deployAtFee; - - // Instead of sending creator's address to AT, send too-short/invalid message - byte[] messageData = new byte[7]; - RANDOM.nextBytes(messageData); - MessageTransaction messageTransaction = sendMessage(repository, deployer, messageData, atAddress); - long messageFee = messageTransaction.getTransactionData().getFee(); - - // AT should process 'cancel' message in next block - // As message is too short, it will be padded to 32bytes but cancel code doesn't care about message content, so should be ok - BlockUtils.mintBlock(repository); - - describeAt(repository, atAddress); - - // Check AT is finished - ATData atData = repository.getATRepository().fromATAddress(atAddress); - assertTrue(atData.getIsFinished()); - - // AT should be in CANCELLED mode - CrossChainTradeData tradeData = LitecoinACCTv1.getInstance().populateTradeData(repository, atData); - assertEquals(AcctMode.CANCELLED, tradeData.mode); - } + @Override + protected byte[] buildTradeMessage(String address, byte[] publicKey, byte[] hashOfSecretA, int lockTimeA, int refundTimeout) { + return LitecoinACCTv1.buildTradeMessage(address, publicKey, hashOfSecretA, lockTimeA, refundTimeout); } - @SuppressWarnings("unused") - @Test - public void testTradingInfoProcessing() throws DataException { - try (final Repository repository = RepositoryManager.getRepository()) { - PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe"); - PrivateKeyAccount tradeAccount = createTradeAccount(repository); - - PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert"); - - long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT); - long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT); - - DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress()); - Account at = deployAtTransaction.getATAccount(); - String atAddress = at.getAddress(); - - long partnersOfferMessageTransactionTimestamp = System.currentTimeMillis(); - int lockTimeA = calcTestLockTimeA(partnersOfferMessageTransactionTimestamp); - int refundTimeout = LitecoinACCTv1.calcRefundTimeout(partnersOfferMessageTransactionTimestamp, lockTimeA); - - // Send trade info to AT - byte[] messageData = LitecoinACCTv1.buildTradeMessage(partner.getAddress(), litecoinPublicKeyHash, hashOfSecretA, lockTimeA, refundTimeout); - MessageTransaction messageTransaction = sendMessage(repository, tradeAccount, messageData, atAddress); - - Block postDeploymentBlock = BlockUtils.mintBlock(repository); - int postDeploymentBlockHeight = postDeploymentBlock.getBlockData().getHeight(); - - long deployAtFee = deployAtTransaction.getTransactionData().getFee(); - long deployersPostDeploymentBalance = deployersInitialBalance - fundingAmount - deployAtFee; - - describeAt(repository, atAddress); - - ATData atData = repository.getATRepository().fromATAddress(atAddress); - CrossChainTradeData tradeData = LitecoinACCTv1.getInstance().populateTradeData(repository, atData); - - // AT should be in TRADE mode - assertEquals(AcctMode.TRADING, tradeData.mode); - - // Check hashOfSecretA was extracted correctly - assertTrue(Arrays.equals(hashOfSecretA, tradeData.hashOfSecretA)); - - // Check trade partner Qortal address was extracted correctly - assertEquals(partner.getAddress(), tradeData.qortalPartnerAddress); - - // Check trade partner's Litecoin PKH was extracted correctly - assertTrue(Arrays.equals(litecoinPublicKeyHash, tradeData.partnerForeignPKH)); - - // Test orphaning - BlockUtils.orphanToBlock(repository, postDeploymentBlockHeight); - - // Check balances - long expectedBalance = deployersPostDeploymentBalance; - long actualBalance = deployer.getConfirmedBalance(Asset.QORT); - - assertEquals("Deployer's post-orphan/pre-refund balance incorrect", expectedBalance, actualBalance); - } + @Override + protected byte[] buildRedeemMessage(byte[] secretA, String address) { + return LitecoinACCTv1.buildRedeemMessage(secretA, address); } - // TEST SENDING TRADING INFO BUT NOT FROM AT CREATOR (SHOULD BE IGNORED) - @SuppressWarnings("unused") - @Test - public void testIncorrectTradeSender() throws DataException { - try (final Repository repository = RepositoryManager.getRepository()) { - PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe"); - PrivateKeyAccount tradeAccount = createTradeAccount(repository); - - PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert"); - - PrivateKeyAccount bystander = Common.getTestAccount(repository, "bob"); - - long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT); - long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT); - - DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress()); - Account at = deployAtTransaction.getATAccount(); - String atAddress = at.getAddress(); - - long partnersOfferMessageTransactionTimestamp = System.currentTimeMillis(); - int lockTimeA = calcTestLockTimeA(partnersOfferMessageTransactionTimestamp); - int refundTimeout = LitecoinACCTv1.calcRefundTimeout(partnersOfferMessageTransactionTimestamp, lockTimeA); - - // Send trade info to AT BUT NOT FROM AT CREATOR - byte[] messageData = LitecoinACCTv1.buildTradeMessage(partner.getAddress(), litecoinPublicKeyHash, hashOfSecretA, lockTimeA, refundTimeout); - MessageTransaction messageTransaction = sendMessage(repository, bystander, messageData, atAddress); - - BlockUtils.mintBlock(repository); - - long expectedBalance = partnersInitialBalance; - long actualBalance = partner.getConfirmedBalance(Asset.QORT); - - assertEquals("Partner's post-initial-payout balance incorrect", expectedBalance, actualBalance); - - describeAt(repository, atAddress); - - ATData atData = repository.getATRepository().fromATAddress(atAddress); - CrossChainTradeData tradeData = LitecoinACCTv1.getInstance().populateTradeData(repository, atData); - - // AT should still be in OFFER mode - assertEquals(AcctMode.OFFERING, tradeData.mode); - } + @Override + protected byte[] getCodeBytesHash() { + return LitecoinACCTv1.CODE_BYTES_HASH; } - @SuppressWarnings("unused") - @Test - public void testAutomaticTradeRefund() throws DataException { - try (final Repository repository = RepositoryManager.getRepository()) { - PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe"); - PrivateKeyAccount tradeAccount = createTradeAccount(repository); - - PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert"); - - long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT); - long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT); - - DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress()); - Account at = deployAtTransaction.getATAccount(); - String atAddress = at.getAddress(); - - long partnersOfferMessageTransactionTimestamp = System.currentTimeMillis(); - int lockTimeA = calcTestLockTimeA(partnersOfferMessageTransactionTimestamp); - int refundTimeout = LitecoinACCTv1.calcRefundTimeout(partnersOfferMessageTransactionTimestamp, lockTimeA); - - // Send trade info to AT - byte[] messageData = LitecoinACCTv1.buildTradeMessage(partner.getAddress(), litecoinPublicKeyHash, hashOfSecretA, lockTimeA, refundTimeout); - MessageTransaction messageTransaction = sendMessage(repository, tradeAccount, messageData, atAddress); - - Block postDeploymentBlock = BlockUtils.mintBlock(repository); - int postDeploymentBlockHeight = postDeploymentBlock.getBlockData().getHeight(); - - // Check refund - long deployAtFee = deployAtTransaction.getTransactionData().getFee(); - long deployersPostDeploymentBalance = deployersInitialBalance - fundingAmount - deployAtFee; - - checkTradeRefund(repository, deployer, deployersInitialBalance, deployAtFee); - - describeAt(repository, atAddress); - - // Check AT is finished - ATData atData = repository.getATRepository().fromATAddress(atAddress); - assertTrue(atData.getIsFinished()); - - // AT should be in REFUNDED mode - CrossChainTradeData tradeData = LitecoinACCTv1.getInstance().populateTradeData(repository, atData); - assertEquals(AcctMode.REFUNDED, tradeData.mode); - - // Test orphaning - BlockUtils.orphanToBlock(repository, postDeploymentBlockHeight); - - // Check balances - long expectedBalance = deployersPostDeploymentBalance; - long actualBalance = deployer.getConfirmedBalance(Asset.QORT); - - assertEquals("Deployer's post-orphan/pre-refund balance incorrect", expectedBalance, actualBalance); - } + @Override + protected String getSymbol() { + return SYMBOL; } - @SuppressWarnings("unused") - @Test - public void testCorrectSecretCorrectSender() throws DataException { - try (final Repository repository = RepositoryManager.getRepository()) { - PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe"); - PrivateKeyAccount tradeAccount = createTradeAccount(repository); - - PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert"); - - long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT); - long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT); - - DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress()); - Account at = deployAtTransaction.getATAccount(); - String atAddress = at.getAddress(); - - long partnersOfferMessageTransactionTimestamp = System.currentTimeMillis(); - int lockTimeA = calcTestLockTimeA(partnersOfferMessageTransactionTimestamp); - int refundTimeout = LitecoinACCTv1.calcRefundTimeout(partnersOfferMessageTransactionTimestamp, lockTimeA); - - // Send trade info to AT - byte[] messageData = LitecoinACCTv1.buildTradeMessage(partner.getAddress(), litecoinPublicKeyHash, hashOfSecretA, lockTimeA, refundTimeout); - MessageTransaction messageTransaction = sendMessage(repository, tradeAccount, messageData, atAddress); - - // Give AT time to process message - BlockUtils.mintBlock(repository); - - // Send correct secret to AT, from correct account - messageData = LitecoinACCTv1.buildRedeemMessage(secretA, partner.getAddress()); - messageTransaction = sendMessage(repository, partner, messageData, atAddress); - - // AT should send funds in the next block - ATStateData preRedeemAtStateData = repository.getATRepository().getLatestATState(atAddress); - BlockUtils.mintBlock(repository); - - describeAt(repository, atAddress); - - // Check AT is finished - ATData atData = repository.getATRepository().fromATAddress(atAddress); - assertTrue(atData.getIsFinished()); - - // AT should be in REDEEMED mode - CrossChainTradeData tradeData = LitecoinACCTv1.getInstance().populateTradeData(repository, atData); - assertEquals(AcctMode.REDEEMED, tradeData.mode); - - // Check balances - long expectedBalance = partnersInitialBalance - messageTransaction.getTransactionData().getFee() + redeemAmount; - long actualBalance = partner.getConfirmedBalance(Asset.QORT); - - assertEquals("Partner's post-redeem balance incorrect", expectedBalance, actualBalance); - - // Orphan redeem - BlockUtils.orphanLastBlock(repository); - - // Check balances - expectedBalance = partnersInitialBalance - messageTransaction.getTransactionData().getFee(); - actualBalance = partner.getConfirmedBalance(Asset.QORT); - - assertEquals("Partner's post-orphan/pre-redeem balance incorrect", expectedBalance, actualBalance); - - // Check AT state - ATStateData postOrphanAtStateData = repository.getATRepository().getLatestATState(atAddress); - - assertTrue("AT states mismatch", Arrays.equals(preRedeemAtStateData.getStateData(), postOrphanAtStateData.getStateData())); - } + @Override + protected String getName() { + return NAME; } - - @SuppressWarnings("unused") - @Test - public void testCorrectSecretIncorrectSender() throws DataException { - try (final Repository repository = RepositoryManager.getRepository()) { - PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe"); - PrivateKeyAccount tradeAccount = createTradeAccount(repository); - - PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert"); - - PrivateKeyAccount bystander = Common.getTestAccount(repository, "bob"); - - long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT); - long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT); - - DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress()); - long deployAtFee = deployAtTransaction.getTransactionData().getFee(); - - Account at = deployAtTransaction.getATAccount(); - String atAddress = at.getAddress(); - - long partnersOfferMessageTransactionTimestamp = System.currentTimeMillis(); - int lockTimeA = calcTestLockTimeA(partnersOfferMessageTransactionTimestamp); - int refundTimeout = LitecoinACCTv1.calcRefundTimeout(partnersOfferMessageTransactionTimestamp, lockTimeA); - - // Send trade info to AT - byte[] messageData = LitecoinACCTv1.buildTradeMessage(partner.getAddress(), litecoinPublicKeyHash, hashOfSecretA, lockTimeA, refundTimeout); - MessageTransaction messageTransaction = sendMessage(repository, tradeAccount, messageData, atAddress); - - // Give AT time to process message - BlockUtils.mintBlock(repository); - - // Send correct secret to AT, but from wrong account - messageData = LitecoinACCTv1.buildRedeemMessage(secretA, partner.getAddress()); - messageTransaction = sendMessage(repository, bystander, messageData, atAddress); - - // AT should NOT send funds in the next block - ATStateData preRedeemAtStateData = repository.getATRepository().getLatestATState(atAddress); - BlockUtils.mintBlock(repository); - - describeAt(repository, atAddress); - - // Check AT is NOT finished - ATData atData = repository.getATRepository().fromATAddress(atAddress); - assertFalse(atData.getIsFinished()); - - // AT should still be in TRADE mode - CrossChainTradeData tradeData = LitecoinACCTv1.getInstance().populateTradeData(repository, atData); - assertEquals(AcctMode.TRADING, tradeData.mode); - - // Check balances - long expectedBalance = partnersInitialBalance; - long actualBalance = partner.getConfirmedBalance(Asset.QORT); - - assertEquals("Partner's balance incorrect", expectedBalance, actualBalance); - - // Check eventual refund - checkTradeRefund(repository, deployer, deployersInitialBalance, deployAtFee); - } - } - - @SuppressWarnings("unused") - @Test - public void testIncorrectSecretCorrectSender() throws DataException { - try (final Repository repository = RepositoryManager.getRepository()) { - PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe"); - PrivateKeyAccount tradeAccount = createTradeAccount(repository); - - PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert"); - - long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT); - long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT); - - DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress()); - long deployAtFee = deployAtTransaction.getTransactionData().getFee(); - - Account at = deployAtTransaction.getATAccount(); - String atAddress = at.getAddress(); - - long partnersOfferMessageTransactionTimestamp = System.currentTimeMillis(); - int lockTimeA = calcTestLockTimeA(partnersOfferMessageTransactionTimestamp); - int refundTimeout = LitecoinACCTv1.calcRefundTimeout(partnersOfferMessageTransactionTimestamp, lockTimeA); - - // Send trade info to AT - byte[] messageData = LitecoinACCTv1.buildTradeMessage(partner.getAddress(), litecoinPublicKeyHash, hashOfSecretA, lockTimeA, refundTimeout); - MessageTransaction messageTransaction = sendMessage(repository, tradeAccount, messageData, atAddress); - - // Give AT time to process message - BlockUtils.mintBlock(repository); - - // Send incorrect secret to AT, from correct account - byte[] wrongSecret = new byte[32]; - RANDOM.nextBytes(wrongSecret); - messageData = LitecoinACCTv1.buildRedeemMessage(wrongSecret, partner.getAddress()); - messageTransaction = sendMessage(repository, partner, messageData, atAddress); - - // AT should NOT send funds in the next block - ATStateData preRedeemAtStateData = repository.getATRepository().getLatestATState(atAddress); - BlockUtils.mintBlock(repository); - - describeAt(repository, atAddress); - - // Check AT is NOT finished - ATData atData = repository.getATRepository().fromATAddress(atAddress); - assertFalse(atData.getIsFinished()); - - // AT should still be in TRADE mode - CrossChainTradeData tradeData = LitecoinACCTv1.getInstance().populateTradeData(repository, atData); - assertEquals(AcctMode.TRADING, tradeData.mode); - - long expectedBalance = partnersInitialBalance - messageTransaction.getTransactionData().getFee(); - long actualBalance = partner.getConfirmedBalance(Asset.QORT); - - assertEquals("Partner's balance incorrect", expectedBalance, actualBalance); - - // Check eventual refund - checkTradeRefund(repository, deployer, deployersInitialBalance, deployAtFee); - } - } - - @SuppressWarnings("unused") - @Test - public void testCorrectSecretCorrectSenderInvalidMessageLength() throws DataException { - try (final Repository repository = RepositoryManager.getRepository()) { - PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe"); - PrivateKeyAccount tradeAccount = createTradeAccount(repository); - - PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert"); - - long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT); - long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT); - - DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress()); - Account at = deployAtTransaction.getATAccount(); - String atAddress = at.getAddress(); - - long partnersOfferMessageTransactionTimestamp = System.currentTimeMillis(); - int lockTimeA = calcTestLockTimeA(partnersOfferMessageTransactionTimestamp); - int refundTimeout = LitecoinACCTv1.calcRefundTimeout(partnersOfferMessageTransactionTimestamp, lockTimeA); - - // Send trade info to AT - byte[] messageData = LitecoinACCTv1.buildTradeMessage(partner.getAddress(), litecoinPublicKeyHash, hashOfSecretA, lockTimeA, refundTimeout); - MessageTransaction messageTransaction = sendMessage(repository, tradeAccount, messageData, atAddress); - - // Give AT time to process message - BlockUtils.mintBlock(repository); - - // Send correct secret to AT, from correct account, but missing receive address, hence incorrect length - messageData = Bytes.concat(secretA); - messageTransaction = sendMessage(repository, partner, messageData, atAddress); - - // AT should NOT send funds in the next block - ATStateData preRedeemAtStateData = repository.getATRepository().getLatestATState(atAddress); - BlockUtils.mintBlock(repository); - - describeAt(repository, atAddress); - - // Check AT is NOT finished - ATData atData = repository.getATRepository().fromATAddress(atAddress); - assertFalse(atData.getIsFinished()); - - // AT should be in TRADING mode - CrossChainTradeData tradeData = LitecoinACCTv1.getInstance().populateTradeData(repository, atData); - assertEquals(AcctMode.TRADING, tradeData.mode); - } - } - - @SuppressWarnings("unused") - @Test - public void testDescribeDeployed() throws DataException { - try (final Repository repository = RepositoryManager.getRepository()) { - PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe"); - PrivateKeyAccount tradeAccount = createTradeAccount(repository); - - PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert"); - - long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT); - long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT); - - DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress()); - - List executableAts = repository.getATRepository().getAllExecutableATs(); - - for (ATData atData : executableAts) { - String atAddress = atData.getATAddress(); - byte[] codeBytes = atData.getCodeBytes(); - byte[] codeHash = Crypto.digest(codeBytes); - - System.out.println(String.format("%s: code length: %d byte%s, code hash: %s", - atAddress, - codeBytes.length, - (codeBytes.length != 1 ? "s": ""), - HashCode.fromBytes(codeHash))); - - // Not one of ours? - if (!Arrays.equals(codeHash, LitecoinACCTv1.CODE_BYTES_HASH)) - continue; - - describeAt(repository, atAddress); - } - } - } - - private int calcTestLockTimeA(long messageTimestamp) { - return (int) (messageTimestamp / 1000L + tradeTimeout * 60); - } - - private DeployAtTransaction doDeploy(Repository repository, PrivateKeyAccount deployer, String tradeAddress) throws DataException { - byte[] creationBytes = LitecoinACCTv1.buildQortalAT(tradeAddress, litecoinPublicKeyHash, redeemAmount, litecoinAmount, tradeTimeout); - - long txTimestamp = System.currentTimeMillis(); - byte[] lastReference = deployer.getLastReference(); - - if (lastReference == null) { - System.err.println(String.format("Qortal account %s has no last reference", deployer.getAddress())); - System.exit(2); - } - - Long fee = null; - String name = "QORT-LTC cross-chain trade"; - String description = String.format("Qortal-Litecoin cross-chain trade"); - String atType = "ACCT"; - String tags = "QORT-LTC ACCT"; - - BaseTransactionData baseTransactionData = new BaseTransactionData(txTimestamp, Group.NO_GROUP, lastReference, deployer.getPublicKey(), fee, null); - TransactionData deployAtTransactionData = new DeployAtTransactionData(baseTransactionData, name, description, atType, tags, creationBytes, fundingAmount, Asset.QORT); - - DeployAtTransaction deployAtTransaction = new DeployAtTransaction(repository, deployAtTransactionData); - - fee = deployAtTransaction.calcRecommendedFee(); - deployAtTransactionData.setFee(fee); - - TransactionUtils.signAndMint(repository, deployAtTransactionData, deployer); - - return deployAtTransaction; - } - - private MessageTransaction sendMessage(Repository repository, PrivateKeyAccount sender, byte[] data, String recipient) throws DataException { - long txTimestamp = System.currentTimeMillis(); - byte[] lastReference = sender.getLastReference(); - - if (lastReference == null) { - System.err.println(String.format("Qortal account %s has no last reference", sender.getAddress())); - System.exit(2); - } - - Long fee = null; - int version = 4; - int nonce = 0; - long amount = 0; - Long assetId = null; // because amount is zero - - BaseTransactionData baseTransactionData = new BaseTransactionData(txTimestamp, Group.NO_GROUP, lastReference, sender.getPublicKey(), fee, null); - TransactionData messageTransactionData = new MessageTransactionData(baseTransactionData, version, nonce, recipient, amount, assetId, data, false, false); - - MessageTransaction messageTransaction = new MessageTransaction(repository, messageTransactionData); - - fee = messageTransaction.calcRecommendedFee(); - messageTransactionData.setFee(fee); - - TransactionUtils.signAndMint(repository, messageTransactionData, sender); - - return messageTransaction; - } - - private void checkTradeRefund(Repository repository, Account deployer, long deployersInitialBalance, long deployAtFee) throws DataException { - long deployersPostDeploymentBalance = deployersInitialBalance - fundingAmount - deployAtFee; - int refundTimeout = tradeTimeout / 2 + 1; // close enough - - // AT should automatically refund deployer after 'refundTimeout' blocks - for (int blockCount = 0; blockCount <= refundTimeout; ++blockCount) - BlockUtils.mintBlock(repository); - - // We don't bother to exactly calculate QORT spent running AT for several blocks, but we do know the expected range - long expectedMinimumBalance = deployersPostDeploymentBalance; - long expectedMaximumBalance = deployersInitialBalance - deployAtFee; - - long actualBalance = deployer.getConfirmedBalance(Asset.QORT); - - assertTrue(String.format("Deployer's balance %s should be above minimum %s", actualBalance, expectedMinimumBalance), actualBalance > expectedMinimumBalance); - assertTrue(String.format("Deployer's balance %s should be below maximum %s", actualBalance, expectedMaximumBalance), actualBalance < expectedMaximumBalance); - } - - private void describeAt(Repository repository, String atAddress) throws DataException { - ATData atData = repository.getATRepository().fromATAddress(atAddress); - CrossChainTradeData tradeData = LitecoinACCTv1.getInstance().populateTradeData(repository, atData); - - Function epochMilliFormatter = (timestamp) -> LocalDateTime.ofInstant(Instant.ofEpochMilli(timestamp), ZoneOffset.UTC).format(DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM)); - int currentBlockHeight = repository.getBlockRepository().getBlockchainHeight(); - - System.out.print(String.format("%s:\n" - + "\tmode: %s\n" - + "\tcreator: %s,\n" - + "\tcreation timestamp: %s,\n" - + "\tcurrent balance: %s QORT,\n" - + "\tis finished: %b,\n" - + "\tredeem payout: %s QORT,\n" - + "\texpected Litecoin: %s LTC,\n" - + "\tcurrent block height: %d,\n", - tradeData.qortalAtAddress, - tradeData.mode, - tradeData.qortalCreator, - epochMilliFormatter.apply(tradeData.creationTimestamp), - Amounts.prettyAmount(tradeData.qortBalance), - atData.getIsFinished(), - Amounts.prettyAmount(tradeData.qortAmount), - Amounts.prettyAmount(tradeData.expectedForeignAmount), - currentBlockHeight)); - - if (tradeData.mode != AcctMode.OFFERING && tradeData.mode != AcctMode.CANCELLED) { - System.out.println(String.format("\trefund timeout: %d minutes,\n" - + "\trefund height: block %d,\n" - + "\tHASH160 of secret-A: %s,\n" - + "\tLitecoin P2SH-A nLockTime: %d (%s),\n" - + "\ttrade partner: %s\n" - + "\tpartner's receiving address: %s", - tradeData.refundTimeout, - tradeData.tradeRefundHeight, - HashCode.fromBytes(tradeData.hashOfSecretA).toString().substring(0, 40), - tradeData.lockTimeA, epochMilliFormatter.apply(tradeData.lockTimeA * 1000L), - tradeData.qortalPartnerAddress, - tradeData.qortalPartnerReceivingAddress)); - } - } - - private PrivateKeyAccount createTradeAccount(Repository repository) { - // We actually use a known test account with funds to avoid PoW compute - return Common.getTestAccount(repository, "alice"); - } - } diff --git a/src/test/java/org/qortal/test/crosschain/litecoinv3/LitecoinACCTv3Tests.java b/src/test/java/org/qortal/test/crosschain/litecoinv3/LitecoinACCTv3Tests.java index 009af5ea..a1a0bfcc 100644 --- a/src/test/java/org/qortal/test/crosschain/litecoinv3/LitecoinACCTv3Tests.java +++ b/src/test/java/org/qortal/test/crosschain/litecoinv3/LitecoinACCTv3Tests.java @@ -1,769 +1,58 @@ package org.qortal.test.crosschain.litecoinv3; import com.google.common.hash.HashCode; -import com.google.common.primitives.Bytes; -import org.junit.Before; -import org.junit.Test; -import org.qortal.account.Account; -import org.qortal.account.PrivateKeyAccount; -import org.qortal.asset.Asset; -import org.qortal.block.Block; -import org.qortal.crosschain.AcctMode; -import org.qortal.crosschain.LitecoinACCTv3; -import org.qortal.crypto.Crypto; -import org.qortal.data.at.ATData; -import org.qortal.data.at.ATStateData; -import org.qortal.data.crosschain.CrossChainTradeData; -import org.qortal.data.transaction.BaseTransactionData; -import org.qortal.data.transaction.DeployAtTransactionData; -import org.qortal.data.transaction.MessageTransactionData; -import org.qortal.data.transaction.TransactionData; -import org.qortal.group.Group; -import org.qortal.repository.DataException; -import org.qortal.repository.Repository; -import org.qortal.repository.RepositoryManager; -import org.qortal.test.common.BlockUtils; -import org.qortal.test.common.Common; -import org.qortal.test.common.TransactionUtils; -import org.qortal.transaction.DeployAtTransaction; -import org.qortal.transaction.MessageTransaction; -import org.qortal.utils.Amounts; +import org.qortal.crosschain.ACCT; +import org.qortal.crosschain.LitecoinACCTv1; +import org.qortal.test.crosschain.ACCTTests; -import java.time.Instant; -import java.time.LocalDateTime; -import java.time.ZoneOffset; -import java.time.format.DateTimeFormatter; -import java.time.format.FormatStyle; -import java.util.Arrays; -import java.util.List; -import java.util.Random; -import java.util.function.Function; +public class LitecoinACCTv3Tests extends ACCTTests { -import static org.junit.Assert.*; - -public class LitecoinACCTv3Tests extends Common { - - public static final byte[] secretA = "This string is exactly 32 bytes!".getBytes(); - public static final byte[] hashOfSecretA = Crypto.hash160(secretA); // daf59884b4d1aec8c1b17102530909ee43c0151a public static final byte[] litecoinPublicKeyHash = HashCode.fromString("bb00bb11bb22bb33bb44bb55bb66bb77bb88bb99").asBytes(); - public static final int tradeTimeout = 20; // blocks - public static final long redeemAmount = 80_40200000L; - public static final long fundingAmount = 123_45600000L; - public static final long litecoinAmount = 864200L; // 0.00864200 LTC + private static final String SYMBOL = "LTC"; + private static final String NAME = "Litecoin"; - private static final Random RANDOM = new Random(); - - @Before - public void beforeTest() throws DataException { - Common.useDefaultSettings(); + @Override + protected byte[] getPublicKey() { + return litecoinPublicKeyHash; } - @Test - public void testCompile() { - PrivateKeyAccount tradeAccount = createTradeAccount(null); - - byte[] creationBytes = LitecoinACCTv3.buildQortalAT(tradeAccount.getAddress(), litecoinPublicKeyHash, redeemAmount, litecoinAmount, tradeTimeout); - assertNotNull(creationBytes); - - System.out.println("AT creation bytes: " + HashCode.fromBytes(creationBytes).toString()); + @Override + protected byte[] buildQortalAT(String address, byte[] publicKey, long redeemAmount, long foreignAmount, int tradeTimeout) { + return LitecoinACCTv1.buildQortalAT(address, publicKey, redeemAmount, foreignAmount, tradeTimeout); } - @Test - public void testDeploy() throws DataException { - try (final Repository repository = RepositoryManager.getRepository()) { - PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe"); - PrivateKeyAccount tradeAccount = createTradeAccount(repository); - - PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert"); - - long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT); - long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT); - - DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress()); - - long expectedBalance = deployersInitialBalance - fundingAmount - deployAtTransaction.getTransactionData().getFee(); - long actualBalance = deployer.getConfirmedBalance(Asset.QORT); - - assertEquals("Deployer's post-deployment balance incorrect", expectedBalance, actualBalance); - - expectedBalance = fundingAmount; - actualBalance = deployAtTransaction.getATAccount().getConfirmedBalance(Asset.QORT); - - assertEquals("AT's post-deployment balance incorrect", expectedBalance, actualBalance); - - expectedBalance = partnersInitialBalance; - actualBalance = partner.getConfirmedBalance(Asset.QORT); - - assertEquals("Partner's post-deployment balance incorrect", expectedBalance, actualBalance); - - // Test orphaning - BlockUtils.orphanLastBlock(repository); - - expectedBalance = deployersInitialBalance; - actualBalance = deployer.getConfirmedBalance(Asset.QORT); - - assertEquals("Deployer's post-orphan/pre-deployment balance incorrect", expectedBalance, actualBalance); - - expectedBalance = 0; - actualBalance = deployAtTransaction.getATAccount().getConfirmedBalance(Asset.QORT); - - assertEquals("AT's post-orphan/pre-deployment balance incorrect", expectedBalance, actualBalance); - - expectedBalance = partnersInitialBalance; - actualBalance = partner.getConfirmedBalance(Asset.QORT); - - assertEquals("Partner's post-orphan/pre-deployment balance incorrect", expectedBalance, actualBalance); - } + @Override + protected ACCT getInstance() { + return LitecoinACCTv1.getInstance(); } - @SuppressWarnings("unused") - @Test - public void testOfferCancel() throws DataException { - try (final Repository repository = RepositoryManager.getRepository()) { - PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe"); - PrivateKeyAccount tradeAccount = createTradeAccount(repository); - - PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert"); - - long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT); - long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT); - - DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress()); - Account at = deployAtTransaction.getATAccount(); - String atAddress = at.getAddress(); - - long deployAtFee = deployAtTransaction.getTransactionData().getFee(); - long deployersPostDeploymentBalance = deployersInitialBalance - fundingAmount - deployAtFee; - - // Send creator's address to AT, instead of typical partner's address - byte[] messageData = LitecoinACCTv3.getInstance().buildCancelMessage(deployer.getAddress()); - MessageTransaction messageTransaction = sendMessage(repository, deployer, messageData, atAddress); - long messageFee = messageTransaction.getTransactionData().getFee(); - - // AT should process 'cancel' message in next block - BlockUtils.mintBlock(repository); - - describeAt(repository, atAddress); - - // Check AT is finished - ATData atData = repository.getATRepository().fromATAddress(atAddress); - assertTrue(atData.getIsFinished()); - - // AT should be in CANCELLED mode - CrossChainTradeData tradeData = LitecoinACCTv3.getInstance().populateTradeData(repository, atData); - assertEquals(AcctMode.CANCELLED, tradeData.mode); - - // Check balances - long expectedMinimumBalance = deployersPostDeploymentBalance; - long expectedMaximumBalance = deployersInitialBalance - deployAtFee - messageFee; - - long actualBalance = deployer.getConfirmedBalance(Asset.QORT); - - assertTrue(String.format("Deployer's balance %s should be above minimum %s", actualBalance, expectedMinimumBalance), actualBalance > expectedMinimumBalance); - assertTrue(String.format("Deployer's balance %s should be below maximum %s", actualBalance, expectedMaximumBalance), actualBalance < expectedMaximumBalance); - - // Test orphaning - BlockUtils.orphanLastBlock(repository); - - // Check balances - long expectedBalance = deployersPostDeploymentBalance - messageFee; - actualBalance = deployer.getConfirmedBalance(Asset.QORT); - - assertEquals("Deployer's post-orphan/pre-refund balance incorrect", expectedBalance, actualBalance); - } + @Override + protected int calcRefundTimeout(long partnersOfferMessageTransactionTimestamp, int lockTimeA) { + return LitecoinACCTv1.calcRefundTimeout(partnersOfferMessageTransactionTimestamp, lockTimeA); } - @SuppressWarnings("unused") - @Test - public void testOfferCancelInvalidLength() throws DataException { - try (final Repository repository = RepositoryManager.getRepository()) { - PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe"); - PrivateKeyAccount tradeAccount = createTradeAccount(repository); - - PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert"); - - long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT); - long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT); - - DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress()); - Account at = deployAtTransaction.getATAccount(); - String atAddress = at.getAddress(); - - long deployAtFee = deployAtTransaction.getTransactionData().getFee(); - long deployersPostDeploymentBalance = deployersInitialBalance - fundingAmount - deployAtFee; - - // Instead of sending creator's address to AT, send too-short/invalid message - byte[] messageData = new byte[7]; - RANDOM.nextBytes(messageData); - MessageTransaction messageTransaction = sendMessage(repository, deployer, messageData, atAddress); - long messageFee = messageTransaction.getTransactionData().getFee(); - - // AT should process 'cancel' message in next block - // As message is too short, it will be padded to 32bytes but cancel code doesn't care about message content, so should be ok - BlockUtils.mintBlock(repository); - - describeAt(repository, atAddress); - - // Check AT is finished - ATData atData = repository.getATRepository().fromATAddress(atAddress); - assertTrue(atData.getIsFinished()); - - // AT should be in CANCELLED mode - CrossChainTradeData tradeData = LitecoinACCTv3.getInstance().populateTradeData(repository, atData); - assertEquals(AcctMode.CANCELLED, tradeData.mode); - } + @Override + protected byte[] buildTradeMessage(String address, byte[] publicKey, byte[] hashOfSecretA, int lockTimeA, int refundTimeout) { + return LitecoinACCTv1.buildTradeMessage(address, publicKey, hashOfSecretA, lockTimeA, refundTimeout); } - @SuppressWarnings("unused") - @Test - public void testTradingInfoProcessing() throws DataException { - try (final Repository repository = RepositoryManager.getRepository()) { - PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe"); - PrivateKeyAccount tradeAccount = createTradeAccount(repository); - - PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert"); - - long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT); - long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT); - - DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress()); - Account at = deployAtTransaction.getATAccount(); - String atAddress = at.getAddress(); - - long partnersOfferMessageTransactionTimestamp = System.currentTimeMillis(); - int lockTimeA = calcTestLockTimeA(partnersOfferMessageTransactionTimestamp); - int refundTimeout = LitecoinACCTv3.calcRefundTimeout(partnersOfferMessageTransactionTimestamp, lockTimeA); - - // Send trade info to AT - byte[] messageData = LitecoinACCTv3.buildTradeMessage(partner.getAddress(), litecoinPublicKeyHash, hashOfSecretA, lockTimeA, refundTimeout); - MessageTransaction messageTransaction = sendMessage(repository, tradeAccount, messageData, atAddress); - - Block postDeploymentBlock = BlockUtils.mintBlock(repository); - int postDeploymentBlockHeight = postDeploymentBlock.getBlockData().getHeight(); - - long deployAtFee = deployAtTransaction.getTransactionData().getFee(); - long deployersPostDeploymentBalance = deployersInitialBalance - fundingAmount - deployAtFee; - - describeAt(repository, atAddress); - - ATData atData = repository.getATRepository().fromATAddress(atAddress); - CrossChainTradeData tradeData = LitecoinACCTv3.getInstance().populateTradeData(repository, atData); - - // AT should be in TRADE mode - assertEquals(AcctMode.TRADING, tradeData.mode); - - // Check hashOfSecretA was extracted correctly - assertTrue(Arrays.equals(hashOfSecretA, tradeData.hashOfSecretA)); - - // Check trade partner Qortal address was extracted correctly - assertEquals(partner.getAddress(), tradeData.qortalPartnerAddress); - - // Check trade partner's Litecoin PKH was extracted correctly - assertTrue(Arrays.equals(litecoinPublicKeyHash, tradeData.partnerForeignPKH)); - - // Test orphaning - BlockUtils.orphanToBlock(repository, postDeploymentBlockHeight); - - // Check balances - long expectedBalance = deployersPostDeploymentBalance; - long actualBalance = deployer.getConfirmedBalance(Asset.QORT); - - assertEquals("Deployer's post-orphan/pre-refund balance incorrect", expectedBalance, actualBalance); - } + @Override + protected byte[] buildRedeemMessage(byte[] secretA, String address) { + return LitecoinACCTv1.buildRedeemMessage(secretA, address); } - // TEST SENDING TRADING INFO BUT NOT FROM AT CREATOR (SHOULD BE IGNORED) - @SuppressWarnings("unused") - @Test - public void testIncorrectTradeSender() throws DataException { - try (final Repository repository = RepositoryManager.getRepository()) { - PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe"); - PrivateKeyAccount tradeAccount = createTradeAccount(repository); - - PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert"); - - PrivateKeyAccount bystander = Common.getTestAccount(repository, "bob"); - - long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT); - long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT); - - DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress()); - Account at = deployAtTransaction.getATAccount(); - String atAddress = at.getAddress(); - - long partnersOfferMessageTransactionTimestamp = System.currentTimeMillis(); - int lockTimeA = calcTestLockTimeA(partnersOfferMessageTransactionTimestamp); - int refundTimeout = LitecoinACCTv3.calcRefundTimeout(partnersOfferMessageTransactionTimestamp, lockTimeA); - - // Send trade info to AT BUT NOT FROM AT CREATOR - byte[] messageData = LitecoinACCTv3.buildTradeMessage(partner.getAddress(), litecoinPublicKeyHash, hashOfSecretA, lockTimeA, refundTimeout); - MessageTransaction messageTransaction = sendMessage(repository, bystander, messageData, atAddress); - - BlockUtils.mintBlock(repository); - - long expectedBalance = partnersInitialBalance; - long actualBalance = partner.getConfirmedBalance(Asset.QORT); - - assertEquals("Partner's post-initial-payout balance incorrect", expectedBalance, actualBalance); - - describeAt(repository, atAddress); - - ATData atData = repository.getATRepository().fromATAddress(atAddress); - CrossChainTradeData tradeData = LitecoinACCTv3.getInstance().populateTradeData(repository, atData); - - // AT should still be in OFFER mode - assertEquals(AcctMode.OFFERING, tradeData.mode); - } + @Override + protected byte[] getCodeBytesHash() { + return LitecoinACCTv1.CODE_BYTES_HASH; } - @SuppressWarnings("unused") - @Test - public void testAutomaticTradeRefund() throws DataException { - try (final Repository repository = RepositoryManager.getRepository()) { - PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe"); - PrivateKeyAccount tradeAccount = createTradeAccount(repository); - - PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert"); - - long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT); - long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT); - - DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress()); - Account at = deployAtTransaction.getATAccount(); - String atAddress = at.getAddress(); - - long partnersOfferMessageTransactionTimestamp = System.currentTimeMillis(); - int lockTimeA = calcTestLockTimeA(partnersOfferMessageTransactionTimestamp); - int refundTimeout = LitecoinACCTv3.calcRefundTimeout(partnersOfferMessageTransactionTimestamp, lockTimeA); - - // Send trade info to AT - byte[] messageData = LitecoinACCTv3.buildTradeMessage(partner.getAddress(), litecoinPublicKeyHash, hashOfSecretA, lockTimeA, refundTimeout); - MessageTransaction messageTransaction = sendMessage(repository, tradeAccount, messageData, atAddress); - - Block postDeploymentBlock = BlockUtils.mintBlock(repository); - int postDeploymentBlockHeight = postDeploymentBlock.getBlockData().getHeight(); - - // Check refund - long deployAtFee = deployAtTransaction.getTransactionData().getFee(); - long deployersPostDeploymentBalance = deployersInitialBalance - fundingAmount - deployAtFee; - - checkTradeRefund(repository, deployer, deployersInitialBalance, deployAtFee); - - describeAt(repository, atAddress); - - // Check AT is finished - ATData atData = repository.getATRepository().fromATAddress(atAddress); - assertTrue(atData.getIsFinished()); - - // AT should be in REFUNDED mode - CrossChainTradeData tradeData = LitecoinACCTv3.getInstance().populateTradeData(repository, atData); - assertEquals(AcctMode.REFUNDED, tradeData.mode); - - // Test orphaning - BlockUtils.orphanToBlock(repository, postDeploymentBlockHeight); - - // Check balances - long expectedBalance = deployersPostDeploymentBalance; - long actualBalance = deployer.getConfirmedBalance(Asset.QORT); - - assertEquals("Deployer's post-orphan/pre-refund balance incorrect", expectedBalance, actualBalance); - } + @Override + protected String getSymbol() { + return SYMBOL; } - @SuppressWarnings("unused") - @Test - public void testCorrectSecretCorrectSender() throws DataException { - try (final Repository repository = RepositoryManager.getRepository()) { - PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe"); - PrivateKeyAccount tradeAccount = createTradeAccount(repository); - - PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert"); - - long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT); - long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT); - - DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress()); - Account at = deployAtTransaction.getATAccount(); - String atAddress = at.getAddress(); - - long partnersOfferMessageTransactionTimestamp = System.currentTimeMillis(); - int lockTimeA = calcTestLockTimeA(partnersOfferMessageTransactionTimestamp); - int refundTimeout = LitecoinACCTv3.calcRefundTimeout(partnersOfferMessageTransactionTimestamp, lockTimeA); - - // Send trade info to AT - byte[] messageData = LitecoinACCTv3.buildTradeMessage(partner.getAddress(), litecoinPublicKeyHash, hashOfSecretA, lockTimeA, refundTimeout); - MessageTransaction messageTransaction = sendMessage(repository, tradeAccount, messageData, atAddress); - - // Give AT time to process message - BlockUtils.mintBlock(repository); - - // Send correct secret to AT, from correct account - messageData = LitecoinACCTv3.buildRedeemMessage(secretA, partner.getAddress()); - messageTransaction = sendMessage(repository, partner, messageData, atAddress); - - // AT should send funds in the next block - ATStateData preRedeemAtStateData = repository.getATRepository().getLatestATState(atAddress); - BlockUtils.mintBlock(repository); - - describeAt(repository, atAddress); - - // Check AT is finished - ATData atData = repository.getATRepository().fromATAddress(atAddress); - assertTrue(atData.getIsFinished()); - - // AT should be in REDEEMED mode - CrossChainTradeData tradeData = LitecoinACCTv3.getInstance().populateTradeData(repository, atData); - assertEquals(AcctMode.REDEEMED, tradeData.mode); - - // Check balances - long expectedBalance = partnersInitialBalance - messageTransaction.getTransactionData().getFee() + redeemAmount; - long actualBalance = partner.getConfirmedBalance(Asset.QORT); - - assertEquals("Partner's post-redeem balance incorrect", expectedBalance, actualBalance); - - // Orphan redeem - BlockUtils.orphanLastBlock(repository); - - // Check balances - expectedBalance = partnersInitialBalance - messageTransaction.getTransactionData().getFee(); - actualBalance = partner.getConfirmedBalance(Asset.QORT); - - assertEquals("Partner's post-orphan/pre-redeem balance incorrect", expectedBalance, actualBalance); - - // Check AT state - ATStateData postOrphanAtStateData = repository.getATRepository().getLatestATState(atAddress); - - assertTrue("AT states mismatch", Arrays.equals(preRedeemAtStateData.getStateData(), postOrphanAtStateData.getStateData())); - } + @Override + protected String getName() { + return NAME; } - - @SuppressWarnings("unused") - @Test - public void testCorrectSecretIncorrectSender() throws DataException { - try (final Repository repository = RepositoryManager.getRepository()) { - PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe"); - PrivateKeyAccount tradeAccount = createTradeAccount(repository); - - PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert"); - - PrivateKeyAccount bystander = Common.getTestAccount(repository, "bob"); - - long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT); - long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT); - - DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress()); - long deployAtFee = deployAtTransaction.getTransactionData().getFee(); - - Account at = deployAtTransaction.getATAccount(); - String atAddress = at.getAddress(); - - long partnersOfferMessageTransactionTimestamp = System.currentTimeMillis(); - int lockTimeA = calcTestLockTimeA(partnersOfferMessageTransactionTimestamp); - int refundTimeout = LitecoinACCTv3.calcRefundTimeout(partnersOfferMessageTransactionTimestamp, lockTimeA); - - // Send trade info to AT - byte[] messageData = LitecoinACCTv3.buildTradeMessage(partner.getAddress(), litecoinPublicKeyHash, hashOfSecretA, lockTimeA, refundTimeout); - MessageTransaction messageTransaction = sendMessage(repository, tradeAccount, messageData, atAddress); - - // Give AT time to process message - BlockUtils.mintBlock(repository); - - // Send correct secret to AT, but from wrong account - messageData = LitecoinACCTv3.buildRedeemMessage(secretA, partner.getAddress()); - messageTransaction = sendMessage(repository, bystander, messageData, atAddress); - - // AT should NOT send funds in the next block - ATStateData preRedeemAtStateData = repository.getATRepository().getLatestATState(atAddress); - BlockUtils.mintBlock(repository); - - describeAt(repository, atAddress); - - // Check AT is NOT finished - ATData atData = repository.getATRepository().fromATAddress(atAddress); - assertFalse(atData.getIsFinished()); - - // AT should still be in TRADE mode - CrossChainTradeData tradeData = LitecoinACCTv3.getInstance().populateTradeData(repository, atData); - assertEquals(AcctMode.TRADING, tradeData.mode); - - // Check balances - long expectedBalance = partnersInitialBalance; - long actualBalance = partner.getConfirmedBalance(Asset.QORT); - - assertEquals("Partner's balance incorrect", expectedBalance, actualBalance); - - // Check eventual refund - checkTradeRefund(repository, deployer, deployersInitialBalance, deployAtFee); - } - } - - @SuppressWarnings("unused") - @Test - public void testIncorrectSecretCorrectSender() throws DataException { - try (final Repository repository = RepositoryManager.getRepository()) { - PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe"); - PrivateKeyAccount tradeAccount = createTradeAccount(repository); - - PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert"); - - long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT); - long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT); - - DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress()); - long deployAtFee = deployAtTransaction.getTransactionData().getFee(); - - Account at = deployAtTransaction.getATAccount(); - String atAddress = at.getAddress(); - - long partnersOfferMessageTransactionTimestamp = System.currentTimeMillis(); - int lockTimeA = calcTestLockTimeA(partnersOfferMessageTransactionTimestamp); - int refundTimeout = LitecoinACCTv3.calcRefundTimeout(partnersOfferMessageTransactionTimestamp, lockTimeA); - - // Send trade info to AT - byte[] messageData = LitecoinACCTv3.buildTradeMessage(partner.getAddress(), litecoinPublicKeyHash, hashOfSecretA, lockTimeA, refundTimeout); - MessageTransaction messageTransaction = sendMessage(repository, tradeAccount, messageData, atAddress); - - // Give AT time to process message - BlockUtils.mintBlock(repository); - - // Send incorrect secret to AT, from correct account - byte[] wrongSecret = new byte[32]; - RANDOM.nextBytes(wrongSecret); - messageData = LitecoinACCTv3.buildRedeemMessage(wrongSecret, partner.getAddress()); - messageTransaction = sendMessage(repository, partner, messageData, atAddress); - - // AT should NOT send funds in the next block - ATStateData preRedeemAtStateData = repository.getATRepository().getLatestATState(atAddress); - BlockUtils.mintBlock(repository); - - describeAt(repository, atAddress); - - // Check AT is NOT finished - ATData atData = repository.getATRepository().fromATAddress(atAddress); - assertFalse(atData.getIsFinished()); - - // AT should still be in TRADE mode - CrossChainTradeData tradeData = LitecoinACCTv3.getInstance().populateTradeData(repository, atData); - assertEquals(AcctMode.TRADING, tradeData.mode); - - long expectedBalance = partnersInitialBalance - messageTransaction.getTransactionData().getFee(); - long actualBalance = partner.getConfirmedBalance(Asset.QORT); - - assertEquals("Partner's balance incorrect", expectedBalance, actualBalance); - - // Check eventual refund - checkTradeRefund(repository, deployer, deployersInitialBalance, deployAtFee); - } - } - - @SuppressWarnings("unused") - @Test - public void testCorrectSecretCorrectSenderInvalidMessageLength() throws DataException { - try (final Repository repository = RepositoryManager.getRepository()) { - PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe"); - PrivateKeyAccount tradeAccount = createTradeAccount(repository); - - PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert"); - - long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT); - long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT); - - DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress()); - Account at = deployAtTransaction.getATAccount(); - String atAddress = at.getAddress(); - - long partnersOfferMessageTransactionTimestamp = System.currentTimeMillis(); - int lockTimeA = calcTestLockTimeA(partnersOfferMessageTransactionTimestamp); - int refundTimeout = LitecoinACCTv3.calcRefundTimeout(partnersOfferMessageTransactionTimestamp, lockTimeA); - - // Send trade info to AT - byte[] messageData = LitecoinACCTv3.buildTradeMessage(partner.getAddress(), litecoinPublicKeyHash, hashOfSecretA, lockTimeA, refundTimeout); - MessageTransaction messageTransaction = sendMessage(repository, tradeAccount, messageData, atAddress); - - // Give AT time to process message - BlockUtils.mintBlock(repository); - - // Send correct secret to AT, from correct account, but missing receive address, hence incorrect length - messageData = Bytes.concat(secretA); - messageTransaction = sendMessage(repository, partner, messageData, atAddress); - - // AT should NOT send funds in the next block - ATStateData preRedeemAtStateData = repository.getATRepository().getLatestATState(atAddress); - BlockUtils.mintBlock(repository); - - describeAt(repository, atAddress); - - // Check AT is NOT finished - ATData atData = repository.getATRepository().fromATAddress(atAddress); - assertFalse(atData.getIsFinished()); - - // AT should be in TRADING mode - CrossChainTradeData tradeData = LitecoinACCTv3.getInstance().populateTradeData(repository, atData); - assertEquals(AcctMode.TRADING, tradeData.mode); - } - } - - @SuppressWarnings("unused") - @Test - public void testDescribeDeployed() throws DataException { - try (final Repository repository = RepositoryManager.getRepository()) { - PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe"); - PrivateKeyAccount tradeAccount = createTradeAccount(repository); - - PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert"); - - long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT); - long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT); - - DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress()); - - List executableAts = repository.getATRepository().getAllExecutableATs(); - - for (ATData atData : executableAts) { - String atAddress = atData.getATAddress(); - byte[] codeBytes = atData.getCodeBytes(); - byte[] codeHash = Crypto.digest(codeBytes); - - System.out.println(String.format("%s: code length: %d byte%s, code hash: %s", - atAddress, - codeBytes.length, - (codeBytes.length != 1 ? "s": ""), - HashCode.fromBytes(codeHash))); - - // Not one of ours? - if (!Arrays.equals(codeHash, LitecoinACCTv3.CODE_BYTES_HASH)) - continue; - - describeAt(repository, atAddress); - } - } - } - - private int calcTestLockTimeA(long messageTimestamp) { - return (int) (messageTimestamp / 1000L + tradeTimeout * 60); - } - - private DeployAtTransaction doDeploy(Repository repository, PrivateKeyAccount deployer, String tradeAddress) throws DataException { - byte[] creationBytes = LitecoinACCTv3.buildQortalAT(tradeAddress, litecoinPublicKeyHash, redeemAmount, litecoinAmount, tradeTimeout); - - long txTimestamp = System.currentTimeMillis(); - byte[] lastReference = deployer.getLastReference(); - - if (lastReference == null) { - System.err.println(String.format("Qortal account %s has no last reference", deployer.getAddress())); - System.exit(2); - } - - Long fee = null; - String name = "QORT-LTC cross-chain trade"; - String description = String.format("Qortal-Litecoin cross-chain trade"); - String atType = "ACCT"; - String tags = "QORT-LTC ACCT"; - - BaseTransactionData baseTransactionData = new BaseTransactionData(txTimestamp, Group.NO_GROUP, lastReference, deployer.getPublicKey(), fee, null); - TransactionData deployAtTransactionData = new DeployAtTransactionData(baseTransactionData, name, description, atType, tags, creationBytes, fundingAmount, Asset.QORT); - - DeployAtTransaction deployAtTransaction = new DeployAtTransaction(repository, deployAtTransactionData); - - fee = deployAtTransaction.calcRecommendedFee(); - deployAtTransactionData.setFee(fee); - - TransactionUtils.signAndMint(repository, deployAtTransactionData, deployer); - - return deployAtTransaction; - } - - private MessageTransaction sendMessage(Repository repository, PrivateKeyAccount sender, byte[] data, String recipient) throws DataException { - long txTimestamp = System.currentTimeMillis(); - byte[] lastReference = sender.getLastReference(); - - if (lastReference == null) { - System.err.println(String.format("Qortal account %s has no last reference", sender.getAddress())); - System.exit(2); - } - - Long fee = null; - int version = 4; - int nonce = 0; - long amount = 0; - Long assetId = null; // because amount is zero - - BaseTransactionData baseTransactionData = new BaseTransactionData(txTimestamp, Group.NO_GROUP, lastReference, sender.getPublicKey(), fee, null); - TransactionData messageTransactionData = new MessageTransactionData(baseTransactionData, version, nonce, recipient, amount, assetId, data, false, false); - - MessageTransaction messageTransaction = new MessageTransaction(repository, messageTransactionData); - - fee = messageTransaction.calcRecommendedFee(); - messageTransactionData.setFee(fee); - - TransactionUtils.signAndMint(repository, messageTransactionData, sender); - - return messageTransaction; - } - - private void checkTradeRefund(Repository repository, Account deployer, long deployersInitialBalance, long deployAtFee) throws DataException { - long deployersPostDeploymentBalance = deployersInitialBalance - fundingAmount - deployAtFee; - int refundTimeout = tradeTimeout / 2 + 1; // close enough - - // AT should automatically refund deployer after 'refundTimeout' blocks - for (int blockCount = 0; blockCount <= refundTimeout; ++blockCount) - BlockUtils.mintBlock(repository); - - // We don't bother to exactly calculate QORT spent running AT for several blocks, but we do know the expected range - long expectedMinimumBalance = deployersPostDeploymentBalance; - long expectedMaximumBalance = deployersInitialBalance - deployAtFee; - - long actualBalance = deployer.getConfirmedBalance(Asset.QORT); - - assertTrue(String.format("Deployer's balance %s should be above minimum %s", actualBalance, expectedMinimumBalance), actualBalance > expectedMinimumBalance); - assertTrue(String.format("Deployer's balance %s should be below maximum %s", actualBalance, expectedMaximumBalance), actualBalance < expectedMaximumBalance); - } - - private void describeAt(Repository repository, String atAddress) throws DataException { - ATData atData = repository.getATRepository().fromATAddress(atAddress); - CrossChainTradeData tradeData = LitecoinACCTv3.getInstance().populateTradeData(repository, atData); - - Function epochMilliFormatter = (timestamp) -> LocalDateTime.ofInstant(Instant.ofEpochMilli(timestamp), ZoneOffset.UTC).format(DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM)); - int currentBlockHeight = repository.getBlockRepository().getBlockchainHeight(); - - System.out.print(String.format("%s:\n" - + "\tmode: %s\n" - + "\tcreator: %s,\n" - + "\tcreation timestamp: %s,\n" - + "\tcurrent balance: %s QORT,\n" - + "\tis finished: %b,\n" - + "\tredeem payout: %s QORT,\n" - + "\texpected Litecoin: %s LTC,\n" - + "\tcurrent block height: %d,\n", - tradeData.qortalAtAddress, - tradeData.mode, - tradeData.qortalCreator, - epochMilliFormatter.apply(tradeData.creationTimestamp), - Amounts.prettyAmount(tradeData.qortBalance), - atData.getIsFinished(), - Amounts.prettyAmount(tradeData.qortAmount), - Amounts.prettyAmount(tradeData.expectedForeignAmount), - currentBlockHeight)); - - if (tradeData.mode != AcctMode.OFFERING && tradeData.mode != AcctMode.CANCELLED) { - System.out.println(String.format("\trefund timeout: %d minutes,\n" - + "\trefund height: block %d,\n" - + "\tHASH160 of secret-A: %s,\n" - + "\tLitecoin P2SH-A nLockTime: %d (%s),\n" - + "\ttrade partner: %s\n" - + "\tpartner's receiving address: %s", - tradeData.refundTimeout, - tradeData.tradeRefundHeight, - HashCode.fromBytes(tradeData.hashOfSecretA).toString().substring(0, 40), - tradeData.lockTimeA, epochMilliFormatter.apply(tradeData.lockTimeA * 1000L), - tradeData.qortalPartnerAddress, - tradeData.qortalPartnerReceivingAddress)); - } - } - - private PrivateKeyAccount createTradeAccount(Repository repository) { - // We actually use a known test account with funds to avoid PoW compute - return Common.getTestAccount(repository, "alice"); - } - } diff --git a/src/test/java/org/qortal/test/crosschain/piratechainv3/PirateChainACCTv3Tests.java b/src/test/java/org/qortal/test/crosschain/piratechainv3/PirateChainACCTv3Tests.java index f9ac9de1..18099872 100644 --- a/src/test/java/org/qortal/test/crosschain/piratechainv3/PirateChainACCTv3Tests.java +++ b/src/test/java/org/qortal/test/crosschain/piratechainv3/PirateChainACCTv3Tests.java @@ -1,771 +1,58 @@ package org.qortal.test.crosschain.piratechainv3; import com.google.common.hash.HashCode; -import com.google.common.primitives.Bytes; -import org.junit.Before; -import org.junit.Test; -import org.qortal.account.Account; -import org.qortal.account.PrivateKeyAccount; -import org.qortal.asset.Asset; -import org.qortal.block.Block; -import org.qortal.crosschain.AcctMode; +import org.qortal.crosschain.ACCT; import org.qortal.crosschain.PirateChainACCTv3; -import org.qortal.crypto.Crypto; -import org.qortal.data.at.ATData; -import org.qortal.data.at.ATStateData; -import org.qortal.data.crosschain.CrossChainTradeData; -import org.qortal.data.transaction.BaseTransactionData; -import org.qortal.data.transaction.DeployAtTransactionData; -import org.qortal.data.transaction.MessageTransactionData; -import org.qortal.data.transaction.TransactionData; -import org.qortal.group.Group; -import org.qortal.repository.DataException; -import org.qortal.repository.Repository; -import org.qortal.repository.RepositoryManager; -import org.qortal.test.common.BlockUtils; -import org.qortal.test.common.Common; -import org.qortal.test.common.TransactionUtils; -import org.qortal.transaction.DeployAtTransaction; -import org.qortal.transaction.MessageTransaction; -import org.qortal.utils.Amounts; +import org.qortal.test.crosschain.ACCTTests; -import java.time.Instant; -import java.time.LocalDateTime; -import java.time.ZoneOffset; -import java.time.format.DateTimeFormatter; -import java.time.format.FormatStyle; -import java.util.Arrays; -import java.util.List; -import java.util.Random; -import java.util.function.Function; +public class PirateChainACCTv3Tests extends ACCTTests { -import static org.junit.Assert.*; - -public class PirateChainACCTv3Tests extends Common { - - public static final byte[] secretA = "This string is exactly 32 bytes!".getBytes(); - public static final byte[] hashOfSecretA = Crypto.hash160(secretA); // daf59884b4d1aec8c1b17102530909ee43c0151a public static final byte[] pirateChainPublicKey = HashCode.fromString("aabb00bb11bb22bb33bb44bb55bb66bb77bb88bb99cc00cc11cc22cc33cc44cc55").asBytes(); // 33 bytes - public static final int tradeTimeout = 20; // blocks - public static final long redeemAmount = 80_40200000L; - public static final long fundingAmount = 123_45600000L; - public static final long arrrAmount = 864200L; // 0.00864200 ARRR + private static final String SYMBOL = "ARRR"; + private static final String NAME = "Pirate Chain"; - private static final Random RANDOM = new Random(); - - @Before - public void beforeTest() throws DataException { - Common.useDefaultSettings(); + @Override + protected byte[] getPublicKey() { + return pirateChainPublicKey; } - @Test - public void testCompile() { - PrivateKeyAccount tradeAccount = createTradeAccount(null); - - byte[] creationBytes = PirateChainACCTv3.buildQortalAT(tradeAccount.getAddress(), pirateChainPublicKey, redeemAmount, arrrAmount, tradeTimeout); - assertNotNull(creationBytes); - - System.out.println("AT creation bytes: " + HashCode.fromBytes(creationBytes).toString()); + @Override + protected byte[] buildQortalAT(String address, byte[] publicKey, long redeemAmount, long foreignAmount, int tradeTimeout) { + return PirateChainACCTv3.buildQortalAT(address, publicKey, redeemAmount, foreignAmount, tradeTimeout); } - @Test - public void testDeploy() throws DataException { - try (final Repository repository = RepositoryManager.getRepository()) { - PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe"); - PrivateKeyAccount tradeAccount = createTradeAccount(repository); - - PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert"); - - long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT); - long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT); - - DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress()); - - long expectedBalance = deployersInitialBalance - fundingAmount - deployAtTransaction.getTransactionData().getFee(); - long actualBalance = deployer.getConfirmedBalance(Asset.QORT); - - assertEquals("Deployer's post-deployment balance incorrect", expectedBalance, actualBalance); - - expectedBalance = fundingAmount; - actualBalance = deployAtTransaction.getATAccount().getConfirmedBalance(Asset.QORT); - - assertEquals("AT's post-deployment balance incorrect", expectedBalance, actualBalance); - - expectedBalance = partnersInitialBalance; - actualBalance = partner.getConfirmedBalance(Asset.QORT); - - assertEquals("Partner's post-deployment balance incorrect", expectedBalance, actualBalance); - - // Test orphaning - BlockUtils.orphanLastBlock(repository); - - expectedBalance = deployersInitialBalance; - actualBalance = deployer.getConfirmedBalance(Asset.QORT); - - assertEquals("Deployer's post-orphan/pre-deployment balance incorrect", expectedBalance, actualBalance); - - expectedBalance = 0; - actualBalance = deployAtTransaction.getATAccount().getConfirmedBalance(Asset.QORT); - - assertEquals("AT's post-orphan/pre-deployment balance incorrect", expectedBalance, actualBalance); - - expectedBalance = partnersInitialBalance; - actualBalance = partner.getConfirmedBalance(Asset.QORT); - - assertEquals("Partner's post-orphan/pre-deployment balance incorrect", expectedBalance, actualBalance); - } + @Override + protected ACCT getInstance() { + return PirateChainACCTv3.getInstance(); } - @SuppressWarnings("unused") - @Test - public void testOfferCancel() throws DataException { - try (final Repository repository = RepositoryManager.getRepository()) { - PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe"); - PrivateKeyAccount tradeAccount = createTradeAccount(repository); - - PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert"); - - long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT); - long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT); - - DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress()); - Account at = deployAtTransaction.getATAccount(); - String atAddress = at.getAddress(); - - long deployAtFee = deployAtTransaction.getTransactionData().getFee(); - long deployersPostDeploymentBalance = deployersInitialBalance - fundingAmount - deployAtFee; - - // Send creator's address to AT, instead of typical partner's address - byte[] messageData = PirateChainACCTv3.getInstance().buildCancelMessage(deployer.getAddress()); - MessageTransaction messageTransaction = sendMessage(repository, deployer, messageData, atAddress); - long messageFee = messageTransaction.getTransactionData().getFee(); - - // AT should process 'cancel' message in next block - BlockUtils.mintBlock(repository); - - describeAt(repository, atAddress); - - // Check AT is finished - ATData atData = repository.getATRepository().fromATAddress(atAddress); - assertTrue(atData.getIsFinished()); - - // AT should be in CANCELLED mode - CrossChainTradeData tradeData = PirateChainACCTv3.getInstance().populateTradeData(repository, atData); - assertEquals(AcctMode.CANCELLED, tradeData.mode); - - // Check balances - long expectedMinimumBalance = deployersPostDeploymentBalance; - long expectedMaximumBalance = deployersInitialBalance - deployAtFee - messageFee; - - long actualBalance = deployer.getConfirmedBalance(Asset.QORT); - - assertTrue(String.format("Deployer's balance %s should be above minimum %s", actualBalance, expectedMinimumBalance), actualBalance > expectedMinimumBalance); - assertTrue(String.format("Deployer's balance %s should be below maximum %s", actualBalance, expectedMaximumBalance), actualBalance < expectedMaximumBalance); - - // Test orphaning - BlockUtils.orphanLastBlock(repository); - - // Check balances - long expectedBalance = deployersPostDeploymentBalance - messageFee; - actualBalance = deployer.getConfirmedBalance(Asset.QORT); - - assertEquals("Deployer's post-orphan/pre-refund balance incorrect", expectedBalance, actualBalance); - } + @Override + protected int calcRefundTimeout(long partnersOfferMessageTransactionTimestamp, int lockTimeA) { + return PirateChainACCTv3.calcRefundTimeout(partnersOfferMessageTransactionTimestamp, lockTimeA); } - @SuppressWarnings("unused") - @Test - public void testOfferCancelInvalidLength() throws DataException { - try (final Repository repository = RepositoryManager.getRepository()) { - PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe"); - PrivateKeyAccount tradeAccount = createTradeAccount(repository); - - PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert"); - - long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT); - long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT); - - DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress()); - Account at = deployAtTransaction.getATAccount(); - String atAddress = at.getAddress(); - - long deployAtFee = deployAtTransaction.getTransactionData().getFee(); - long deployersPostDeploymentBalance = deployersInitialBalance - fundingAmount - deployAtFee; - - // Instead of sending creator's address to AT, send too-short/invalid message - byte[] messageData = new byte[7]; - RANDOM.nextBytes(messageData); - MessageTransaction messageTransaction = sendMessage(repository, deployer, messageData, atAddress); - long messageFee = messageTransaction.getTransactionData().getFee(); - - // AT should process 'cancel' message in next block - // As message is too short, it will be padded to 32bytes but cancel code doesn't care about message content, so should be ok - BlockUtils.mintBlock(repository); - - describeAt(repository, atAddress); - - // Check AT is finished - ATData atData = repository.getATRepository().fromATAddress(atAddress); - assertTrue(atData.getIsFinished()); - - // AT should be in CANCELLED mode - CrossChainTradeData tradeData = PirateChainACCTv3.getInstance().populateTradeData(repository, atData); - assertEquals(AcctMode.CANCELLED, tradeData.mode); - } + @Override + protected byte[] buildTradeMessage(String address, byte[] publicKey, byte[] hashOfSecretA, int lockTimeA, int refundTimeout) { + return PirateChainACCTv3.buildTradeMessage(address, publicKey, hashOfSecretA, lockTimeA, refundTimeout); } - @SuppressWarnings("unused") - @Test - public void testTradingInfoProcessing() throws DataException { - try (final Repository repository = RepositoryManager.getRepository()) { - PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe"); - PrivateKeyAccount tradeAccount = createTradeAccount(repository); - - PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert"); - - long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT); - long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT); - - DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress()); - Account at = deployAtTransaction.getATAccount(); - String atAddress = at.getAddress(); - - long partnersOfferMessageTransactionTimestamp = System.currentTimeMillis(); - int lockTimeA = calcTestLockTimeA(partnersOfferMessageTransactionTimestamp); - int refundTimeout = PirateChainACCTv3.calcRefundTimeout(partnersOfferMessageTransactionTimestamp, lockTimeA); - - // Send trade info to AT - byte[] messageData = PirateChainACCTv3.buildTradeMessage(partner.getAddress(), pirateChainPublicKey, hashOfSecretA, lockTimeA, refundTimeout); - MessageTransaction messageTransaction = sendMessage(repository, tradeAccount, messageData, atAddress); - - Block postDeploymentBlock = BlockUtils.mintBlock(repository); - int postDeploymentBlockHeight = postDeploymentBlock.getBlockData().getHeight(); - - long deployAtFee = deployAtTransaction.getTransactionData().getFee(); - long deployersPostDeploymentBalance = deployersInitialBalance - fundingAmount - deployAtFee; - - describeAt(repository, atAddress); - - System.out.println(String.format("pirateChainPublicKey: %s", HashCode.fromBytes(pirateChainPublicKey))); - - ATData atData = repository.getATRepository().fromATAddress(atAddress); - CrossChainTradeData tradeData = PirateChainACCTv3.getInstance().populateTradeData(repository, atData); - - // AT should be in TRADE mode - assertEquals(AcctMode.TRADING, tradeData.mode); - - // Check hashOfSecretA was extracted correctly - assertTrue(Arrays.equals(hashOfSecretA, tradeData.hashOfSecretA)); - - // Check trade partner Qortal address was extracted correctly - assertEquals(partner.getAddress(), tradeData.qortalPartnerAddress); - - // Check trade partner's Litecoin PKH was extracted correctly - assertTrue(Arrays.equals(pirateChainPublicKey, tradeData.partnerForeignPKH)); - - // Test orphaning - BlockUtils.orphanToBlock(repository, postDeploymentBlockHeight); - - // Check balances - long expectedBalance = deployersPostDeploymentBalance; - long actualBalance = deployer.getConfirmedBalance(Asset.QORT); - - assertEquals("Deployer's post-orphan/pre-refund balance incorrect", expectedBalance, actualBalance); - } + @Override + protected byte[] buildRedeemMessage(byte[] secretA, String address) { + return PirateChainACCTv3.buildRedeemMessage(secretA, address); } - // TEST SENDING TRADING INFO BUT NOT FROM AT CREATOR (SHOULD BE IGNORED) - @SuppressWarnings("unused") - @Test - public void testIncorrectTradeSender() throws DataException { - try (final Repository repository = RepositoryManager.getRepository()) { - PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe"); - PrivateKeyAccount tradeAccount = createTradeAccount(repository); - - PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert"); - - PrivateKeyAccount bystander = Common.getTestAccount(repository, "bob"); - - long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT); - long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT); - - DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress()); - Account at = deployAtTransaction.getATAccount(); - String atAddress = at.getAddress(); - - long partnersOfferMessageTransactionTimestamp = System.currentTimeMillis(); - int lockTimeA = calcTestLockTimeA(partnersOfferMessageTransactionTimestamp); - int refundTimeout = PirateChainACCTv3.calcRefundTimeout(partnersOfferMessageTransactionTimestamp, lockTimeA); - - // Send trade info to AT BUT NOT FROM AT CREATOR - byte[] messageData = PirateChainACCTv3.buildTradeMessage(partner.getAddress(), pirateChainPublicKey, hashOfSecretA, lockTimeA, refundTimeout); - MessageTransaction messageTransaction = sendMessage(repository, bystander, messageData, atAddress); - - BlockUtils.mintBlock(repository); - - long expectedBalance = partnersInitialBalance; - long actualBalance = partner.getConfirmedBalance(Asset.QORT); - - assertEquals("Partner's post-initial-payout balance incorrect", expectedBalance, actualBalance); - - describeAt(repository, atAddress); - - ATData atData = repository.getATRepository().fromATAddress(atAddress); - CrossChainTradeData tradeData = PirateChainACCTv3.getInstance().populateTradeData(repository, atData); - - // AT should still be in OFFER mode - assertEquals(AcctMode.OFFERING, tradeData.mode); - } + @Override + protected byte[] getCodeBytesHash() { + return PirateChainACCTv3.CODE_BYTES_HASH; } - @SuppressWarnings("unused") - @Test - public void testAutomaticTradeRefund() throws DataException { - try (final Repository repository = RepositoryManager.getRepository()) { - PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe"); - PrivateKeyAccount tradeAccount = createTradeAccount(repository); - - PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert"); - - long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT); - long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT); - - DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress()); - Account at = deployAtTransaction.getATAccount(); - String atAddress = at.getAddress(); - - long partnersOfferMessageTransactionTimestamp = System.currentTimeMillis(); - int lockTimeA = calcTestLockTimeA(partnersOfferMessageTransactionTimestamp); - int refundTimeout = PirateChainACCTv3.calcRefundTimeout(partnersOfferMessageTransactionTimestamp, lockTimeA); - - // Send trade info to AT - byte[] messageData = PirateChainACCTv3.buildTradeMessage(partner.getAddress(), pirateChainPublicKey, hashOfSecretA, lockTimeA, refundTimeout); - MessageTransaction messageTransaction = sendMessage(repository, tradeAccount, messageData, atAddress); - - Block postDeploymentBlock = BlockUtils.mintBlock(repository); - int postDeploymentBlockHeight = postDeploymentBlock.getBlockData().getHeight(); - - // Check refund - long deployAtFee = deployAtTransaction.getTransactionData().getFee(); - long deployersPostDeploymentBalance = deployersInitialBalance - fundingAmount - deployAtFee; - - checkTradeRefund(repository, deployer, deployersInitialBalance, deployAtFee); - - describeAt(repository, atAddress); - - // Check AT is finished - ATData atData = repository.getATRepository().fromATAddress(atAddress); - assertTrue(atData.getIsFinished()); - - // AT should be in REFUNDED mode - CrossChainTradeData tradeData = PirateChainACCTv3.getInstance().populateTradeData(repository, atData); - assertEquals(AcctMode.REFUNDED, tradeData.mode); - - // Test orphaning - BlockUtils.orphanToBlock(repository, postDeploymentBlockHeight); - - // Check balances - long expectedBalance = deployersPostDeploymentBalance; - long actualBalance = deployer.getConfirmedBalance(Asset.QORT); - - assertEquals("Deployer's post-orphan/pre-refund balance incorrect", expectedBalance, actualBalance); - } + @Override + protected String getSymbol() { + return SYMBOL; } - @SuppressWarnings("unused") - @Test - public void testCorrectSecretCorrectSender() throws DataException { - try (final Repository repository = RepositoryManager.getRepository()) { - PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe"); - PrivateKeyAccount tradeAccount = createTradeAccount(repository); - - PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert"); - - long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT); - long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT); - - DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress()); - Account at = deployAtTransaction.getATAccount(); - String atAddress = at.getAddress(); - - long partnersOfferMessageTransactionTimestamp = System.currentTimeMillis(); - int lockTimeA = calcTestLockTimeA(partnersOfferMessageTransactionTimestamp); - int refundTimeout = PirateChainACCTv3.calcRefundTimeout(partnersOfferMessageTransactionTimestamp, lockTimeA); - - // Send trade info to AT - byte[] messageData = PirateChainACCTv3.buildTradeMessage(partner.getAddress(), pirateChainPublicKey, hashOfSecretA, lockTimeA, refundTimeout); - MessageTransaction messageTransaction = sendMessage(repository, tradeAccount, messageData, atAddress); - - // Give AT time to process message - BlockUtils.mintBlock(repository); - - // Send correct secret to AT, from correct account - messageData = PirateChainACCTv3.buildRedeemMessage(secretA, partner.getAddress()); - messageTransaction = sendMessage(repository, partner, messageData, atAddress); - - // AT should send funds in the next block - ATStateData preRedeemAtStateData = repository.getATRepository().getLatestATState(atAddress); - BlockUtils.mintBlock(repository); - - describeAt(repository, atAddress); - - // Check AT is finished - ATData atData = repository.getATRepository().fromATAddress(atAddress); - assertTrue(atData.getIsFinished()); - - // AT should be in REDEEMED mode - CrossChainTradeData tradeData = PirateChainACCTv3.getInstance().populateTradeData(repository, atData); - assertEquals(AcctMode.REDEEMED, tradeData.mode); - - // Check balances - long expectedBalance = partnersInitialBalance - messageTransaction.getTransactionData().getFee() + redeemAmount; - long actualBalance = partner.getConfirmedBalance(Asset.QORT); - - assertEquals("Partner's post-redeem balance incorrect", expectedBalance, actualBalance); - - // Orphan redeem - BlockUtils.orphanLastBlock(repository); - - // Check balances - expectedBalance = partnersInitialBalance - messageTransaction.getTransactionData().getFee(); - actualBalance = partner.getConfirmedBalance(Asset.QORT); - - assertEquals("Partner's post-orphan/pre-redeem balance incorrect", expectedBalance, actualBalance); - - // Check AT state - ATStateData postOrphanAtStateData = repository.getATRepository().getLatestATState(atAddress); - - assertTrue("AT states mismatch", Arrays.equals(preRedeemAtStateData.getStateData(), postOrphanAtStateData.getStateData())); - } + @Override + protected String getName() { + return NAME; } - - @SuppressWarnings("unused") - @Test - public void testCorrectSecretIncorrectSender() throws DataException { - try (final Repository repository = RepositoryManager.getRepository()) { - PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe"); - PrivateKeyAccount tradeAccount = createTradeAccount(repository); - - PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert"); - - PrivateKeyAccount bystander = Common.getTestAccount(repository, "bob"); - - long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT); - long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT); - - DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress()); - long deployAtFee = deployAtTransaction.getTransactionData().getFee(); - - Account at = deployAtTransaction.getATAccount(); - String atAddress = at.getAddress(); - - long partnersOfferMessageTransactionTimestamp = System.currentTimeMillis(); - int lockTimeA = calcTestLockTimeA(partnersOfferMessageTransactionTimestamp); - int refundTimeout = PirateChainACCTv3.calcRefundTimeout(partnersOfferMessageTransactionTimestamp, lockTimeA); - - // Send trade info to AT - byte[] messageData = PirateChainACCTv3.buildTradeMessage(partner.getAddress(), pirateChainPublicKey, hashOfSecretA, lockTimeA, refundTimeout); - MessageTransaction messageTransaction = sendMessage(repository, tradeAccount, messageData, atAddress); - - // Give AT time to process message - BlockUtils.mintBlock(repository); - - // Send correct secret to AT, but from wrong account - messageData = PirateChainACCTv3.buildRedeemMessage(secretA, partner.getAddress()); - messageTransaction = sendMessage(repository, bystander, messageData, atAddress); - - // AT should NOT send funds in the next block - ATStateData preRedeemAtStateData = repository.getATRepository().getLatestATState(atAddress); - BlockUtils.mintBlock(repository); - - describeAt(repository, atAddress); - - // Check AT is NOT finished - ATData atData = repository.getATRepository().fromATAddress(atAddress); - assertFalse(atData.getIsFinished()); - - // AT should still be in TRADE mode - CrossChainTradeData tradeData = PirateChainACCTv3.getInstance().populateTradeData(repository, atData); - assertEquals(AcctMode.TRADING, tradeData.mode); - - // Check balances - long expectedBalance = partnersInitialBalance; - long actualBalance = partner.getConfirmedBalance(Asset.QORT); - - assertEquals("Partner's balance incorrect", expectedBalance, actualBalance); - - // Check eventual refund - checkTradeRefund(repository, deployer, deployersInitialBalance, deployAtFee); - } - } - - @SuppressWarnings("unused") - @Test - public void testIncorrectSecretCorrectSender() throws DataException { - try (final Repository repository = RepositoryManager.getRepository()) { - PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe"); - PrivateKeyAccount tradeAccount = createTradeAccount(repository); - - PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert"); - - long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT); - long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT); - - DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress()); - long deployAtFee = deployAtTransaction.getTransactionData().getFee(); - - Account at = deployAtTransaction.getATAccount(); - String atAddress = at.getAddress(); - - long partnersOfferMessageTransactionTimestamp = System.currentTimeMillis(); - int lockTimeA = calcTestLockTimeA(partnersOfferMessageTransactionTimestamp); - int refundTimeout = PirateChainACCTv3.calcRefundTimeout(partnersOfferMessageTransactionTimestamp, lockTimeA); - - // Send trade info to AT - byte[] messageData = PirateChainACCTv3.buildTradeMessage(partner.getAddress(), pirateChainPublicKey, hashOfSecretA, lockTimeA, refundTimeout); - MessageTransaction messageTransaction = sendMessage(repository, tradeAccount, messageData, atAddress); - - // Give AT time to process message - BlockUtils.mintBlock(repository); - - // Send incorrect secret to AT, from correct account - byte[] wrongSecret = new byte[32]; - RANDOM.nextBytes(wrongSecret); - messageData = PirateChainACCTv3.buildRedeemMessage(wrongSecret, partner.getAddress()); - messageTransaction = sendMessage(repository, partner, messageData, atAddress); - - // AT should NOT send funds in the next block - ATStateData preRedeemAtStateData = repository.getATRepository().getLatestATState(atAddress); - BlockUtils.mintBlock(repository); - - describeAt(repository, atAddress); - - // Check AT is NOT finished - ATData atData = repository.getATRepository().fromATAddress(atAddress); - assertFalse(atData.getIsFinished()); - - // AT should still be in TRADE mode - CrossChainTradeData tradeData = PirateChainACCTv3.getInstance().populateTradeData(repository, atData); - assertEquals(AcctMode.TRADING, tradeData.mode); - - long expectedBalance = partnersInitialBalance - messageTransaction.getTransactionData().getFee(); - long actualBalance = partner.getConfirmedBalance(Asset.QORT); - - assertEquals("Partner's balance incorrect", expectedBalance, actualBalance); - - // Check eventual refund - checkTradeRefund(repository, deployer, deployersInitialBalance, deployAtFee); - } - } - - @SuppressWarnings("unused") - @Test - public void testCorrectSecretCorrectSenderInvalidMessageLength() throws DataException { - try (final Repository repository = RepositoryManager.getRepository()) { - PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe"); - PrivateKeyAccount tradeAccount = createTradeAccount(repository); - - PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert"); - - long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT); - long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT); - - DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress()); - Account at = deployAtTransaction.getATAccount(); - String atAddress = at.getAddress(); - - long partnersOfferMessageTransactionTimestamp = System.currentTimeMillis(); - int lockTimeA = calcTestLockTimeA(partnersOfferMessageTransactionTimestamp); - int refundTimeout = PirateChainACCTv3.calcRefundTimeout(partnersOfferMessageTransactionTimestamp, lockTimeA); - - // Send trade info to AT - byte[] messageData = PirateChainACCTv3.buildTradeMessage(partner.getAddress(), pirateChainPublicKey, hashOfSecretA, lockTimeA, refundTimeout); - MessageTransaction messageTransaction = sendMessage(repository, tradeAccount, messageData, atAddress); - - // Give AT time to process message - BlockUtils.mintBlock(repository); - - // Send correct secret to AT, from correct account, but missing receive address, hence incorrect length - messageData = Bytes.concat(secretA); - messageTransaction = sendMessage(repository, partner, messageData, atAddress); - - // AT should NOT send funds in the next block - ATStateData preRedeemAtStateData = repository.getATRepository().getLatestATState(atAddress); - BlockUtils.mintBlock(repository); - - describeAt(repository, atAddress); - - // Check AT is NOT finished - ATData atData = repository.getATRepository().fromATAddress(atAddress); - assertFalse(atData.getIsFinished()); - - // AT should be in TRADING mode - CrossChainTradeData tradeData = PirateChainACCTv3.getInstance().populateTradeData(repository, atData); - assertEquals(AcctMode.TRADING, tradeData.mode); - } - } - - @SuppressWarnings("unused") - @Test - public void testDescribeDeployed() throws DataException { - try (final Repository repository = RepositoryManager.getRepository()) { - PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe"); - PrivateKeyAccount tradeAccount = createTradeAccount(repository); - - PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert"); - - long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT); - long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT); - - DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress()); - - List executableAts = repository.getATRepository().getAllExecutableATs(); - - for (ATData atData : executableAts) { - String atAddress = atData.getATAddress(); - byte[] codeBytes = atData.getCodeBytes(); - byte[] codeHash = Crypto.digest(codeBytes); - - System.out.println(String.format("%s: code length: %d byte%s, code hash: %s", - atAddress, - codeBytes.length, - (codeBytes.length != 1 ? "s": ""), - HashCode.fromBytes(codeHash))); - - // Not one of ours? - if (!Arrays.equals(codeHash, PirateChainACCTv3.CODE_BYTES_HASH)) - continue; - - describeAt(repository, atAddress); - } - } - } - - private int calcTestLockTimeA(long messageTimestamp) { - return (int) (messageTimestamp / 1000L + tradeTimeout * 60); - } - - private DeployAtTransaction doDeploy(Repository repository, PrivateKeyAccount deployer, String tradeAddress) throws DataException { - byte[] creationBytes = PirateChainACCTv3.buildQortalAT(tradeAddress, pirateChainPublicKey, redeemAmount, arrrAmount, tradeTimeout); - - long txTimestamp = System.currentTimeMillis(); - byte[] lastReference = deployer.getLastReference(); - - if (lastReference == null) { - System.err.println(String.format("Qortal account %s has no last reference", deployer.getAddress())); - System.exit(2); - } - - Long fee = null; - String name = "QORT-ARRR cross-chain trade"; - String description = String.format("Qortal-PirateChain cross-chain trade"); - String atType = "ACCT"; - String tags = "QORT-ARRR ACCT"; - - BaseTransactionData baseTransactionData = new BaseTransactionData(txTimestamp, Group.NO_GROUP, lastReference, deployer.getPublicKey(), fee, null); - TransactionData deployAtTransactionData = new DeployAtTransactionData(baseTransactionData, name, description, atType, tags, creationBytes, fundingAmount, Asset.QORT); - - DeployAtTransaction deployAtTransaction = new DeployAtTransaction(repository, deployAtTransactionData); - - fee = deployAtTransaction.calcRecommendedFee(); - deployAtTransactionData.setFee(fee); - - TransactionUtils.signAndMint(repository, deployAtTransactionData, deployer); - - return deployAtTransaction; - } - - private MessageTransaction sendMessage(Repository repository, PrivateKeyAccount sender, byte[] data, String recipient) throws DataException { - long txTimestamp = System.currentTimeMillis(); - byte[] lastReference = sender.getLastReference(); - - if (lastReference == null) { - System.err.println(String.format("Qortal account %s has no last reference", sender.getAddress())); - System.exit(2); - } - - Long fee = null; - int version = 4; - int nonce = 0; - long amount = 0; - Long assetId = null; // because amount is zero - - BaseTransactionData baseTransactionData = new BaseTransactionData(txTimestamp, Group.NO_GROUP, lastReference, sender.getPublicKey(), fee, null); - TransactionData messageTransactionData = new MessageTransactionData(baseTransactionData, version, nonce, recipient, amount, assetId, data, false, false); - - MessageTransaction messageTransaction = new MessageTransaction(repository, messageTransactionData); - - fee = messageTransaction.calcRecommendedFee(); - messageTransactionData.setFee(fee); - - TransactionUtils.signAndMint(repository, messageTransactionData, sender); - - return messageTransaction; - } - - private void checkTradeRefund(Repository repository, Account deployer, long deployersInitialBalance, long deployAtFee) throws DataException { - long deployersPostDeploymentBalance = deployersInitialBalance - fundingAmount - deployAtFee; - int refundTimeout = tradeTimeout / 2 + 1; // close enough - - // AT should automatically refund deployer after 'refundTimeout' blocks - for (int blockCount = 0; blockCount <= refundTimeout; ++blockCount) - BlockUtils.mintBlock(repository); - - // We don't bother to exactly calculate QORT spent running AT for several blocks, but we do know the expected range - long expectedMinimumBalance = deployersPostDeploymentBalance; - long expectedMaximumBalance = deployersInitialBalance - deployAtFee; - - long actualBalance = deployer.getConfirmedBalance(Asset.QORT); - - assertTrue(String.format("Deployer's balance %s should be above minimum %s", actualBalance, expectedMinimumBalance), actualBalance > expectedMinimumBalance); - assertTrue(String.format("Deployer's balance %s should be below maximum %s", actualBalance, expectedMaximumBalance), actualBalance < expectedMaximumBalance); - } - - private void describeAt(Repository repository, String atAddress) throws DataException { - ATData atData = repository.getATRepository().fromATAddress(atAddress); - CrossChainTradeData tradeData = PirateChainACCTv3.getInstance().populateTradeData(repository, atData); - - Function epochMilliFormatter = (timestamp) -> LocalDateTime.ofInstant(Instant.ofEpochMilli(timestamp), ZoneOffset.UTC).format(DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM)); - int currentBlockHeight = repository.getBlockRepository().getBlockchainHeight(); - - System.out.print(String.format("%s:\n" - + "\tmode: %s\n" - + "\tcreator: %s,\n" - + "\tcreation timestamp: %s,\n" - + "\tcurrent balance: %s QORT,\n" - + "\tis finished: %b,\n" - + "\tredeem payout: %s QORT,\n" - + "\texpected ARRR: %s ARRR,\n" - + "\tcurrent block height: %d,\n", - tradeData.qortalAtAddress, - tradeData.mode, - tradeData.qortalCreator, - epochMilliFormatter.apply(tradeData.creationTimestamp), - Amounts.prettyAmount(tradeData.qortBalance), - atData.getIsFinished(), - Amounts.prettyAmount(tradeData.qortAmount), - Amounts.prettyAmount(tradeData.expectedForeignAmount), - currentBlockHeight)); - - if (tradeData.mode != AcctMode.OFFERING && tradeData.mode != AcctMode.CANCELLED) { - System.out.println(String.format("\trefund timeout: %d minutes,\n" - + "\trefund height: block %d,\n" - + "\tHASH160 of secret-A: %s,\n" - + "\tPirate Chain P2SH-A nLockTime: %d (%s),\n" - + "\ttrade partner: %s\n" - + "\tpartner's receiving address: %s", - tradeData.refundTimeout, - tradeData.tradeRefundHeight, - HashCode.fromBytes(tradeData.hashOfSecretA).toString().substring(0, 40), - tradeData.lockTimeA, epochMilliFormatter.apply(tradeData.lockTimeA * 1000L), - tradeData.qortalPartnerAddress, - tradeData.qortalPartnerReceivingAddress)); - } - } - - private PrivateKeyAccount createTradeAccount(Repository repository) { - // We actually use a known test account with funds to avoid PoW compute - return Common.getTestAccount(repository, "alice"); - } - } diff --git a/src/test/java/org/qortal/test/crosschain/ravencoinv3/RavencoinACCTv3Tests.java b/src/test/java/org/qortal/test/crosschain/ravencoinv3/RavencoinACCTv3Tests.java index 012d5f5d..1af0f7d6 100644 --- a/src/test/java/org/qortal/test/crosschain/ravencoinv3/RavencoinACCTv3Tests.java +++ b/src/test/java/org/qortal/test/crosschain/ravencoinv3/RavencoinACCTv3Tests.java @@ -1,769 +1,58 @@ package org.qortal.test.crosschain.ravencoinv3; import com.google.common.hash.HashCode; -import com.google.common.primitives.Bytes; -import org.junit.Before; -import org.junit.Test; -import org.qortal.account.Account; -import org.qortal.account.PrivateKeyAccount; -import org.qortal.asset.Asset; -import org.qortal.block.Block; -import org.qortal.crosschain.AcctMode; +import org.qortal.crosschain.ACCT; import org.qortal.crosschain.RavencoinACCTv3; -import org.qortal.crypto.Crypto; -import org.qortal.data.at.ATData; -import org.qortal.data.at.ATStateData; -import org.qortal.data.crosschain.CrossChainTradeData; -import org.qortal.data.transaction.BaseTransactionData; -import org.qortal.data.transaction.DeployAtTransactionData; -import org.qortal.data.transaction.MessageTransactionData; -import org.qortal.data.transaction.TransactionData; -import org.qortal.group.Group; -import org.qortal.repository.DataException; -import org.qortal.repository.Repository; -import org.qortal.repository.RepositoryManager; -import org.qortal.test.common.BlockUtils; -import org.qortal.test.common.Common; -import org.qortal.test.common.TransactionUtils; -import org.qortal.transaction.DeployAtTransaction; -import org.qortal.transaction.MessageTransaction; -import org.qortal.utils.Amounts; +import org.qortal.test.crosschain.ACCTTests; -import java.time.Instant; -import java.time.LocalDateTime; -import java.time.ZoneOffset; -import java.time.format.DateTimeFormatter; -import java.time.format.FormatStyle; -import java.util.Arrays; -import java.util.List; -import java.util.Random; -import java.util.function.Function; +public class RavencoinACCTv3Tests extends ACCTTests { -import static org.junit.Assert.*; - -public class RavencoinACCTv3Tests extends Common { - - public static final byte[] secretA = "This string is exactly 32 bytes!".getBytes(); - public static final byte[] hashOfSecretA = Crypto.hash160(secretA); // daf59884b4d1aec8c1b17102530909ee43c0151a public static final byte[] ravencoinPublicKeyHash = HashCode.fromString("bb00bb11bb22bb33bb44bb55bb66bb77bb88bb99").asBytes(); - public static final int tradeTimeout = 20; // blocks - public static final long redeemAmount = 80_40200000L; - public static final long fundingAmount = 123_45600000L; - public static final long ravencoinAmount = 864200L; // 0.00864200 RVN + private static final String SYMBOL = "RVN"; + private static final String NAME = "Ravencoin"; - private static final Random RANDOM = new Random(); - - @Before - public void beforeTest() throws DataException { - Common.useDefaultSettings(); + @Override + protected byte[] getPublicKey() { + return ravencoinPublicKeyHash; } - @Test - public void testCompile() { - PrivateKeyAccount tradeAccount = createTradeAccount(null); - - byte[] creationBytes = RavencoinACCTv3.buildQortalAT(tradeAccount.getAddress(), ravencoinPublicKeyHash, redeemAmount, ravencoinAmount, tradeTimeout); - assertNotNull(creationBytes); - - System.out.println("AT creation bytes: " + HashCode.fromBytes(creationBytes).toString()); + @Override + protected byte[] buildQortalAT(String address, byte[] publicKey, long redeemAmount, long foreignAmount, int tradeTimeout) { + return RavencoinACCTv3.buildQortalAT(address, publicKey, redeemAmount, foreignAmount, tradeTimeout); } - @Test - public void testDeploy() throws DataException { - try (final Repository repository = RepositoryManager.getRepository()) { - PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe"); - PrivateKeyAccount tradeAccount = createTradeAccount(repository); - - PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert"); - - long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT); - long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT); - - DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress()); - - long expectedBalance = deployersInitialBalance - fundingAmount - deployAtTransaction.getTransactionData().getFee(); - long actualBalance = deployer.getConfirmedBalance(Asset.QORT); - - assertEquals("Deployer's post-deployment balance incorrect", expectedBalance, actualBalance); - - expectedBalance = fundingAmount; - actualBalance = deployAtTransaction.getATAccount().getConfirmedBalance(Asset.QORT); - - assertEquals("AT's post-deployment balance incorrect", expectedBalance, actualBalance); - - expectedBalance = partnersInitialBalance; - actualBalance = partner.getConfirmedBalance(Asset.QORT); - - assertEquals("Partner's post-deployment balance incorrect", expectedBalance, actualBalance); - - // Test orphaning - BlockUtils.orphanLastBlock(repository); - - expectedBalance = deployersInitialBalance; - actualBalance = deployer.getConfirmedBalance(Asset.QORT); - - assertEquals("Deployer's post-orphan/pre-deployment balance incorrect", expectedBalance, actualBalance); - - expectedBalance = 0; - actualBalance = deployAtTransaction.getATAccount().getConfirmedBalance(Asset.QORT); - - assertEquals("AT's post-orphan/pre-deployment balance incorrect", expectedBalance, actualBalance); - - expectedBalance = partnersInitialBalance; - actualBalance = partner.getConfirmedBalance(Asset.QORT); - - assertEquals("Partner's post-orphan/pre-deployment balance incorrect", expectedBalance, actualBalance); - } + @Override + protected ACCT getInstance() { + return RavencoinACCTv3.getInstance(); } - @SuppressWarnings("unused") - @Test - public void testOfferCancel() throws DataException { - try (final Repository repository = RepositoryManager.getRepository()) { - PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe"); - PrivateKeyAccount tradeAccount = createTradeAccount(repository); - - PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert"); - - long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT); - long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT); - - DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress()); - Account at = deployAtTransaction.getATAccount(); - String atAddress = at.getAddress(); - - long deployAtFee = deployAtTransaction.getTransactionData().getFee(); - long deployersPostDeploymentBalance = deployersInitialBalance - fundingAmount - deployAtFee; - - // Send creator's address to AT, instead of typical partner's address - byte[] messageData = RavencoinACCTv3.getInstance().buildCancelMessage(deployer.getAddress()); - MessageTransaction messageTransaction = sendMessage(repository, deployer, messageData, atAddress); - long messageFee = messageTransaction.getTransactionData().getFee(); - - // AT should process 'cancel' message in next block - BlockUtils.mintBlock(repository); - - describeAt(repository, atAddress); - - // Check AT is finished - ATData atData = repository.getATRepository().fromATAddress(atAddress); - assertTrue(atData.getIsFinished()); - - // AT should be in CANCELLED mode - CrossChainTradeData tradeData = RavencoinACCTv3.getInstance().populateTradeData(repository, atData); - assertEquals(AcctMode.CANCELLED, tradeData.mode); - - // Check balances - long expectedMinimumBalance = deployersPostDeploymentBalance; - long expectedMaximumBalance = deployersInitialBalance - deployAtFee - messageFee; - - long actualBalance = deployer.getConfirmedBalance(Asset.QORT); - - assertTrue(String.format("Deployer's balance %s should be above minimum %s", actualBalance, expectedMinimumBalance), actualBalance > expectedMinimumBalance); - assertTrue(String.format("Deployer's balance %s should be below maximum %s", actualBalance, expectedMaximumBalance), actualBalance < expectedMaximumBalance); - - // Test orphaning - BlockUtils.orphanLastBlock(repository); - - // Check balances - long expectedBalance = deployersPostDeploymentBalance - messageFee; - actualBalance = deployer.getConfirmedBalance(Asset.QORT); - - assertEquals("Deployer's post-orphan/pre-refund balance incorrect", expectedBalance, actualBalance); - } + @Override + protected int calcRefundTimeout(long partnersOfferMessageTransactionTimestamp, int lockTimeA) { + return RavencoinACCTv3.calcRefundTimeout(partnersOfferMessageTransactionTimestamp, lockTimeA); } - @SuppressWarnings("unused") - @Test - public void testOfferCancelInvalidLength() throws DataException { - try (final Repository repository = RepositoryManager.getRepository()) { - PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe"); - PrivateKeyAccount tradeAccount = createTradeAccount(repository); - - PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert"); - - long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT); - long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT); - - DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress()); - Account at = deployAtTransaction.getATAccount(); - String atAddress = at.getAddress(); - - long deployAtFee = deployAtTransaction.getTransactionData().getFee(); - long deployersPostDeploymentBalance = deployersInitialBalance - fundingAmount - deployAtFee; - - // Instead of sending creator's address to AT, send too-short/invalid message - byte[] messageData = new byte[7]; - RANDOM.nextBytes(messageData); - MessageTransaction messageTransaction = sendMessage(repository, deployer, messageData, atAddress); - long messageFee = messageTransaction.getTransactionData().getFee(); - - // AT should process 'cancel' message in next block - // As message is too short, it will be padded to 32bytes but cancel code doesn't care about message content, so should be ok - BlockUtils.mintBlock(repository); - - describeAt(repository, atAddress); - - // Check AT is finished - ATData atData = repository.getATRepository().fromATAddress(atAddress); - assertTrue(atData.getIsFinished()); - - // AT should be in CANCELLED mode - CrossChainTradeData tradeData = RavencoinACCTv3.getInstance().populateTradeData(repository, atData); - assertEquals(AcctMode.CANCELLED, tradeData.mode); - } + @Override + protected byte[] buildTradeMessage(String address, byte[] publicKey, byte[] hashOfSecretA, int lockTimeA, int refundTimeout) { + return RavencoinACCTv3.buildTradeMessage(address, publicKey, hashOfSecretA, lockTimeA, refundTimeout); } - @SuppressWarnings("unused") - @Test - public void testTradingInfoProcessing() throws DataException { - try (final Repository repository = RepositoryManager.getRepository()) { - PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe"); - PrivateKeyAccount tradeAccount = createTradeAccount(repository); - - PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert"); - - long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT); - long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT); - - DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress()); - Account at = deployAtTransaction.getATAccount(); - String atAddress = at.getAddress(); - - long partnersOfferMessageTransactionTimestamp = System.currentTimeMillis(); - int lockTimeA = calcTestLockTimeA(partnersOfferMessageTransactionTimestamp); - int refundTimeout = RavencoinACCTv3.calcRefundTimeout(partnersOfferMessageTransactionTimestamp, lockTimeA); - - // Send trade info to AT - byte[] messageData = RavencoinACCTv3.buildTradeMessage(partner.getAddress(), ravencoinPublicKeyHash, hashOfSecretA, lockTimeA, refundTimeout); - MessageTransaction messageTransaction = sendMessage(repository, tradeAccount, messageData, atAddress); - - Block postDeploymentBlock = BlockUtils.mintBlock(repository); - int postDeploymentBlockHeight = postDeploymentBlock.getBlockData().getHeight(); - - long deployAtFee = deployAtTransaction.getTransactionData().getFee(); - long deployersPostDeploymentBalance = deployersInitialBalance - fundingAmount - deployAtFee; - - describeAt(repository, atAddress); - - ATData atData = repository.getATRepository().fromATAddress(atAddress); - CrossChainTradeData tradeData = RavencoinACCTv3.getInstance().populateTradeData(repository, atData); - - // AT should be in TRADE mode - assertEquals(AcctMode.TRADING, tradeData.mode); - - // Check hashOfSecretA was extracted correctly - assertTrue(Arrays.equals(hashOfSecretA, tradeData.hashOfSecretA)); - - // Check trade partner Qortal address was extracted correctly - assertEquals(partner.getAddress(), tradeData.qortalPartnerAddress); - - // Check trade partner's ravencoin PKH was extracted correctly - assertTrue(Arrays.equals(ravencoinPublicKeyHash, tradeData.partnerForeignPKH)); - - // Test orphaning - BlockUtils.orphanToBlock(repository, postDeploymentBlockHeight); - - // Check balances - long expectedBalance = deployersPostDeploymentBalance; - long actualBalance = deployer.getConfirmedBalance(Asset.QORT); - - assertEquals("Deployer's post-orphan/pre-refund balance incorrect", expectedBalance, actualBalance); - } + @Override + protected byte[] buildRedeemMessage(byte[] secretA, String address) { + return RavencoinACCTv3.buildRedeemMessage(secretA, address); } - // TEST SENDING TRADING INFO BUT NOT FROM AT CREATOR (SHOULD BE IGNORED) - @SuppressWarnings("unused") - @Test - public void testIncorrectTradeSender() throws DataException { - try (final Repository repository = RepositoryManager.getRepository()) { - PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe"); - PrivateKeyAccount tradeAccount = createTradeAccount(repository); - - PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert"); - - PrivateKeyAccount bystander = Common.getTestAccount(repository, "bob"); - - long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT); - long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT); - - DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress()); - Account at = deployAtTransaction.getATAccount(); - String atAddress = at.getAddress(); - - long partnersOfferMessageTransactionTimestamp = System.currentTimeMillis(); - int lockTimeA = calcTestLockTimeA(partnersOfferMessageTransactionTimestamp); - int refundTimeout = RavencoinACCTv3.calcRefundTimeout(partnersOfferMessageTransactionTimestamp, lockTimeA); - - // Send trade info to AT BUT NOT FROM AT CREATOR - byte[] messageData = RavencoinACCTv3.buildTradeMessage(partner.getAddress(), ravencoinPublicKeyHash, hashOfSecretA, lockTimeA, refundTimeout); - MessageTransaction messageTransaction = sendMessage(repository, bystander, messageData, atAddress); - - BlockUtils.mintBlock(repository); - - long expectedBalance = partnersInitialBalance; - long actualBalance = partner.getConfirmedBalance(Asset.QORT); - - assertEquals("Partner's post-initial-payout balance incorrect", expectedBalance, actualBalance); - - describeAt(repository, atAddress); - - ATData atData = repository.getATRepository().fromATAddress(atAddress); - CrossChainTradeData tradeData = RavencoinACCTv3.getInstance().populateTradeData(repository, atData); - - // AT should still be in OFFER mode - assertEquals(AcctMode.OFFERING, tradeData.mode); - } + @Override + protected byte[] getCodeBytesHash() { + return RavencoinACCTv3.CODE_BYTES_HASH; } - @SuppressWarnings("unused") - @Test - public void testAutomaticTradeRefund() throws DataException { - try (final Repository repository = RepositoryManager.getRepository()) { - PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe"); - PrivateKeyAccount tradeAccount = createTradeAccount(repository); - - PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert"); - - long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT); - long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT); - - DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress()); - Account at = deployAtTransaction.getATAccount(); - String atAddress = at.getAddress(); - - long partnersOfferMessageTransactionTimestamp = System.currentTimeMillis(); - int lockTimeA = calcTestLockTimeA(partnersOfferMessageTransactionTimestamp); - int refundTimeout = RavencoinACCTv3.calcRefundTimeout(partnersOfferMessageTransactionTimestamp, lockTimeA); - - // Send trade info to AT - byte[] messageData = RavencoinACCTv3.buildTradeMessage(partner.getAddress(), ravencoinPublicKeyHash, hashOfSecretA, lockTimeA, refundTimeout); - MessageTransaction messageTransaction = sendMessage(repository, tradeAccount, messageData, atAddress); - - Block postDeploymentBlock = BlockUtils.mintBlock(repository); - int postDeploymentBlockHeight = postDeploymentBlock.getBlockData().getHeight(); - - // Check refund - long deployAtFee = deployAtTransaction.getTransactionData().getFee(); - long deployersPostDeploymentBalance = deployersInitialBalance - fundingAmount - deployAtFee; - - checkTradeRefund(repository, deployer, deployersInitialBalance, deployAtFee); - - describeAt(repository, atAddress); - - // Check AT is finished - ATData atData = repository.getATRepository().fromATAddress(atAddress); - assertTrue(atData.getIsFinished()); - - // AT should be in REFUNDED mode - CrossChainTradeData tradeData = RavencoinACCTv3.getInstance().populateTradeData(repository, atData); - assertEquals(AcctMode.REFUNDED, tradeData.mode); - - // Test orphaning - BlockUtils.orphanToBlock(repository, postDeploymentBlockHeight); - - // Check balances - long expectedBalance = deployersPostDeploymentBalance; - long actualBalance = deployer.getConfirmedBalance(Asset.QORT); - - assertEquals("Deployer's post-orphan/pre-refund balance incorrect", expectedBalance, actualBalance); - } + @Override + protected String getSymbol() { + return SYMBOL; } - @SuppressWarnings("unused") - @Test - public void testCorrectSecretCorrectSender() throws DataException { - try (final Repository repository = RepositoryManager.getRepository()) { - PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe"); - PrivateKeyAccount tradeAccount = createTradeAccount(repository); - - PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert"); - - long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT); - long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT); - - DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress()); - Account at = deployAtTransaction.getATAccount(); - String atAddress = at.getAddress(); - - long partnersOfferMessageTransactionTimestamp = System.currentTimeMillis(); - int lockTimeA = calcTestLockTimeA(partnersOfferMessageTransactionTimestamp); - int refundTimeout = RavencoinACCTv3.calcRefundTimeout(partnersOfferMessageTransactionTimestamp, lockTimeA); - - // Send trade info to AT - byte[] messageData = RavencoinACCTv3.buildTradeMessage(partner.getAddress(), ravencoinPublicKeyHash, hashOfSecretA, lockTimeA, refundTimeout); - MessageTransaction messageTransaction = sendMessage(repository, tradeAccount, messageData, atAddress); - - // Give AT time to process message - BlockUtils.mintBlock(repository); - - // Send correct secret to AT, from correct account - messageData = RavencoinACCTv3.buildRedeemMessage(secretA, partner.getAddress()); - messageTransaction = sendMessage(repository, partner, messageData, atAddress); - - // AT should send funds in the next block - ATStateData preRedeemAtStateData = repository.getATRepository().getLatestATState(atAddress); - BlockUtils.mintBlock(repository); - - describeAt(repository, atAddress); - - // Check AT is finished - ATData atData = repository.getATRepository().fromATAddress(atAddress); - assertTrue(atData.getIsFinished()); - - // AT should be in REDEEMED mode - CrossChainTradeData tradeData = RavencoinACCTv3.getInstance().populateTradeData(repository, atData); - assertEquals(AcctMode.REDEEMED, tradeData.mode); - - // Check balances - long expectedBalance = partnersInitialBalance - messageTransaction.getTransactionData().getFee() + redeemAmount; - long actualBalance = partner.getConfirmedBalance(Asset.QORT); - - assertEquals("Partner's post-redeem balance incorrect", expectedBalance, actualBalance); - - // Orphan redeem - BlockUtils.orphanLastBlock(repository); - - // Check balances - expectedBalance = partnersInitialBalance - messageTransaction.getTransactionData().getFee(); - actualBalance = partner.getConfirmedBalance(Asset.QORT); - - assertEquals("Partner's post-orphan/pre-redeem balance incorrect", expectedBalance, actualBalance); - - // Check AT state - ATStateData postOrphanAtStateData = repository.getATRepository().getLatestATState(atAddress); - - assertTrue("AT states mismatch", Arrays.equals(preRedeemAtStateData.getStateData(), postOrphanAtStateData.getStateData())); - } + @Override + protected String getName() { + return NAME; } - - @SuppressWarnings("unused") - @Test - public void testCorrectSecretIncorrectSender() throws DataException { - try (final Repository repository = RepositoryManager.getRepository()) { - PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe"); - PrivateKeyAccount tradeAccount = createTradeAccount(repository); - - PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert"); - - PrivateKeyAccount bystander = Common.getTestAccount(repository, "bob"); - - long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT); - long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT); - - DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress()); - long deployAtFee = deployAtTransaction.getTransactionData().getFee(); - - Account at = deployAtTransaction.getATAccount(); - String atAddress = at.getAddress(); - - long partnersOfferMessageTransactionTimestamp = System.currentTimeMillis(); - int lockTimeA = calcTestLockTimeA(partnersOfferMessageTransactionTimestamp); - int refundTimeout = RavencoinACCTv3.calcRefundTimeout(partnersOfferMessageTransactionTimestamp, lockTimeA); - - // Send trade info to AT - byte[] messageData = RavencoinACCTv3.buildTradeMessage(partner.getAddress(), ravencoinPublicKeyHash, hashOfSecretA, lockTimeA, refundTimeout); - MessageTransaction messageTransaction = sendMessage(repository, tradeAccount, messageData, atAddress); - - // Give AT time to process message - BlockUtils.mintBlock(repository); - - // Send correct secret to AT, but from wrong account - messageData = RavencoinACCTv3.buildRedeemMessage(secretA, partner.getAddress()); - messageTransaction = sendMessage(repository, bystander, messageData, atAddress); - - // AT should NOT send funds in the next block - ATStateData preRedeemAtStateData = repository.getATRepository().getLatestATState(atAddress); - BlockUtils.mintBlock(repository); - - describeAt(repository, atAddress); - - // Check AT is NOT finished - ATData atData = repository.getATRepository().fromATAddress(atAddress); - assertFalse(atData.getIsFinished()); - - // AT should still be in TRADE mode - CrossChainTradeData tradeData = RavencoinACCTv3.getInstance().populateTradeData(repository, atData); - assertEquals(AcctMode.TRADING, tradeData.mode); - - // Check balances - long expectedBalance = partnersInitialBalance; - long actualBalance = partner.getConfirmedBalance(Asset.QORT); - - assertEquals("Partner's balance incorrect", expectedBalance, actualBalance); - - // Check eventual refund - checkTradeRefund(repository, deployer, deployersInitialBalance, deployAtFee); - } - } - - @SuppressWarnings("unused") - @Test - public void testIncorrectSecretCorrectSender() throws DataException { - try (final Repository repository = RepositoryManager.getRepository()) { - PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe"); - PrivateKeyAccount tradeAccount = createTradeAccount(repository); - - PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert"); - - long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT); - long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT); - - DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress()); - long deployAtFee = deployAtTransaction.getTransactionData().getFee(); - - Account at = deployAtTransaction.getATAccount(); - String atAddress = at.getAddress(); - - long partnersOfferMessageTransactionTimestamp = System.currentTimeMillis(); - int lockTimeA = calcTestLockTimeA(partnersOfferMessageTransactionTimestamp); - int refundTimeout = RavencoinACCTv3.calcRefundTimeout(partnersOfferMessageTransactionTimestamp, lockTimeA); - - // Send trade info to AT - byte[] messageData = RavencoinACCTv3.buildTradeMessage(partner.getAddress(), ravencoinPublicKeyHash, hashOfSecretA, lockTimeA, refundTimeout); - MessageTransaction messageTransaction = sendMessage(repository, tradeAccount, messageData, atAddress); - - // Give AT time to process message - BlockUtils.mintBlock(repository); - - // Send incorrect secret to AT, from correct account - byte[] wrongSecret = new byte[32]; - RANDOM.nextBytes(wrongSecret); - messageData = RavencoinACCTv3.buildRedeemMessage(wrongSecret, partner.getAddress()); - messageTransaction = sendMessage(repository, partner, messageData, atAddress); - - // AT should NOT send funds in the next block - ATStateData preRedeemAtStateData = repository.getATRepository().getLatestATState(atAddress); - BlockUtils.mintBlock(repository); - - describeAt(repository, atAddress); - - // Check AT is NOT finished - ATData atData = repository.getATRepository().fromATAddress(atAddress); - assertFalse(atData.getIsFinished()); - - // AT should still be in TRADE mode - CrossChainTradeData tradeData = RavencoinACCTv3.getInstance().populateTradeData(repository, atData); - assertEquals(AcctMode.TRADING, tradeData.mode); - - long expectedBalance = partnersInitialBalance - messageTransaction.getTransactionData().getFee(); - long actualBalance = partner.getConfirmedBalance(Asset.QORT); - - assertEquals("Partner's balance incorrect", expectedBalance, actualBalance); - - // Check eventual refund - checkTradeRefund(repository, deployer, deployersInitialBalance, deployAtFee); - } - } - - @SuppressWarnings("unused") - @Test - public void testCorrectSecretCorrectSenderInvalidMessageLength() throws DataException { - try (final Repository repository = RepositoryManager.getRepository()) { - PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe"); - PrivateKeyAccount tradeAccount = createTradeAccount(repository); - - PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert"); - - long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT); - long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT); - - DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress()); - Account at = deployAtTransaction.getATAccount(); - String atAddress = at.getAddress(); - - long partnersOfferMessageTransactionTimestamp = System.currentTimeMillis(); - int lockTimeA = calcTestLockTimeA(partnersOfferMessageTransactionTimestamp); - int refundTimeout = RavencoinACCTv3.calcRefundTimeout(partnersOfferMessageTransactionTimestamp, lockTimeA); - - // Send trade info to AT - byte[] messageData = RavencoinACCTv3.buildTradeMessage(partner.getAddress(), ravencoinPublicKeyHash, hashOfSecretA, lockTimeA, refundTimeout); - MessageTransaction messageTransaction = sendMessage(repository, tradeAccount, messageData, atAddress); - - // Give AT time to process message - BlockUtils.mintBlock(repository); - - // Send correct secret to AT, from correct account, but missing receive address, hence incorrect length - messageData = Bytes.concat(secretA); - messageTransaction = sendMessage(repository, partner, messageData, atAddress); - - // AT should NOT send funds in the next block - ATStateData preRedeemAtStateData = repository.getATRepository().getLatestATState(atAddress); - BlockUtils.mintBlock(repository); - - describeAt(repository, atAddress); - - // Check AT is NOT finished - ATData atData = repository.getATRepository().fromATAddress(atAddress); - assertFalse(atData.getIsFinished()); - - // AT should be in TRADING mode - CrossChainTradeData tradeData = RavencoinACCTv3.getInstance().populateTradeData(repository, atData); - assertEquals(AcctMode.TRADING, tradeData.mode); - } - } - - @SuppressWarnings("unused") - @Test - public void testDescribeDeployed() throws DataException { - try (final Repository repository = RepositoryManager.getRepository()) { - PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe"); - PrivateKeyAccount tradeAccount = createTradeAccount(repository); - - PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert"); - - long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT); - long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT); - - DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress()); - - List executableAts = repository.getATRepository().getAllExecutableATs(); - - for (ATData atData : executableAts) { - String atAddress = atData.getATAddress(); - byte[] codeBytes = atData.getCodeBytes(); - byte[] codeHash = Crypto.digest(codeBytes); - - System.out.println(String.format("%s: code length: %d byte%s, code hash: %s", - atAddress, - codeBytes.length, - (codeBytes.length != 1 ? "s": ""), - HashCode.fromBytes(codeHash))); - - // Not one of ours? - if (!Arrays.equals(codeHash, RavencoinACCTv3.CODE_BYTES_HASH)) - continue; - - describeAt(repository, atAddress); - } - } - } - - private int calcTestLockTimeA(long messageTimestamp) { - return (int) (messageTimestamp / 1000L + tradeTimeout * 60); - } - - private DeployAtTransaction doDeploy(Repository repository, PrivateKeyAccount deployer, String tradeAddress) throws DataException { - byte[] creationBytes = RavencoinACCTv3.buildQortalAT(tradeAddress, ravencoinPublicKeyHash, redeemAmount, ravencoinAmount, tradeTimeout); - - long txTimestamp = System.currentTimeMillis(); - byte[] lastReference = deployer.getLastReference(); - - if (lastReference == null) { - System.err.println(String.format("Qortal account %s has no last reference", deployer.getAddress())); - System.exit(2); - } - - Long fee = null; - String name = "QORT-RVN cross-chain trade"; - String description = String.format("Qortal-Ravencoin cross-chain trade"); - String atType = "ACCT"; - String tags = "QORT-RVN ACCT"; - - BaseTransactionData baseTransactionData = new BaseTransactionData(txTimestamp, Group.NO_GROUP, lastReference, deployer.getPublicKey(), fee, null); - TransactionData deployAtTransactionData = new DeployAtTransactionData(baseTransactionData, name, description, atType, tags, creationBytes, fundingAmount, Asset.QORT); - - DeployAtTransaction deployAtTransaction = new DeployAtTransaction(repository, deployAtTransactionData); - - fee = deployAtTransaction.calcRecommendedFee(); - deployAtTransactionData.setFee(fee); - - TransactionUtils.signAndMint(repository, deployAtTransactionData, deployer); - - return deployAtTransaction; - } - - private MessageTransaction sendMessage(Repository repository, PrivateKeyAccount sender, byte[] data, String recipient) throws DataException { - long txTimestamp = System.currentTimeMillis(); - byte[] lastReference = sender.getLastReference(); - - if (lastReference == null) { - System.err.println(String.format("Qortal account %s has no last reference", sender.getAddress())); - System.exit(2); - } - - Long fee = null; - int version = 4; - int nonce = 0; - long amount = 0; - Long assetId = null; // because amount is zero - - BaseTransactionData baseTransactionData = new BaseTransactionData(txTimestamp, Group.NO_GROUP, lastReference, sender.getPublicKey(), fee, null); - TransactionData messageTransactionData = new MessageTransactionData(baseTransactionData, version, nonce, recipient, amount, assetId, data, false, false); - - MessageTransaction messageTransaction = new MessageTransaction(repository, messageTransactionData); - - fee = messageTransaction.calcRecommendedFee(); - messageTransactionData.setFee(fee); - - TransactionUtils.signAndMint(repository, messageTransactionData, sender); - - return messageTransaction; - } - - private void checkTradeRefund(Repository repository, Account deployer, long deployersInitialBalance, long deployAtFee) throws DataException { - long deployersPostDeploymentBalance = deployersInitialBalance - fundingAmount - deployAtFee; - int refundTimeout = tradeTimeout / 2 + 1; // close enough - - // AT should automatically refund deployer after 'refundTimeout' blocks - for (int blockCount = 0; blockCount <= refundTimeout; ++blockCount) - BlockUtils.mintBlock(repository); - - // We don't bother to exactly calculate QORT spent running AT for several blocks, but we do know the expected range - long expectedMinimumBalance = deployersPostDeploymentBalance; - long expectedMaximumBalance = deployersInitialBalance - deployAtFee; - - long actualBalance = deployer.getConfirmedBalance(Asset.QORT); - - assertTrue(String.format("Deployer's balance %s should be above minimum %s", actualBalance, expectedMinimumBalance), actualBalance > expectedMinimumBalance); - assertTrue(String.format("Deployer's balance %s should be below maximum %s", actualBalance, expectedMaximumBalance), actualBalance < expectedMaximumBalance); - } - - private void describeAt(Repository repository, String atAddress) throws DataException { - ATData atData = repository.getATRepository().fromATAddress(atAddress); - CrossChainTradeData tradeData = RavencoinACCTv3.getInstance().populateTradeData(repository, atData); - - Function epochMilliFormatter = (timestamp) -> LocalDateTime.ofInstant(Instant.ofEpochMilli(timestamp), ZoneOffset.UTC).format(DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM)); - int currentBlockHeight = repository.getBlockRepository().getBlockchainHeight(); - - System.out.print(String.format("%s:\n" - + "\tmode: %s\n" - + "\tcreator: %s,\n" - + "\tcreation timestamp: %s,\n" - + "\tcurrent balance: %s QORT,\n" - + "\tis finished: %b,\n" - + "\tredeem payout: %s QORT,\n" - + "\texpected ravencoin: %s RVN,\n" - + "\tcurrent block height: %d,\n", - tradeData.qortalAtAddress, - tradeData.mode, - tradeData.qortalCreator, - epochMilliFormatter.apply(tradeData.creationTimestamp), - Amounts.prettyAmount(tradeData.qortBalance), - atData.getIsFinished(), - Amounts.prettyAmount(tradeData.qortAmount), - Amounts.prettyAmount(tradeData.expectedForeignAmount), - currentBlockHeight)); - - if (tradeData.mode != AcctMode.OFFERING && tradeData.mode != AcctMode.CANCELLED) { - System.out.println(String.format("\trefund timeout: %d minutes,\n" - + "\trefund height: block %d,\n" - + "\tHASH160 of secret-A: %s,\n" - + "\tRavencoin P2SH-A nLockTime: %d (%s),\n" - + "\ttrade partner: %s\n" - + "\tpartner's receiving address: %s", - tradeData.refundTimeout, - tradeData.tradeRefundHeight, - HashCode.fromBytes(tradeData.hashOfSecretA).toString().substring(0, 40), - tradeData.lockTimeA, epochMilliFormatter.apply(tradeData.lockTimeA * 1000L), - tradeData.qortalPartnerAddress, - tradeData.qortalPartnerReceivingAddress)); - } - } - - private PrivateKeyAccount createTradeAccount(Repository repository) { - // We actually use a known test account with funds to avoid PoW compute - return Common.getTestAccount(repository, "alice"); - } - }