diff --git a/contracts/v1/ergonames_v1_collection.es b/contracts/v1/ergonames_v1_collection.es index 2f53327..6dcd1e6 100644 --- a/contracts/v1/ergonames_v1_collection.es +++ b/contracts/v1/ergonames_v1_collection.es @@ -28,6 +28,8 @@ // ===== Compile Time Constants ($) ===== // // $revealErgoTreeBytesHash: Coll[Byte] // $revealProxyErgoTreeBytesHash: Coll[Byte] + // $ergonameCollectionSingletonTokenId: Coll[Byte] + // $ergonameCollectionTokenId: Coll[Byte] // ===== Context Variables (_) ===== // // None @@ -37,8 +39,9 @@ // ===== Relevant Variables ===== // val minerFeeErgoTreeHash: Coll[Byte] = fromBase16("e540cceffd3b8dd0f401193576cc413467039695969427df94454193dddfb375") - val ergonameCollectionTokenId: Coll[Byte] = SELF.tokens(1)._1 - val ergonameCollectionTokenAmount: Long = SELF.tokens(1)._2 + val ergonameCollectionTokenAmount = SELF.tokens.fold(0L, { (sum: Long, t: (Coll[Byte], Long)) => + if (t._1 == $ergonameCollectionTokenId) sum + t._2 else sum + }) val isRefund: Boolean = (OUTPUTS.size == 3) if (!isRefund) { @@ -56,11 +59,27 @@ val validSelfRecreation: Boolean = { + val validSingletonTransfer: Boolean = { + + ergonameCollectionBoxOut.tokens.exists({ (t: (Coll[Byte], Long)) => + t._1 == $ergonameCollectionSingletonTokenId + }) + + } + + val validCollectionTokensTransfer: Boolean = { + + ergonameCollectionBoxOut.tokens.exists({ (t: (Coll[Byte], Long)) => + t == ($ergonameCollectionTokenId, ergonameCollectionTokenAmount - 1L) + }) + + } + allOf(Coll( (ergonameCollectionBoxOut.value == SELF.value), (ergonameCollectionBoxOut.propositionBytes == SELF.propositionBytes), - (ergonameCollectionBoxOut.tokens(0) == SELF.tokens(0)), - (ergonameCollectionBoxOut.tokens(1) == (ergonameCollectionTokenId, ergonameCollectionTokenAmount - 1L)) + validSingletonTransfer, + validCollectionTokensTransfer )) } @@ -71,8 +90,8 @@ allOf(Coll( (blake2b256(revealBoxOut.propositionBytes) == $revealErgoTreeBytesHash), - (revealBoxOut.tokens(0) == (ergonameCollectionTokenId, 1L)), - (revealBoxOut.R7[Coll[Byte]].get == ergonameCollectionTokenId), // For artwork standard v2 (EIP-24). + (revealBoxOut.tokens(0) == ($ergonameCollectionTokenId, 1L)), + (revealBoxOut.R7[Coll[Byte]].get == $ergonameCollectionTokenId), // For artwork standard v2 (EIP-24). )) } @@ -105,7 +124,7 @@ (ergonameCollectionBoxOut.value == SELF.value), (ergonameCollectionBoxOut.propositionBytes == SELF.propositionBytes), (ergonameCollectionBoxOut.tokens(0) == SELF.tokens(0)), - (ergonameCollectionBoxOut.tokens(1) == (ergonameCollectionTokenId, ergonameCollectionTokenAmount + 1L)) + (ergonameCollectionBoxOut.tokens(1) == ($ergonameCollectionTokenId, ergonameCollectionTokenAmount + 1L)) )) } @@ -114,7 +133,7 @@ allOf(Coll( (blake2b256(revealBoxIn.propositionBytes) == $revealErgoTreeBytesHash), - (revealBoxIn.tokens(0) == (ergonameCollectionTokenId, 1L)) + (revealBoxIn.tokens(0) == ($ergonameCollectionTokenId, 1L)) )) } diff --git a/contracts/v1/ergonames_v1_commit.es b/contracts/v1/ergonames_v1_commit.es index a8a8e0b..6c3d56b 100644 --- a/contracts/v1/ergonames_v1_commit.es +++ b/contracts/v1/ergonames_v1_commit.es @@ -125,7 +125,7 @@ } - sigmaProp(validRefundTx) + sigmaProp(validRefundTx) && userPKSigmaProp } diff --git a/contracts/v1/ergonames_v1_registry.es b/contracts/v1/ergonames_v1_registry.es index e904793..7b58ad4 100644 --- a/contracts/v1/ergonames_v1_registry.es +++ b/contracts/v1/ergonames_v1_registry.es @@ -160,7 +160,7 @@ val validErgoNameInsertion: Boolean = { - val newRegistry: AvlTree = previousRegistry.insert(Coll((_ergoNameHash, ergoNameTokenId)), _insertionProof).get + val newRegistry: AvlTree = previousRegistry.insert(Coll((_ergoNameHash, ergoNameTokenId)), _insertionProof).get allOf(Coll( (registryBoxOut.R4[AvlTree].get.digest == newRegistry.digest), @@ -174,7 +174,9 @@ allOf(Coll( (registryBoxOut.value == SELF.value), (registryBoxOut.propositionBytes == SELF.propositionBytes), - (registryBoxOut.tokens(0) == SELF.tokens(0)) + (registryBoxOut.tokens(0) == SELF.tokens(0)), + (registryBoxOut.R6[(Int, Int)].get == ageThreshold), + (registryBoxOut.R7[Coll[BigInt]].get == priceMap) )) } @@ -213,7 +215,25 @@ if (isDefaultPaymentMode) { - val validFeePayment: Boolean = (ergoNameFeeBoxOut.value.toBigInt >= equivalentNanoErg) + val validFeePayment: Boolean = { + + val amount: BigInt = revealBoxIn.value.toBigInt // Reveal box contains target price when reveal was created + 5% slippage. + val target: BigInt = (amount * 100.toBigInt) / 105.toBigInt // 5% slippage + val slippage: BigInt = (amount - target) + val difference: BigInt = (equivalentNanoErg - target) + val isWithin: Boolean = (difference >= 0 && difference < slippage) || (difference <= 0 && difference > -1.toBigInt * slippage) + + val validFee: Boolean = (ergoNameFeeBoxOut.value.toBigInt >= equivalentNanoErg) + val validChange: Boolean = (ergoNameIssuanceBoxOut.value.toBigInt >= (amount - equivalentNanoErg)) + + allOf(Coll( + isWithin, + validFee, + validChange + )) + + } + val validFeeAddress: Boolean = (blake2b256(ergoNameFeeBoxOut.propositionBytes) == $ergoNameFeeContractBytesHash) allOf(Coll( diff --git a/contracts/v1/ergonames_v1_reveal.es b/contracts/v1/ergonames_v1_reveal.es index 010743c..7d36efb 100644 --- a/contracts/v1/ergonames_v1_reveal.es +++ b/contracts/v1/ergonames_v1_reveal.es @@ -142,7 +142,7 @@ val validRevealBoxInValue: Boolean = { - val validErgValue: Boolean = (SELF.value == subNameRegistryAmount + ergoNameIssuanceAmount + ergoNameFeeErgAmount + minerFeeAmount + txOperatorFeeAmount) + val validErgValue: Boolean = (SELF.value == subNameRegistryAmount + ergoNameIssuanceAmount + ergoNameFeeErgAmount + minerFeeAmount) val validTokenValue: Boolean = { if (isPayingWithToken) { @@ -202,6 +202,8 @@ validCommitBoxIn, validSubNameRegistryAmount, validErgonameIssuanceAmount, + validErgoNameMint, + validCollectionTokenBurn, validMinerFeeBoxOut, validTxOperatorFeeBoxOut, (OUTPUTS.size == 6) @@ -229,10 +231,12 @@ val validUser: Boolean = { val propAndBox: (SigmaProp, Box) = (userPKSigmaProp, userPKBoxOut) + val validPaymentTokenTransfer: Boolean = if isPayingWithToken (userPKBoxOut.tokens(0) == SELF.tokens(1)) else (userPKBoxOut.tokens.size == 0) allOf(Coll( (userPKBoxOut.value == SELF.value - minerFee), - isSigmaPropEqualToBoxProp(propAndBox) + isSigmaPropEqualToBoxProp(propAndBox), + validPaymentTokenTransfer )) } @@ -241,7 +245,8 @@ allOf(Coll( (minerFeeBoxOut.value == minerFee), - (blake2b256(minerFeeBoxOut.propositionBytes) == minerFeeErgoTreeHash) + (blake2b256(minerFeeBoxOut.propositionBytes) == minerFeeErgoTreeHash), + (minerFeeBoxOut.tokens.size == 0) )) } diff --git a/contracts/v1/ergonames_v1_reveal_proxy.es b/contracts/v1/ergonames_v1_reveal_proxy.es index 981038f..ba3592d 100644 --- a/contracts/v1/ergonames_v1_reveal_proxy.es +++ b/contracts/v1/ergonames_v1_reveal_proxy.es @@ -13,6 +13,7 @@ // R4: Coll[Byte] RevealBoxHash // R5: Long MinerFee // R6: Long TxOperatorFee + // R7: GroupElement UserPKGroupElement // ===== Relevant Transactions ===== // // 1. Reveal @@ -20,6 +21,11 @@ // Data Inputs: None // Outputs: ErgoNameCollection, Reveal, MinerFee, TxOperatorFee // Context Variables: None + // 2. Refund Reveal Proxy + // Inputs: RevealProxy + // DataInputs: None + // Outputs: UserPKBoxOut, MinerFee + // Context Variables: None // ===== Compile Time Constants ($) ===== // // $ergoNameCollectionSingletonTokenId: Coll[Byte] @@ -28,62 +34,135 @@ // None // ===== User Defined Functions ===== // - // None + // def isSigmaPropEqualToBoxProp: ((SigmaProp, Box) => Boolean) + + def isSigmaPropEqualToBoxProp(propAndBox: (SigmaProp, Box)): Boolean = { + + val prop: SigmaProp = propAndBox._1 + val box: Box = propAndBox._2 + + val propBytes: Coll[Byte] = prop.propBytes + val treeBytes: Coll[Byte] = box.propositionBytes + + if (treeBytes(0) == 0) { + + (treeBytes == propBytes) + + } else { + + // offset = 1 + + val offset = if (treeBytes.size > 127) 3 else 2 + (propBytes.slice(1, propBytes.size) == treeBytes.slice(offset, treeBytes.size)) + + } + + } // ===== Relevant Variables ===== // val minerFeeErgoTreeHash: Coll[Byte] = fromBase16("e540cceffd3b8dd0f401193576cc413467039695969427df94454193dddfb375") val revealBoxHash: Coll[Byte] = SELF.R4[Coll[Byte]].get val minerFee: Long = SELF.R5[Long].get val txOperatorFee: Long = SELF.R6[Long].get + val userPKGroupElement: GroupElement = SELF.R7[GroupElement].get + val userPKSigmaProp: SigmaProp = proveDlog(userPKGroupElement) val isPayingWithToken: Boolean = (SELF.tokens.size == 1) + val isRefund: Boolean = (OUTPUTS.size == 2) - val validRevealTx: Boolean = { + if (!isRefund) { - // Inputs - val ergonameCollectionBoxIn: Box = INPUTS(0) + val validRevealTx: Boolean = { - // Outputs - val ergonameCollectionBoxOut: Box = OUTPUTS(0) - val revealBoxOut: Box = OUTPUTS(1) - val minerFeeBoxOut: Box = OUTPUTS(2) - val txOperatorFeeBoxOut: Box = OUTPUTS(3) + // Inputs + val ergonameCollectionBoxIn: Box = INPUTS(0) - val validCollection: Boolean = (ergonameCollectionBoxIn.tokens(0)._1 == $ergoNameCollectionSingletonTokenId) + // Outputs + val ergonameCollectionBoxOut: Box = OUTPUTS(0) + val revealBoxOut: Box = OUTPUTS(1) + val minerFeeBoxOut: Box = OUTPUTS(2) + val txOperatorFeeBoxOut: Box = OUTPUTS(3) - val validReveal: Boolean = { + val validCollection: Boolean = { - val revealHash: Coll[Byte] = blake2b256(revealBoxOut.bytesWithoutRef) // Bytes of box contents without transaction id and output index. - val validPaymentToken: Boolean = if (isPayingWithToken) (revealBoxOut.tokens(0) == SELF.tokens(0)) else true + ergonameCollectionBoxIn.tokens.exists({ (t: (Coll[Byte], Long)) => + t._1 == $ergoNameCollectionSingletonTokenId + }) - allOf(Coll( - (revealBoxOut.value == SELF.value - minerFee - txOperatorFee), - validPaymentToken, - (revealHash == revealBoxHash) - )) + } - } + val validReveal: Boolean = { + + val revealHash: Coll[Byte] = blake2b256(revealBoxOut.bytesWithoutRef) // Bytes of box contents without transaction id and output index. + val validPaymentToken: Boolean = if (isPayingWithToken) (revealBoxOut.tokens(1) == SELF.tokens(0)) else true + + allOf(Coll( + (revealBoxOut.value == SELF.value - minerFee - txOperatorFee), + validPaymentToken, + (revealHash == revealBoxHash) + )) + + } - val validMinerFee: Boolean = { + val validMinerFee: Boolean = { + + allOf(Coll( + (minerFeeBoxOut.value == minerFee), + (blake2b256(minerFeeBoxOut.propositionBytes) == minerFeeErgoTreeHash) + )) + + } + + val validTxOperatorFee: Boolean = (txOperatorFeeBoxOut.value == txOperatorFee) + + allOf(Coll( + validCollection, + validReveal, + validMinerFee, + validTxOperatorFee, + (OUTPUTS.size == 4) + )) + + } + + sigmaProp(validRevealTx) + + } else { + + val validRefundTx: Boolean = { + + // Outputs + val userPKBoxOut: Box = OUTPUTS(0) + val minerFeeBoxOut: Box = OUTPUTS(1) + + val validUser: Boolean = { + + val propAndBox: (SigmaProp, Box) = (userPKSigmaProp, userPKBoxOut) + + allOf(Coll( + (userPKBoxOut.value == SELF.value - minerFee), + isSigmaPropEqualToBoxProp(propAndBox), + (userPKBoxOut.tokens == SELF.tokens) + )) + + } + + val validMinerFee: Boolean = { + + allOf(Coll( + (minerFeeBoxOut.value == minerFee), + (blake2b256(minerFeeBoxOut.propositionBytes) == minerFeeErgoTreeHash) + )) + + } allOf(Coll( - (minerFeeBoxOut.value == minerFee), - (blake2b256(minerFeeBoxOut.propositionBytes) == minerFeeErgoTreeHash) + validUser, + validMinerFee )) - + } - val validTxOperatorFee: Boolean = (txOperatorFeeBoxOut.value == txOperatorFee) - - allOf(Coll( - validCollection, - validReveal, - validMinerFee, - validTxOperatorFee, - (OUTPUTS.size == 4) - )) + sigmaProp(validRefundTx) && userPKSigmaProp } - sigmaProp(validRevealTx) - } \ No newline at end of file