diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 70f2bcf..550e07f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -3,7 +3,7 @@ name: RELEASE on: push: branches: - - master + - dev tags: - '*' pull_request: @@ -100,6 +100,14 @@ jobs: tokensMap-*.json env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: git-sync + uses: wei/git-sync@v3 + with: + source_repo: ${GITHUB_REPOSITORY} + source_branch: "refs/tags/*" + destination_repo: 'https://${{ secrets.DESTINATION_USER }}:${{ secrets.DESTINATION_TOKEN }}@${{ secrets.DESTINATION_SERVER }}/ergo/${GITHUB_REPOSITORY}.git' + destination_branch: "refs/tags/*" - name: RUN Jar File release if: github.ref_type == 'tag' diff --git a/README.md b/README.md index 974913f..71267da 100644 --- a/README.md +++ b/README.md @@ -1,42 +1,54 @@ # Rosen Bridge -Rosen bridge is an Ergo-centric bridge enabling users to send and receive coins and tokens between Ergo and any other chain. In this document, we will explain the Rosen bridge design technically. You may also want to read about Rosen bridge's high-level concepts [here](https://github.com/rosen-bridge). +The Rosen Bridge is an Ergo-centric bridge designed to facilitate the seamless exchange of coins and tokens between the Ergo blockchain and other supported chains. In this document, we will delve into the technical intricacies of the Rosen Bridge's design. For a broader understanding of the Rosen Bridge's high-level concepts, you can refer to this [link](https://github.com/rosen-bridge). ## Rosen Bridge Components -Before explaining the main idea and procedures, it's better to get familiar with its components and concepts: +To gain a comprehensive understanding of the Rosen Bridge and its operations, it's essential to get familiar with its core components and concepts: ### Roles -1. **Guard:** A guard is a trusted party performing final actions in the system. Actually, a set of trusted guards are needed to transfer money between chains. The guard set is a small set including real parties. Each guard individually verifies the events and performs the required action. However, all (or a quorum of) guards should agree on one event to make the final operation. - -2. **Watcher:** As the name suggests, watchers are some volunteer participants who watch two chains to create the transfer events. After a quorum of watchers reported the same event, a watcher will make an event trigger for guards. Watchers are not trusted and may have faults, intentionally or unintentionally. Anyway, the guilty watcher will be fined for his faulty action, and on the other hand, honest watchers will be rewarded. +1. **Guard:** A guard serves as a trusted entity responsible for executing final actions within the system. In practice, a set of trusted guards is essential for facilitating cross-chain transfers. Each guard independently verifies events and carries out the necessary actions. However, for a final operation to proceed, all guards (or a quorum) must reach a consensus on a specific event. +2. **Watcher:** As the name implies, watchers are volunteer participants tasked with watching chains to create transfer reports. Once a quorum of watchers reported the same event, a watcher initiates the event trigger for the guards. Watchers are not trusted and may exhibit faults, whether intentionally or accidentally. In such cases, the responsible watcher is subject to penalties for their erroneous actions, while honest watchers are duly rewarded after each report. ### Tokens -1. **Rosen Bridge Token (RSN):** The Rosen bridge token defines the bridge participation rights. Each person who has a portion of this token can participate in the bridge as a watcher. +As previously mentioned, Rosen operates as an Ergo-centric bridge, with the primary procedures designed to run on the Ergo network. In addition to tokens that are transferred on the bridge, transfer procedures involve the use of administrative tokens on the Ergo network, each serving distinct functions: + +1. **Rosen Bridge Token (RSN):** The Rosen bridge token defines the bridge participation rights. Holders of this token are eligible to engage as watchers within the bridge ecosystem. -2. **X-Rosen Watcher Token (X-RWT):** Each watcher volunteer needs to lock his RSN tokens to be a watcher of a specific bridge. The legitimate watchers receive X-RWT tokens on behalf of their RSN. Each of these tokens can be used to create new events, and if the event were true, guards would return the X-RWT to the watcher. RWT is a chain-specific token; thus, chain X watchers will use X-RWTs for event creation. +2. **Guard NFT:** This NFT is collectively owned by the guards and its expenditure represents a consensus among guards on a transaction. This NFT resides in a multi-signature address controlled by the guards, then its presence in a transaction input signifies the approval of the guards. It is used for transfer payments and system update transactions. -3. **Guard NFT:** This NFT belongs to the bridge wallet, a multi-sig address of guards. Thus, if it was spent in a transaction input, it means that guards agreed on that transaction. This NFT is used for system update transactions. +3. **RWT Repo NFT:** Each chain has unique configuration for watchers, all of which are stored in the RWT-Repo box. This NFT serves as an identifier for verified repos associated with each chain. -4. **X-Cleanup NFT:** This token is the cleanup service identifier. We have different cleanup services for each registered bridge. +4. **X-Rosen Watcher Token (X-RWT):** Watcher volunteers are required to lock their RSN tokens to become watchers of a specific chain. The legitimate watchers receive X-RWT tokens in exchange of their RSN tokens. Each pack of these tokens can be used to create a new action report. If the report was correct, guards will return the X-RWT tokens to the respective watcher. RWT is a chain-specific token; thus, chain X watchers will employ X-RWT tokens for report creation. -5. **Watcher Identifier Token (WID Token):** After locking RSNs by a volunteer, the X-RWT tokens are paid to the watcher as well as a newly issued token that identifies the watcher. A watcher may have multiple X-RWT tokens, enabling him to simultaneously create different events on one bridge. Though, the WID token is a unique token that identifies the watcher (we may decide to use a set of WID tokens to handle the concurrency, but it won't damage its uniqueness concept). The watcher must use his WID token to authenticate every new event generation, reward reception, or X-RWT token redemption actions. +5. **X-Cleanup NFT:** This token is the cleanup service identifier. Different cleanup services are available for each registered chain, responsible for eliminating older, unused boxes, which may potentially involve fraudulent activity. +6. **Watcher Identifier Token (WID Token):** After a volunteer locks their RSN tokens, they receive X-RWT tokens as well as a newly issued token that serves as their unique watcher identifier. A watcher may possess multiple X-RWT tokens, allowing them to concurrently create various reports for a chain. However, the WID Token is a unique token assigned to each watcher, which they must utilize to authenticate actions like new report generation, reward receipt, or X-RWT token redemption (we may decide to use a set of WID tokens to handle the concurrency, but it won't damage its uniqueness concept). ### Contracts -1. **X-Bridge Wallet:** This is a simple multi-sig address storing all X-wrapped tokens. All guards must agree on a payment transaction in order to spend this box and pay the requested tokens to the user. +To execute bridge operations, Rosen utilizes a straightforward multi-signature wallet on each supported chain. This wallet (X-Chain Wallet), contains all X-native and X-wrapped tokens. +To transfer assets from this wallet and issue the requested tokens to users, an agreement among all guards is required (signing the same transaction). +Rather than these simple multi-sig wallets, more complicated procedures are designed on ergo which requires ergo-side contracts. These contracts include: + +1. **Ergo-Chain Wallet:** Similar to other X-Chain Wallets, all ergo-wrapped tokens resist in this wallet. However, in order to simplify ergo-side bridge config updates its not a simple multi-sig wallet. +Boxes governed by this contract can only be spent when the Guard NFT is included in the spending transaction. -1. **X-RWT Repo:** This contract is the system configuration for each chain. This contract is responsible for tracking the corresponding X-RWT tokens and locking RSNs to emit new X-RWTs. It stores the X-RWT/RSN factor, the quorum percentage of watchers, and the maximum needed event commitment count for this chain. It also stores the list of available watcher WIDs alongside their X-RWT token count. +2. **Guard Sign:** Guard sign is a box that stores all guard pks, required sign thresholds, and the guard NFT. +To perform asset transfers and configuration updates, guards must spend this box by signing transactions with their secrets. +As guard public keys are stored within this box's registers, updating the guard set in the Ergo network is as straightforward as executing a standard multi-signature transaction. This eliminates the need to alter bridge addresses or transfer assets to a new wallet. -2. **X-Watcher Permit:** When a new watcher is registered, his X-RWT tokens would be locked in this contract. It also stores the WID in the registers. It can be spent only if the WID token exists in the spending transaction. +3. **X-RWT Repo:** This contract governs the system configuration for each supported chain. It is responsible for tracking the corresponding X-RWT tokens and locking RSNs to emit new X-RWTs. It stores the number of required X-RWTs for each event commitment, the quorum percentage of watchers, the maximum needed event commitment count for this chain, and needed watcher collateral in ERG and RSN. Additionally, it maintains a list of available watcher WIDs alongside their respective X-RWT token counts. -3. **X-Event Commitment:** When a watcher detects a new event on the target chain, it creates an event commitment. +4. **Watcher Collateral:** Watchers are required to provide a security deposit or collateral as part of their participation in the bridge. This collateral is safely kept in this contract. Whenever the watcher redeemed its permits, the collateral will automatically returned to their address. -4. **X-Event Trigger:** A watcher creates the event trigger after a quorum of watchers reported the same event. He will spend all commitments and reveals the event contents to generate the event trigger. It also stores the reporter WIDs in the event trigger that the guards will process. +5. **X-Watcher Permit:** When a new watcher registers, their X-RWT tokens are locked within this contract. Furthermore, the contract stores the WID (Watcher Identifier) in its registers. It can only be spent if the corresponding WID token exists within the spending transaction. -5. **X-Fraud:** Some watchers may create faulty events. The guards will process the faulty events if a quorum of watchers created that event (resulting in an event trigger), but guards will ignore these triggers since they are not verifiable. After a while, the cleanup service collects all these faulty events and moves their X-RWT tokens to the fraud contract as punishment. The fraud contract will be spent to redeem RSNs from the X-RWT repo. +6. **X-Event Commitment:** This contract comes into play when a watcher detects a new event on the observing chain. Then, the watcher creates a report commitment with the event data digest. +7. **X-Event Trigger:** A watcher creates the event trigger after a quorum of watchers reported the same event. This action involves spending all commitments and revealing the event's contents. The event trigger also stores the WIDs of the reporting watchers. + +8. **X-Fraud:** Occasionally, watchers may create erroneous or fraudulent reports. Guards will process these events if a watcher generate the event trigger (after a quorum of watchers generated the corresponding commitment). However, guards will disregard these triggers as they are not verifiable. After a while, the cleanup service collects these faulty triggers and transfers their associated X-RWT tokens to the fraud contract as a form of penalty. ### Data @@ -60,19 +72,20 @@ Before explaining the main idea and procedures, it's better to get familiar with 3. Event id (hash of related txId on source chain) ## Rosen Bridge Life Cycle -In this section, we will review the introduced procedures, stored data, and component dependence in detail. These phases are the life cycle of a bridge, but the Rosen bridge is the collection of different such bridges connecting Ergo to the outside world. +In this section, we will delve into a comprehensive examination of the introduced procedures, stored data, and component interdependencies. These phases collectively represent the life cycle of a bridge (between two chains). It's important to note that the Rosen Bridge comprises a collection of such bridges that connect Ergo to the external world. ### A. Watcher Registration Procedure -Each person who has enough portion of X-RWT tokens can be an X bridge watcher. Thus watcher registration has these steps: +Any individual possessing a sufficient amount of X-RWT tokens can become a X-chain watcher. Thus watcher registration has these steps: #### 1- Get Watcher Permit As mentioned earlier, each watcher volunteer needs to lock his RSN tokens to receive corresponding X-RWTs to obtain the watcher permit. So in this transaction, these requirements must be satisfied: * X-RWT repo data should be updated: - * Append his WID to the WID list + * Append the new WID to the WID list * Append the number of receiving X-RWT tokens to the token count list - * based on X-RWT/RSN factor, pay RSN to the repo and get the X-RWTs + * Pay RSN to the repo and get the respective X-RWTs tokens * A watcher permit box is created containing all X-RWT tokens. It also stores the WID in its registers. +* Collateral box is created containing the required collateral for the watcher. * Issue the WID token and send it to the watcher's address.
@@ -81,20 +94,28 @@ As mentioned earlier, each watcher volunteer needs to lock his RSN tokens to rec #### 2- Merge Watcher Permits -In the payment procedure-reward distribution phase (B.4), we will see guards will pay back the X-RWT token besides the watcher reward after accepting the event triggers. A watcher can spend all these boxes with his WID token as authentication. He may want to merge these boxes and extract the collected rewards. Also, he can create a new event commitment while merging boxes (See B.2). +In the payment procedure-reward distribution phase (B.4), we will see guards will pay back the X-RWT token besides the watcher reward after accepting the event triggers. A watcher can spend all these boxes with their WID token as authentication. He may want to merge these boxes and extract the collected rewards. Also, he can create a new commitment report while merging boxes (See B.2).
#### 3- Return Watcher Permit -At any time, a watcher may want to redeem his RSNs (to sell them or to change his bridge). Thus, he should return his watcher permits to unlock the X-RWTs and redeem the RSNs based on the X-RWT/RSN factor stored in the X-RWT repo. -In this transaction, the watcher should update the token count corresponding to his WID, and if he is returning all X-RWTs, he must remove his WID from the registered watchers list. +At any given time, a watcher may wish to withdraw their RSN tokens, whether for the purpose of selling them or watching a different chain. To facilitate this, the watcher must return their watcher permits to unlock the X-RWTs and initiate the withdrawal of RSN tokens. + +In this transaction, the watcher is required to update the token count associated with their WID (Watcher Identifier Token).
+ +If a watcher returns all the corresponding X-RWT tokens, then the collateral can be withdrawn accordingly. + +
+ +
+ ### B. Payment Procedure Payment is the main procedure of bridge, in which the user gets involved directly. At first, the user sends his funds to the bridge wallet and creates the transfer request. Then, watchers verify the request and create commitments; once enough commitments are created, one watcher creates the event trigger. Finally, Guards process the event trigger and do the final required actions. As the scenario that the source chain is Ergo is more complicated, we will first focus on sending funds from ergo to chain X. The opposite direction and the differences are all discussed in the end. @@ -107,22 +128,22 @@ A transfer request starts by sending the funds to the bridge wallet on the sourc #### 2- Event Commitment Creation -Each watcher observes both chains and, in case of detecting any valid transfer requests, creates the event commitment in the Ergo network. It also stores the request information in its local database for future usage. +Each watcher observing chain X, upon detecting valid transfer requests, generates a commitment report within the Ergo network. Additionally, it maintains the request information in its local database for potential future use. In this transaction: -* Inputs are: +* Inputs include: * Watcher permit box containing X-RWT * A box containing his WID token as the first token * (Optional) Any other watcher permit boxes in the network can be merged in this transaction (A.2) -* Only one new watcher permit box is created so that all remaining X-RWTs will be aggregated. -* The created commitment box has exactly one X-RWT. It also stores all commitment information in its registers. +* Only one new watcher permit box is created, containing all remaining X-RWTs. +* The newly created commitment box contains a quantity of X-RWT tokens based on the specified amount required in X-RWT-Repo. It also stores all commitment-related information within its registers.#### 3- Event Trigger Creation -In this phase, a watcher creates the event trigger after a quorum of watchers reported the same event. He will spend all commitments and reveals the event contents to generate the event trigger. +In this phase, a watcher creates the event trigger after a quorum of watchers reported the same event. The watcher will spend all commitments and reveals the event contents to generate the event trigger. At first, the watcher finds all commitments with the same id. Then, he verifies the commitments with the event data and the issuer's WID. Finally, if there are sufficient verified commitments, he spends them all and creates the event trigger. Created event trigger stores: diff --git a/build.sbt b/build.sbt index ae7992e..cb1151e 100644 --- a/build.sbt +++ b/build.sbt @@ -21,7 +21,7 @@ libraryDependencies ++= Seq( name := "contract" ThisBuild / scalaVersion := "2.12.7" -ThisBuild / version := "1.0.0" +ThisBuild / version := "2.0.0" ThisBuild / organization := "rosen.bridge" ThisBuild / organizationName := "rosen-bridge" ThisBuild / publishMavenStyle := true diff --git a/images/Get_Permit.png b/images/Get_Permit.png index bb41f09..afd3268 100644 Binary files a/images/Get_Permit.png and b/images/Get_Permit.png differ diff --git a/images/Return_Total_Permit.png b/images/Return_Total_Permit.png new file mode 100644 index 0000000..3fce35d Binary files /dev/null and b/images/Return_Total_Permit.png differ diff --git a/src/main/resources/application.conf b/src/main/resources/application.conf index a0e660e..897b517 100644 --- a/src/main/resources/application.conf +++ b/src/main/resources/application.conf @@ -9,12 +9,12 @@ box = { } network-general = { - mainnet-alpha-1 = { + public-launch = { main-tokens = { - RepoNFT="3ac8a90d0aa8c5c50e99dd2588a990fd37b5d3aee70e32d56241f41ed49e9f03" - GuardNFT="a96b8049f0741c7255b60cf01954439d2e4b2d19ae7d8ebc688ecb190a33b538" - RSN="10278c102bf890fdab8ef5111e94053c90b3541bc25b0de2ee8aa6305ccec3de", - RSNRatioNFT="febf2d60bbcccb94ed27f49f496521415280bb7b59086455371808c4f740f36a" + RepoNFT="eceee4b72d8572c032789927a1847557292fb80b78fb58d6214754d2d474272d" + GuardNFT="55bf993b4376ba132264d084f06fe033321ab43c68db1435a0e7e0bf77ddb735" + RSN="8b08cdd5449a9592a9e79711d7d79249d7a03c535d17efaee83e216e80a44c4b" + RSNRatioNFT="1ce1ea41ceaed94a6976a047645ffb3f3da3916685268c8f2d8f80f892147caa" } ergo-network = { node = "https://node.ergopool.io/" @@ -22,25 +22,12 @@ network-general = { type = "MAINNET" } } - softlaunch = { + loen = { main-tokens = { - RepoNFT="4b1e5bcfbd6763b9cea8411841213611258fabed16293af6aa8cd8200b7e1286" - GuardNFT="b039e4445337697deaad703fc8d1fcb129c4577e8673394fa2403e54d484eeda" - RSN="51c1745883a62db6cf47f5765bd695317a01e54bcaaaeaa4aab0b517d2f46a24", - RSNRatioNFT="45e873d4e5af0d0fd6905ef51053fa7e9c672cd26b23b8a7f4feb17254f25392" - } - ergo-network = { - node = "https://node.ergopool.io/" - explorer-url = "https://api.ergoplatform.com/" - type = "MAINNET" - } - } - public-soft-launch = { - main-tokens = { - RepoNFT="a599bb94b230f8d3ac94856ab24c31b235ab493d3415097102916c600fbbf969" - GuardNFT="6721f30af5d1063bf0fd7dd2842e55f419bc79f4f1acb664352f2a02cc3c5017" - RSN="844e3cf44b3181b4cacbccbf7596d341f41147d73daf4b565ecaac983aba2508", - RSNRatioNFT="1ea49e1c052803576e91275b27dffbae3f4ccad9da5219bcda51ea5006fe7661" + RepoNFT="923aa955f4f1b67409044ee28b78bb6caff649c9de45a192fd313703743dd8e3" + GuardNFT="648e9cf0b39560dd33454bb8e5c5e5bf48f12e161dc7c3112d8edfdb1606c40d" + RSN="cdf549fccbb09ab8f38ecbf9a5ed37c926707753adf8fed19b039684a0772bfe", + RSNRatioNFT="05690d3e7a8daae13495b32af8ab58aaec8a5435f5974f6adf17095d28cac1f5" } ergo-network = { node = "https://node.ergopool.io/" @@ -52,48 +39,36 @@ network-general = { networks = { cardano = { - mainnet-alpha-1 = { + public-launch = { tokens = { - RWTId="1d9a45e047b45a4a4c565ac49e70b5e2f03f625975cbe41912833e9a547d060d" - CleanupNFT="f833f756e1bf9d15b8f984aefa64482040d6c8b3ec217b2be8b8a477e82d900d" + RWTId="bee1398b0a7acc0a4a0bc8619ef473a462f4c23208072f2aaeddfef67d30166a" + CleanupNFT="cffae825313193178cfe8c528e521bc5c88f74a970ad1da6f25f12c728416a83" } - cleanup-confirm = 7200 + lock-address="addr1v8kqhz5lkdxqm8qtkn4lgd9f4890v0j6advjfmk5k9amu4c535lsu" + cleanup-confirm=21600 } - softlaunch = { + loen = { tokens = { - RWTId="747ffbe3403d8013e5033398dc8f09a3594f51d0250f2075cc559782ae2fa846" - CleanupNFT="c79e1748e862a80c4f7193077b600f9ffb65c612297f9a8829407044c1eb1066" + RWTId="01553e0f9122ce7a8dc696fa227d6b09a63dc7eb20a351bfa4f1108c14238895" + CleanupNFT="0ca1d31e93b42f9e44e5290e3411aaa2ba67aac158e57916e645612d110729f5" } + lock-address = "addr1v9kmp9flrq8gzh287q4kku8vmad3vkrw0rwqvjas6vyrf9s9at4dn" cleanup-confirm = 7200 } - public-soft-launch = { - tokens = { - RWTId="8e5b02ba729ad364867619d2a8b9ff1438190c14979a12aa0a249e996194f074" - CleanupNFT="246eb5dd39a11d96d2bc7373b3166835fc72998b045d67ea7adce842c4585a62" - } - cleanup-confirm = 7200 - } - idKey = "fingerprint" + idKey = "tokenId" } ergo = { - mainnet-alpha-1 = { + public-launch = { tokens = { - RWTId="383d70ab083cc23336a46370fe730b2c51db0e831586b6d545202cbc33938ee1" - CleanupNFT="37080ea7925c407965f27013fe66d2e7d692e68dc0de9219effe4819cea8c7b3" + RWTId="b0eef7ef45e9c01845d9eb377561e0e06bb6ac4fa0261114ba20de2e5397dcfb" + CleanupNFT="af906a9ffa6f5b819872a135e2a7a6ddd52272e88e0f19842b3730fd9fb65477" } - cleanup-confirm = 7200 - } - softlaunch = { - tokens = { - RWTId="9410db5b39388c6b515160e7248346d7ec63d5457292326da12a26cc02efb526" - CleanupNFT="65214ca53fbf09c0875d71f67f28a0471a4d6d4f66c38ae47b6a417e645f68fd" - } - cleanup-confirm = 7200 + cleanup-confirm = 21600 } - public-soft-launch = { + loen = { tokens = { - RWTId="bd7ec56095ce16501dff9331ba7eb687805904b1ef97100dc4b8abaf472366b3" - CleanupNFT="3443950555c6088a4b3fbd99b853a1db89765c9c7be318a6134db2ee0f8ecbca" + RWTId="c5c6e337aa9bebf87b1f174e1bc1c3019be44ebf72de5b55fbbdcd1f659b4881" + CleanupNFT="eca2e0240bea93c5340cca325d1f1d7eea760ae44dbcf6bb3c2f35ca3329b33e" } cleanup-confirm = 7200 } diff --git a/src/main/scala/helpers/Configs.scala b/src/main/scala/helpers/Configs.scala index 02e544f..d7b74e8 100644 --- a/src/main/scala/helpers/Configs.scala +++ b/src/main/scala/helpers/Configs.scala @@ -62,8 +62,10 @@ object Configs extends ConfigHelper { readKeyDynamic(networkTokensConfig, "CleanupNFT"), readKeyDynamic(networkTokensConfig, "RWTId") ) + val lockAddress = if (networkName != "ergo") readKeyDynamic(networkDataConfig, "lock-address", "PLEASE SET LOCK_ADDRESS MANUALLY") else "" allNetworksToken((networkName, networkType.toString)) = Network( tokens, + lockAddress, readKeyDynamic(networkDataConfig, "cleanup-confirm").toInt ) }) diff --git a/src/main/scala/helpers/Models.scala b/src/main/scala/helpers/Models.scala index 073580d..4fedf11 100644 --- a/src/main/scala/helpers/Models.scala +++ b/src/main/scala/helpers/Models.scala @@ -26,4 +26,4 @@ case class Tokens(CleanupNFT: String, RWTId: String) { } } -case class Network(tokens: Tokens, cleanupConfirm: Int) +case class Network(tokens: Tokens, lockAddress: String, cleanupConfirm: Int) diff --git a/src/main/scala/rosen/bridge/Contracts.scala b/src/main/scala/rosen/bridge/Contracts.scala index e22a3fd..7f83a2a 100644 --- a/src/main/scala/rosen/bridge/Contracts.scala +++ b/src/main/scala/rosen/bridge/Contracts.scala @@ -6,8 +6,10 @@ import scorex.util.encode.{Base16, Base64} import io.circe.Json import java.io.PrintWriter +import scala.io.{BufferedSource, Source} class Contracts(ergoGeneralConfig: ErgoNetwork, networkConfig: (Network, MainTokens)) { + lazy val WatcherCollateral: (ErgoContract, String) = generateWatcherCollateralContract() lazy val RWTRepo: (ErgoContract, String) = generateRWTRepoContract() lazy val WatcherPermit: (ErgoContract, String) = generateWatcherPermitContract() lazy val Commitment: (ErgoContract, String) = generateCommitmentContract() @@ -16,22 +18,31 @@ class Contracts(ergoGeneralConfig: ErgoNetwork, networkConfig: (Network, MainTok lazy val Lock: (ErgoContract, String) = generateLockContract() lazy val GuardSign: (ErgoContract, String) = generateGuardSignContract() - def toJsonAddresses: Json = { + def readScript(path: String) = { + val scriptSource: BufferedSource = Source.fromFile("src/main/scala/rosen/bridge/scripts/"+ path, "utf-8") + val script: String = scriptSource.getLines.mkString("\n") + scriptSource.close() + script + } + + def toJsonAddresses(networkName: String): Json = { + val lockAddress = if (networkName != "ergo") networkConfig._1.lockAddress else Lock._2 Json.fromFields(List( ("RWTRepo", Json.fromString(RWTRepo._2)), ("WatcherPermit", Json.fromString(WatcherPermit._2)), ("Fraud", Json.fromString(Fraud._2)), - ("lock", Json.fromString(Lock._2)), + ("lock", Json.fromString(lockAddress)), ("guardSign", Json.fromString(GuardSign._2)), ("Commitment", Json.fromString(Commitment._2)), - ("WatcherTriggerEvent", Json.fromString(WatcherTriggerEvent._2)) + ("WatcherTriggerEvent", Json.fromString(WatcherTriggerEvent._2)), + ("WatcherCollateral", Json.fromString(WatcherCollateral._2)) )) } def createContractsJson(networkName: String, networkType: String, networkVersion: String): Unit = { val result = { Json.fromFields(List( - ("addresses", this.toJsonAddresses), + ("addresses", this.toJsonAddresses(networkName)), ("tokens", networkConfig._1.tokens.toJson().deepMerge(networkConfig._2.toJson())), ("cleanupConfirm", Json.fromInt(networkConfig._1.cleanupConfirm)) )) @@ -43,13 +54,26 @@ class Contracts(ergoGeneralConfig: ErgoNetwork, networkConfig: (Network, MainTok } } + private def generateWatcherCollateralContract(): (ErgoContract, String) = { + ergoGeneralConfig.ergoClient.execute(ctx => { + val watcherCollateralScript = readScript("WatcherCollateral.es") + .replace("REPO_NFT", Base64.encode(Base16.decode(networkConfig._2.RepoNFT).get)) + val contract = ctx.compileContract(ConstantsBuilder.create().build(), watcherCollateralScript) + val address = Utils.getContractAddress(contract, ergoGeneralConfig.addressEncoder) + println(s"Watcher collateral address is : \t\t\t$address") + (contract, address) + }) + } + private def generateRWTRepoContract(): (ErgoContract, String) = { ergoGeneralConfig.ergoClient.execute(ctx => { val watcherPermitHash = Base64.encode(Utils.getContractScriptHash(WatcherPermit._1)) - val RwtRepoScript = Scripts.RwtRepoScript + val watcherCollateralHash = Base64.encode(Utils.getContractScriptHash(WatcherCollateral._1)) + val RwtRepoScript = readScript("RwtRepo.es") .replace("GUARD_NFT", Base64.encode(Base16.decode(networkConfig._2.GuardNFT).get)) .replace("RSN_TOKEN", Base64.encode(Base16.decode(networkConfig._2.RSN).get)) .replace("PERMIT_SCRIPT_HASH", watcherPermitHash) + .replace("WATCHER_COLLATERAL_SCRIPT_HASH", watcherCollateralHash) val contract = ctx.compileContract(ConstantsBuilder.create().build(), RwtRepoScript) val address = Utils.getContractAddress(contract, ergoGeneralConfig.addressEncoder) println(s"Watcher repo address is : \t\t\t$address") @@ -60,7 +84,7 @@ class Contracts(ergoGeneralConfig: ErgoNetwork, networkConfig: (Network, MainTok private def generateWatcherPermitContract(): (ErgoContract, String) = { ergoGeneralConfig.ergoClient.execute(ctx => { val commitmentHash = Base64.encode(Utils.getContractScriptHash(Commitment._1)) - val watcherPermitScript = Scripts.WatcherPermitScript + val watcherPermitScript = readScript("Permit.es") .replace("REPO_NFT", Base64.encode(Base16.decode(networkConfig._2.RepoNFT).get)) .replace("COMMITMENT_SCRIPT_HASH", commitmentHash) @@ -74,7 +98,7 @@ class Contracts(ergoGeneralConfig: ErgoNetwork, networkConfig: (Network, MainTok private def generateCommitmentContract(): (ErgoContract, String) = { ergoGeneralConfig.ergoClient.execute(ctx => { val triggerEvent = Base64.encode(Utils.getContractScriptHash(WatcherTriggerEvent._1)) - val commitmentScript = Scripts.CommitmentScript + val commitmentScript = readScript("Commitment.es") .replace("REPO_NFT", Base64.encode(Base16.decode(networkConfig._2.RepoNFT).get)) .replace("EVENT_TRIGGER_SCRIPT_HASH", triggerEvent) @@ -89,7 +113,7 @@ class Contracts(ergoGeneralConfig: ErgoNetwork, networkConfig: (Network, MainTok ergoGeneralConfig.ergoClient.execute(ctx => { val fraud = Base64.encode(Utils.getContractScriptHash(Fraud._1)) val lock = Base64.encode(Utils.getContractScriptHash(Lock._1)) - val triggerScript = Scripts.EventTriggerScript + val triggerScript = readScript("EventTrigger.es") .replace("CLEANUP_NFT", Base64.encode(Base16.decode(networkConfig._1.tokens.CleanupNFT).get)) .replace("LOCK_SCRIPT_HASH", lock) .replace("FRAUD_SCRIPT_HASH", fraud) @@ -104,7 +128,7 @@ class Contracts(ergoGeneralConfig: ErgoNetwork, networkConfig: (Network, MainTok private def generateFraudContract(): (ErgoContract, String) = { ergoGeneralConfig.ergoClient.execute(ctx => { - val fraudScript = Scripts.FraudScript + val fraudScript = readScript("Fraud.es") .replace("CLEANUP_NFT", Base64.encode(Base16.decode(networkConfig._1.tokens.CleanupNFT).get)) .replace("REPO_NFT", Base64.encode(Base16.decode(networkConfig._2.RepoNFT).get)) @@ -117,7 +141,7 @@ class Contracts(ergoGeneralConfig: ErgoNetwork, networkConfig: (Network, MainTok private def generateLockContract(): (ErgoContract, String) = { ergoGeneralConfig.ergoClient.execute(ctx => { - val lockScript = Scripts.LockScript + val lockScript = readScript("Lock.es") .replace("GUARD_NFT", Base64.encode(Base16.decode(networkConfig._2.GuardNFT).get)) val contract = ctx.compileContract(ConstantsBuilder.create().build(), lockScript) @@ -129,7 +153,7 @@ class Contracts(ergoGeneralConfig: ErgoNetwork, networkConfig: (Network, MainTok private def generateGuardSignContract(): (ErgoContract, String) = { ergoGeneralConfig.ergoClient.execute(ctx => { - val guardSignScript = Scripts.GuardSignScript + val guardSignScript = readScript("GuardSign.es") val contract = ctx.compileContract(ConstantsBuilder.create().build(), guardSignScript) val address = Utils.getContractAddress(contract, ergoGeneralConfig.addressEncoder) diff --git a/src/main/scala/rosen/bridge/Scripts.scala b/src/main/scala/rosen/bridge/Scripts.scala deleted file mode 100644 index fa329f4..0000000 --- a/src/main/scala/rosen/bridge/Scripts.scala +++ /dev/null @@ -1,417 +0,0 @@ -package rosen.bridge - -object Scripts { - - lazy val RwtRepoScript: String = - s"""{ - | // ----------------- REGISTERS - | // R4: Coll[Coll[Byte]] = [Chain id, WID_0, WID_1, ...] (Stores Chain id and related watcher ids) - | // R5: Coll[Long] = [0, X-RWT_0, X-RWT_1, ...] (The first element is zero and the rest indicates X-RWT count for watcher i) - | // R6: Coll[Long] = [RSN/X-RWT factor, Watcher quorum percentage, minimum needed approval, maximum needed approval] - | // (Minimum number of commitments needed for an event is: min(R6[3], R6[1] * (len(R4) - 1) / 100 + R6[2]) ) - | // R7: Int = Watcher index (only used in returning or extending permits) - | // ----------------- TOKENS - | // 0: X-RWT Repo NFT - | // 1: X-RWT - | // 2: RSN - | - | val GuardNFT = fromBase64("GUARD_NFT"); - | if(INPUTS(1).tokens(0)._1 == GuardNFT){ - | // RWT Repo Update transaction - | sigmaProp(true) - | } else { - | val permitScriptHash = fromBase64("PERMIT_SCRIPT_HASH"); - | val repoOut = OUTPUTS(0) - | val repo = SELF - | val widListSize = repo.R5[Coll[Long]].get.size - | val widOutListSize = repoOut.R5[Coll[Long]].get.size - | val repoReplication = allOf( - | Coll( - | repoOut.propositionBytes == repo.propositionBytes, - | repoOut.R6[Coll[Long]].get == repo.R6[Coll[Long]].get, - | repoOut.tokens(0)._1 == repo.tokens(0)._1, - | repoOut.tokens(1)._1 == repo.tokens(1)._1, - | repoOut.tokens(2)._1 == repo.tokens(2)._1, - | ) - | ) - | if(repo.tokens(1)._2 > repoOut.tokens(1)._2){ - | // Getting Watcher Permit - | val WIDIndex = repoOut.R7[Int].getOrElse(-1) - | val permit = OUTPUTS(1) - | val outWIDBox = OUTPUTS(2) - | val RWTOut = repo.tokens(1)._2 - repoOut.tokens(1)._2 - | val permitCreation = allOf( - | Coll( - | repoReplication, - | RWTOut * repo.R6[Coll[Long]].get(0) == repoOut.tokens(2)._2 - repo.tokens(2)._2, - | permit.tokens(0)._2 == RWTOut, - | blake2b256(permit.propositionBytes) == permitScriptHash, - | ) - | ) - | if(WIDIndex == -1){ - | // Getting initial permit - | // [Repo, UserInputs] => [Repo, watcherPermit, WIDBox] - | sigmaProp( - | allOf( - | Coll( - | permitCreation, - | repoOut.R4[Coll[Coll[Byte]]].get.size == widListSize + 1, - | repoOut.R4[Coll[Coll[Byte]]].get.slice(0, widOutListSize - 1) == repo.R4[Coll[Coll[Byte]]].get, - | repoOut.R4[Coll[Coll[Byte]]].get(widOutListSize - 1) == repo.id, - | repoOut.R5[Coll[Long]].get.size == widListSize + 1, - | repoOut.R5[Coll[Long]].get.slice(0, widOutListSize - 1) == repo.R5[Coll[Long]].get, - | repoOut.R5[Coll[Long]].get(widOutListSize - 1) == RWTOut, - | permit.R4[Coll[Coll[Byte]]].get == Coll(repo.id), - | outWIDBox.tokens(0)._1 == repo.id, - | ) - | ) - | ) - | } else { - | // Extending Permit - | // [Repo, WIDBox] => [Repo, watcherPermit, WIDBox] - | val WID = repo.R4[Coll[Coll[Byte]]].get(WIDIndex) - | val currentRWT = repo.R5[Coll[Long]].get(WIDIndex) - | val WIDBox = INPUTS(1) - | sigmaProp( - | allOf( - | Coll( - | permitCreation, - | WID == WIDBox.tokens(0)._1, - | repoOut.R4[Coll[Coll[Byte]]].get == repo.R4[Coll[Coll[Byte]]].get, - | repoOut.R5[Coll[Long]].get(WIDIndex) == currentRWT + RWTOut, - | repoOut.R5[Coll[Long]].get.slice(0, WIDIndex) == repo.R5[Coll[Long]].get.slice(0, WIDIndex), - | repoOut.R5[Coll[Long]].get.slice(WIDIndex + 1, widOutListSize) == repo.R5[Coll[Long]].get.slice(WIDIndex + 1, widOutListSize), - | permit.R4[Coll[Coll[Byte]]].get == Coll(WID), - | outWIDBox.tokens(0)._1 == WID, - | ) - | ) - | ) - | } - | }else{ - | // Returning Watcher Permit - | // [repo, Permit, WIDToken] => [repo, Permit(Optional), WIDToken(+userChange)] - | val permit = INPUTS(1) - | val RWTIn = repoOut.tokens(1)._2 - repo.tokens(1)._2 - | val WIDIndex = repoOut.R7[Int].get - | val watcherCount = repo.R5[Coll[Long]].get.size - | val WIDCheckInRepo = if(repo.R5[Coll[Long]].get(WIDIndex) > RWTIn) { - | // Returning some RWTs - | allOf( - | Coll( - | repo.R5[Coll[Long]].get(WIDIndex) == repoOut.R5[Coll[Long]].get(WIDIndex) + RWTIn, - | repo.R4[Coll[Coll[Byte]]].get == repoOut.R4[Coll[Coll[Byte]]].get - | ) - | ) - | }else{ - | // Returning the permit - | allOf( - | Coll( - | repo.R5[Coll[Long]].get(WIDIndex) == RWTIn, - | repo.R4[Coll[Coll[Byte]]].get.slice(0, WIDIndex) == repoOut.R4[Coll[Coll[Byte]]].get.slice(0, WIDIndex), - | repo.R4[Coll[Coll[Byte]]].get.slice(WIDIndex + 1, watcherCount) == repoOut.R4[Coll[Coll[Byte]]].get.slice(WIDIndex, watcherCount - 1), - | repo.R5[Coll[Long]].get.slice(0, WIDIndex) == repoOut.R5[Coll[Long]].get.slice(0, WIDIndex), - | repo.R5[Coll[Long]].get.slice(WIDIndex + 1, watcherCount) == repoOut.R5[Coll[Long]].get.slice(WIDIndex, watcherCount - 1) - | ) - | ) - | } - | val WID = repo.R4[Coll[Coll[Byte]]].get(WIDIndex) - | sigmaProp( - | allOf( - | Coll( - | repoReplication, - | Coll(WID) == permit.R4[Coll[Coll[Byte]]].get, - | RWTIn * repo.R6[Coll[Long]].get(0) == repo.tokens(2)._2 - repoOut.tokens(2)._2, - | WIDCheckInRepo - | ) - | ) - | ) - | } - | } - |}""".stripMargin - - lazy val WatcherPermitScript: String = - s"""{ - | // ----------------- REGISTERS - | // R4: Coll[Coll[Byte]] = [WID] - | // ----------------- TOKENS - | // 0: X-RWT - | - | val repoNFT = fromBase64("REPO_NFT"); - | val commitmentScriptHash = fromBase64("COMMITMENT_SCRIPT_HASH"); - | val WID = SELF.R4[Coll[Coll[Byte]]].get - | val outputWithToken = OUTPUTS.slice(2, OUTPUTS.size).filter { (box: Box) => box.tokens.size > 0 } - | val outputWithRWT = outputWithToken.exists { (box: Box) => box.tokens.exists { (token: (Coll[Byte], Long)) => token._1 == SELF.tokens(0)._1 } } - | val secondBoxHasRWT = OUTPUTS(1).tokens.exists { (token: (Coll[Byte], Long)) => token._1 == SELF.tokens(0)._1 } - | if(OUTPUTS(0).tokens(0)._1 == repoNFT){ - | // Updating Permit (Return or receive more tokens) - | // [Repo, Permit(SELF), WID] => [Repo, Permit(optional), WID(+userChange)] - | val outputPermitCheck = if(secondBoxHasRWT){ - | allOf( - | Coll( - | OUTPUTS(1).tokens(0)._1 == SELF.tokens(0)._1, - | OUTPUTS(1).propositionBytes == SELF.propositionBytes, - | ) - | ) - | }else{ - | true - | } - | sigmaProp( - | allOf( - | Coll( - | outputWithRWT == false, - | INPUTS(2).tokens(0)._1 == WID(0), - | outputPermitCheck, - | ) - | ) - | ) - | }else{ - | // Event Commitment Creation - | // [Permit, WID] => [Permit(Optional), Commitment, WID] - | val outputCommitmentCheck = if(secondBoxHasRWT){ - | allOf( - | Coll( - | OUTPUTS(1).tokens(0)._1 == SELF.tokens(0)._1, - | blake2b256(OUTPUTS(1).propositionBytes) == commitmentScriptHash, - | OUTPUTS(1).R5[Coll[Coll[Byte]]].isDefined, - | OUTPUTS(1).R6[Coll[Byte]].isDefined, - | OUTPUTS(1).R7[Coll[Byte]].get == blake2b256(SELF.propositionBytes), - | OUTPUTS(1).R4[Coll[Coll[Byte]]].get == WID, - | OUTPUTS(1).tokens(0)._2 == 1, - | ) - | ) - | }else{ - | true - | } - | sigmaProp( - | allOf( - | Coll( - | outputWithRWT == false, - | OUTPUTS(0).propositionBytes == SELF.propositionBytes, - | OUTPUTS(0).R4[Coll[Coll[Byte]]].get == WID, - | INPUTS(1).tokens(0)._1 == WID(0), - | outputCommitmentCheck, - | ) - | ) - | ) - | } - |}""".stripMargin - - lazy val CommitmentScript: String = - s"""{ - | // ----------------- REGISTERS - | // R4: Coll[Coll[Byte]] = [WID] - | // R5: Coll[Coll[Byte]] = [Request ID (Hash(TxId))] - | // R6: Coll[Byte] = Event Data Digest - | // R7: Coll[Byte] = Permit Script Digest - | // ----------------- TOKENS - | // 0: X-RWT - | - | val eventTriggerHash = fromBase64("EVENT_TRIGGER_SCRIPT_HASH"); - | val event = if (blake2b256(INPUTS(0).propositionBytes) == eventTriggerHash) INPUTS(0) else OUTPUTS(0) - | val myWID = SELF.R4[Coll[Coll[Byte]]].get - | val WIDs = event.R4[Coll[Coll[Byte]]].get - | val paddedData = if(event.R5[Coll[Coll[Byte]]].isDefined) { - | event.R5[Coll[Coll[Byte]]].get.fold(Coll(0.toByte), { (a: Coll[Byte], b: Coll[Byte]) => a ++ b } ) - | }else{ - | Coll(0.toByte) - | } - | val eventData = paddedData.slice(1, paddedData.size) - | if(blake2b256(INPUTS(0).propositionBytes) == eventTriggerHash){ - | // Reward Distribution (for missed commitments) - | // [EventTrigger, Commitments[], BridgeWallet] => [WatcherPermits[], BridgeWallet] - | val permitBox = OUTPUTS.filter {(box:Box) => - | if(box.R4[Coll[Coll[Byte]]].isDefined) - | box.R4[Coll[Coll[Byte]]].get == myWID - | else false - | }(0) - | val WIDExists = WIDs.exists {(WID: Coll[Byte]) => myWID == Coll(WID)} - | sigmaProp( - | allOf( - | Coll( - | blake2b256(permitBox.propositionBytes) == SELF.R7[Coll[Byte]].get, - | permitBox.tokens(0)._1 == SELF.tokens(0)._1, - | // check for duplicates - | WIDExists == false, - | // validate commitment - | blake2b256(eventData ++ myWID(0)) == SELF.R6[Coll[Byte]].get - | ) - | ) - | ) - | - | } else if (blake2b256(OUTPUTS(0).propositionBytes) == eventTriggerHash){ - | // Event Trigger Creation - | // [Commitments[], WID] + [Repo(DataInput)] => [EventTrigger, WID] - | val commitmentBoxes = INPUTS.filter { (box: Box) => SELF.propositionBytes == box.propositionBytes } - | val myWIDCommitments = commitmentBoxes.filter{ (box: Box) => box.R4[Coll[Coll[Byte]]].get == myWID } - | val EventBoxErgs = commitmentBoxes.map { (box: Box) => box.value }.fold(0L, { (a: Long, b: Long) => a + b }) - | val myWIDExists = WIDs.exists{ (WID: Coll[Byte]) => Coll(WID) == myWID } - | val repo = CONTEXT.dataInputs(0) - | val requestId = if(event.R5[Coll[Coll[Byte]]].isDefined) { - | blake2b256(event.R5[Coll[Coll[Byte]]].get(0)) - | } else { - | Coll(0.toByte) - | } - | val repoR6 = repo.R6[Coll[Long]].get - | val maxCommitment = repoR6(3) - | val requiredCommitmentFromFormula: Long = repoR6(2) + repoR6(1) * (repo.R4[Coll[Coll[Byte]]].get.size - 1L) / 100L - | val requiredCommitment = if(maxCommitment < requiredCommitmentFromFormula) { - | maxCommitment - | } else { - | requiredCommitmentFromFormula - | } - | sigmaProp( - | allOf( - | Coll( - | OUTPUTS(0).value >= EventBoxErgs, - | myWIDCommitments.size == 1, - | myWIDExists, - | event.R6[Coll[Byte]].get == SELF.R7[Coll[Byte]].get, - | WIDs.size == commitmentBoxes.size, - | // TODO verify commitment to be correct - | blake2b256(eventData ++ myWID(0)) == SELF.R6[Coll[Byte]].get, - | // check event id - | SELF.R5[Coll[Coll[Byte]]].get == Coll(requestId), - | // check commitment count - | commitmentBoxes.size > requiredCommitment, - | event.tokens(0)._1 == SELF.tokens(0)._1, - | event.tokens(0)._2 >= SELF.tokens(0)._2 * WIDs.size, - | ) - | ) - | ) - | } else { - | // Commitment Redeem - | // [Commitment, WID] => [Permit, WID] - | sigmaProp( - | allOf( - | Coll( - | SELF.id == INPUTS(0).id, - | OUTPUTS(0).tokens(0)._1 == SELF.tokens(0)._1, - | // check WID copied - | OUTPUTS(0).R4[Coll[Coll[Byte]]].get == myWID, - | // check user WID - | INPUTS(1).tokens(0)._1 == myWID(0), - | // check permit contract address - | blake2b256(OUTPUTS(0).propositionBytes) == SELF.R7[Coll[Byte]].get - | ) - | ) - | ) - | } - |}""".stripMargin - - lazy val EventTriggerScript: String = - s"""{ - | // ----------------- REGISTERS - | // R4: Coll[Coll[Byte]] [WID[]] - | // R5: Coll[Coll[Byte]] Event data - | // R6: Coll[Byte] Permit contract script digest - | // ----------------- TOKENS - | // 0: RWT - | - | // In case of fraud: [TriggerEvent, CleanupToken] => [Fraud1, Fraud2, ...] - | // In case of payment: [TriggerEvent, Commitments(if exists), LockBox](dataInput: GuardNFTBox) => [Permit1, ..., changeBox] - | val cleanupNFT = fromBase64("CLEANUP_NFT"); - | val cleanupConfirmation = CLEANUP_CONFIRMATION; - | val LockScriptHash = fromBase64("LOCK_SCRIPT_HASH"); - | val FraudScriptHash = fromBase64("FRAUD_SCRIPT_HASH"); - | val GuardLockExists = INPUTS.exists { (box: Box) => blake2b256(box.propositionBytes) == LockScriptHash} - | val fraudScriptCheck = if(blake2b256(OUTPUTS(0).propositionBytes) == FraudScriptHash) { - | allOf( - | Coll( - | INPUTS(1).tokens(0)._1 == cleanupNFT, - | HEIGHT - cleanupConfirmation >= SELF.creationInfo._1 - | ) - | ) - | } else { - | allOf( - | Coll( - | GuardLockExists, - | blake2b256(OUTPUTS(0).propositionBytes) == SELF.R6[Coll[Byte]].get - | ) - | ) - | } - | val WIDs: Coll[Coll[Byte]] = SELF.R4[Coll[Coll[Byte]]].get - | val mergeBoxes = OUTPUTS.slice(0, WIDs.size) - | val checkAllWIDs = WIDs.zip(mergeBoxes).forall { - | (data: (Coll[Byte], Box)) => { - | Coll(data._1) == data._2.R4[Coll[Coll[Byte]]].get && data._2.propositionBytes == OUTPUTS(0).propositionBytes - | } - | } - | sigmaProp( - | allOf( - | Coll( - | WIDs.size == mergeBoxes.size, - | checkAllWIDs, - | fraudScriptCheck, - | ) - | ) - | ) - |}""".stripMargin - - lazy val FraudScript: String = - s"""{ - | // ----------------- REGISTERS - | // R4: Coll[Coll[Byte]] = [WID] - | // ----------------- TOKENS - | // 0: RWT - | - | val repoNFT = fromBase64("REPO_NFT"); - | val cleanupNFT = fromBase64("CLEANUP_NFT"); - | val outputWithToken = OUTPUTS.slice(1, OUTPUTS.size).filter { (box: Box) => box.tokens.size > 0 } - | val outputWithRWT = outputWithToken.exists { (box: Box) => box.tokens.exists { (token: (Coll[Byte], Long)) => token._1 == SELF.tokens(0)._1 } } - | // RSN Slash - | // [Repo, Fraud, Cleanup] => [Repo, Cleanup, Slashed] - | sigmaProp( - | allOf( - | Coll( - | outputWithRWT == false, - | INPUTS(0).tokens(0)._1 == repoNFT, - | INPUTS(2).tokens(0)._1 == cleanupNFT, - | ) - | ) - | ) - |}""".stripMargin - - lazy val LockScript: String = - s"""{ - | val GuardNFT = fromBase64("GUARD_NFT"); - | val GuardBox = CONTEXT.dataInputs(0); - | val paymentSignCount = GuardBox.R5[Coll[Int]].get(0); - | val signedColl = GuardBox.R4[Coll[Coll[Byte]]].get.map { (row: Coll[Byte]) => proveDlog(decodePoint(row)) }; - | val verifyGuard = GuardBox.tokens.exists { (token: (Coll[Byte], Long)) => token._1 == GuardNFT }; - | sigmaProp( - | allOf( - | Coll( - | verifyGuard, - | atLeast(paymentSignCount, signedColl) - | ) - | ) - | ) - |}""".stripMargin - - lazy val GuardSignScript: String = - s"""{ - | // ----------------- REGISTERS - | // R4: Coll[Coll[Byte]] = [GUARD_PKS] - | // R5: Coll[Int] = [Payment Required Signs, Update Required Signs] - | // ----------------- TOKENS - | // 0: GuardNFT - | // -------------------- - | // [GuardSign (sign atleast update required sign on it)] => [GuardSign] - | val guardBoxes = INPUTS.filter { (box: Box) => box.tokens.size > 0 && box.tokens(0)._1 == SELF.tokens(0)._1 } - | val signedColl = SELF.R4[Coll[Coll[Byte]]].get.map { (row: Coll[Byte]) => proveDlog(decodePoint(row)) } - | val updateSignCount = SELF.R5[Coll[Int]].get(1) - | val updateSignLeast = atLeast(updateSignCount, signedColl) - | sigmaProp( - | allOf( - | Coll( - | guardBoxes.size == 1, - | OUTPUTS(0).propositionBytes == SELF.propositionBytes, - | OUTPUTS(0).tokens(0)._1 == SELF.tokens(0)._1, - | OUTPUTS(0).tokens(0)._2 == SELF.tokens(0)._2, - | updateSignLeast - | ) - | ) - | ) - |}""".stripMargin - -} \ No newline at end of file diff --git a/src/main/scala/rosen/bridge/scripts/Commitment.es b/src/main/scala/rosen/bridge/scripts/Commitment.es new file mode 100644 index 0000000..a66ca74 --- /dev/null +++ b/src/main/scala/rosen/bridge/scripts/Commitment.es @@ -0,0 +1,100 @@ +{ + // ----------------- REGISTERS + // R4: Coll[Coll[Byte]] = [WID] + // R5: Coll[Coll[Byte]] = [Request ID (Hash(TxId))] + // R6: Coll[Byte] = Event Data Digest + // R7: Coll[Byte] = Permit Script Digest + // ----------------- TOKENS + // 0: X-RWT + + val eventTriggerHash = fromBase64("EVENT_TRIGGER_SCRIPT_HASH"); + val repoNFT = fromBase64("REPO_NFT"); + val event = if (blake2b256(INPUTS(0).propositionBytes) == eventTriggerHash) INPUTS(0) else OUTPUTS(0) + val myWID = SELF.R4[Coll[Coll[Byte]]].get + val WIDs = event.R4[Coll[Coll[Byte]]].get + val paddedData = event.R5[Coll[Coll[Byte]]].get.fold(Coll(0.toByte), { (a: Coll[Byte], b: Coll[Byte]) => a ++ b } ) + val eventData = paddedData.slice(1, paddedData.size) + if(blake2b256(INPUTS(0).propositionBytes) == eventTriggerHash){ + // Reward Distribution (for missed commitments) + // [EventTrigger, Commitments[], BridgeWallet] => [WatcherPermits[], BridgeWallet] + val permitBox = OUTPUTS.filter {(box:Box) => + box.R4[Coll[Coll[Byte]]].isDefined && + box.R4[Coll[Coll[Byte]]].get == myWID + }(0) + val WIDExists = WIDs.exists {(WID: Coll[Byte]) => myWID == Coll(WID)} + sigmaProp( + allOf( + Coll( + blake2b256(permitBox.propositionBytes) == SELF.R7[Coll[Byte]].get, + permitBox.tokens(0)._1 == SELF.tokens(0)._1, + permitBox.tokens(0)._2 == SELF.tokens(0)._2, + // check for duplicates + WIDExists == false, + // validate commitment + blake2b256(eventData ++ myWID(0)) == SELF.R6[Coll[Byte]].get + ) + ) + ) + + } else if (blake2b256(OUTPUTS(0).propositionBytes) == eventTriggerHash){ + // Event Trigger Creation + // [Commitments[]] + [Repo(DataInput)] => [EventTrigger] + val commitmentBoxes = INPUTS.filter { (box: Box) => SELF.propositionBytes == box.propositionBytes } + val myWIDCommitments = commitmentBoxes.filter{ (box: Box) => box.R4[Coll[Coll[Byte]]].get == myWID } + val EventBoxErgs = commitmentBoxes.map { (box: Box) => box.value }.fold(0L, { (a: Long, b: Long) => a + b }) + val myWIDExists = WIDs.exists{ (WID: Coll[Byte]) => Coll(WID) == myWID } + val repo = CONTEXT.dataInputs(0) + val requestId = blake2b256(event.R5[Coll[Coll[Byte]]].get(0)) + val repoR6 = repo.R6[Coll[Long]].get + val maxCommitment = repoR6(3) + val requiredCommitmentFromFormula: Long = repoR6(2) + repoR6(1) * (repo.R4[Coll[Coll[Byte]]].get.size - 1L) / 100L + val requiredCommitment = if(maxCommitment < requiredCommitmentFromFormula) { + maxCommitment + } else { + requiredCommitmentFromFormula + } + sigmaProp( + allOf( + Coll( + //check repo + repo.tokens(0)._1 == repoNFT, + repo.tokens(1)._1 == SELF.tokens(0)._1, + + OUTPUTS(0).value >= EventBoxErgs, + myWIDCommitments.size == 1, + myWIDExists, + event.R6[Coll[Byte]].get == SELF.R7[Coll[Byte]].get, + WIDs.size == commitmentBoxes.size, + // verify commitment to be correct + blake2b256(eventData ++ myWID(0)) == SELF.R6[Coll[Byte]].get, + // check event id + SELF.R5[Coll[Coll[Byte]]].get == Coll(requestId), + // check commitment count + commitmentBoxes.size > requiredCommitment, + // Check required RWT + SELF.tokens(0)._2 == repoR6(0), + event.tokens(0)._2 == repoR6(0) * commitmentBoxes.size, + event.tokens(0)._1 == SELF.tokens(0)._1 + ) + ) + ) + } else { + // Commitment Redeem + // [Commitment, WID] => [Permit, WID] + sigmaProp( + allOf( + Coll( + SELF.id == INPUTS(0).id, + OUTPUTS(0).tokens(0)._1 == SELF.tokens(0)._1, + OUTPUTS(0).tokens(0)._2 == SELF.tokens(0)._2, + // check WID copied + OUTPUTS(0).R4[Coll[Coll[Byte]]].get == myWID, + // check user WID + INPUTS(1).tokens(0)._1 == myWID(0), + // check permit contract address + blake2b256(OUTPUTS(0).propositionBytes) == SELF.R7[Coll[Byte]].get + ) + ) + ) + } +} diff --git a/src/main/scala/rosen/bridge/scripts/EventTrigger.es b/src/main/scala/rosen/bridge/scripts/EventTrigger.es new file mode 100644 index 0000000..12b2969 --- /dev/null +++ b/src/main/scala/rosen/bridge/scripts/EventTrigger.es @@ -0,0 +1,50 @@ +{ + // ----------------- REGISTERS + // R4: Coll[Coll[Byte]] [WID[]] + // R5: Coll[Coll[Byte]] Event data + // R6: Coll[Byte] Permit contract script digest + // ----------------- TOKENS + // 0: RWT + + // In case of fraud: [TriggerEvent, CleanupToken] => [Fraud1, Fraud2, ...] + // In case of payment: [TriggerEvent, Commitments(if exists), LockBox](dataInput: GuardNFTBox) => [Permit1, ..., changeBox] + val cleanupNFT = fromBase64("CLEANUP_NFT"); + val cleanupConfirmation = CLEANUP_CONFIRMATION; + val LockScriptHash = fromBase64("LOCK_SCRIPT_HASH"); + val FraudScriptHash = fromBase64("FRAUD_SCRIPT_HASH"); + val GuardLockExists = INPUTS.exists { (box: Box) => blake2b256(box.propositionBytes) == LockScriptHash} + val fraudScriptCheck = if(blake2b256(OUTPUTS(0).propositionBytes) == FraudScriptHash) { + allOf( + Coll( + INPUTS(1).tokens(0)._1 == cleanupNFT, + HEIGHT - cleanupConfirmation >= SELF.creationInfo._1 + ) + ) + } else { + allOf( + Coll( + GuardLockExists, + blake2b256(OUTPUTS(0).propositionBytes) == SELF.R6[Coll[Byte]].get + ) + ) + } + val WIDs: Coll[Coll[Byte]] = SELF.R4[Coll[Coll[Byte]]].get + val mergeBoxes = OUTPUTS.slice(0, WIDs.size) + val checkAllWIDs = WIDs.zip(mergeBoxes).forall { + (data: (Coll[Byte], Box)) => { + Coll(data._1) == data._2.R4[Coll[Coll[Byte]]].get && + data._2.propositionBytes == OUTPUTS(0).propositionBytes && + data._2.tokens(0)._1 == SELF.tokens(0)._1 && + data._2.tokens(0)._2 == SELF.tokens(0)._2 / WIDs.size + } + } + sigmaProp( + allOf( + Coll( + WIDs.size == mergeBoxes.size, + checkAllWIDs, + fraudScriptCheck, + ) + ) + ) +} diff --git a/src/main/scala/rosen/bridge/scripts/Fraud.es b/src/main/scala/rosen/bridge/scripts/Fraud.es new file mode 100644 index 0000000..4bf7fbe --- /dev/null +++ b/src/main/scala/rosen/bridge/scripts/Fraud.es @@ -0,0 +1,23 @@ +{ + // ----------------- REGISTERS + // R4: Coll[Coll[Byte]] = [WID] + // ----------------- TOKENS + // 0: RWT + + val repoNFT = fromBase64("REPO_NFT"); + val cleanupNFT = fromBase64("CLEANUP_NFT"); + val outputWithToken = OUTPUTS.slice(1, OUTPUTS.size).filter { (box: Box) => box.tokens.size > 0 } + val outputWithRWT = outputWithToken.exists { (box: Box) => box.tokens.exists { (token: (Coll[Byte], Long)) => token._1 == SELF.tokens(0)._1 } } + // RSN Slash + // [Repo, Fraud, Cleanup] => [Repo, Cleanup, Slashed] + sigmaProp( + allOf( + Coll( + outputWithRWT == false, + SELF.id == INPUTS(1).id, + INPUTS(0).tokens(0)._1 == repoNFT, + INPUTS(2).tokens(0)._1 == cleanupNFT, + ) + ) + ) +} diff --git a/src/main/scala/rosen/bridge/scripts/GuardSign.es b/src/main/scala/rosen/bridge/scripts/GuardSign.es new file mode 100644 index 0000000..2367365 --- /dev/null +++ b/src/main/scala/rosen/bridge/scripts/GuardSign.es @@ -0,0 +1,23 @@ +{ + // ----------------- REGISTERS + // R4: Coll[Coll[Byte]] = [GUARD_PKS] + // R5: Coll[Int] = [Payment Required Signs, Update Required Signs] + // ----------------- TOKENS + // 0: GuardNFT + // -------------------- + // [GuardSign (sign atleast update required sign on it)] => [GuardSign] + val guardBoxes = INPUTS.filter { (box: Box) => box.tokens.size > 0 && box.tokens(0)._1 == SELF.tokens(0)._1 } + val signedColl = SELF.R4[Coll[Coll[Byte]]].get.map { (row: Coll[Byte]) => proveDlog(decodePoint(row)) } + val updateSignCount = SELF.R5[Coll[Int]].get(1) + val updateSignLeast = atLeast(updateSignCount, signedColl) + sigmaProp( + allOf( + Coll( + guardBoxes.size == 1, + OUTPUTS(0).tokens(0)._1 == SELF.tokens(0)._1, + OUTPUTS(0).tokens(0)._2 == SELF.tokens(0)._2, + updateSignLeast + ) + ) + ) +} diff --git a/src/main/scala/rosen/bridge/scripts/Lock.es b/src/main/scala/rosen/bridge/scripts/Lock.es new file mode 100644 index 0000000..2f1dcfb --- /dev/null +++ b/src/main/scala/rosen/bridge/scripts/Lock.es @@ -0,0 +1,15 @@ +{ + val GuardNFT = fromBase64("GUARD_NFT"); + val GuardBox = CONTEXT.dataInputs(0); + val paymentSignCount = GuardBox.R5[Coll[Int]].get(0); + val signedColl = GuardBox.R4[Coll[Coll[Byte]]].get.map { (row: Coll[Byte]) => proveDlog(decodePoint(row)) }; + val verifyGuard = GuardBox.tokens.exists { (token: (Coll[Byte], Long)) => token._1 == GuardNFT }; + sigmaProp( + allOf( + Coll( + verifyGuard, + atLeast(paymentSignCount, signedColl) + ) + ) + ) +} diff --git a/src/main/scala/rosen/bridge/scripts/Permit.es b/src/main/scala/rosen/bridge/scripts/Permit.es new file mode 100644 index 0000000..43f4767 --- /dev/null +++ b/src/main/scala/rosen/bridge/scripts/Permit.es @@ -0,0 +1,64 @@ +{ + // ----------------- REGISTERS + // R4: Coll[Coll[Byte]] = [WID] + // ----------------- TOKENS + // 0: X-RWT + + val repoNFT = fromBase64("REPO_NFT"); + val commitmentScriptHash = fromBase64("COMMITMENT_SCRIPT_HASH"); + val WID = SELF.R4[Coll[Coll[Byte]]].get + val outputWithToken = OUTPUTS.slice(2, OUTPUTS.size).filter { (box: Box) => box.tokens.size > 0 } + val outputWithRWT = outputWithToken.exists { (box: Box) => box.tokens.exists { (token: (Coll[Byte], Long)) => token._1 == SELF.tokens(0)._1 } } + val secondBoxHasRWT = OUTPUTS(1).tokens.exists { (token: (Coll[Byte], Long)) => token._1 == SELF.tokens(0)._1 } + if(OUTPUTS(0).tokens(0)._1 == repoNFT){ + // Updating Permit (Return or receive more tokens) + // [Repo, Permit(SELF), WID] => [Repo, Permit(optional), WID(+userChange)] + val outputPermitCheck = if(secondBoxHasRWT){ + allOf( + Coll( + SELF.id == INPUTS(1).id, + OUTPUTS(1).tokens(0)._1 == SELF.tokens(0)._1, + OUTPUTS(1).propositionBytes == SELF.propositionBytes, + SELF.R4[Coll[Coll[Byte]]].get == OUTPUTS(1).R4[Coll[Coll[Byte]]].get + ) + ) + }else{ + true + } + sigmaProp( + allOf( + Coll( + outputWithRWT == false, + INPUTS(2).tokens(0)._1 == WID(0), + outputPermitCheck, + ) + ) + ) + }else{ + // Event Commitment Creation + // [Permit(s), WID] => [Permit, Commitment, WID] + val totalPermits = INPUTS.filter{(box:Box) + => box.tokens.size > 0 && box.tokens(0)._1 == SELF.tokens(0)._1 + } + .map{(box:Box) => box.tokens(0)._2} + .fold(0L, { (a: Long, b: Long) => a + b }) + sigmaProp( + allOf( + Coll( + OUTPUTS(0).tokens(0)._1 == SELF.tokens(0)._1, + OUTPUTS(1).tokens(0)._2 == totalPermits - OUTPUTS(0).tokens(0)._2, + OUTPUTS(1).tokens(0)._1 == SELF.tokens(0)._1, + blake2b256(OUTPUTS(1).propositionBytes) == commitmentScriptHash, + OUTPUTS(1).R5[Coll[Coll[Byte]]].isDefined, + OUTPUTS(1).R6[Coll[Byte]].isDefined, + OUTPUTS(1).R7[Coll[Byte]].get == blake2b256(SELF.propositionBytes), + OUTPUTS(1).R4[Coll[Coll[Byte]]].get == WID, + outputWithRWT == false, + OUTPUTS(0).propositionBytes == SELF.propositionBytes, + OUTPUTS(0).R4[Coll[Coll[Byte]]].get == WID, + OUTPUTS(2).tokens(0)._1 == WID(0), + ) + ) + ) + } +} diff --git a/src/main/scala/rosen/bridge/scripts/RwtRepo.es b/src/main/scala/rosen/bridge/scripts/RwtRepo.es new file mode 100644 index 0000000..6a41e03 --- /dev/null +++ b/src/main/scala/rosen/bridge/scripts/RwtRepo.es @@ -0,0 +1,148 @@ +{ + // ----------------- REGISTERS + // R4: Coll[Coll[Byte]] = [Chain id, WID_0, WID_1, ...] (Stores Chain id and related watcher ids) + // R5: Coll[Long] = [0, X-RWT_0, X-RWT_1, ...] (The first element is zero and the rest indicates X-RWT count for watcher i) + // R6: Coll[Long] = [Commitment RWT count, Watcher quorum percentage, minimum needed approval, maximum needed approval, Collateral Erg amount, Collateral Rsn Amount] + // (Minimum number of commitments needed for an event is: min(R6[3], R6[1] * (len(R4) - 1) / 100 + R6[2]) ) + // R7: Int = Watcher index (only used in returning or extending permits) + // ----------------- TOKENS + // 0: X-RWT Repo NFT + // 1: X-RWT + // 2: RSN + + val GuardNFT = fromBase64("GUARD_NFT"); + val watcherCollateralScriptHash = fromBase64("WATCHER_COLLATERAL_SCRIPT_HASH"); + if(OUTPUTS(0).tokens(0)._1 == GuardNFT){ + // RWT Repo Update transaction + sigmaProp(true) + } else { + val permitScriptHash = fromBase64("PERMIT_SCRIPT_HASH"); + val repoOut = OUTPUTS(0) + val repo = SELF + val widListSize = repo.R5[Coll[Long]].get.size + val widOutListSize = repoOut.R5[Coll[Long]].get.size + val repoReplication = allOf( + Coll( + repoOut.propositionBytes == repo.propositionBytes, + repoOut.R6[Coll[Long]].get == repo.R6[Coll[Long]].get, + repoOut.tokens(0)._1 == repo.tokens(0)._1, + repoOut.tokens(0)._2 == repo.tokens(0)._2, + repoOut.tokens(1)._1 == repo.tokens(1)._1, + repoOut.tokens(2)._1 == repo.tokens(2)._1, + repoOut.R4[Coll[Coll[Byte]]].get.size == repoOut.R5[Coll[Long]].get.size, + ) + ) + if(repo.tokens(1)._2 > repoOut.tokens(1)._2){ + // Getting Watcher Permit + val WIDIndex = repoOut.R7[Int].getOrElse(-1) + val permit = OUTPUTS(1) + val outWIDBox = OUTPUTS(2) + val RWTOut = repo.tokens(1)._2 - repoOut.tokens(1)._2 + val permitCreation = allOf( + Coll( + repoReplication, + RWTOut == repoOut.tokens(2)._2 - repo.tokens(2)._2, + permit.tokens(0)._2 == RWTOut, + permit.tokens(0)._1 == SELF.tokens(1)._1, + blake2b256(permit.propositionBytes) == permitScriptHash, + ) + ) + if(WIDIndex == -1){ + // Getting initial permit + // [Repo, UserInputs] => [Repo, watcherPermit, WIDBox, watcherCollateral] + val watcherCollateral = OUTPUTS(3) + sigmaProp( + allOf( + Coll( + permitCreation, + widOutListSize == widListSize + 1, + repoOut.R4[Coll[Coll[Byte]]].get.slice(0, widOutListSize - 1) == repo.R4[Coll[Coll[Byte]]].get, + repoOut.R4[Coll[Coll[Byte]]].get(widOutListSize - 1) == repo.id, + repoOut.R5[Coll[Long]].get.slice(0, widOutListSize - 1) == repo.R5[Coll[Long]].get, + repoOut.R5[Coll[Long]].get(widOutListSize - 1) == RWTOut, + permit.R4[Coll[Coll[Byte]]].get == Coll(repo.id), + outWIDBox.tokens(0)._1 == repo.id, + blake2b256(watcherCollateral.propositionBytes) == watcherCollateralScriptHash, + watcherCollateral.R4[Coll[Byte]].get == repo.id, + watcherCollateral.value >= repo.R6[Coll[Long]].get(4), + if(repo.R6[Coll[Long]].get(5) > 0){ + allOf( + Coll( + watcherCollateral.tokens(0)._1 == repo.tokens(2)._1, + watcherCollateral.tokens(0)._2 >= repo.R6[Coll[Long]].get(5) + ) + ) + }else{ + true + } + ) + ) + ) + } else { + // Extending Permit + // [Repo, WIDBox] => [Repo, watcherPermit, WIDBox] + val WID = repo.R4[Coll[Coll[Byte]]].get(WIDIndex) + val currentRWT = repo.R5[Coll[Long]].get(WIDIndex) + val WIDBox = INPUTS(1) + sigmaProp( + allOf( + Coll( + permitCreation, + WID == WIDBox.tokens(0)._1, + repoOut.R4[Coll[Coll[Byte]]].get == repo.R4[Coll[Coll[Byte]]].get, + repoOut.R5[Coll[Long]].get(WIDIndex) == currentRWT + RWTOut, + repoOut.R5[Coll[Long]].get.slice(0, WIDIndex) == repo.R5[Coll[Long]].get.slice(0, WIDIndex), + repoOut.R5[Coll[Long]].get.slice(WIDIndex + 1, widOutListSize) == repo.R5[Coll[Long]].get.slice(WIDIndex + 1, widOutListSize), + permit.R4[Coll[Coll[Byte]]].get == Coll(WID), + outWIDBox.tokens(0)._1 == WID, + ) + ) + ) + } + }else{ + // Returning Watcher Permit + val permit = INPUTS(1) + val RWTIn = repoOut.tokens(1)._2 - repo.tokens(1)._2 + val WIDIndex = repoOut.R7[Int].get + val WIDCheckInRepo = if(repo.R5[Coll[Long]].get(WIDIndex) > RWTIn) { + // Returning some RWTs + // [repo, Permit, WIDToken] => [repo, Permit(Optional), WIDToken(+userChange)] + // [repo, Fraud, Cleanup] => [repo, Cleanup] + allOf( + Coll( + repo.R5[Coll[Long]].get(WIDIndex) == repoOut.R5[Coll[Long]].get(WIDIndex) + RWTIn, + repo.R4[Coll[Coll[Byte]]].get == repoOut.R4[Coll[Coll[Byte]]].get, + repo.R5[Coll[Long]].get.slice(0, WIDIndex) == repoOut.R5[Coll[Long]].get.slice(0, WIDIndex), + repo.R5[Coll[Long]].get.slice(WIDIndex + 1, widListSize) == repoOut.R5[Coll[Long]].get.slice(WIDIndex + 1, widListSize) + ) + ) + }else{ + // Returning the permit + // [repo, Permit, WIDToken, watcherCollateral] => [repo, WIDToken(+userChange)] + val watcherCollateral = INPUTS(3); + allOf( + Coll( + widOutListSize == widListSize - 1, + repo.R5[Coll[Long]].get(WIDIndex) == RWTIn, + repo.R4[Coll[Coll[Byte]]].get.slice(0, WIDIndex) == repoOut.R4[Coll[Coll[Byte]]].get.slice(0, WIDIndex), + repo.R4[Coll[Coll[Byte]]].get.slice(WIDIndex + 1, widListSize) == repoOut.R4[Coll[Coll[Byte]]].get.slice(WIDIndex, widOutListSize), + repo.R5[Coll[Long]].get.slice(0, WIDIndex) == repoOut.R5[Coll[Long]].get.slice(0, WIDIndex), + repo.R5[Coll[Long]].get.slice(WIDIndex + 1, widListSize) == repoOut.R5[Coll[Long]].get.slice(WIDIndex, widOutListSize), + blake2b256(watcherCollateral.propositionBytes) == watcherCollateralScriptHash, + ) + ) + } + val WID = repo.R4[Coll[Coll[Byte]]].get(WIDIndex) + sigmaProp( + allOf( + Coll( + repoReplication, + Coll(WID) == permit.R4[Coll[Coll[Byte]]].get, + RWTIn == repo.tokens(2)._2 - repoOut.tokens(2)._2, + WIDCheckInRepo + ) + ) + ) + } + } +} diff --git a/src/main/scala/rosen/bridge/scripts/WatcherCollateral.es b/src/main/scala/rosen/bridge/scripts/WatcherCollateral.es new file mode 100644 index 0000000..16fc906 --- /dev/null +++ b/src/main/scala/rosen/bridge/scripts/WatcherCollateral.es @@ -0,0 +1,22 @@ +{ + // ----------------- REGISTERS + // R4: Coll[Byte] = Owner WID + // ----------------- TOKENS + // [repo, Permit, WIDToken, Collateral] => [repo, WIDToken(+userChange)] + val repoNFT = fromBase64("REPO_NFT"); + val repo = INPUTS(0); + val repoOut = OUTPUTS(0); + val widBox = INPUTS(2); + val watcherIndex = repoOut.R7[Int].getOrElse(-1); + val watcherWID = SELF.R4[Coll[Byte]].get; + sigmaProp( + allOf( + Coll( + widBox.tokens(0)._1 == watcherWID, + repo.R4[Coll[Coll[Byte]]].get(watcherIndex) == watcherWID, + repoOut.R4[Coll[Coll[Byte]]].get.size == watcherIndex || repoOut.R4[Coll[Coll[Byte]]].get(watcherIndex) != watcherWID, + repo.tokens(0)._1 == repoNFT + ) + ) + ) +} diff --git a/src/test/scala/contracts/ContractTest.scala b/src/test/scala/contracts/ContractTest.scala new file mode 100644 index 0000000..d06a8f8 --- /dev/null +++ b/src/test/scala/contracts/ContractTest.scala @@ -0,0 +1,1110 @@ +package contracts + +import helpers.{Configs, ErgoNetwork, MainTokens, Network, Utils} +import org.ergoplatform.appkit.{ErgoProver, ErgoToken} +import rosen.bridge.Contracts +import scorex.util.encode.Base16 +import testUtils.{Boxes, Commitment, TestSuite} +import java.io.{PrintWriter, StringWriter} + + +class ContractTest extends TestSuite { + val sk: BigInt = Utils.randBigInt + + val networkConfig: (ErgoNetwork, Network, MainTokens) = Utils.selectConfig("cardano", "mainnet") + val contracts = new Contracts(networkConfig._1, (networkConfig._2, networkConfig._3)) + + def getProver(): ErgoProver = { + networkConfig._1.ergoClient.execute(ctx => { + ctx.newProverBuilder().withDLogSecret(sk.bigInteger).build() + }) + } + + def generateRandomWIDList(count: Int): Seq[Array[Byte]] = { + var res: Seq[Array[Byte]] = Seq() + while (count > res.length) { + res = res ++ Seq(Base16.decode(Boxes.getRandomHexString()).get) + } + res + } + + property("test get permit when there is no other permit on network") { + networkConfig._1.ergoClient.execute(ctx => { + try { + val prover = getProver() + val userBox = Boxes.createBoxForUser(ctx, prover.getAddress, 2e9.toLong, new ErgoToken(networkConfig._3.RSN, 200L)) + val repoBox = Boxes.createRepo(ctx, 100000, 1L, Seq(), Seq()).convertToInputWith(Boxes.getRandomHexString(), 0) + val repoOut = Boxes.createRepo(ctx, 99900, 101L, Seq(repoBox.getId.getBytes), Seq(100L)) + val permitBox = Boxes.createPermitBox(ctx, 100L, repoBox.getId.getBytes) + val WID = Boxes.createBoxCandidateForUser(ctx, prover.getAddress, Configs.minBoxValue, new ErgoToken(repoBox.getId.getBytes, 1L)) + val watcherCollateral = Boxes.createWatcherCollateralBox(ctx,1e9.toLong, 100, repoBox.getId.getBytes) + val tx = ctx.newTxBuilder().addInputs(repoBox, userBox) + .fee(Configs.fee) + .addOutputs(repoOut, permitBox, WID, watcherCollateral) + .sendChangeTo(prover.getAddress) + .build() + prover.sign(tx) + } catch { + case exp: Throwable => + println(exp.toString) + fail("transaction not signed") + } + }) + } + + property("test get permit when there is another permit on network") { + networkConfig._1.ergoClient.execute(ctx => { + try { + val prover = getProver() + val userBox = Boxes.createBoxForUser(ctx, prover.getAddress, 2e9.toLong, new ErgoToken(networkConfig._3.RSN, 200L)) + val otherWID = Base16.decode(Boxes.getRandomHexString()).get + val repoBox = Boxes.createRepo(ctx, 100000, 5801L, Seq(otherWID), Seq(5800L)).convertToInputWith(Boxes.getRandomHexString(), 0) + val repoOut = Boxes.createRepo(ctx, 99900, 5901L, Seq(otherWID, repoBox.getId.getBytes), Seq(5800L, 100L)) + val permitBox = Boxes.createPermitBox(ctx, 100L, repoBox.getId.getBytes) + val WID = Boxes.createBoxCandidateForUser(ctx, prover.getAddress, Configs.minBoxValue, new ErgoToken(repoBox.getId.getBytes, 1L)) + val watcherCollateral = Boxes.createWatcherCollateralBox(ctx,1e9.toLong, 100, repoBox.getId.getBytes) + val tx = ctx.newTxBuilder().addInputs(repoBox, userBox) + .fee(Configs.fee) + .addOutputs(repoOut, permitBox, WID, watcherCollateral) + .sendChangeTo(prover.getAddress) + .build() + prover.sign(tx) + } catch { + case exp: Throwable => + fail("transaction not signed") + } + }) + } + + property("test get permit while created permit first token is not RWT") { + networkConfig._1.ergoClient.execute(ctx => { + assertThrows[AnyRef] { + val prover = getProver() + val userBox = Boxes.createBoxForUser(ctx, prover.getAddress, 2e9.toLong, new ErgoToken(networkConfig._3.RSN, 300L)) + val otherWID = Base16.decode(Boxes.getRandomHexString()).get + val repoBox = Boxes.createRepo(ctx, 100000, 5801L, Seq(otherWID), Seq(5800L)).convertToInputWith(Boxes.getRandomHexString(), 0) + val repoOut = Boxes.createRepo(ctx, 99900, 5901L, Seq(otherWID, repoBox.getId.getBytes), Seq(5800L, 100L)) + val permitBox = Boxes.createInvalidPermitBox(ctx, 100L, repoBox.getId.getBytes) + val WID = Boxes.createBoxCandidateForUser(ctx, prover.getAddress, Configs.minBoxValue, new ErgoToken(repoBox.getId.getBytes, 1L)) + val watcherCollateral = Boxes.createWatcherCollateralBox(ctx, 1e9.toLong, 100, repoBox.getId.getBytes) + val tx = ctx.newTxBuilder().addInputs(repoBox, userBox) + .fee(Configs.fee) + .addOutputs(repoOut, permitBox, WID, watcherCollateral) + .sendChangeTo(prover.getAddress) + .build() + val signedTx = prover.sign(tx) + println(signedTx.toJson(false)) + } + }) + } + + property("test extend permit when there is just one permit on network") { + networkConfig._1.ergoClient.execute(ctx => { + try { + val prover = getProver() + val WID = Base16.decode(Boxes.getRandomHexString()).get + val userBox = Boxes.createBoxForUser(ctx, prover.getAddress, 1e9.toLong, new ErgoToken(WID, 1L), new ErgoToken(networkConfig._3.RSN, 100L)) + val repoBox = Boxes.createRepo(ctx, 100000, 5801L, Seq(WID), Seq(5800L)).convertToInputWith(Boxes.getRandomHexString(), 0) + val repoOut = Boxes.createRepoWithR7(ctx, 99900, 5901L, Seq(WID), Seq(5900L), 1) + val permitBox = Boxes.createPermitBox(ctx, 100L, WID) + val WIDBox = Boxes.createBoxCandidateForUser(ctx, prover.getAddress, Configs.minBoxValue, new ErgoToken(WID, 1L)) + val tx = ctx.newTxBuilder().addInputs(repoBox, userBox) + .fee(Configs.fee) + .addOutputs(repoOut, permitBox, WIDBox) + .sendChangeTo(prover.getAddress) + .build() + prover.sign(tx) + } catch { + case exp: Throwable => + println(exp.toString) + fail("transaction not signed") + } + }) + } + + property("test extend first permit when there is other permits on network") { + networkConfig._1.ergoClient.execute(ctx => { + try { + val prover = getProver() + val WID = Base16.decode(Boxes.getRandomHexString()).get + val otherWID = Base16.decode(Boxes.getRandomHexString()).get + val userBox = Boxes.createBoxForUser(ctx, prover.getAddress, 1e9.toLong, new ErgoToken(WID, 1L), new ErgoToken(networkConfig._3.RSN, 100L)) + val repoBox = Boxes.createRepo(ctx, 100000, 6459L, Seq(WID, otherWID), Seq(58L, 6400L)).convertToInputWith(Boxes.getRandomHexString(), 0) + val repoOut = Boxes.createRepoWithR7(ctx, 99900, 6559L, Seq(WID, otherWID), Seq(158L, 6400L), 1) + val permitBox = Boxes.createPermitBox(ctx, 100L, WID) + val WIDBox = Boxes.createBoxCandidateForUser(ctx, prover.getAddress, Configs.minBoxValue, new ErgoToken(WID, 1L)) + val tx = ctx.newTxBuilder().addInputs(repoBox, userBox) + .fee(Configs.fee) + .addOutputs(repoOut, permitBox, WIDBox) + .sendChangeTo(prover.getAddress) + .build() + prover.sign(tx) + } catch { + case exp: Throwable => + println(exp.toString) + fail("transaction not signed") + } + }) + } + + property("test extend middle permit when there is other permits on network") { + networkConfig._1.ergoClient.execute(ctx => { + try { + val prover = getProver() + val WID = Base16.decode(Boxes.getRandomHexString()).get + val otherWID = Base16.decode(Boxes.getRandomHexString()).get + val otherWID2 = Base16.decode(Boxes.getRandomHexString()).get + val userBox = Boxes.createBoxForUser(ctx, prover.getAddress, 1e9.toLong, new ErgoToken(WID, 1L), new ErgoToken(networkConfig._3.RSN, 100L)) + val repoBox = Boxes.createRepo(ctx, 100000, 9659L, Seq(otherWID, WID, otherWID2), Seq(3200L, 58L, 6400L)).convertToInputWith(Boxes.getRandomHexString(), 0) + val repoOut = Boxes.createRepoWithR7(ctx, 99900, 9759L, Seq(otherWID, WID, otherWID2), Seq(3200L, 158L, 6400L), 2) + val permitBox = Boxes.createPermitBox(ctx, 100L, WID) + val WIDBox = Boxes.createBoxCandidateForUser(ctx, prover.getAddress, Configs.minBoxValue, new ErgoToken(WID, 1L)) + val tx = ctx.newTxBuilder().addInputs(repoBox, userBox) + .fee(Configs.fee) + .addOutputs(repoOut, permitBox, WIDBox) + .sendChangeTo(prover.getAddress) + .build() + prover.sign(tx) + } catch { + case exp: Throwable => + println(exp.toString) + fail("transaction not signed") + } + }) + } + + property("test extend first permit while extended permit wid has changed") { + networkConfig._1.ergoClient.execute(ctx => { + assertThrows[AnyRef] { + val prover = getProver() + val WID = Base16.decode(Boxes.getRandomHexString()).get + val otherWID = Base16.decode(Boxes.getRandomHexString()).get + val userBox = Boxes.createBoxForUser(ctx, prover.getAddress, 1e9.toLong, new ErgoToken(WID, 1L), new ErgoToken(networkConfig._3.RSN, 100L)) + val repoBox = Boxes.createRepo(ctx, 100000, 6459L, Seq(WID, otherWID), Seq(58L, 6400L)).convertToInputWith(Boxes.getRandomHexString(), 0) + val repoOut = Boxes.createRepoWithR7(ctx, 99900, 6559L, Seq(WID, otherWID), Seq(158L, 6400L), 1) + val permitBox = Boxes.createPermitBox(ctx, 100L, otherWID) + val WIDBox = Boxes.createBoxCandidateForUser(ctx, prover.getAddress, Configs.minBoxValue, new ErgoToken(WID, 1L)) + val tx = ctx.newTxBuilder().addInputs(repoBox, userBox) + .fee(Configs.fee) + .addOutputs(repoOut, permitBox, WIDBox) + .sendChangeTo(prover.getAddress) + .build() + val signedTx = prover.sign(tx) + println(signedTx.toJson(false)) + } + }) + } + + property("test extend first permit while extended permit first token is not RWT") { + networkConfig._1.ergoClient.execute(ctx => { + assertThrows[AnyRef] { + val prover = getProver() + val WID = Base16.decode(Boxes.getRandomHexString()).get + val otherWID = Base16.decode(Boxes.getRandomHexString()).get + val userBox = Boxes.createBoxForUser(ctx, prover.getAddress, 1e9.toLong, new ErgoToken(WID, 1L), new ErgoToken(networkConfig._3.RSN, 200L)) + val repoBox = Boxes.createRepo(ctx, 100000, 6459L, Seq(WID, otherWID), Seq(58L, 6400L)).convertToInputWith(Boxes.getRandomHexString(), 0) + val repoOut = Boxes.createRepoWithR7(ctx, 99900, 6559L, Seq(WID, otherWID), Seq(158L, 6400L), 1) + val permitBox = Boxes.createInvalidPermitBox(ctx, 100L, WID) + val WIDBox = Boxes.createBoxCandidateForUser(ctx, prover.getAddress, Configs.minBoxValue, new ErgoToken(WID, 1L)) + val tx = ctx.newTxBuilder().addInputs(repoBox, userBox) + .fee(Configs.fee) + .addOutputs(repoOut, permitBox, WIDBox) + .sendChangeTo(prover.getAddress) + .build() + val signedTx = prover.sign(tx) + println(signedTx.toJson(false)) + } + }) + } + + property("test extend permit while mutating other permits amount on repo") { + networkConfig._1.ergoClient.execute(ctx => { + assertThrows[AnyRef] { + val prover = getProver() + val WID = Base16.decode(Boxes.getRandomHexString()).get + val otherWID = Base16.decode(Boxes.getRandomHexString()).get + val otherWID2 = Base16.decode(Boxes.getRandomHexString()).get + val userBox = Boxes.createBoxForUser(ctx, prover.getAddress, 1e9.toLong, new ErgoToken(WID, 1L), new ErgoToken(networkConfig._3.RSN, 100L)) + val repoBox = Boxes.createRepo(ctx, 100000, 9659L, Seq(otherWID, WID, otherWID2), Seq(3200L, 58L, 6400L)).convertToInputWith(Boxes.getRandomHexString(), 0) + val repoOut = Boxes.createRepoWithR7(ctx, 99900, 9759L, Seq(otherWID, WID, otherWID2), Seq(320L, 158L, 6400L), 2) + val permitBox = Boxes.createPermitBox(ctx, 100L, WID) + val WIDBox = Boxes.createBoxCandidateForUser(ctx, prover.getAddress, Configs.minBoxValue, new ErgoToken(WID, 1L)) + val tx = ctx.newTxBuilder().addInputs(repoBox, userBox) + .fee(Configs.fee) + .addOutputs(repoOut, permitBox, WIDBox) + .sendChangeTo(prover.getAddress) + .build() + prover.sign(tx) + } + }) + } + + property("test extend permit while mutating other permits WID on repo") { + networkConfig._1.ergoClient.execute(ctx => { + assertThrows[AnyRef] { + val prover = getProver() + val WID = Base16.decode(Boxes.getRandomHexString()).get + val otherWID = Base16.decode(Boxes.getRandomHexString()).get + val otherWID2 = Base16.decode(Boxes.getRandomHexString()).get + val userBox = Boxes.createBoxForUser(ctx, prover.getAddress, 1e9.toLong, new ErgoToken(WID, 1L), new ErgoToken(networkConfig._3.RSN, 100L)) + val repoBox = Boxes.createRepo(ctx, 100000, 9659L, Seq(otherWID, WID, otherWID2), Seq(3200L, 58L, 6400L)).convertToInputWith(Boxes.getRandomHexString(), 0) + val repoOut = Boxes.createRepoWithR7(ctx, 99900, 9759L, Seq(otherWID, WID, otherWID), Seq(3200L, 158L, 6400L), 2) + val permitBox = Boxes.createPermitBox(ctx, 100L, WID) + val WIDBox = Boxes.createBoxCandidateForUser(ctx, prover.getAddress, Configs.minBoxValue, new ErgoToken(WID, 1L)) + val tx = ctx.newTxBuilder().addInputs(repoBox, userBox) + .fee(Configs.fee) + .addOutputs(repoOut, permitBox, WIDBox) + .sendChangeTo(prover.getAddress) + .build() + prover.sign(tx) + fail("transaction should not sign, the permit WID have changed") + } + }) + } + + property("test partially return permits") { + networkConfig._1.ergoClient.execute(ctx => { + try { + val prover = getProver() + val userWID = Base16.decode(Boxes.getRandomHexString()).get + val WIDs = Seq( + Base16.decode(Boxes.getRandomHexString()).get, + Base16.decode(Boxes.getRandomHexString()).get, + userWID, + Base16.decode(Boxes.getRandomHexString()).get + ) + val repoBox = Boxes.createRepo(ctx, 100000, 321L, WIDs, Seq(100L, 120L, 60L, 40L)).convertToInputWith(Boxes.getRandomHexString(), 0) + val permitBox = Boxes.createPermitBox(ctx, 60L, userWID).convertToInputWith(Boxes.getRandomHexString(), 0) + val WIDBox = Boxes.createBoxForUser(ctx, prover.getAddress, 1e9.toLong, new ErgoToken(userWID, 1L)) + val repoOut = Boxes.createRepoWithR7(ctx, 100020, 301L, WIDs, Seq(100L, 120L, 40L, 40L), 3) + val permitOut = Boxes.createPermitBox(ctx, 40L, userWID) + val userOut = Boxes.createBoxCandidateForUser(ctx, prover.getAddress, 1e8.toLong, new ErgoToken(userWID, 1), new ErgoToken(networkConfig._3.RSN, 20)) + val tx = ctx.newTxBuilder().addInputs(repoBox, permitBox, WIDBox) + .fee(Configs.fee) + .addOutputs(repoOut, permitOut, userOut) + .sendChangeTo(prover.getAddress) + .build() + prover.sign(tx) + } catch { + case exp: Throwable => + fail("transaction not signed") + } + }) + } + + property("test partially return permits when output permit WID has changed") { + networkConfig._1.ergoClient.execute(ctx => { + assertThrows[AnyRef] { + val prover = getProver() + val userWID = Base16.decode(Boxes.getRandomHexString()).get + val otherWID = Base16.decode(Boxes.getRandomHexString()).get + val WIDs = Seq( + Base16.decode(Boxes.getRandomHexString()).get, + otherWID, + userWID, + Base16.decode(Boxes.getRandomHexString()).get + ) + val repoBox = Boxes.createRepo(ctx, 100000, 321L, WIDs, Seq(100L, 120L, 60L, 40L)).convertToInputWith(Boxes.getRandomHexString(), 0) + val permitBox = Boxes.createPermitBox(ctx, 60L, userWID).convertToInputWith(Boxes.getRandomHexString(), 0) + val WIDBox = Boxes.createBoxForUser(ctx, prover.getAddress, 1e9.toLong, new ErgoToken(userWID, 1L)) + val repoOut = Boxes.createRepoWithR7(ctx, 100020, 301L, WIDs, Seq(100L, 120L, 40L, 40L), 3) + val permitOut = Boxes.createPermitBox(ctx, 40L, otherWID) + val userOut = Boxes.createBoxCandidateForUser(ctx, prover.getAddress, 1e8.toLong, new ErgoToken(userWID, 1), new ErgoToken(networkConfig._3.RSN, 20)) + val tx = ctx.newTxBuilder().addInputs(repoBox, permitBox, WIDBox) + .fee(Configs.fee) + .addOutputs(repoOut, permitOut, userOut) + .sendChangeTo(prover.getAddress) + .build() + val signedTx = prover.sign(tx) + println(signedTx.toJson(false)) + } + }) + } + + property("test partially return permits when changing other wid permits in repo") { + networkConfig._1.ergoClient.execute(ctx => { + assertThrows[AnyRef] { + val prover = getProver() + val userWID = Base16.decode(Boxes.getRandomHexString()).get + val otherWID = Base16.decode(Boxes.getRandomHexString()).get + val WIDs = Seq( + Base16.decode(Boxes.getRandomHexString()).get, + otherWID, + userWID, + Base16.decode(Boxes.getRandomHexString()).get + ) + val repoBox = Boxes.createRepo(ctx, 100000, 321L, WIDs, Seq(100L, 120L, 60L, 40L)).convertToInputWith(Boxes.getRandomHexString(), 0) + val userBox = Boxes.createBoxForUser(ctx, prover.getAddress, 1e9.toLong, otherWID) + val permitBox = Boxes.createPermitBox(ctx, 60L, userWID).convertToInputWith(Boxes.getRandomHexString(), 0) + val WIDBox = Boxes.createBoxForUser(ctx, prover.getAddress, 1e9.toLong, new ErgoToken(userWID, 1L)) + val repoOut = Boxes.createRepoWithR7(ctx, 100020, 301L, WIDs, Seq(100L, 100L, 60L, 40L), 2) + val permitOut = Boxes.createPermitBox(ctx, 40L, userWID) + val userOut = Boxes.createBoxCandidateForUser(ctx, prover.getAddress, 1e8.toLong, new ErgoToken(userWID, 1), new ErgoToken(networkConfig._3.RSN, 20)) + val tx = ctx.newTxBuilder().addInputs(repoBox, userBox, WIDBox, permitBox) + .fee(Configs.fee) + .addOutputs(repoOut, permitOut, userOut) + .sendChangeTo(prover.getAddress) + .build() + val signedTx = prover.sign(tx) + println(signedTx.toJson(false)) + } + }) + } + + property("test complete return permits") { + networkConfig._1.ergoClient.execute(ctx => { + var userIndex = 0 + try { + val prover = getProver() + val WIDs = generateRandomWIDList(6) + val amounts = Seq(100L, 120L, 140L, 20L, 40L, 250L) + for (userIndex <- 0 to 5) { + val userWID = WIDs(userIndex) + val totalPermitOut = amounts.sum + val repoBox = Boxes.createRepo(ctx, 100000L, totalPermitOut + 1L, WIDs, amounts).convertToInputWith(Boxes.getRandomHexString(), 0) + val permitBox = Boxes.createPermitBox(ctx, amounts(userIndex), userWID).convertToInputWith(Boxes.getRandomHexString(), 0) + val WIDBox = Boxes.createBoxForUser(ctx, prover.getAddress, 1e9.toLong, new ErgoToken(userWID, 1L)) + val outputWIDs = WIDs.take(userIndex) ++ WIDs.drop(userIndex + 1) + val outAmounts = amounts.take(userIndex) ++ amounts.drop(userIndex + 1) + val repoOut = Boxes.createRepoWithR7(ctx, 100000L + amounts(userIndex), (totalPermitOut - amounts(userIndex)) + 1, outputWIDs, outAmounts, userIndex + 1) // 4 + first element in WID list is chain name + val userOut = Boxes.createBoxCandidateForUser(ctx, prover.getAddress, 1e8.toLong, new ErgoToken(userWID, 1), new ErgoToken(networkConfig._3.RSN, amounts(userIndex))) + val watcherCollateral = Boxes.createWatcherCollateralBoxInput(ctx,1e9.toLong, 100, userWID) + val tx = ctx.newTxBuilder().addInputs(repoBox, permitBox, WIDBox, watcherCollateral) + .fee(Configs.fee) + .addOutputs(repoOut, userOut) + .sendChangeTo(prover.getAddress) + .build() + prover.sign(tx) + } + } catch { + case exp: Throwable => + println(exp.toString) + fail(s"transaction not signed for user index ${userIndex}") + } + }) + } + + property("test complete return permit while extending the wid list in repo") { + networkConfig._1.ergoClient.execute(ctx => { + assertThrows[AnyRef] { + val prover = getProver() + val WIDs = generateRandomWIDList(6) + val amounts = Seq(100L, 120L, 140L, 20L, 40L, 250L) + val userIndex = 0 + val userWID = WIDs(userIndex) + val totalPermitOut = amounts.sum + val repoBox = Boxes.createRepo(ctx, 100000L, totalPermitOut + 1L, WIDs, amounts).convertToInputWith(Boxes.getRandomHexString(), 0) + val permitBox = Boxes.createPermitBox(ctx, amounts(userIndex), userWID).convertToInputWith(Boxes.getRandomHexString(), 0) + val WIDBox = Boxes.createBoxForUser(ctx, prover.getAddress, 1e9.toLong, new ErgoToken(userWID, 1L)) + val outputWIDs = WIDs.take(userIndex) ++ WIDs.drop(userIndex + 1) ++ Seq(WIDs(userIndex)) + val outAmounts = amounts.take(userIndex) ++ amounts.drop(userIndex + 1) + val repoOut = Boxes.createRepoWithR7(ctx, 100000L + amounts(userIndex), (totalPermitOut - amounts(userIndex)) + 1, outputWIDs, outAmounts, userIndex + 1) // 4 + first element in WID list is chain name + val userOut = Boxes.createBoxCandidateForUser(ctx, prover.getAddress, 1e8.toLong, new ErgoToken(userWID, 1), new ErgoToken(networkConfig._3.RSN, amounts(userIndex))) + val watcherCollateral = Boxes.createWatcherCollateralBoxInput(ctx,1e9.toLong, 100, userWID) + val tx = ctx.newTxBuilder().addInputs(repoBox, permitBox, WIDBox, watcherCollateral) + .fee(Configs.fee) + .addOutputs(repoOut, userOut) + .sendChangeTo(prover.getAddress) + .build() + prover.sign(tx) + } + }) + } + + property("test complete return permit while extending the rwt count list in repo") { + networkConfig._1.ergoClient.execute(ctx => { + assertThrows[AnyRef] { + val prover = getProver() + val WIDs = generateRandomWIDList(6) + val amounts = Seq(100L, 120L, 140L, 20L, 40L, 250L) + val userIndex = 0 + val userWID = WIDs(userIndex) + val totalPermitOut = amounts.sum + val repoBox = Boxes.createRepo(ctx, 100000L, totalPermitOut + 1L, WIDs, amounts).convertToInputWith(Boxes.getRandomHexString(), 0) + val permitBox = Boxes.createPermitBox(ctx, amounts(userIndex), userWID).convertToInputWith(Boxes.getRandomHexString(), 0) + val WIDBox = Boxes.createBoxForUser(ctx, prover.getAddress, 1e9.toLong, new ErgoToken(userWID, 1L)) + val outputWIDs = WIDs.take(userIndex) ++ WIDs.drop(userIndex + 1) + val outAmounts = amounts.take(userIndex) ++ amounts.drop(userIndex + 1) ++ Seq(100L) + val repoOut = Boxes.createRepoWithR7(ctx, 100000L + amounts(userIndex), (totalPermitOut - amounts(userIndex)) + 1, outputWIDs, outAmounts, userIndex + 1) // 4 + first element in WID list is chain name + val userOut = Boxes.createBoxCandidateForUser(ctx, prover.getAddress, 1e8.toLong, new ErgoToken(userWID, 1), new ErgoToken(networkConfig._3.RSN, amounts(userIndex))) + val watcherCollateral = Boxes.createWatcherCollateralBoxInput(ctx, 1e9.toLong, 100, userWID) + val tx = ctx.newTxBuilder().addInputs(repoBox, permitBox, WIDBox, watcherCollateral) + .fee(Configs.fee) + .addOutputs(repoOut, userOut) + .sendChangeTo(prover.getAddress) + .build() + prover.sign(tx) + } + }) + } + + property("test complete return permit while extending both rwt count and wid list in repo") { + networkConfig._1.ergoClient.execute(ctx => { + assertThrows[AnyRef] { + val prover = getProver() + val WIDs = generateRandomWIDList(6) + val amounts = Seq(100L, 120L, 140L, 20L, 40L, 250L) + val userIndex = 0 + val userWID = WIDs(userIndex) + val totalPermitOut = amounts.sum + val repoBox = Boxes.createRepo(ctx, 100000L, totalPermitOut + 1L, WIDs, amounts).convertToInputWith(Boxes.getRandomHexString(), 0) + val permitBox = Boxes.createPermitBox(ctx, amounts(userIndex), userWID).convertToInputWith(Boxes.getRandomHexString(), 0) + val WIDBox = Boxes.createBoxForUser(ctx, prover.getAddress, 1e9.toLong, new ErgoToken(userWID, 1L)) + val outputWIDs = WIDs.take(userIndex) ++ WIDs.drop(userIndex + 1) ++ Seq(WIDs(userIndex)) + val outAmounts = amounts.take(userIndex) ++ amounts.drop(userIndex + 1) ++ Seq(100L) + val repoOut = Boxes.createRepoWithR7(ctx, 100000L + amounts(userIndex), (totalPermitOut - amounts(userIndex)) + 1, outputWIDs, outAmounts, userIndex + 1) // 4 + first element in WID list is chain name + val userOut = Boxes.createBoxCandidateForUser(ctx, prover.getAddress, 1e8.toLong, new ErgoToken(userWID, 1), new ErgoToken(networkConfig._3.RSN, amounts(userIndex))) + val watcherCollateral = Boxes.createWatcherCollateralBoxInput(ctx, 1e9.toLong, 100, userWID) + val tx = ctx.newTxBuilder().addInputs(repoBox, permitBox, WIDBox, watcherCollateral) + .fee(Configs.fee) + .addOutputs(repoOut, userOut) + .sendChangeTo(prover.getAddress) + .build() + prover.sign(tx) + } + }) + } + + property("test complete return of last permit") { + networkConfig._1.ergoClient.execute(ctx => { + try { + val prover = getProver() + val userWID = Base16.decode(Boxes.getRandomHexString()).get + val repoBox = Boxes.createRepo(ctx, 100000, 41L, Seq(userWID), Seq(40L)).convertToInputWith(Boxes.getRandomHexString(), 0) + val permitBox = Boxes.createPermitBox(ctx, 40L, userWID).convertToInputWith(Boxes.getRandomHexString(), 0) + val WIDBox = Boxes.createBoxForUser(ctx, prover.getAddress, 1e9.toLong, new ErgoToken(userWID, 1L)) + val repoOut = Boxes.createRepoWithR7(ctx, 100040, 1L, Seq(), Seq(), 1) + val userOut = Boxes.createBoxCandidateForUser(ctx, prover.getAddress, 1e8.toLong, new ErgoToken(userWID, 1), new ErgoToken(networkConfig._3.RSN, 40)) + val watcherCollateral = Boxes.createWatcherCollateralBoxInput(ctx,1e9.toLong, 100, userWID) + val tx = ctx.newTxBuilder().addInputs(repoBox, permitBox, WIDBox, watcherCollateral) + .fee(Configs.fee) + .addOutputs(repoOut, userOut) + .sendChangeTo(prover.getAddress) + .build() + prover.sign(tx) + } catch { + case exp: Throwable => + fail("transaction not signed") + } + }) + } + + property("test redeem repo") { + networkConfig._1.ergoClient.execute(ctx => { + try { + val prover = getProver() + val WIDs = generateRandomWIDList(7) + val repoBox = Boxes.createRepo(ctx, 100000, 32001L, WIDs, Seq(100L, 120L, 140L, 20L, 40L, 250L, 123L)).convertToInputWith(Boxes.getRandomHexString(), 0) + val guardBox = Boxes.createBoxForUser(ctx, prover.getAddress, 1e9.toLong, new ErgoToken(networkConfig._3.GuardNFT, 1L)) + val inputs = Seq(repoBox, guardBox) + val boxBuilder = ctx.newTxBuilder().outBoxBuilder() + .contract(ctx.newContract(prover.getAddress.asP2PK().script)) + .registers( + repoBox.getRegisters.get(0), + repoBox.getRegisters.get(1), + ) + .tokens(new ErgoToken(networkConfig._3.GuardNFT, 1L)) + boxBuilder.value(inputs.map(item => item.getValue).sum - Configs.fee) + repoBox.getTokens.forEach(token => boxBuilder.tokens(token)) + val tx = ctx.newTxBuilder().addInputs(repoBox, guardBox) + .fee(Configs.fee) + .addOutputs(boxBuilder.build()) + .sendChangeTo(prover.getAddress) + .build() + prover.sign(tx) + } catch { + case exp: Throwable => + println(exp.toString) + fail("transaction not signed") + } + }) + } + + property("test create new commitment") { + networkConfig._1.ergoClient.execute(ctx => { + try { + val commitment = new Commitment() + val prover = getProver() + val WID = Base16.decode(Boxes.getRandomHexString()).get + val box1 = Boxes.createBoxForUser(ctx, prover.getAddress, 1e9.toLong, new ErgoToken(WID, 1)) + val permit = Boxes.createPermitBox(ctx, 10L, WID).convertToInputWith(Boxes.getRandomHexString(), 0) + val permitOut = Boxes.createPermitBox(ctx, 9L, WID) + val commitmentBox = Boxes.createCommitment(ctx, WID, commitment.requestId(), commitment.hash(WID), 1l) + val WIDOut = Boxes.createBoxCandidateForUser(ctx, prover.getAddress, 1e8.toLong, new ErgoToken(WID, 1)) + val tx = ctx.newTxBuilder().addInputs(permit, box1) + .fee(Configs.fee) + .sendChangeTo(prover.getAddress) + .addOutputs(permitOut, commitmentBox, WIDOut) + .build() + prover.sign(tx) + } catch { + case exp: Throwable => + println(exp.toString) + fail("transaction not signed") + } + }) + } + + property("test create new commitment with extra fee box") { + networkConfig._1.ergoClient.execute(ctx => { + try { + val commitment = new Commitment() + val prover = getProver() + val WID = Base16.decode(Boxes.getRandomHexString()).get + val box1 = Boxes.createBoxForUser(ctx, prover.getAddress, 1e5.toLong, new ErgoToken(WID, 1)) + val box2 = Boxes.createBoxForUser(ctx, prover.getAddress, 1e9.toLong) + val permit = Boxes.createPermitBox(ctx, 10L, WID).convertToInputWith(Boxes.getRandomHexString(), 0) + val permitOut = Boxes.createPermitBox(ctx, 9L, WID) + val commitmentBox = Boxes.createCommitment(ctx, WID, commitment.requestId(), commitment.hash(WID), 1l) + val WIDOut = Boxes.createBoxCandidateForUser(ctx, prover.getAddress, 1e8.toLong, new ErgoToken(WID, 1)) + val tx = ctx.newTxBuilder().addInputs(permit, box1, box2) + .fee(Configs.fee) + .sendChangeTo(prover.getAddress) + .addOutputs(permitOut, commitmentBox, WIDOut) + .build() + prover.sign(tx) + } catch { + case exp: Throwable => + println(exp.toString) + fail("transaction not signed") + } + }) + } + + property("test create new commitment with RWT second place of permit tokens") { + networkConfig._1.ergoClient.execute(ctx => { + assertThrows[AnyRef] { + val commitment = new Commitment() + val prover = getProver() + val WID = Base16.decode(Boxes.getRandomHexString()).get + val box1 = Boxes.createBoxForUser(ctx, prover.getAddress, 1e9.toLong, new ErgoToken(WID, 1), new ErgoToken(networkConfig._3.RSN, 9)) + val permit = Boxes.createPermitBox(ctx, 10L, WID).convertToInputWith(Boxes.getRandomHexString(), 0) + val permitOut = Boxes.createInvalidMixedPermitBox(ctx, 9L, WID) + val commitmentBox = Boxes.createCommitment(ctx, WID, commitment.requestId(), commitment.hash(WID), 1l) + val WIDOut = Boxes.createBoxCandidateForUser(ctx, prover.getAddress, 1e8.toLong, new ErgoToken(WID, 1)) + val tx = ctx.newTxBuilder().addInputs(permit, box1) + .fee(Configs.fee) + .sendChangeTo(prover.getAddress) + .addOutputs(permitOut, commitmentBox, WIDOut) + .build() + val signedTx = prover.sign(tx) + println(signedTx.toJson(false)) + } + }) + } + + property("test create new commitment with more than one RWT") { + networkConfig._1.ergoClient.execute(ctx => { + try { + val commitment = new Commitment() + val prover = getProver() + val WID = Base16.decode(Boxes.getRandomHexString()).get + val box1 = Boxes.createBoxForUser(ctx, prover.getAddress, 1e9.toLong, new ErgoToken(WID, 1)) + val permit = Boxes.createPermitBox(ctx, 10L, WID).convertToInputWith(Boxes.getRandomHexString(), 0) + val permitOut = Boxes.createPermitBox(ctx, 8L, WID) + val commitmentBox = Boxes.createCommitment(ctx, WID, commitment.requestId(), commitment.hash(WID), 2l) + val WIDOut = Boxes.createBoxCandidateForUser(ctx, prover.getAddress, 1e8.toLong, new ErgoToken(WID, 1)) + val tx = ctx.newTxBuilder().addInputs(permit, box1) + .fee(Configs.fee) + .sendChangeTo(prover.getAddress) + .addOutputs(permitOut, commitmentBox, WIDOut) + .build() + prover.sign(tx) + } catch { + case exp: Throwable => + println(exp.toString) + fail("transaction not signed") + } + }) + } + + property("test create new commitment with more than one permit") { + networkConfig._1.ergoClient.execute(ctx => { + try { + val commitment = new Commitment() + val prover = getProver() + val WID = Base16.decode(Boxes.getRandomHexString()).get + val box1 = Boxes.createBoxForUser(ctx, prover.getAddress, 1e9.toLong, new ErgoToken(WID, 1)) + val permit = Boxes.createPermitBox(ctx, 10L, WID).convertToInputWith(Boxes.getRandomHexString(), 0) + val permit2 = Boxes.createPermitBox(ctx, 20L, WID).convertToInputWith(Boxes.getRandomHexString(), 0) + val permitOut = Boxes.createPermitBox(ctx, 25L, WID) + val commitmentBox = Boxes.createCommitment(ctx, WID, commitment.requestId(), commitment.hash(WID), 5l) + val WIDOut = Boxes.createBoxCandidateForUser(ctx, prover.getAddress, 1e8.toLong, new ErgoToken(WID, 1)) + val tx = ctx.newTxBuilder().addInputs(permit, permit2, box1) + .fee(Configs.fee) + .sendChangeTo(prover.getAddress) + .addOutputs(permitOut, commitmentBox, WIDOut) + .build() + prover.sign(tx) + } catch { + case exp: Throwable => + println(exp.toString) + fail("transaction not signed") + } + }) + } + + property("test redeem commitment") { + networkConfig._1.ergoClient.execute(ctx => { + try { + val commitment = new Commitment() + val prover = getProver() + val WID = Base16.decode(Boxes.getRandomHexString()).get + val commitmentBox = Boxes.createCommitment(ctx, WID, commitment.requestId(), commitment.hash(WID), 1l).convertToInputWith(Boxes.getRandomHexString(), 1) + val box = Boxes.createBoxForUser(ctx, prover.getAddress, 1e9.toLong, new ErgoToken(WID, 1L)) + val newPermit = Boxes.createPermitBox(ctx, 1, WID) + val inputs = Seq(commitmentBox, box) + val redeemUnsigned = ctx.newTxBuilder().addInputs(inputs: _*) + .fee(Configs.fee) + .addOutputs(newPermit) + .sendChangeTo(prover.getAddress) + .build() + prover.sign(redeemUnsigned) + } catch { + case exp: Throwable => + println(exp.toString) + fail("transaction not signed") + } + }) + } + + property("test create event trigger for all watcher") { + networkConfig._1.ergoClient.execute(ctx => { + try { + val commitment = new Commitment() + val prover = getProver() + val WIDs = generateRandomWIDList(5) + val repo = Boxes.createRepo(ctx, 1000L, 10001L, WIDs, Seq(10L, 30L, 20L, 35L, 5L)).convertToInputWith(Boxes.getRandomHexString(), 1) + val commitments = WIDs.map(WID => Boxes.createCommitment(ctx, WID, commitment.requestId(), commitment.hash(WID), 10L).convertToInputWith(Boxes.getRandomHexString(), 1)) + val trigger = Boxes.createTriggerEventBox(ctx, WIDs, commitment, 50L) + val feeBox = Boxes.createBoxForUser(ctx, prover.getAddress, 1e9.toLong) + val tx = ctx.newTxBuilder().addInputs(commitments ++ Seq(feeBox): _*) + .fee(Configs.fee) + .addOutputs(trigger) + .addDataInputs(repo) + .sendChangeTo(prover.getAddress) + .build() + prover.sign(tx) + } catch { + case exp: Throwable => + println(exp.toString) + fail("transaction not signed") + } + }) + } + + property("test create event trigger for minimum required watcher") { + networkConfig._1.ergoClient.execute(ctx => { + try { + val commitment = new Commitment() + val prover = getProver() + val WIDs = generateRandomWIDList(7) + val repo = Boxes.createRepo(ctx, 1000L, 10001L, WIDs, Seq(10L, 30L, 20L, 25L, 5L, 4L, 6L)).convertToInputWith(Boxes.getRandomHexString(), 1) + val commitments = WIDs.slice(0, 4).map(WID => Boxes.createCommitment(ctx, WID, commitment.requestId(), commitment.hash(WID), 10L).convertToInputWith(Boxes.getRandomHexString(), 1)) + val trigger = Boxes.createTriggerEventBox(ctx, WIDs.slice(0, 4), commitment, 40L) + val feeBox = Boxes.createBoxForUser(ctx, prover.getAddress, 1e9.toLong) + val tx = ctx.newTxBuilder().addInputs(commitments ++ Seq(feeBox): _*) + .fee(Configs.fee) + .addOutputs(trigger) + .addDataInputs(repo) + .sendChangeTo(prover.getAddress) + .build() + prover.sign(tx) + } catch { + case exp: Throwable => + println(exp.toString) + fail("transaction not signed") + } + }) + } + + property("test cant create event trigger for lower than minimum required watcher") { + networkConfig._1.ergoClient.execute(ctx => { + assertThrows[AnyRef] { + val commitment = new Commitment() + val prover = getProver() + val WIDs = generateRandomWIDList(7) + val repo = Boxes.createRepo(ctx, 1000L, 10001L, WIDs, Seq(10L, 30L, 20L, 25L, 5L, 4L, 6L)).convertToInputWith(Boxes.getRandomHexString(), 1) + val commitments = WIDs.slice(0, 3).map(WID => Boxes.createCommitment(ctx, WID, commitment.requestId(), commitment.hash(WID), 10L).convertToInputWith(Boxes.getRandomHexString(), 1)) + val trigger = Boxes.createTriggerEventBox(ctx, WIDs.slice(0, 3), commitment, 30L) + val feeBox = Boxes.createBoxForUser(ctx, prover.getAddress, 1e9.toLong) + val tx = ctx.newTxBuilder().addInputs(commitments ++ Seq(feeBox): _*) + .fee(Configs.fee) + .addOutputs(trigger) + .addDataInputs(repo) + .sendChangeTo(prover.getAddress) + .build() + prover.sign(tx) + } + }) + } + + property("test create event trigger with repo box of different chain") { + networkConfig._1.ergoClient.execute(ctx => { + assertThrows[AnyRef] { + val commitment = new Commitment() + val prover = getProver() + val WIDs = generateRandomWIDList(7) + val repo = Boxes.createRepoWithTokens( + ctx, + 1000L, + 10001L, + WIDs, + Seq(10L, 30L, 20L, 25L, 5L, 4L, 6L), + networkConfig._3.RepoNFT, + Boxes.getRandomHexString(), + ).convertToInputWith(Boxes.getRandomHexString(), 1) + val commitments = WIDs.slice(0, 3).map(WID => Boxes.createCommitment(ctx, WID, commitment.requestId(), commitment.hash(WID), 10L).convertToInputWith(Boxes.getRandomHexString(), 1)) + val trigger = Boxes.createTriggerEventBox(ctx, WIDs.slice(0, 3), commitment, 30L) + val feeBox = Boxes.createBoxForUser(ctx, prover.getAddress, 1e9.toLong) + val tx = ctx.newTxBuilder().addInputs(commitments ++ Seq(feeBox): _*) + .fee(Configs.fee) + .addOutputs(trigger) + .addDataInputs(repo) + .sendChangeTo(prover.getAddress) + .build() + prover.sign(tx) + } + }) + } + + property("test create event trigger with invalid repo box (invalid nft and valid rwt)") { + networkConfig._1.ergoClient.execute(ctx => { + assertThrows[AnyRef] { + val commitment = new Commitment() + val prover = getProver() + val WIDs = generateRandomWIDList(7) + val repo = Boxes.createRepoWithTokens( + ctx, + 1000L, + 10001L, + WIDs, + Seq(10L, 30L, 20L, 25L, 5L, 4L, 6L), + Boxes.getRandomHexString(), + networkConfig._2.tokens.RWTId, + ).convertToInputWith(Boxes.getRandomHexString(), 1) + val commitments = WIDs.slice(0, 3).map(WID => Boxes.createCommitment(ctx, WID, commitment.requestId(), commitment.hash(WID), 10L).convertToInputWith(Boxes.getRandomHexString(), 1)) + val trigger = Boxes.createTriggerEventBox(ctx, WIDs.slice(0, 3), commitment, 30L) + val feeBox = Boxes.createBoxForUser(ctx, prover.getAddress, 1e9.toLong) + val tx = ctx.newTxBuilder().addInputs(commitments ++ Seq(feeBox): _*) + .fee(Configs.fee) + .addOutputs(trigger) + .addDataInputs(repo) + .sendChangeTo(prover.getAddress) + .build() + prover.sign(tx) + } + }) + } + + property("test create event trigger for all watchers with wrong trigger RWT sum") { + networkConfig._1.ergoClient.execute(ctx => { + assertThrows[AnyRef] { + val commitment = new Commitment() + val prover = getProver() + val WIDs = generateRandomWIDList(5) + val repo = Boxes.createRepo(ctx, 1000L, 10001L, WIDs, Seq(10L, 30L, 20L, 35L, 5L)).convertToInputWith(Boxes.getRandomHexString(), 1) + val commitments = WIDs.map(WID => Boxes.createCommitment(ctx, WID, commitment.requestId(), commitment.hash(WID), 10L).convertToInputWith(Boxes.getRandomHexString(), 1)) + val trigger = Boxes.createTriggerEventBox(ctx, WIDs, commitment, 48L) + val feeBox = Boxes.createBoxForUser(ctx, prover.getAddress, 1e9.toLong) + val tx = ctx.newTxBuilder().addInputs(commitments ++ Seq(feeBox): _*) + .fee(Configs.fee) + .addOutputs(trigger) + .addDataInputs(repo) + .sendChangeTo(prover.getAddress) + .build() + prover.sign(tx) + } + }) + } + + property("test guard payment without not merged commitment") { + networkConfig._1.ergoClient.execute(ctx => { + try { + val commitment = new Commitment() + val prover = getProver() + val WIDs = generateRandomWIDList(7) + val secrets = (0 until 7).map(ind => Utils.randBigInt.bigInteger) + val guards = secrets.map(item => ctx.newProverBuilder().withDLogSecret(item).build()) + val guardsPks = guards.map(item => item.getAddress.getPublicKey.pkBytes).toArray + val userFee: Long = math.floor((commitment.fee * 0.6) / WIDs.length).toLong + val guardBox = Boxes.createGuardNftBox(ctx, guardsPks, 5, 6).convertToInputWith(Boxes.getRandomHexString(), 0) + val lockBox = Boxes.createLockBox( + ctx, + Configs.minBoxValue, + new ErgoToken(commitment.targetChainTokenId, userFee * WIDs.length) + ).convertToInputWith(Boxes.getRandomHexString(), 0) + val eventTrigger = Boxes.createTriggerEventBox(ctx, WIDs, commitment, 70L).convertToInputWith(Boxes.getRandomHexString(), 1) + val newPermits = WIDs.map(item => { + Boxes.createPermitBox(ctx, 10, item, new ErgoToken(commitment.targetChainTokenId, userFee)) + }) + val inputs = Seq(eventTrigger, lockBox) + val unsignedTx = ctx.newTxBuilder().addInputs(inputs: _*) + .addDataInputs(guardBox) + .fee(Configs.fee) + .sendChangeTo(prover.getAddress) + .addOutputs(newPermits: _*) + .build() + val multiSigProverBuilder = ctx.newProverBuilder() + secrets.map(item => multiSigProverBuilder.withDLogSecret(item)) + val multiSigProver = multiSigProverBuilder.build() + multiSigProver.sign(unsignedTx) + } catch { + case exp: Throwable => + println(exp.toString) + fail("transaction not signed") + } + }) + } + + property("test guard payment with wrong permit RWT count") { + networkConfig._1.ergoClient.execute(ctx => { + assertThrows[AnyRef] { + val commitment = new Commitment() + val prover = getProver() + val WIDs = generateRandomWIDList(7) + val secrets = (0 until 7).map(ind => Utils.randBigInt.bigInteger) + val guards = secrets.map(item => ctx.newProverBuilder().withDLogSecret(item).build()) + val guardsPks = guards.map(item => item.getAddress.getPublicKey.pkBytes).toArray + val userFee: Long = math.floor((commitment.fee * 0.6) / WIDs.length).toLong + val guardBox = Boxes.createGuardNftBox(ctx, guardsPks, 5, 6).convertToInputWith(Boxes.getRandomHexString(), 0) + val lockBox = Boxes.createLockBox( + ctx, + 1e9.toLong, + new ErgoToken(commitment.targetChainTokenId, userFee * WIDs.length) + ).convertToInputWith(Boxes.getRandomHexString(), 0) + val eventTrigger = Boxes.createTriggerEventBox(ctx, WIDs, commitment, 70L).convertToInputWith(Boxes.getRandomHexString(), 1) + val newPermits = WIDs.map(item => { + Boxes.createPermitBox(ctx, 9, item, new ErgoToken(commitment.targetChainTokenId, userFee)) + }) + val inputs = Seq(eventTrigger, lockBox) + val unsignedTx = ctx.newTxBuilder().addInputs(inputs: _*) + .addDataInputs(guardBox) + .fee(Configs.fee) + .sendChangeTo(prover.getAddress) + .addOutputs(newPermits: _*) + .build() + val multiSigProverBuilder = ctx.newProverBuilder() + secrets.map(item => multiSigProverBuilder.withDLogSecret(item)) + val multiSigProver = multiSigProverBuilder.build() + multiSigProver.sign(unsignedTx) + } + }) + } + + property("test guard payment with not merged commitment") { + networkConfig._1.ergoClient.execute(ctx => { + try { + val commitment = new Commitment() + val prover = getProver() + val WIDs = generateRandomWIDList(7) + val notMergedWIDs = generateRandomWIDList(3) + val allWIDs = WIDs ++ notMergedWIDs + val notMergedCommitments = notMergedWIDs.map(WID => Boxes.createCommitment(ctx, WID, commitment.requestId(), commitment.hash(WID), 10L).convertToInputWith(Boxes.getRandomHexString(), 1)) + val secrets = (0 until 7).map(ind => Utils.randBigInt.bigInteger) + val guards = secrets.map(item => ctx.newProverBuilder().withDLogSecret(item).build()) + val guardsPks = guards.map(item => item.getAddress.getPublicKey.pkBytes).toArray + val eventTrigger = Boxes.createTriggerEventBox(ctx, WIDs, commitment, 70L).convertToInputWith(Boxes.getRandomHexString(), 1) + val userFee: Long = math.floor((commitment.fee * 0.6) / allWIDs.length).toLong + val guardBox = Boxes.createGuardNftBox(ctx, guardsPks, 5, 6).convertToInputWith(Boxes.getRandomHexString(), 0) + val lockBox = Boxes.createLockBox( + ctx, + Configs.minBoxValue, + new ErgoToken(commitment.targetChainTokenId, userFee * (WIDs ++ notMergedWIDs).length) + ).convertToInputWith(Boxes.getRandomHexString(), 0) + val newPermits = (WIDs ++ notMergedWIDs).map(item => { + Boxes.createPermitBox(ctx, 10, item, new ErgoToken(commitment.targetChainTokenId, userFee)) + }) + val inputs = Seq(eventTrigger) ++ notMergedCommitments ++ Seq(lockBox) + val unsignedTx = ctx.newTxBuilder().addInputs(inputs: _*) + .fee(Configs.fee) + .addDataInputs(guardBox) + .sendChangeTo(prover.getAddress) + .addOutputs(newPermits: _*) + .build() + val multiSigProverBuilder = ctx.newProverBuilder() + secrets.map(item => multiSigProverBuilder.withDLogSecret(item)) + val multiSigProver = multiSigProverBuilder.build() + multiSigProver.sign(unsignedTx) + } catch { + case exp: Throwable => + println(exp.toString) + fail("transaction not signed") + } + }) + } + + property("test guard payment with not merged wrong commitment") { + networkConfig._1.ergoClient.execute(ctx => { + assertThrows[AnyRef] { + val commitment = new Commitment() + val prover = getProver() + val WIDs = generateRandomWIDList(7) + val notMergedWIDs = generateRandomWIDList(1) + val allWIDs = WIDs ++ notMergedWIDs + val notMergedCommitments = notMergedWIDs.map(WID => Boxes.createCommitment(ctx, WID, commitment.requestId(), commitment.hash(WID), 11L).convertToInputWith(Boxes.getRandomHexString(), 1)) + val secrets = (0 until 7).map(ind => Utils.randBigInt.bigInteger) + val guards = secrets.map(item => ctx.newProverBuilder().withDLogSecret(item).build()) + val guardsPks = guards.map(item => item.getAddress.getPublicKey.pkBytes).toArray + val eventTrigger = Boxes.createTriggerEventBox(ctx, WIDs, commitment, 70L).convertToInputWith(Boxes.getRandomHexString(), 1) + val userFee: Long = math.floor((commitment.fee * 0.6) / allWIDs.length).toLong + val guardBox = Boxes.createGuardNftBox(ctx, guardsPks, 5, 6).convertToInputWith(Boxes.getRandomHexString(), 0) + val lockBox = Boxes.createLockBox( + ctx, + 1e9.toLong, + new ErgoToken(commitment.targetChainTokenId, userFee * (WIDs ++ notMergedWIDs).length) + ).convertToInputWith(Boxes.getRandomHexString(), 0) + val newPermits = (WIDs ++ notMergedWIDs).map(item => { + Boxes.createPermitBox(ctx, 10, item, new ErgoToken(commitment.targetChainTokenId, userFee)) + }) + val inputs = Seq(eventTrigger) ++ notMergedCommitments ++ Seq(lockBox) + val unsignedTx = ctx.newTxBuilder().addInputs(inputs: _*) + .fee(Configs.fee) + .addDataInputs(guardBox) + .sendChangeTo(prover.getAddress) + .addOutputs(newPermits: _*) + .build() + val multiSigProverBuilder = ctx.newProverBuilder() + secrets.map(item => multiSigProverBuilder.withDLogSecret(item)) + val multiSigProver = multiSigProverBuilder.build() + multiSigProver.sign(unsignedTx) + } + }) + } + + property("test create fraud from event trigger") { + networkConfig._1.ergoClient.execute(ctx => { + try { + val prover = getProver() + val commitment = new Commitment() + val WIDs = generateRandomWIDList(7) + val triggerEvent = Boxes.createTriggerEventBox(ctx, WIDs, commitment, 70L).convertToInputWith(Boxes.getRandomHexString(), 0) + val box1 = Boxes.createBoxForUser(ctx, prover.getAddress, 1e9.toLong, new ErgoToken(networkConfig._2.tokens.CleanupNFT, 1L)) + val newFraud = WIDs.indices.map(index => { + Boxes.createFraudBox(ctx, WIDs(index), 10L) + }) + val unsignedTx = ctx.newTxBuilder().addInputs(triggerEvent, box1) + .fee(Configs.fee) + .sendChangeTo(prover.getAddress) + .addOutputs(newFraud: _*) + .build() + prover.sign(unsignedTx) + } catch { + case exp: Throwable => + println(exp.toString) + fail("transaction not signed") + } + }) + } + + property("test redeem fraud to repo") { + networkConfig._1.ergoClient.execute(ctx => { + var userIndex = 0 + try { + val prover = getProver() + val globalWIDs = generateRandomWIDList(3) + val globalAmounts = Seq(100L, 11L, 20L) + val repo = Boxes.createRepo(ctx, 1000L, globalAmounts.sum + 1, globalWIDs, globalAmounts).convertToInputWith(Boxes.getRandomHexString(), 1) + for (userIndex <- 0 until globalWIDs.length) { + var amounts = globalAmounts.map(item => item).toArray + var WIDs = globalWIDs.map(item => item).toArray + val WID = WIDs(userIndex) + val box2 = Boxes.createBoxForUser(ctx, prover.getAddress, 1e9.toLong, new ErgoToken(networkConfig._2.tokens.CleanupNFT, 1L)) + val fraud = Boxes.createFraudBox(ctx, WID, 10L).convertToInputWith(Boxes.getRandomHexString(), 1) + val RWTCount = repo.getTokens.get(1).getValue.toLong + 10 + val RSNCount = repo.getTokens.get(2).getValue.toLong - 10 + amounts(userIndex) -= 10 + val repoCandidate = Boxes.createRepoWithR7(ctx, RWTCount, RSNCount, WIDs, amounts, userIndex + 1) + val unsigned = ctx.newTxBuilder().addInputs(repo, fraud, box2) + .fee(Configs.fee) + .addOutputs(repoCandidate) + .sendChangeTo(prover.getAddress) + .build() + val signed = prover.sign(unsigned) + } + } catch { + case exp: Throwable => + println(exp.toString) + fail(s"transaction not signed on index ${userIndex}") + } + }) + } + + property("test spent lock script when guard token is in data input") { + networkConfig._1.ergoClient.execute(ctx => { + try { + val secrets = (0 until 7).map(ind => Utils.randBigInt.bigInteger) + val guards = secrets.map(item => ctx.newProverBuilder().withDLogSecret(item).build()) + val guardsPks = guards.map(item => item.getAddress.getPublicKey.pkBytes).toArray + val prover = getProver() + val box = Boxes.createCustomBox(ctx, contracts.Lock._1, 1e9.toLong) + val boxNft = Boxes.createGuardNftBox(ctx, guardsPks, 5, 6).convertToInputWith(Boxes.getRandomHexString(), 32) + val outBox = Boxes.createBoxCandidateForUser(ctx, prover.getAddress, 5e8.toLong) + val multiSigProverBuilder = ctx.newProverBuilder() + secrets.slice(0, 5).foreach(item => multiSigProverBuilder.withDLogSecret(item)) + val multiSigProver = multiSigProverBuilder.build() + val tx = ctx.newTxBuilder().addInputs(box) + .fee(Configs.fee) + .addOutputs(outBox) + .addDataInputs(boxNft) + .sendChangeTo(prover.getAddress) + .build() + multiSigProver.sign(tx) + } catch { + case exp: Throwable => + println(exp.toString) + fail(s"transaction not signed") + } + }) + } + + property("test guard nft box spend with update with 6 sign.") { + networkConfig._1.ergoClient.execute(ctx => { + try { + val secrets = (0 until 7).map(ind => Utils.randBigInt.bigInteger) + val guards = secrets.map(item => ctx.newProverBuilder().withDLogSecret(item).build()) + val prover = guards(0) + val guardsPks = guards.map(item => item.getAddress.getPublicKey.pkBytes).toArray + val signBox = Boxes.createGuardNftBox(ctx, guardsPks, 5, 6).convertToInputWith(Boxes.getRandomHexString(), 1) + val box2 = Boxes.createBoxForUser(ctx, guards(0).getAddress, 1e9.toLong) + val outSignBox = Boxes.createGuardNftBox(ctx, guardsPks, 4,6) + val outBox = Boxes.createBoxCandidateForUser(ctx, guards(1).getAddress, 1e8.toLong) + val tx = ctx.newTxBuilder().addInputs(signBox, box2) + .fee(Configs.fee) + .addOutputs(outSignBox, outBox) + .sendChangeTo(prover.getAddress) + .build() + val proverBuilder = ctx.newProverBuilder() + secrets.slice(0, 6).map(item => proverBuilder.withDLogSecret(item)) + proverBuilder.build().sign(tx) + } catch { + case exp: Throwable => + println(exp.toString) + fail(s"transaction not signed") + } + }) + } + + property("test guard nft box cant spend with update with 5 sign") { + networkConfig._1.ergoClient.execute(ctx => { + assertThrows[AnyRef] { + val secrets = (0 until 7).map(ind => Utils.randBigInt.bigInteger) + val guards = secrets.map(item => ctx.newProverBuilder().withDLogSecret(item).build()) + val prover = guards(0) + val guardsPks = guards.map(item => item.getAddress.getPublicKey.pkBytes).toArray + val signBox = Boxes.createGuardNftBox(ctx, guardsPks, 5, 6).convertToInputWith(Boxes.getRandomHexString(), 1) + val box2 = Boxes.createBoxForUser(ctx, guards(0).getAddress, 1e9.toLong) + val outSignBox = Boxes.createGuardNftBox(ctx, guardsPks, 4,6) + val outBox = Boxes.createBoxCandidateForUser(ctx, guards(1).getAddress, 1e8.toLong) + val tx = ctx.newTxBuilder().addInputs(signBox, box2) + .fee(Configs.fee) + .addOutputs(outSignBox, outBox) + .sendChangeTo(prover.getAddress) + .build() + val proverBuilder = ctx.newProverBuilder() + secrets.slice(0, 5).map(item => proverBuilder.withDLogSecret(item)) + proverBuilder.build().sign(tx) + } + }) + } +} diff --git a/src/test/scala/contracts/PermitTest.scala b/src/test/scala/contracts/PermitTest.scala deleted file mode 100644 index be334dc..0000000 --- a/src/test/scala/contracts/PermitTest.scala +++ /dev/null @@ -1,635 +0,0 @@ -package contracts - -import helpers.{Configs, ErgoNetwork, MainTokens, Network, Utils} -import org.ergoplatform.appkit.{ErgoProver, ErgoToken} -import rosen.bridge.Contracts -import scorex.util.encode.Base16 -import testUtils.{Boxes, Commitment, TestSuite} - - -class PermitTest extends TestSuite { - val sk: BigInt = Utils.randBigInt - - val networkConfig: (ErgoNetwork, Network, MainTokens) = Utils.selectConfig("cardano", "mainnet") - val contracts = new Contracts(networkConfig._1, (networkConfig._2, networkConfig._3)) - - def getProver(): ErgoProver = { - networkConfig._1.ergoClient.execute(ctx => { - ctx.newProverBuilder().withDLogSecret(sk.bigInteger).build() - }) - } - - def generateRandomWIDList(count: Int): Seq[Array[Byte]] = { - var res: Seq[Array[Byte]] = Seq() - while (count > res.length) { - res = res ++ Seq(Base16.decode(Boxes.getRandomHexString()).get) - } - res - } - - property("test get permit when there is no other permit on network") { - networkConfig._1.ergoClient.execute(ctx => { - try { - val prover = getProver() - val userBox = Boxes.createBoxForUser(ctx, prover.getAddress, 1e9.toLong, new ErgoToken(networkConfig._3.RSN, 10000L)) - val repoBox = Boxes.createRepo(ctx, 100000, 1L, Seq(), Seq()).convertToInputWith(Boxes.getRandomHexString(), 0) - val repoOut = Boxes.createRepo(ctx, 99900, 10001L, Seq(repoBox.getId.getBytes), Seq(100L)) - val permitBox = Boxes.createPermitBox(ctx, 100L, repoBox.getId.getBytes) - val WID = Boxes.createBoxCandidateForUser(ctx, prover.getAddress, Configs.minBoxValue, new ErgoToken(repoBox.getId.getBytes, 1L)) - val tx = ctx.newTxBuilder().addInputs(repoBox, userBox) - .fee(Configs.fee) - .addOutputs(repoOut, permitBox, WID) - .sendChangeTo(prover.getAddress) - .build() - prover.sign(tx) - } catch { - case exp: Throwable => - fail("transaction not signed") - } - }) - } - - property("test get permit when there is another permit on network") { - networkConfig._1.ergoClient.execute(ctx => { - try { - val prover = getProver() - val userBox = Boxes.createBoxForUser(ctx, prover.getAddress, 1e9.toLong, new ErgoToken(networkConfig._3.RSN, 10000L)) - val otherWID = Base16.decode(Boxes.getRandomHexString()).get - val repoBox = Boxes.createRepo(ctx, 100000, 5801L, Seq(otherWID), Seq(58L)).convertToInputWith(Boxes.getRandomHexString(), 0) - val repoOut = Boxes.createRepo(ctx, 99900, 15801L, Seq(otherWID, repoBox.getId.getBytes), Seq(58L, 100L)) - val permitBox = Boxes.createPermitBox(ctx, 100L, repoBox.getId.getBytes) - val WID = Boxes.createBoxCandidateForUser(ctx, prover.getAddress, Configs.minBoxValue, new ErgoToken(repoBox.getId.getBytes, 1L)) - val tx = ctx.newTxBuilder().addInputs(repoBox, userBox) - .fee(Configs.fee) - .addOutputs(repoOut, permitBox, WID) - .sendChangeTo(prover.getAddress) - .build() - prover.sign(tx) - } catch { - case exp: Throwable => - fail("transaction not signed") - } - }) - } - - property("test extend permit when there is just one permit on network") { - networkConfig._1.ergoClient.execute(ctx => { - try { - val prover = getProver() - val WID = Base16.decode(Boxes.getRandomHexString()).get - val userBox = Boxes.createBoxForUser(ctx, prover.getAddress, 1e9.toLong, new ErgoToken(WID, 1L), new ErgoToken(networkConfig._3.RSN, 10000L)) - val repoBox = Boxes.createRepo(ctx, 100000, 5801L, Seq(WID), Seq(58L)).convertToInputWith(Boxes.getRandomHexString(), 0) - val repoOut = Boxes.createRepoWithR7(ctx, 99900, 15801L, Seq(WID), Seq(158L), 1) - val permitBox = Boxes.createPermitBox(ctx, 100L, WID) - val WIDBox = Boxes.createBoxCandidateForUser(ctx, prover.getAddress, Configs.minBoxValue, new ErgoToken(WID, 1L)) - val tx = ctx.newTxBuilder().addInputs(repoBox, userBox) - .fee(Configs.fee) - .addOutputs(repoOut, permitBox, WIDBox) - .sendChangeTo(prover.getAddress) - .build() - prover.sign(tx) - } catch { - case exp: Throwable => - println(exp.toString) - fail("transaction not signed") - } - }) - } - - property("test extend first permit when there is other permits on network") { - networkConfig._1.ergoClient.execute(ctx => { - try { - val prover = getProver() - val WID = Base16.decode(Boxes.getRandomHexString()).get - val otherWID = Base16.decode(Boxes.getRandomHexString()).get - val userBox = Boxes.createBoxForUser(ctx, prover.getAddress, 1e9.toLong, new ErgoToken(WID, 1L), new ErgoToken(networkConfig._3.RSN, 10000L)) - val repoBox = Boxes.createRepo(ctx, 100000, 5801L, Seq(WID, otherWID), Seq(58L, 64L)).convertToInputWith(Boxes.getRandomHexString(), 0) - val repoOut = Boxes.createRepoWithR7(ctx, 99900, 15801L, Seq(WID, otherWID), Seq(158L, 64L), 1) - val permitBox = Boxes.createPermitBox(ctx, 100L, WID) - val WIDBox = Boxes.createBoxCandidateForUser(ctx, prover.getAddress, Configs.minBoxValue, new ErgoToken(WID, 1L)) - val tx = ctx.newTxBuilder().addInputs(repoBox, userBox) - .fee(Configs.fee) - .addOutputs(repoOut, permitBox, WIDBox) - .sendChangeTo(prover.getAddress) - .build() - prover.sign(tx) - } catch { - case exp: Throwable => - println(exp.toString) - fail("transaction not signed") - } - }) - } - - property("test extend middle permit when there is other permits on network") { - networkConfig._1.ergoClient.execute(ctx => { - try { - val prover = getProver() - val WID = Base16.decode(Boxes.getRandomHexString()).get - val otherWID = Base16.decode(Boxes.getRandomHexString()).get - val otherWID2 = Base16.decode(Boxes.getRandomHexString()).get - val userBox = Boxes.createBoxForUser(ctx, prover.getAddress, 1e9.toLong, new ErgoToken(WID, 1L), new ErgoToken(networkConfig._3.RSN, 10000L)) - val repoBox = Boxes.createRepo(ctx, 100000, 5801L, Seq(otherWID, WID, otherWID2), Seq(32L, 58L, 64L)).convertToInputWith(Boxes.getRandomHexString(), 0) - val repoOut = Boxes.createRepoWithR7(ctx, 99900, 15801L, Seq(otherWID, WID, otherWID2), Seq(32L, 158L, 64L), 2) - val permitBox = Boxes.createPermitBox(ctx, 100L, WID) - val WIDBox = Boxes.createBoxCandidateForUser(ctx, prover.getAddress, Configs.minBoxValue, new ErgoToken(WID, 1L)) - val tx = ctx.newTxBuilder().addInputs(repoBox, userBox) - .fee(Configs.fee) - .addOutputs(repoOut, permitBox, WIDBox) - .sendChangeTo(prover.getAddress) - .build() - prover.sign(tx) - } catch { - case exp: Throwable => - println(exp.toString) - fail("transaction not signed") - } - }) - } - - property("test extend permit while mutating other permits amount on repo") { - networkConfig._1.ergoClient.execute(ctx => { - try { - val prover = getProver() - val WID = Base16.decode(Boxes.getRandomHexString()).get - val otherWID = Base16.decode(Boxes.getRandomHexString()).get - val otherWID2 = Base16.decode(Boxes.getRandomHexString()).get - val userBox = Boxes.createBoxForUser(ctx, prover.getAddress, 1e9.toLong, new ErgoToken(WID, 1L), new ErgoToken(networkConfig._3.RSN, 10000L)) - val repoBox = Boxes.createRepo(ctx, 100000, 5801L, Seq(otherWID, WID, otherWID2), Seq(32L, 58L, 64L)).convertToInputWith(Boxes.getRandomHexString(), 0) - val repoOut = Boxes.createRepoWithR7(ctx, 99900, 15801L, Seq(otherWID, WID, otherWID2), Seq(32L, 158L, 63L), 2) - val permitBox = Boxes.createPermitBox(ctx, 100L, WID) - val WIDBox = Boxes.createBoxCandidateForUser(ctx, prover.getAddress, Configs.minBoxValue, new ErgoToken(WID, 1L)) - val tx = ctx.newTxBuilder().addInputs(repoBox, userBox) - .fee(Configs.fee) - .addOutputs(repoOut, permitBox, WIDBox) - .sendChangeTo(prover.getAddress) - .build() - prover.sign(tx) - fail("transaction should not sign, the permit amounts have changed") - } catch { - case exp: Throwable => - println(exp.toString) - } - }) - } - - property("test extend permit while mutating other permits WID on repo") { - networkConfig._1.ergoClient.execute(ctx => { - try { - val prover = getProver() - val WID = Base16.decode(Boxes.getRandomHexString()).get - val otherWID = Base16.decode(Boxes.getRandomHexString()).get - val otherWID2 = Base16.decode(Boxes.getRandomHexString()).get - val userBox = Boxes.createBoxForUser(ctx, prover.getAddress, 1e9.toLong, new ErgoToken(WID, 1L), new ErgoToken(networkConfig._3.RSN, 10000L)) - val repoBox = Boxes.createRepo(ctx, 100000, 5801L, Seq(otherWID, WID, otherWID2), Seq(32L, 58L, 64L)).convertToInputWith(Boxes.getRandomHexString(), 0) - val repoOut = Boxes.createRepoWithR7(ctx, 99900, 15801L, Seq(otherWID, WID, otherWID), Seq(32L, 158L, 63L), 2) - val permitBox = Boxes.createPermitBox(ctx, 100L, WID) - val WIDBox = Boxes.createBoxCandidateForUser(ctx, prover.getAddress, Configs.minBoxValue, new ErgoToken(WID, 1L)) - val tx = ctx.newTxBuilder().addInputs(repoBox, userBox) - .fee(Configs.fee) - .addOutputs(repoOut, permitBox, WIDBox) - .sendChangeTo(prover.getAddress) - .build() - prover.sign(tx) - fail("transaction should not sign, the permit WID have changed") - } catch { - case exp: Throwable => - println(exp.toString) - } - }) - } - - property("test partially return permits") { - networkConfig._1.ergoClient.execute(ctx => { - try { - val prover = getProver() - val userWID = Base16.decode(Boxes.getRandomHexString()).get - val WIDs = Seq( - Base16.decode(Boxes.getRandomHexString()).get, - Base16.decode(Boxes.getRandomHexString()).get, - userWID, - Base16.decode(Boxes.getRandomHexString()).get - ) - val repoBox = Boxes.createRepo(ctx, 100000, 32001L, WIDs, Seq(100L, 120L, 60L, 40L)).convertToInputWith(Boxes.getRandomHexString(), 0) - val permitBox = Boxes.createPermitBox(ctx, 60L, userWID).convertToInputWith(Boxes.getRandomHexString(), 0) - val WIDBox = Boxes.createBoxForUser(ctx, prover.getAddress, 1e9.toLong, new ErgoToken(userWID, 1L)) - val repoOut = Boxes.createRepoWithR7(ctx, 100020, 30001L, WIDs, Seq(100L, 120L, 40L, 40L), 3) - val permitOut = Boxes.createPermitBox(ctx, 40L, userWID) - val userOut = Boxes.createBoxCandidateForUser(ctx, prover.getAddress, 1e8.toLong, new ErgoToken(userWID, 1), new ErgoToken(networkConfig._3.RSN, 2000)) - val tx = ctx.newTxBuilder().addInputs(repoBox, permitBox, WIDBox) - .fee(Configs.fee) - .addOutputs(repoOut, permitOut, userOut) - .sendChangeTo(prover.getAddress) - .build() - prover.sign(tx) - } catch { - case exp: Throwable => - fail("transaction not signed") - } - }) - } - - property("test complete return permits") { - networkConfig._1.ergoClient.execute(ctx => { - var userIndex = 0 - try { - val prover = getProver() - val WIDs = generateRandomWIDList(6) - val amounts = Seq(100L, 120L, 140L, 20L, 40L, 250L) - for (userIndex <- 0 to 5) { - val userWID = WIDs(userIndex) - val totalPermitOut = amounts.sum - val repoBox = Boxes.createRepo(ctx, 100000L, totalPermitOut * 100L + 1L, WIDs, amounts).convertToInputWith(Boxes.getRandomHexString(), 0) - val permitBox = Boxes.createPermitBox(ctx, amounts(userIndex), userWID).convertToInputWith(Boxes.getRandomHexString(), 0) - val WIDBox = Boxes.createBoxForUser(ctx, prover.getAddress, 1e9.toLong, new ErgoToken(userWID, 1L)) - val outputWIDs = WIDs.take(userIndex) ++ WIDs.drop(userIndex + 1) - val outAmounts = amounts.take(userIndex) ++ amounts.drop(userIndex + 1) - val repoOut = Boxes.createRepoWithR7(ctx, 100000L + amounts(userIndex), (totalPermitOut - amounts(userIndex)) * 100 + 1, outputWIDs, outAmounts, userIndex + 1) // 4 + first element in WID list is chain name - val userOut = Boxes.createBoxCandidateForUser(ctx, prover.getAddress, 1e8.toLong, new ErgoToken(userWID, 1), new ErgoToken(networkConfig._3.RSN, 2000)) - val tx = ctx.newTxBuilder().addInputs(repoBox, permitBox, WIDBox) - .fee(Configs.fee) - .addOutputs(repoOut, userOut) - .sendChangeTo(prover.getAddress) - .build() - prover.sign(tx) - } - } catch { - case exp: Throwable => - println(exp.toString) - fail(s"transaction not signed for user index ${userIndex}") - } - }) - } - - property("test redeem repo") { - networkConfig._1.ergoClient.execute(ctx => { - try { - val prover = getProver() - val WIDs = generateRandomWIDList(7) - val repoBox = Boxes.createRepo(ctx, 100000, 32001L, WIDs, Seq(100L, 120L, 140L, 20L, 40L, 250L, 123L)).convertToInputWith(Boxes.getRandomHexString(), 0) - val guardBox = Boxes.createBoxForUser(ctx, prover.getAddress, 1e9.toLong, new ErgoToken(networkConfig._3.GuardNFT, 1L)) - val inputs = Seq(repoBox, guardBox) - val boxBuilder = ctx.newTxBuilder().outBoxBuilder() - .contract(ctx.newContract(prover.getAddress.asP2PK().script)) - .registers( - repoBox.getRegisters.get(0), - repoBox.getRegisters.get(1), - repoBox.getRegisters.get(2), - ) - boxBuilder.value(inputs.map(item => item.getValue).sum - Configs.fee) - inputs.foreach(box => box.getTokens.forEach(token => boxBuilder.tokens(token))) - val tx = ctx.newTxBuilder().addInputs(repoBox, guardBox) - .fee(Configs.fee) - .addOutputs(boxBuilder.build()) - .sendChangeTo(prover.getAddress) - .build() - prover.sign(tx) - } catch { - case exp: Throwable => - println(exp.toString) - fail("transaction not signed") - } - }) - } - - property("test create new commitment") { - networkConfig._1.ergoClient.execute(ctx => { - try { - val commitment = new Commitment() - val prover = getProver() - val WID = Base16.decode(Boxes.getRandomHexString()).get - val box1 = Boxes.createBoxForUser(ctx, prover.getAddress, 1e9.toLong, new ErgoToken(WID, 1)) - val permit = Boxes.createPermitBox(ctx, 10L, WID).convertToInputWith(Boxes.getRandomHexString(), 0) - val permitOut = Boxes.createPermitBox(ctx, 9L, WID) - val commitmentBox = Boxes.createCommitment(ctx, WID, commitment.requestId(), commitment.hash(WID)) - val tx = ctx.newTxBuilder().addInputs(permit, box1) - .fee(Configs.fee) - .sendChangeTo(prover.getAddress) - .addOutputs(permitOut, commitmentBox) - .build() - prover.sign(tx) - } catch { - case exp: Throwable => - println(exp.toString) - fail("transaction not signed") - } - }) - } - - property("test redeem commitment") { - networkConfig._1.ergoClient.execute(ctx => { - try { - val commitment = new Commitment() - val prover = getProver() - val WID = Base16.decode(Boxes.getRandomHexString()).get - val commitmentBox = Boxes.createCommitment(ctx, WID, commitment.requestId(), commitment.hash(WID)).convertToInputWith(Boxes.getRandomHexString(), 1) - val box = Boxes.createBoxForUser(ctx, prover.getAddress, 1e9.toLong, new ErgoToken(WID, 1L)) - val newPermit = Boxes.createPermitBox(ctx, 1, WID) - val inputs = Seq(commitmentBox, box) - val redeemUnsigned = ctx.newTxBuilder().addInputs(inputs: _*) - .fee(Configs.fee) - .addOutputs(newPermit) - .sendChangeTo(prover.getAddress) - .build() - prover.sign(redeemUnsigned) - } catch { - case exp: Throwable => - println(exp.toString) - fail("transaction not signed") - } - }) - } - - property("test create event trigger for all watcher") { - networkConfig._1.ergoClient.execute(ctx => { - try { - val commitment = new Commitment() - val prover = getProver() - val WIDs = generateRandomWIDList(5) - val repo = Boxes.createRepo(ctx, 1000L, 10001L, WIDs, Seq(10L, 30L, 20L, 35L, 5L)).convertToInputWith(Boxes.getRandomHexString(), 1) - val commitments = WIDs.map(WID => Boxes.createCommitment(ctx, WID, commitment.requestId(), commitment.hash(WID)).convertToInputWith(Boxes.getRandomHexString(), 1)) - val trigger = Boxes.createTriggerEventBox(ctx, WIDs, commitment) - val feeBox = Boxes.createBoxForUser(ctx, prover.getAddress, 1e9.toLong) - val tx = ctx.newTxBuilder().addInputs(commitments ++ Seq(feeBox): _*) - .fee(Configs.fee) - .addOutputs(trigger) - .addDataInputs(repo) - .sendChangeTo(prover.getAddress) - .build() - prover.sign(tx) - } catch { - case exp: Throwable => - println(exp.toString) - fail("transaction not signed") - } - }) - } - - property("test create event trigger for minimum required watcher") { - networkConfig._1.ergoClient.execute(ctx => { - try { - val commitment = new Commitment() - val prover = getProver() - val WIDs = generateRandomWIDList(7) - val repo = Boxes.createRepo(ctx, 1000L, 10001L, WIDs, Seq(10L, 30L, 20L, 25L, 5L, 4L, 6L)).convertToInputWith(Boxes.getRandomHexString(), 1) - val commitments = WIDs.slice(0, 4).map(WID => Boxes.createCommitment(ctx, WID, commitment.requestId(), commitment.hash(WID)).convertToInputWith(Boxes.getRandomHexString(), 1)) - val trigger = Boxes.createTriggerEventBox(ctx, WIDs.slice(0, 4), commitment) - val feeBox = Boxes.createBoxForUser(ctx, prover.getAddress, 1e9.toLong) - val tx = ctx.newTxBuilder().addInputs(commitments ++ Seq(feeBox): _*) - .fee(Configs.fee) - .addOutputs(trigger) - .addDataInputs(repo) - .sendChangeTo(prover.getAddress) - .build() - prover.sign(tx) - } catch { - case exp: Throwable => - println(exp.toString) - fail("transaction not signed") - } - }) - } - - property("test cant create event trigger for lower than minimum required watcher") { - networkConfig._1.ergoClient.execute(ctx => { - assertThrows[AnyRef] { - val commitment = new Commitment() - val prover = getProver() - val WIDs = generateRandomWIDList(7) - val repo = Boxes.createRepo(ctx, 1000L, 10001L, WIDs, Seq(10L, 30L, 20L, 25L, 5L, 4L, 6L)).convertToInputWith(Boxes.getRandomHexString(), 1) - val commitments = WIDs.slice(0, 3).map(WID => Boxes.createCommitment(ctx, WID, commitment.requestId(), commitment.hash(WID)).convertToInputWith(Boxes.getRandomHexString(), 1)) - val trigger = Boxes.createTriggerEventBox(ctx, WIDs.slice(0, 3), commitment) - val feeBox = Boxes.createBoxForUser(ctx, prover.getAddress, 1e9.toLong) - val tx = ctx.newTxBuilder().addInputs(commitments ++ Seq(feeBox): _*) - .fee(Configs.fee) - .addOutputs(trigger) - .addDataInputs(repo) - .sendChangeTo(prover.getAddress) - .build() - prover.sign(tx) - } - }) - } - - property("test guard payment without not merged commitment") { - networkConfig._1.ergoClient.execute(ctx => { - try { - val commitment = new Commitment() - val prover = getProver() - val WIDs = generateRandomWIDList(7) - val secrets = (0 until 7).map(ind => Utils.randBigInt.bigInteger) - val guards = secrets.map(item => ctx.newProverBuilder().withDLogSecret(item).build()) - val guardsPks = guards.map(item => item.getAddress.getPublicKey.pkBytes).toArray - val userFee: Long = math.floor((commitment.fee * 0.6) / WIDs.length).toLong - val guardBox = Boxes.createGuardNftBox(ctx, guardsPks, 5, 6).convertToInputWith(Boxes.getRandomHexString(), 0) - val lockBox = Boxes.createLockBox( - ctx, - Configs.minBoxValue, - new ErgoToken(commitment.targetChainTokenId, userFee * WIDs.length) - ).convertToInputWith(Boxes.getRandomHexString(), 0) - val eventTrigger = Boxes.createTriggerEventBox(ctx, WIDs, commitment).convertToInputWith(Boxes.getRandomHexString(), 1) - val newPermits = WIDs.map(item => { - Boxes.createPermitBox(ctx, 1, item, new ErgoToken(commitment.targetChainTokenId, userFee)) - }) - val inputs = Seq(eventTrigger, lockBox) - val unsignedTx = ctx.newTxBuilder().addInputs(inputs: _*) - .addDataInputs(guardBox) - .fee(Configs.fee) - .sendChangeTo(prover.getAddress) - .addOutputs(newPermits: _*) - .build() - val multiSigProverBuilder = ctx.newProverBuilder() - secrets.map(item => multiSigProverBuilder.withDLogSecret(item)) - val multiSigProver = multiSigProverBuilder.build() - multiSigProver.sign(unsignedTx) - } catch { - case exp: Throwable => - println(exp.toString) - fail("transaction not signed") - } - }) - } - - property("test guard payment with not merged commitment") { - networkConfig._1.ergoClient.execute(ctx => { - try { - val commitment = new Commitment() - val prover = getProver() - val WIDs = generateRandomWIDList(7) - val notMergedWIDs = generateRandomWIDList(3) - val allWIDs = WIDs ++ notMergedWIDs - val notMergedCommitments = notMergedWIDs.map(WID => Boxes.createCommitment(ctx, WID, commitment.requestId(), commitment.hash(WID)).convertToInputWith(Boxes.getRandomHexString(), 1)) - val secrets = (0 until 7).map(ind => Utils.randBigInt.bigInteger) - val guards = secrets.map(item => ctx.newProverBuilder().withDLogSecret(item).build()) - val guardsPks = guards.map(item => item.getAddress.getPublicKey.pkBytes).toArray - val eventTrigger = Boxes.createTriggerEventBox(ctx, WIDs, commitment).convertToInputWith(Boxes.getRandomHexString(), 1) - val userFee: Long = math.floor((commitment.fee * 0.6) / allWIDs.length).toLong - val guardBox = Boxes.createGuardNftBox(ctx, guardsPks, 5, 6).convertToInputWith(Boxes.getRandomHexString(), 0) - val lockBox = Boxes.createLockBox( - ctx, - Configs.minBoxValue, - new ErgoToken(commitment.targetChainTokenId, userFee * (WIDs ++ notMergedWIDs).length) - ).convertToInputWith(Boxes.getRandomHexString(), 0) - val newPermits = (WIDs ++ notMergedWIDs).map(item => { - Boxes.createPermitBox(ctx, 1, item, new ErgoToken(commitment.targetChainTokenId, userFee)) - }) - val inputs = Seq(eventTrigger) ++ notMergedCommitments ++ Seq(lockBox) - val unsignedTx = ctx.newTxBuilder().addInputs(inputs: _*) - .fee(Configs.fee) - .addDataInputs(guardBox) - .sendChangeTo(prover.getAddress) - .addOutputs(newPermits: _*) - .build() - val multiSigProverBuilder = ctx.newProverBuilder() - secrets.map(item => multiSigProverBuilder.withDLogSecret(item)) - val multiSigProver = multiSigProverBuilder.build() - multiSigProver.sign(unsignedTx) - } catch { - case exp: Throwable => - println(exp.toString) - fail("transaction not signed") - } - }) - } - - property("test create fraud from event trigger") { - networkConfig._1.ergoClient.execute(ctx => { - try { - val prover = getProver() - val commitment = new Commitment() - val WIDs = generateRandomWIDList(7) - val triggerEvent = Boxes.createTriggerEventBox(ctx, WIDs, commitment).convertToInputWith(Boxes.getRandomHexString(), 0) - val box1 = Boxes.createBoxForUser(ctx, prover.getAddress, 1e9.toLong, new ErgoToken(networkConfig._2.tokens.CleanupNFT, 1L)) - val newFraud = WIDs.indices.map(index => { - Boxes.createFraudBox(ctx, WIDs(index)) - }) - val unsignedTx = ctx.newTxBuilder().addInputs(triggerEvent, box1) - .fee(Configs.fee) - .sendChangeTo(prover.getAddress) - .addOutputs(newFraud: _*) - .build() - prover.sign(unsignedTx) - } catch { - case exp: Throwable => - println(exp.toString) - fail("transaction not signed") - } - }) - } - - property("test redeem fraud to repo") { - networkConfig._1.ergoClient.execute(ctx => { - var userIndex = 0 - try { - val prover = getProver() - val globalWIDs = generateRandomWIDList(3) - val globalAmounts = Seq(10L, 1L, 2L) - val repo = Boxes.createRepo(ctx, 1000L, 100 * globalAmounts.sum + 1, globalWIDs, globalAmounts).convertToInputWith(Boxes.getRandomHexString(), 1) - for (userIndex <- 0 until globalWIDs.length) { - var amounts = globalAmounts.map(item => item).toArray - var WIDs = globalWIDs.map(item => item).toArray - val WID = WIDs(userIndex) - val box2 = Boxes.createBoxForUser(ctx, prover.getAddress, 1e9.toLong, new ErgoToken(networkConfig._2.tokens.CleanupNFT, 1L)) - val fraud = Boxes.createFraudBox(ctx, WID).convertToInputWith(Boxes.getRandomHexString(), 1) - val RWTCount = repo.getTokens.get(1).getValue.toLong + 1 - val RSNCount = repo.getTokens.get(2).getValue.toLong - 100 - if (amounts(userIndex) > 1) { - amounts(userIndex) -= 1 - } else { - amounts = amounts.patch(userIndex, Nil, 1) - WIDs = WIDs.patch(userIndex, Nil, 1) - } - val repoCandidate = Boxes.createRepoWithR7(ctx, RWTCount, RSNCount, WIDs, amounts, userIndex + 1) - val unsigned = ctx.newTxBuilder().addInputs(repo, fraud, box2) - .fee(Configs.fee) - .addOutputs(repoCandidate) - .sendChangeTo(prover.getAddress) - .build() - val signed = prover.sign(unsigned) - } - } catch { - case exp: Throwable => - println(exp.toString) - fail(s"transaction not signed on index ${userIndex}") - } - }) - } - - property("test spent lock script when guard token is in data input") { - networkConfig._1.ergoClient.execute(ctx => { - try { - val secrets = (0 until 7).map(ind => Utils.randBigInt.bigInteger) - val guards = secrets.map(item => ctx.newProverBuilder().withDLogSecret(item).build()) - val guardsPks = guards.map(item => item.getAddress.getPublicKey.pkBytes).toArray - val prover = getProver() - val box = Boxes.createCustomBox(ctx, contracts.Lock._1, 1e9.toLong) - val boxNft = Boxes.createGuardNftBox(ctx, guardsPks, 5, 6).convertToInputWith(Boxes.getRandomHexString(), 32) - val outBox = Boxes.createBoxCandidateForUser(ctx, prover.getAddress, 5e8.toLong) - val multiSigProverBuilder = ctx.newProverBuilder() - secrets.slice(0, 5).foreach(item => multiSigProverBuilder.withDLogSecret(item)) - val multiSigProver = multiSigProverBuilder.build() - val tx = ctx.newTxBuilder().addInputs(box) - .fee(Configs.fee) - .addOutputs(outBox) - .addDataInputs(boxNft) - .sendChangeTo(prover.getAddress) - .build() - multiSigProver.sign(tx) - } catch { - case exp: Throwable => - println(exp.toString) - fail(s"transaction not signed") - } - }) - } - - property("test guard nft box spend with update with 6 sign.") { - networkConfig._1.ergoClient.execute(ctx => { - try { - val secrets = (0 until 7).map(ind => Utils.randBigInt.bigInteger) - val guards = secrets.map(item => ctx.newProverBuilder().withDLogSecret(item).build()) - val prover = guards(0) - val guardsPks = guards.map(item => item.getAddress.getPublicKey.pkBytes).toArray - val signBox = Boxes.createGuardNftBox(ctx, guardsPks, 5, 6).convertToInputWith(Boxes.getRandomHexString(), 1) - val box2 = Boxes.createBoxForUser(ctx, guards(0).getAddress, 1e9.toLong) - val outSignBox = Boxes.createGuardNftBox(ctx, guardsPks, 4,6) - val outBox = Boxes.createBoxCandidateForUser(ctx, guards(1).getAddress, 1e8.toLong) - val tx = ctx.newTxBuilder().addInputs(signBox, box2) - .fee(Configs.fee) - .addOutputs(outSignBox, outBox) - .sendChangeTo(prover.getAddress) - .build() - val proverBuilder = ctx.newProverBuilder() - secrets.slice(0, 6).map(item => proverBuilder.withDLogSecret(item)) - proverBuilder.build().sign(tx) - } catch { - case exp: Throwable => - println(exp.toString) - fail(s"transaction not signed") - } - }) - } - - property("test guard nft box cant spend with update with 5 sign") { - networkConfig._1.ergoClient.execute(ctx => { - assertThrows[AnyRef] { - val secrets = (0 until 7).map(ind => Utils.randBigInt.bigInteger) - val guards = secrets.map(item => ctx.newProverBuilder().withDLogSecret(item).build()) - val prover = guards(0) - val guardsPks = guards.map(item => item.getAddress.getPublicKey.pkBytes).toArray - val signBox = Boxes.createGuardNftBox(ctx, guardsPks, 5, 6).convertToInputWith(Boxes.getRandomHexString(), 1) - val box2 = Boxes.createBoxForUser(ctx, guards(0).getAddress, 1e9.toLong) - val outSignBox = Boxes.createGuardNftBox(ctx, guardsPks, 4,6) - val outBox = Boxes.createBoxCandidateForUser(ctx, guards(1).getAddress, 1e8.toLong) - val tx = ctx.newTxBuilder().addInputs(signBox, box2) - .fee(Configs.fee) - .addOutputs(outSignBox, outBox) - .sendChangeTo(prover.getAddress) - .build() - val proverBuilder = ctx.newProverBuilder() - secrets.slice(0, 5).map(item => proverBuilder.withDLogSecret(item)) - proverBuilder.build().sign(tx) - } - }) - } -} diff --git a/src/test/scala/testUtils/Boxes.scala b/src/test/scala/testUtils/Boxes.scala index 3fda8f4..e25eb90 100644 --- a/src/test/scala/testUtils/Boxes.scala +++ b/src/test/scala/testUtils/Boxes.scala @@ -82,6 +82,10 @@ object Boxes { createBoxCandidateForUser(ctx, address, amount).convertToInputWith(getRandomHexString(), 0) } + def createBoxForUser(ctx: BlockchainContext, address: Address, amount: Long, WID: Array[Byte]): InputBox = { + createBoxCandidateForUser(ctx, address, amount, WID).convertToInputWith(getRandomHexString(), 0) + } + def createBoxCandidateForUser(ctx: BlockchainContext, address: Address, amount: Long, tokens: ErgoToken*): OutBox = { val txb = ctx.newTxBuilder() txb.outBoxBuilder() @@ -99,6 +103,17 @@ object Boxes { .build() } + def createBoxCandidateForUser(ctx: BlockchainContext, address: Address, amount: Long, WID: Array[Byte]): OutBox = { + val txb = ctx.newTxBuilder() + txb.outBoxBuilder() + .value(amount) + .contract(ctx.newContract(address.asP2PK().script)) + .registers( + ErgoValueBuilder.buildFor(Colls.fromArray(Seq(WID).map(item => Colls.fromArray(item)).toArray)), + ) + .build() + } + def createCustomBox(ctx: BlockchainContext, contract: ErgoContract, amount: Long, tokens: ErgoToken*): InputBox = { val txb = ctx.newTxBuilder() txb.outBoxBuilder() @@ -118,25 +133,43 @@ object Boxes { } - def createRepo( - ctx: BlockchainContext, - RWTCount: Long, - RSNCount: Long, - users: Seq[Array[Byte]], - userRWT: Seq[Long] - ): OutBox = { + + def createWatcherCollateralBoxInput(ctx: BlockchainContext, erg: Long, rsn: Long, wid: Array[Byte]): InputBox = { + Boxes.createWatcherCollateralBox(ctx, erg, rsn, wid).convertToInputWith(getRandomHexString(), 3) + } + def createWatcherCollateralBox(ctx: BlockchainContext, erg: Long, rsn: Long, wid: Array[Byte]): OutBox = { + ctx.newTxBuilder().outBoxBuilder() + .value(erg) + .tokens( + new ErgoToken(networkConfig._3.RSN, rsn) + ) + .contract(contracts.WatcherCollateral._1) + .registers( + ErgoValueBuilder.buildFor(Colls.fromArray(wid)), + ).build() + } + + def createRepoWithTokens( + ctx: BlockchainContext, + RWTCount: Long, + RSNCount: Long, + users: Seq[Array[Byte]], + userRWT: Seq[Long], + nftId: String, + rwtId: String + ): OutBox = { val txB = ctx.newTxBuilder() val repoBuilder = txB.outBoxBuilder() .value(Configs.minBoxValue) .tokens( - new ErgoToken(networkConfig._3.RepoNFT, 1), - new ErgoToken(networkConfig._2.tokens.RWTId, RWTCount) + new ErgoToken(nftId, 1), + new ErgoToken(rwtId, RWTCount) ) .contract(contracts.RWTRepo._1) .registers( ErgoValueBuilder.buildFor(Colls.fromArray((Seq("ADA".getBytes()) ++ users).map(item => Colls.fromArray(item)).toArray)), ErgoValueBuilder.buildFor(Colls.fromArray((Seq(0L) ++ userRWT).toArray)), - ErgoValueBuilder.buildFor(Colls.fromArray(Array(100L, 51L, 0L, 9999L))), + ErgoValueBuilder.buildFor(Colls.fromArray(Array(10L, 51L, 0L, 9999L, 1e9.toLong, 100))), ) if (RSNCount > 0) { repoBuilder.tokens(new ErgoToken(networkConfig._3.RSN, RSNCount)) @@ -144,14 +177,24 @@ object Boxes { repoBuilder.build() } - def createRepoWithR7( + def createRepo( ctx: BlockchainContext, RWTCount: Long, RSNCount: Long, users: Seq[Array[Byte]], userRWT: Seq[Long], - R7: Int ): OutBox = { + return createRepoWithTokens(ctx, RWTCount, RSNCount, users, userRWT, networkConfig._3.RepoNFT, networkConfig._2.tokens.RWTId) + } + + def createRepoWithR7( + ctx: BlockchainContext, + RWTCount: Long, + RSNCount: Long, + users: Seq[Array[Byte]], + userRWT: Seq[Long], + R7: Int + ): OutBox = { val txB = ctx.newTxBuilder() val repoBuilder = txB.outBoxBuilder() .value(Configs.minBoxValue) @@ -163,7 +206,7 @@ object Boxes { .registers( ErgoValueBuilder.buildFor(Colls.fromArray((Seq("ADA".getBytes()) ++ users).map(item => Colls.fromArray(item)).toArray)), ErgoValueBuilder.buildFor(Colls.fromArray((Seq(0L) ++ userRWT).toArray)), - ErgoValueBuilder.buildFor(Colls.fromArray(Array(100L, 51L, 0L, 9999L))), + ErgoValueBuilder.buildFor(Colls.fromArray(Array(10L, 51L, 0L, 9999L, 1e9.toLong, 100))), ErgoValueBuilder.buildFor(R7) ) if (RSNCount > 0) { @@ -187,23 +230,55 @@ object Boxes { .build() } - def createFraudBox(ctx: BlockchainContext, WID: Array[Byte]): OutBox = { + def createInvalidMixedPermitBox(ctx: BlockchainContext, RWTCount: Long, WID: Array[Byte], tokens: ErgoToken*): OutBox = { + val txB = ctx.newTxBuilder() + val tokensSeq = Seq( + new ErgoToken(networkConfig._3.RSN, RWTCount), + new ErgoToken(networkConfig._2.tokens.RWTId, RWTCount), + ) ++ tokens.toSeq + txB.outBoxBuilder() + .value(Configs.minBoxValue) + .contract(contracts.WatcherPermit._1) + .tokens(tokensSeq: _*) + .registers( + ErgoValueBuilder.buildFor(Colls.fromArray(Seq(WID).map(item => Colls.fromArray(item)).toArray)), + // this value must exists in case of redeem commitment. + ErgoValueBuilder.buildFor(Colls.fromArray(Seq(Array(0.toByte)).map(item => Colls.fromArray(item)).toArray)), + ) + .build() + } + def createInvalidPermitBox(ctx: BlockchainContext, RWTCount: Long, WID: Array[Byte], tokens: ErgoToken*): OutBox = { + val txB = ctx.newTxBuilder() + val tokensSeq = Seq(new ErgoToken(networkConfig._3.RSN, RWTCount)) ++ tokens.toSeq + txB.outBoxBuilder() + .value(Configs.minBoxValue) + .contract(contracts.WatcherPermit._1) + .tokens(tokensSeq: _*) + .registers( + ErgoValueBuilder.buildFor(Colls.fromArray(Seq(WID).map(item => Colls.fromArray(item)).toArray)), + // this value must exists in case of redeem commitment. + ErgoValueBuilder.buildFor(Colls.fromArray(Seq(Array(0.toByte)).map(item => Colls.fromArray(item)).toArray)), + ) + .build() + } + + def createFraudBox(ctx: BlockchainContext, WID: Array[Byte], RWTCount: Long): OutBox = { val txB = ctx.newTxBuilder() txB.outBoxBuilder() .value(Configs.minBoxValue) .contract(contracts.Fraud._1) - .tokens(new ErgoToken(networkConfig._2.tokens.RWTId, 1)) + .tokens(new ErgoToken(networkConfig._2.tokens.RWTId, RWTCount)) .registers( ErgoValueBuilder.buildFor(Colls.fromArray(Seq(WID).map(item => Colls.fromArray(item)).toArray)), ) .build() } - def createCommitment(ctx: BlockchainContext, WID: Array[Byte], RequestId: Array[Byte], commitment: Array[Byte]): OutBox = { + def createCommitment(ctx: BlockchainContext, WID: Array[Byte], RequestId: Array[Byte], commitment: Array[Byte], RWTCount: Long): OutBox = { ctx.newTxBuilder().outBoxBuilder() .value(Configs.minBoxValue) .contract(contracts.Commitment._1) - .tokens(new ErgoToken(networkConfig._2.tokens.RWTId, 1)) + .tokens(new ErgoToken(networkConfig._2.tokens.RWTId, RWTCount)) .registers( ErgoValueBuilder.buildFor(Colls.fromArray(Seq(WID).map(item => Colls.fromArray(item)).toArray)), ErgoValueBuilder.buildFor(Colls.fromArray(Seq(RequestId).map(item => Colls.fromArray(item)).toArray)), @@ -212,12 +287,12 @@ object Boxes { ).build() } - def createTriggerEventBox(ctx: BlockchainContext, WID: Seq[Array[Byte]], commitment: Commitment): OutBox = { + def createTriggerEventBox(ctx: BlockchainContext, WID: Seq[Array[Byte]], commitment: Commitment, RWTCount: Long): OutBox = { val size = WID.length ctx.newTxBuilder().outBoxBuilder() .value(Configs.minBoxValue * size) .contract(contracts.WatcherTriggerEvent._1) - .tokens(new ErgoToken(networkConfig._2.tokens.RWTId, size)) + .tokens(new ErgoToken(networkConfig._2.tokens.RWTId, RWTCount)) .registers( ErgoValueBuilder.buildFor(Colls.fromArray(WID.map(item => Colls.fromArray(item)).toArray)), ErgoValueBuilder.buildFor(Colls.fromArray(commitment.partsArray().map(item => Colls.fromArray(item)))), diff --git a/tokensMap/WrappedAda.mainnet-softlaunch.json b/tokensMap/Ada.mainnet-loen.json similarity index 59% rename from tokensMap/WrappedAda.mainnet-softlaunch.json rename to tokensMap/Ada.mainnet-loen.json index 338b667..444e292 100644 --- a/tokensMap/WrappedAda.mainnet-softlaunch.json +++ b/tokensMap/Ada.mainnet-loen.json @@ -1,7 +1,7 @@ { "ergo": { - "tokenId": "4ed6449240d166b0e44c529b5bf06d210796473d3811b9aa0e15329599164c24", - "tokenName": "RST-ADA.V-test", + "tokenId": "0bf47c19e49944a38948c635c0aef93d89737aa68df5ad881b07c8f9a63e398d", + "name": "wrADA-loen", "decimals": 6, "metaData": { "type": "EIP-004", @@ -9,12 +9,13 @@ } }, "cardano": { - "fingerprint": "lovelace", + "tokenId": "ada", "policyId": "", "assetName": "414441", + "name": "ADA", "decimals": 6, "metaData": { - "type": "ADA", + "type": "native", "residency": "native" } } diff --git a/tokensMap/Ada.mainnet-public-soft-launch.json b/tokensMap/Ada.mainnet-public-launch.json similarity index 65% rename from tokensMap/Ada.mainnet-public-soft-launch.json rename to tokensMap/Ada.mainnet-public-launch.json index 2b572ce..764397d 100644 --- a/tokensMap/Ada.mainnet-public-soft-launch.json +++ b/tokensMap/Ada.mainnet-public-launch.json @@ -1,7 +1,7 @@ { "ergo": { - "tokenId": "38cb230f68a28436fb3b73ae4b927626673e4620bc7c94896178567d436e416b", - "tokenName": "RstAdaVTest2", + "tokenId": "e023c5f382b6e96fbd878f6811aac73345489032157ad5affb84aefd4956c297", + "name": "rsADA", "decimals": 6, "metaData": { "type": "EIP-004", @@ -9,9 +9,10 @@ } }, "cardano": { - "fingerprint": "lovelace", + "tokenId": "ada", "policyId": "", "assetName": "414441", + "name": "ADA", "decimals": 6, "metaData": { "type": "native", diff --git a/tokensMap/CardanoToken.mainnet-softlaunch.json b/tokensMap/CardanoToken.mainnet-softlaunch.json deleted file mode 100644 index 2f91083..0000000 --- a/tokensMap/CardanoToken.mainnet-softlaunch.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "ergo": { - "tokenId": "c59e86ef9d0280de582d6266add18fca339a77dfb321268e83033fe47101dc4d", - "tokenName": "RST-Cardano-Token.V-test", - "decimals": 4, - "metaData": { - "type": "EIP-004", - "residency": "wrapped" - } - }, - "cardano": { - "fingerprint": "asset14d5uaspqyn87ecp8j4yawmguwrgun5086533z7", - "policyId": "cfd784ccfe5fe8ce7d09f4ddb65624378cc8022bf3ec240cf41ea6be", - "assetName": "43617264616e6f546f6b656e7654657374", - "decimals": 0, - "metaData": { - "type": "native", - "residency": "native" - } - } -} diff --git a/tokensMap/Comet.mainnet-public-soft-launch.json b/tokensMap/Comet.mainnet-public-soft-launch.json deleted file mode 100644 index 93b963c..0000000 --- a/tokensMap/Comet.mainnet-public-soft-launch.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "ergo": { - "tokenId": "0cd8c9f416e5b1ca9f986a7f10a84191dfb85941619e49e53c0dc30ebf83324b", - "tokenName": "COMET", - "decimals": 0, - "metaData": { - "type": "EIP-004", - "residency": "native" - } - }, - "cardano": { - "fingerprint": "asset1m62zdrt2fhlm9wpqrskxka6t0wvq5vag58cytl", - "policyId": "bb2250e4c589539fd141fbbd2c322d380f1ce2aaef812cd87110d61b", - "assetName": "527374434f4d4554565465737432", - "decimals": 0, - "metaData": { - "type": "CIP26", - "residency": "wrapped" - } - } -} diff --git a/tokensMap/ERT-WT.mainnet-alpha-1.json b/tokensMap/ERT-WT.mainnet-alpha-1.json deleted file mode 100644 index 398b9b5..0000000 --- a/tokensMap/ERT-WT.mainnet-alpha-1.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "ergo": { - "tokenId": "517c91b4ea680166ddd3f67b27b0274c20bbd2aeb82b60eaf5bf5471b37f684a", - "tokenName": "Ergo Rosen Test Wrap Token", - "decimals": 0, - "metaData": { - "type": "EIP-004", - "residency": "wrapped" - } - }, - "cardano": { - "fingerprint": "asset1vwun0a52xjv5tc2x92wgr6x3p6q3u4frnmq8q0", - "policyId": "8e3e19131f96c186335b23bf7983ab00867a987ca900abb27ae0f2b9", - "assetName": "52535457", - "decimals": 0, - "metaData": { - "type": "native", - "residency": "native" - } - } -} diff --git a/tokensMap/Erg.mainnet-loen.json b/tokensMap/Erg.mainnet-loen.json new file mode 100644 index 0000000..2b0320c --- /dev/null +++ b/tokensMap/Erg.mainnet-loen.json @@ -0,0 +1,22 @@ +{ + "ergo": { + "tokenId": "erg", + "name": "ERG", + "decimals": 9, + "metaData": { + "type": "native", + "residency": "native" + } + }, + "cardano": { + "tokenId": "fca58ef8ba9ef1961e132b611de2f8abcd2f34831e615a6f80c5bb48.77724552472d6c6f656e", + "policyId": "fca58ef8ba9ef1961e132b611de2f8abcd2f34831e615a6f80c5bb48", + "assetName": "77724552472d6c6f656e", + "name": "wrERG-loen", + "decimals": 9, + "metaData": { + "type": "CIP26", + "residency": "wrapped" + } + } +} diff --git a/tokensMap/Erg.mainnet-public-soft-launch.json b/tokensMap/Erg.mainnet-public-launch.json similarity index 51% rename from tokensMap/Erg.mainnet-public-soft-launch.json rename to tokensMap/Erg.mainnet-public-launch.json index e28678c..37e53e6 100644 --- a/tokensMap/Erg.mainnet-public-soft-launch.json +++ b/tokensMap/Erg.mainnet-public-launch.json @@ -1,7 +1,7 @@ { "ergo": { "tokenId": "erg", - "tokenName": "erg", + "name": "ERG", "decimals": 9, "metaData": { "type": "native", @@ -9,9 +9,10 @@ } }, "cardano": { - "fingerprint": "asset1epz7gzjqg5py4xrgps6ccv25gz7gd6v8e5gmxx", - "policyId": "d2f6eb37450a3d568de93d623e69bd0ba1238daacc883d75736abd23", - "assetName": "527374457267565465737432", + "tokenId": "04b95368393c821f180deee8229fbd941baaf9bd748ebcdbf7adbb14.7273455247", + "policyId": "04b95368393c821f180deee8229fbd941baaf9bd748ebcdbf7adbb14", + "assetName": "7273455247", + "name": "rsERG", "decimals": 9, "metaData": { "type": "CIP26", diff --git a/tokensMap/ErgoToken.mainnet-softlaunch.json b/tokensMap/ErgoToken.mainnet-softlaunch.json deleted file mode 100644 index 6150b5d..0000000 --- a/tokensMap/ErgoToken.mainnet-softlaunch.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "ergo": { - "tokenId": "a1143e81c5ab485a807e6f0f76af1dd70cc5359b29e0b1229d0edfe490d33b67", - "tokenName": "Ergo-Token.V-test", - "decimals": 4, - "metaData": { - "type": "EIP-004", - "residency": "native" - } - }, - "cardano": { - "fingerprint": "asset1v25eyenfzrv6me9hw4vczfprdctzy5ed3x99p2", - "policyId": "48d4a14b8407af8407702df3afda4cc8a945ce55235e9808c62c5f9b", - "assetName": "5273744572676f546f6b656e7654657374", - "decimals": 0, - "metaData": { - "type": "native", - "residency": "wrapped" - } - } -} diff --git a/tokensMap/Hosky.mainnet-public-soft-launch.json b/tokensMap/Hosky.mainnet-public-soft-launch.json deleted file mode 100644 index 1253b23..0000000 --- a/tokensMap/Hosky.mainnet-public-soft-launch.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "ergo": { - "tokenId": "b37bfa41c2d9e61b4e478ddfc459a03d25b658a2305ffb428fbc47ad6abbeeaa", - "tokenName": "RstHoskyVTest2", - "decimals": 0, - "metaData": { - "type": "EIP-004", - "residency": "wrapped" - } - }, - "cardano": { - "fingerprint": "asset17q7r59zlc3dgw0venc80pdv566q6yguw03f0d9", - "policyId": "a0028f350aaabe0545fdcb56b039bfb08e4bb4d8c4d7c3c7d481c235", - "assetName": "484f534b59", - "decimals": 0, - "metaData": { - "type": "CIP26", - "residency": "native" - } - } -} diff --git a/tokensMap/RSN.mainnet-loen.json b/tokensMap/RSN.mainnet-loen.json new file mode 100644 index 0000000..7efa929 --- /dev/null +++ b/tokensMap/RSN.mainnet-loen.json @@ -0,0 +1,22 @@ +{ + "ergo": { + "tokenId": "cdf549fccbb09ab8f38ecbf9a5ed37c926707753adf8fed19b039684a0772bfe", + "name": "RSN-loen", + "decimals": 3, + "metaData": { + "type": "EIP-004", + "residency": "native" + } + }, + "cardano": { + "tokenId": "fca58ef8ba9ef1961e132b611de2f8abcd2f34831e615a6f80c5bb48.777252534e2d6c6f656e", + "policyId": "fca58ef8ba9ef1961e132b611de2f8abcd2f34831e615a6f80c5bb48", + "assetName": "777252534e2d6c6f656e", + "name": "wrRSN-loen", + "decimals": 3, + "metaData": { + "type": "CIP26", + "residency": "wrapped" + } + } +} diff --git a/tokensMap/RSN.mainnet-public-launch.json b/tokensMap/RSN.mainnet-public-launch.json new file mode 100644 index 0000000..2784189 --- /dev/null +++ b/tokensMap/RSN.mainnet-public-launch.json @@ -0,0 +1,22 @@ +{ + "ergo": { + "tokenId": "8b08cdd5449a9592a9e79711d7d79249d7a03c535d17efaee83e216e80a44c4b", + "name": "RSN", + "decimals": 3, + "metaData": { + "type": "EIP-004", + "residency": "native" + } + }, + "cardano": { + "tokenId": "04b95368393c821f180deee8229fbd941baaf9bd748ebcdbf7adbb14.727352534e", + "policyId": "04b95368393c821f180deee8229fbd941baaf9bd748ebcdbf7adbb14", + "assetName": "727352534e", + "name": "rsRSN", + "decimals": 3, + "metaData": { + "type": "CIP26", + "residency": "wrapped" + } + } +} diff --git a/tokensMap/SigRSV.mainnet-public-launch.json b/tokensMap/SigRSV.mainnet-public-launch.json new file mode 100644 index 0000000..7e1cfeb --- /dev/null +++ b/tokensMap/SigRSV.mainnet-public-launch.json @@ -0,0 +1,22 @@ +{ + "ergo": { + "tokenId": "003bd19d0187117f130b62e1bcab0939929ff5c7709f843c5c4dd158949285d0", + "name": "SigRSV", + "decimals": 0, + "metaData": { + "type": "EIP-004", + "residency": "native" + } + }, + "cardano": { + "tokenId": "e6e5dbd69a741e73b0d89d50c23c96dff38a60ab26dcd40077e23704.7273536967525356", + "policyId": "e6e5dbd69a741e73b0d89d50c23c96dff38a60ab26dcd40077e23704", + "assetName": "7273536967525356", + "name": "rsSigRSV", + "decimals": 0, + "metaData": { + "type": "CIP26", + "residency": "wrapped" + } + } +} diff --git a/tokensMap/SigUSD.mainnet-public-launch.json b/tokensMap/SigUSD.mainnet-public-launch.json new file mode 100644 index 0000000..54ca20d --- /dev/null +++ b/tokensMap/SigUSD.mainnet-public-launch.json @@ -0,0 +1,22 @@ +{ + "ergo": { + "tokenId": "03faf2cb329f2e90d6d23b58d91bbb6c046aa143261cc21f52fbe2824bfcbf04", + "name": "SigUSD", + "decimals": 2, + "metaData": { + "type": "EIP-004", + "residency": "native" + } + }, + "cardano": { + "tokenId": "04b95368393c821f180deee8229fbd941baaf9bd748ebcdbf7adbb14.7273536967555344", + "policyId": "04b95368393c821f180deee8229fbd941baaf9bd748ebcdbf7adbb14", + "assetName": "7273536967555344", + "name": "rsRSN", + "decimals": 2, + "metaData": { + "type": "CIP26", + "residency": "wrapped" + } + } +} diff --git a/tokensMap/Token.mainnet-loen.json b/tokensMap/Token.mainnet-loen.json new file mode 100644 index 0000000..032c6c5 --- /dev/null +++ b/tokensMap/Token.mainnet-loen.json @@ -0,0 +1,22 @@ +{ + "ergo": { + "tokenId": "9d9887f126735dbce60a4562eb2f8b19a9100f85a0cb1c88fd3963d7a39dec46", + "name": "wrToken-loen", + "decimals": 2, + "metaData": { + "type": "EIP-004", + "residency": "wrapped" + } + }, + "cardano": { + "tokenId": "fca58ef8ba9ef1961e132b611de2f8abcd2f34831e615a6f80c5bb48.546f6b656e2d6c6f656e", + "policyId": "fca58ef8ba9ef1961e132b611de2f8abcd2f34831e615a6f80c5bb48", + "assetName": "546f6b656e2d6c6f656e", + "name": "Token-loen", + "decimals": 2, + "metaData": { + "type": "CIP26", + "residency": "native" + } + } +} diff --git a/tokensMap/WrappedAda.mainnet-alpha-1.json b/tokensMap/WrappedAda.mainnet-alpha-1.json deleted file mode 100644 index 9f5254a..0000000 --- a/tokensMap/WrappedAda.mainnet-alpha-1.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "ergo": { - "tokenId": "fc6c2070eb004fc08fcde1514dee56b1d0587477748d8af647179b098f52f559", - "tokenName": "RsADA", - "decimals": 6, - "metaData": { - "type": "EIP-004", - "residency": "wrapped" - } - }, - "cardano": { - "fingerprint": "lovelace", - "policyId": "", - "assetName": "414441", - "decimals": 6, - "metaData": { - "type": "ADA", - "residency": "native" - } - } -} diff --git a/tokensMap/WrappedErg.mainnet-alpha-1.json b/tokensMap/WrappedErg.mainnet-alpha-1.json deleted file mode 100644 index 5ae9566..0000000 --- a/tokensMap/WrappedErg.mainnet-alpha-1.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "ergo": { - "tokenId": "erg", - "tokenName": "erg", - "decimals": 9, - "metaData": { - "type": "ERG", - "residency": "native" - } - }, - "cardano": { - "fingerprint": "asset1p40r0eun2alszlxhj7k4uylya4cj54lxkjjmsm", - "policyId": "0dad352d8f0d5ce3f5be8b025d6a16141ecceab5a921871792d91f47", - "assetName": "5273455247", - "decimals": 0, - "metaData": { - "type": "native", - "residency": "wrapped" - } - } -} diff --git a/tokensMap/WrappedErg.mainnet-softlaunch.json b/tokensMap/WrappedErg.mainnet-softlaunch.json deleted file mode 100644 index 78b9bdb..0000000 --- a/tokensMap/WrappedErg.mainnet-softlaunch.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "ergo": { - "tokenId": "erg", - "tokenName": "erg", - "decimals": 9, - "metaData": { - "type": "ERG", - "residency": "native" - } - }, - "cardano": { - "fingerprint": "asset1jy5q5a0vpstutq5q6d8cgdmrd4qu5yefcdnjgz", - "policyId": "ef6aa6200e21634e58ce6796b4b61d1d7d059d2ebe93c2996eeaf286", - "assetName": "5273744552477654657374", - "decimals": 0, - "metaData": { - "type": "native", - "residency": "wrapped" - } - } -}