diff --git a/src/main/scala/ContractTesting.scala b/src/main/scala/ContractTesting.scala index 97512a3..6517e8f 100644 --- a/src/main/scala/ContractTesting.scala +++ b/src/main/scala/ContractTesting.scala @@ -54,7 +54,9 @@ object ContractTesting { println(s"The contract's FreeMint address is: ${dexyAddresses.freeMintAddress.toString}") println(s"The contract's ArbitrageMint address is: ${dexyAddresses.arbitrageMintAddress.toString}") freeMint(ctx) + freeMintCounterReset(ctx) arbitrageMint(ctx) + arbitrageMintCounterReset(ctx) }) } @@ -159,6 +161,107 @@ object ContractTesting { println("---- FreeMint scenario Done!! ----") } + def freeMintCounterReset(ctx: BlockchainContext): Unit = { + println("---- Start FreeMint with CounterReset scenario !! ----") + val ORACLE_ERG_USD_PRICE = 584663187L + val RESET_HEIGHT: Int = 100 + val REMAINING_DEXY_USD: Long = 1e15.toLong + val WANT_DEXY = 1000L + val feeNum = 10 + val feeDenom = 1000 + val NEEDED_ERG = WANT_DEXY * ORACLE_ERG_USD_PRICE * (feeNum + feeDenom) / feeDenom + val DEXY_RESERVES = 185080L + val USER_FOUND: Long = 4000e9.toLong + val BASE_FREE_MINT_VALUE: Long = 2e9.toLong + val BASE_LP_BOX_VALUE: Long = 110806126325195L + val DEXY_FEE: Long = 1e9.toLong // TODO: This value is just for testing purpose and isn't true + val t_free: Int = 100 + // dummy prover with dummy secret + val prover = ctx.newProverBuilder() + .withDLogSecret(BigInt.apply(0).bigInteger) + .build() + + val tb = ctx.newTxBuilder() + val lpBox = tb.outBoxBuilder() + .contract(new ErgoTreeContract(dexyAddresses.lpAddress.script)) + .value(BASE_LP_BOX_VALUE) + .tokens(DexyTokenObj.lpNFT, DexyTokenObj.LPToken, new ErgoToken(DexyToken.dexyUSDToken, DEXY_RESERVES)) + .build() + .convertToInputWith("adfd114c8b145dd758248c9dadf818927da3a7cd56bbe4129ba053d1ce0cbc30", 0) + + val oracleBox = tb.outBoxBuilder() + .contract(new ErgoTreeContract(Address.create("4MQyML64GnzMxZgm").getErgoAddress.script)) + .value(2e9.toLong) + .tokens(DexyTokenObj.oracleNFT) + .registers(ErgoValue.of(ORACLE_ERG_USD_PRICE)) + .build() + .convertToInputWith("96be1e2f79bfb4ef587b4480966efe9e90f0dddd89c616a1f65222ea2ac4e351", 0) + + val bankBox = tb.outBoxBuilder() + .contract(new ErgoTreeContract(dexyAddresses.bankAddress.script)) + .value((11610).toLong) + .tokens(DexyTokenObj.bankNFT, DexyTokenObj.dexyUSDToken) + .build() + .convertToInputWith("ce552663312afc2379a91f803c93e2b10b424f176fbc930055c10def2fd88a5d", 0) + + val freeMintBox = tb.outBoxBuilder() + .contract(new ErgoTreeContract(dexyAddresses.freeMintAddress.script)) + .value(BASE_FREE_MINT_VALUE) + .tokens(DexyTokenObj.freeMintNFT) + .registers(ErgoValue.of(RESET_HEIGHT), ErgoValue.of(REMAINING_DEXY_USD)) + .build() + .convertToInputWith("55c0537aafa6932c9a25c856fcd064d317af96a4fa733012c26d836bbaf5689a", 1) + + val userInBox = tb.outBoxBuilder() + .contract(new ErgoTreeContract(Address.create("4MQyML64GnzMxZgm").getErgoAddress.script)) + .value(USER_FOUND) + .build() + .convertToInputWith("b84e555e05181c1c8cf661535bb197853fe4ec2299f2fd399ba7bb1e73f5e69b", 0) + + val newBankBox = tb.outBoxBuilder() + .contract(new ErgoTreeContract(dexyAddresses.bankAddress.script)) + .value(bankBox.getValue + NEEDED_ERG) + .tokens(DexyTokenObj.bankNFT, new ErgoToken(DexyToken.dexyUSDToken, bankBox.getTokens.get(1).getValue - WANT_DEXY)) + .build() + + val newFreeMintBox = tb.outBoxBuilder() + .contract(new ErgoTreeContract(dexyAddresses.freeMintAddress.script)) + .value(BASE_FREE_MINT_VALUE + DEXY_FEE) + .tokens(DexyTokenObj.freeMintNFT) + .registers(ErgoValue.of(ctx.getHeight + t_free), ErgoValue.of(DEXY_RESERVES / 100 - WANT_DEXY)) + .build() + + val userOutBox = tb.outBoxBuilder() + .contract(new ErgoTreeContract(Address.create("4MQyML64GnzMxZgm").getErgoAddress.script)) + .value(USER_FOUND - NEEDED_ERG - FEE - DEXY_FEE) + .tokens(new ErgoToken(DexyToken.dexyUSDToken, WANT_DEXY)) + .build() + + val tx = tb.boxesToSpend(Seq( + freeMintBox, + bankBox, + userInBox + ).asJava) + .fee(FEE) + .outputs(newFreeMintBox, newBankBox, userOutBox) + .withDataInputs(List(oracleBox, lpBox).asJava) + .sendChangeTo(Address.create("4MQyML64GnzMxZgm").getErgoAddress) + .build() + + var signed: SignedTransaction = null + try { + signed = prover.sign(tx) + } catch { + case e: Exception => { + println("User could not sing tx due to:") + println(e) + return + } + } + println(s"signed tx: ${signed.toJson(false)}") + println("---- FreeMint scenario with CounterReset Done!! ----") + } + def arbitrageMint(ctx: BlockchainContext): Unit = { println("---- Start ArbitrageMint scenario !! ----") val ORACLE_ERG_USD_PRICE = 584663187L @@ -167,7 +270,7 @@ object ContractTesting { val WANT_DEXY = 2000L val feeNum = 5 val feeDenom = 1000 - val NEEDED_ERG: Long = WANT_DEXY*ORACLE_ERG_USD_PRICE * (feeDenom+feeNum+1)/feeDenom + val NEEDED_ERG: Long = WANT_DEXY * ORACLE_ERG_USD_PRICE * (feeDenom + feeNum + 1) / feeDenom val DEXY_RESERVES = 16000L val USER_FOUND: Long = 4000e9.toLong val BASE_FREE_MINT_VALUE: Long = 2e9.toLong @@ -184,7 +287,7 @@ object ContractTesting { .contract(new ErgoTreeContract(dexyAddresses.lpAddress.script)) .value(BASE_LP_BOX_VALUE) .tokens(DexyTokenObj.lpNFT, DexyTokenObj.LPToken, new ErgoToken(DexyToken.dexyUSDToken, DEXY_RESERVES)) - .registers(ErgoValue.of(ORACLE_ERG_USD_PRICE),ErgoValue.of(LB_BOX_R5)) + .registers(ErgoValue.of(ORACLE_ERG_USD_PRICE), ErgoValue.of(LB_BOX_R5)) .build() .convertToInputWith("adfd114c8b145dd758248c9dadf818927da3a7cd56bbe4129ba053d1ce0cbc30", 0) @@ -260,4 +363,111 @@ object ContractTesting { println(s"signed tx: ${signed.toJson(false)}") println("---- ArbitrageMint scenario Done !! ----") } + + def arbitrageMintCounterReset(ctx: BlockchainContext): Unit = { + println("---- Start ArbitrageMint with CounterReset scenario !! ----") + val ORACLE_ERG_USD_PRICE = 584663187L + val RESET_HEIGHT: Int = 1000 + val REMAINING_DEXY_USD: Long = 1e15.toLong + val WANT_DEXY = 2000L + val feeNum = 5 + val feeDenom = 1000 + val NEEDED_ERG: Long = WANT_DEXY * ORACLE_ERG_USD_PRICE * (feeDenom + feeNum + 1) / feeDenom + val DEXY_RESERVES = 16000L + val USER_FOUND: Long = 4000e9.toLong + val BASE_FREE_MINT_VALUE: Long = 2e9.toLong + val BASE_LP_BOX_VALUE: Long = 10806126325195L + val DEXY_FEE: Long = 1e9.toLong // TODO: This value is just for testing purpose and isn't true + val LB_BOX_R5: Int = 859700 + val t_arb: Int = 30 + val oracleRate: Long = ORACLE_ERG_USD_PRICE * (1000 + 5) / 1000 + val maxAllowedIfReset: Long = (BASE_LP_BOX_VALUE - (oracleRate * DEXY_RESERVES)) / oracleRate + println(maxAllowedIfReset) + println(WANT_DEXY) + // dummy prover with dummy secret + val prover = ctx.newProverBuilder() + .withDLogSecret(BigInt.apply(0).bigInteger) + .build() + + val tb = ctx.newTxBuilder() + val lpBox = tb.outBoxBuilder() + .contract(new ErgoTreeContract(dexyAddresses.lpAddress.script)) + .value(BASE_LP_BOX_VALUE) + .tokens(DexyTokenObj.lpNFT, DexyTokenObj.LPToken, new ErgoToken(DexyToken.dexyUSDToken, DEXY_RESERVES)) + .registers(ErgoValue.of(ORACLE_ERG_USD_PRICE), ErgoValue.of(LB_BOX_R5)) + .build() + .convertToInputWith("adfd114c8b145dd758248c9dadf818927da3a7cd56bbe4129ba053d1ce0cbc30", 0) + + val oracleBox = tb.outBoxBuilder() + .contract(new ErgoTreeContract(Address.create("4MQyML64GnzMxZgm").getErgoAddress.script)) + .value(2e9.toLong) + .tokens(DexyTokenObj.oracleNFT) + .registers(ErgoValue.of(ORACLE_ERG_USD_PRICE)) + .build() + .convertToInputWith("96be1e2f79bfb4ef587b4480966efe9e90f0dddd89c616a1f65222ea2ac4e351", 0) + + val bankBox = tb.outBoxBuilder() + .contract(new ErgoTreeContract(dexyAddresses.bankAddress.script)) + .value((11610).toLong) + .tokens(DexyTokenObj.bankNFT, DexyTokenObj.dexyUSDToken) + .build() + .convertToInputWith("ce552663312afc2379a91f803c93e2b10b424f176fbc930055c10def2fd88a5d", 0) + + val arbitrageMintBox = tb.outBoxBuilder() + .contract(new ErgoTreeContract(dexyAddresses.arbitrageMintAddress.script)) + .value(BASE_FREE_MINT_VALUE) + .tokens(DexyTokenObj.arbitrageMintNFT) + .registers(ErgoValue.of(RESET_HEIGHT), ErgoValue.of(REMAINING_DEXY_USD)) + .build() + .convertToInputWith("55c0537aafa6932c9a25c856fcd064d317af96a4fa733012c26d836bbaf5689a", 1) + + val userInBox = tb.outBoxBuilder() + .contract(new ErgoTreeContract(Address.create("4MQyML64GnzMxZgm").getErgoAddress.script)) + .value(USER_FOUND) + .build() + .convertToInputWith("b84e555e05181c1c8cf661535bb197853fe4ec2299f2fd399ba7bb1e73f5e69b", 0) + + val newBankBox = tb.outBoxBuilder() + .contract(new ErgoTreeContract(dexyAddresses.bankAddress.script)) + .value(bankBox.getValue + NEEDED_ERG) + .tokens(DexyTokenObj.bankNFT, new ErgoToken(DexyToken.dexyUSDToken, bankBox.getTokens.get(1).getValue - WANT_DEXY)) + .build() + + val newArbitrageMintBox = tb.outBoxBuilder() + .contract(new ErgoTreeContract(dexyAddresses.arbitrageMintAddress.script)) + .value(BASE_FREE_MINT_VALUE + DEXY_FEE) + .tokens(DexyTokenObj.arbitrageMintNFT) + .registers(ErgoValue.of(ctx.getHeight + t_arb), ErgoValue.of(maxAllowedIfReset - WANT_DEXY)) + .build() + + val userOutBox = tb.outBoxBuilder() + .contract(new ErgoTreeContract(Address.create("4MQyML64GnzMxZgm").getErgoAddress.script)) + .value(USER_FOUND - NEEDED_ERG - FEE - DEXY_FEE) + .tokens(new ErgoToken(DexyToken.dexyUSDToken, WANT_DEXY)) + .build() + + val tx = tb.boxesToSpend(Seq( + arbitrageMintBox, + bankBox, + userInBox + ).asJava) + .fee(FEE) + .outputs(newArbitrageMintBox, newBankBox, userOutBox) + .withDataInputs(List(oracleBox, lpBox).asJava) + .sendChangeTo(Address.create("4MQyML64GnzMxZgm").getErgoAddress) + .build() + + var signed: SignedTransaction = null + try { + signed = prover.sign(tx) + } catch { + case e: Exception => { + println("User could not sing tx due to:") + println(e) + return + } + } + println(s"signed tx: ${signed.toJson(false)}") + println("---- ArbitrageMint with CounterReset scenario Done !! ----") + } } diff --git a/src/main/scala/DexyContracts.scala b/src/main/scala/DexyContracts.scala index 1b5b428..006d515 100755 --- a/src/main/scala/DexyContracts.scala +++ b/src/main/scala/DexyContracts.scala @@ -297,7 +297,7 @@ object DexyContracts { | val validSuccessorR4 = successorR4 == (if (isCounterReset) HEIGHT + t_free else selfInR4) | val validSuccessorR5 = successorR5 == availableToMint - dexyMinted | - | val validBankBoxInOut = bankBoxIn.tokens(0)._1 == bankNFT && bankBoxOut.tokens(0)._1 == bankNFT + | val validBankBoxInOut = bankBoxIn.tokens(0)._1 == bankNFT | val validLpBox = lpBox.tokens(0)._1 == lpNFT | val validOracleBox = oracleBox.tokens(0)._1 == oracleNFT | val validSuccessor = successor.tokens == SELF.tokens && // NFT preserved @@ -381,7 +381,10 @@ object DexyContracts { | | val dexyInCirculation = ${initialDexyTokens}L - bankDexy | - | // referring to the section "4 Worst Scenario and Bank Reserves" in paper, the following notation is used + | // We have the parameter payoutThreshold for ensuring that the bank has at least that many ergs + | // before payout. However, we need to also ensure that there are enough ergs in the bank to + | // cover the "worst case scenario" described in section "4 Worst Scenario and Bank Reserves" in paper, + | // The following notation is used | val O = dexyInCirculation // (note: capital o, not zero) | val p = lpRate // initial rate | val s = oracleRate // final lower rate after crash @@ -708,7 +711,7 @@ object DexyContracts { | val resetNow = trackerHeightOut == ${Int.MaxValue} // Infinity | | val trigger = ((isBelowIn && x < y) || (!isBelowIn && x > y)) && notTriggeredEarlier && triggeredNow - | val reset = (isBelowIn && x >= y) || (!isBelowIn && x <= y) && notResetEarlier && resetNow + | val reset = ((isBelowIn && x >= y) || (!isBelowIn && x <= y)) && notResetEarlier && resetNow | val correctAction = trigger || reset | | numOut == numIn &&