diff --git a/.gitignore b/.gitignore index de738f5..902511c 100644 --- a/.gitignore +++ b/.gitignore @@ -5,7 +5,10 @@ # The erdpy output output* +deployOutput wallets/ commands.txt wallet.pem +erdpy.data-storage.json +deploy-devnet.interaction.json diff --git a/README.md b/README.md index 84601a1..ad3d8f6 100644 --- a/README.md +++ b/README.md @@ -1,34 +1,234 @@ -# Itheum Core Elrond - Claim Smart Contract -The core itheum elrond smart contract for `claims` +# Itheum Core Elrond - Claims Smart Contract -### How to Dev -## Build Environment -- You need `erdpy` on your system. Install as per `https://docs.elrond.com/sdk-and-tools/erdpy/installing-erdpy/`. (pay attention to min python version) -- On Mac, this method worked `https://docs.elrond.com/sdk-and-tools/erdpy/installing-erdpy/`. After installation, you need to restart your terminal session. If you are using ZSH shell, then this will work `source ~/.zshrc`. After this is done, to test if it works, you should be able to run `erdpy` anywhere and get a response. You `erdpy --version` to find version. +## Abstract -## Build App -- Clone the repo +The claims smart contract is the tool that stands at the heart of collaboration between Itheum and its community. Whether it's a reward for helping the project, an airdrop or some allocation of tokens, the claims smart contract is the tool that allows Itheum to give tokens to all community members that are using the Elrond blockchain. -## Build via IDE -- The framework is designed to be easiest to use with the Elrond IDE VSCode extension: https://marketplace.visualstudio.com/items?itemName=Elrond.vscode-elrond-ide +## Introduction -## Manual build -- To build a smart contract without the IDE, run the following command in the project root: +This contract allows the owner of it to send tokens to the smart contract and reserve them for a specific address of their choice. There are 3 types of claims that are defined in the smart contract: rewards, airdrops and allocations. If a user has claims, they can harvest each type individually or can choose to harvest all of them in the same transaction. The contract is designed such that a user can only take their designated tokens from the contract. +## Prerequisites + +This documentation assumes the user has previous programming experience. Moreover, the user should have a basic understanding of the Elrond blockchain. If you are new to the blockchain, please refer to the [Elrond documentation](https://docs.elrond.com/). In order to develop Elrond smart contract related solutions, one needs to have installed [erdpy](https://docs.elrond.com/sdk-and-tools/erdpy/installing-erdpy/). + +Understanding this document is also easier if one knows how [ESDT token transactions](https://docs.elrond.com/developers/esdt-tokens/#transfers-to-a-smart-contract) are structured on the Elrond blockchain. + +## Itheum deployed claims contract addresses + +| Devnet | Mainnet | +| -------------------------------------------------------------- | ---------------- | +| erd1qqqqqqqqqqqqqpgqtywnp7z0war94rpzk00p2n2wjwaws2xr7yqsejxy7f | Not deployed yet | + +## Endpoints + +### Setup endpoints + +The setup workflow for the claims smart contract is as follows: + +- The SC deployment +- Setting up the claims token. + +#### init + +```rust + #[init] + fn init(&self); +``` + +The init function is called when deploying or upgrading the smart contract. It receives no arguments and it the only thing it does for the claims smart contract is to pause it. + +#### setClaimToken + +```rust + #[endpoint(setClaimToken)] + fn set_claim_token(&self, + token: TokenIdentifier + ); +``` + +Endpoint that sets the claims token. It can only be used once and it can only be called by the owner of the contract. +Call structure: "setClaimToken" + "@" + TokenIdentifier hex encoded +Example: "setClaimToken@49544845554d2d613631333137" + +### Only owner endpoints + +#### unpause + +```rust + #[endpoint(unpause)] + fn unpause(&self); +``` + +Endpoint that unpauses the claims harvesting from the smart contract. +Call structure: "unpause" +Example: "unpause" + +#### addPrivilegedAddress + +```rust + #[endpoint(addPrivilegedAddress)] + fn add_privileged_address(&self, + address: ManagedAddress + ); +``` + +Endpoint that gives an address privileges to add claims or pause the contract. The contract can only store up to two privileged addresses at a time. +Call structure: "addPrivilegedAddress" + "@" + Address hex encoded +Example: "addPrivilegedAddress@8bc1730b9afdd4546a039c3baa043f37525822100e04cfc986b6955e05cbf101" + +#### removePrivilegedAddress + +```rust + #[endpoint(removePrivilegedAddress)] + fn remove_privileged_address(&self, + address: ManagedAddress + ); +``` + +Endpoint that removes privileges of an already privileged address. +Call structure: "removePrivilegedAddress" + "@" + Address hex encoded +Example: "removePrivilegedAddress@8bc1730b9afdd4546a039c3baa043f37525822100e04cfc986b6955e05cbf101" + +#### removeClaim + +```rust + #[endpoint(removeClaim)] + fn remove_claim(&self, + address: &ManagedAddress, + claim_type: ClaimType, + amount: BigUint + ); +``` + +Endpoint that allows the owner of the smart contract to remove a claim from the smart contract. Receives an address, the claim type and the amount of tokens to remove as arguments. +Call structure: "removeClaim" + "@" +address hex encoded + "@" + claim type hex encoded + "@" + amount to remove hex encoded +Example: "removeClaim@8bc1730b9afdd4546a039c3baa043f37525822100e04cfc986b6955e05cbf101@01@8ac7230489e80000" + +#### removeClaims + +```rust + #[endpoint(removeClaims)] + fn remove_claims(&self, + claims: MultiValueEncoded>, + ); +``` + +Similar to the removeClaim endpoint, but it allows the owner to remove multiple claims from the smart contract through a single transaction. Receives a list of claims as arguments. +Call structure: "removeClaims" + "@" + address hex encoded + "@" + claim type hex encoded + "@" + amount to remove hex encoded (but can add as many pairs as needed) +Example: "removeClaims@8bc1730b9afdd4546a039c3baa043f37525822100e04cfc986b6955e05cbf101@01@8ac7230489e80000" + +### Priviledged address endpoints + +These endpoints are endpoints that are callable by both the owner of the Smart Contract and up to two other addresses designated by the owner to have extra privileges. + +#### pause + +```rust + #[endpoint(pause)] + fn pause(&self); +``` + +Endpoint that pauses the claims harvesting from the smart contract. +Call structure: "pause" +Example: "pause" + +#### addClaim + +```rust + #[payable("*")] + #[endpoint(addClaim)] + fn add_claim(&self, + address: &ManagedAddress, + claim_type: ClaimType + ); +``` + +Endpoint that allows the owner of the smart contract to add a claim to the smart contract. Receives an address and the claim type as arguments. The claim is set for the address and the claim type received as arguments. +Call structure:"ESDTTransfer"+ "@" + TokenIdentifier hex encoded + "@" + amount hex encoded + "@" + "addClaim" hex encoded + "@" + address hex encoded + "@" + claim type hex encoded +Example: "ESDTTransfer@49544845554d2d613631333137@8ac7230489e80000@616464436c61696d@8bc1730b9afdd4546a039c3baa043f37525822100e04cfc986b6955e05cbf101@00" + +#### addClaims + +```rust + #[payable("*")] + #[endpoint(addClaims)] + fn add_claims(&self, + claims: MultiValueEncoded> + ); +``` + +Similar to the addClaim endpoint, but it allows the owner to add multiple claims to the smart contract through a single transaction. Receives a list of claims as arguments. +Call structure: "ESDTTransfer" + "@" + TokenIdentifier hex encoded + "@" + total amounts of tokens added to claims hex encoded + "@" + "addClaims" hex encoded + "@" + address hex encoded + "@" + claim type hex encoded + "@" + amount for this address hex encoded (but can add as many address/claim type/amount pairs as needed) +Example: "ESDTTransfer@49544845554d2d613631333137@8ac7230489e80000@616464436c61696d73@8bc1730b9afdd4546a039c3baa043f37525822100e04cfc986b6955e05cbf101@00@8ac7230489e80000" + +### Public endpoints + +#### claim + +```rust + #[endpoint(claim)] + fn harvest_claim(&self, + claim_type: OptionalValue + ); +``` + +Endpoint that allows anyone to harvest their designated claims. Allows the user to input a claim type as argument, but that argument is optional. If no claim type is provided, the user will receive all claims attributed to themseles. If a claim type is provided as argument, the user will only receive that claim type. + +Call structure without claim type: "harvestClaim" +Example without claim type: "harvestClaim" + +Call structure wit claim type: "harvestClaim" + "@" + claim type hex encoded +Example with claim type: "harvestClaim@02" + +## Development + +This smart contract, albeit being a simple one, aims to set the standard when it comes to the quality of testing and documentation for which smart contract developers should aim. The above average level of documentation present aims specifically to take advantage of our open source codebase in order to learn, contribute and take good practices from the smart contract. + +### Architecture + +The Claims Smart Contract is structured in 5 files: + +- events: This files has all the defined events of the smart contract. They are emitted whenever something relevant happens in the smart contract. Their role is to make debugging and logging easier and to allow data collecting based on the smart contract. +- storage: This file has all the storage/memory declaration of the smart contract. This is the main file that allows the smart contract to save data in the blockchain. +- views: This file contains all the read-only endpoints of the smart contract. These endpoints are used to retrieve relevant data from the smart contract. +- requirements: This file contains requirements for the endpoints of the smart contract. In order to avoid code duplication, encourage a healthy project structure and increase code readability we have decided to separate most of the requirements that would otherwise have been duplicated from the endpoints and put them here. +- lib: This is the main file of the smart contract, where all the logic of the smart contract is implemented. This connects all the other files (modules) and uses them to implement what is the claims contract itself. + +### How to test + +The tests are located in the tests folder, in the rust_tests file. In order to run the tests one can use the command: + +```shell + cargo test --package claims --test rust_tests -- --nocapture ``` -./build-wasm.sh + +Another way of running the tests is by using the rust-analyzer extension in Visual Studio Code, which is also very helpful for Elrond Smart Contract development. If one has the extension installed, they can go open and go to the top of the rust_tests file and click the Run Tests button. + +Note: In order to run the tests, one has to use the rust nightly version. One can switch to the nightly version by using: + +```shell + rustup default nightly +``` + +### How to deploy + +In order to deploy the smart contract on devnet one can use the interaction snippets present in the devnet.snippets file (which is located in the interactions folder). Before using the snippets, make sure to add your pem file in the root of the project under the name "wallet.pem" (or change the name to whichever one you wish to use in the interaction snippets). If you need info about how to derive a pem file you can find them [here](https://docs.elrond.com/sdk-and-tools/erdpy/deriving-the-wallet-pem-file/). To run the functions from the interaction file, one can use: + +```shell + source interaction/devnet.snippets.sh +``` + +After using that, to deploy one can simply use: + +```shell + deploy ``` -### How to Test -- To run tests you need to swap to the nightly via `rustup default nightly` -- In VS-code, install the `rust-analyzer extension`. You also need `rustup` which installs rust on your system. See here for requirements `https://marketplace.visualstudio.com/items?itemName=rust-lang.rust-analyzer` -- In VS-code you can go on the tests folder and at the top of the first line of the empty_rust_test file you should have a `run tests` button. Click it -- Note: You can also run tests like so: `cargo test --package claims --test empty_rust_test -- --nocapture` +### How to interact -### Deployed Contract Addresses -Devnet | Mainnet ---- | --- -0x | 0x +After deployment, one can interact with the smart contract and test its functionality. To do so, one can use the interaction snippets already presented above. More explanations can be found about the snippets inside the devnet.snippets file. +## Contributing -## Known Issues +Feel free the contact the development team if you wish to contribute or if you have any questions. If you find any issues, please report them in the Issues sections of the repository. You can also create your own pull requests which will be analyzed by the team. diff --git a/deployOutput b/deployOutput deleted file mode 100644 index c689914..0000000 --- a/deployOutput +++ /dev/null @@ -1,19 +0,0 @@ -{ - "emitted_tx": { - "tx": { - "nonce": 3676, - "value": "0", - "receiver": "erd1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq6gq4hu", - "sender": "erd130qhxzu6lh29g6srnsa65pplxaf9sgsspczvljvxk624upwt7yqstswx9l", - "gasPrice": 1000000000, - "gasLimit": 150000000, - "data": "MDA2MTczNmQwMTAwMDAwMDAxNmUxMzYwMDAwMDYwMDE3ZjAxN2Y2MDAyN2Y3ZjAxN2Y2MDAyN2Y3ZjAwNjAwMDAxN2Y2MDAxN2YwMDYwMDM3ZjdmN2YwMDYwMDQ3ZjdmN2Y3ZjAwNjAwMzdmN2Y3ZjAxN2Y2MDA1N2Y3ZjdlN2Y3ZjAxN2Y2MDAyN2Y3ZTAwNjAwMTdmMDE3ZTYwMDE3ZTAwNjAwNDdmN2Y3ZjdmMDE3ZjYwMDAwMTdlNjAwNTdmN2Y3ZjdmN2YwMDYwMDM3ZjdlN2YwMDYwMDQ3ZjdlN2Y3ZjAwNjAwMjdmN2YwMTdlMDJhODA3MjcwMzY1NmU3NjBiNzM2OTY3NmU2MTZjNDU3MjcyNmY3MjAwMDMwMzY1NmU3NjBhNmQ0Mjc1NjY2NjY1NzI0ZTY1NzcwMDA0MDM2NTZlNzYwZDZkNDI3NTY2NjY2NTcyNDE3MDcwNjU2ZTY0MDAwMjAzNjU2ZTc2MDk2ZDQyNzU2NjY2NjU3MjQ1NzEwMDAyMDM2NTZlNzYxYjZkNjE2ZTYxNjc2NTY0NTQ3MjYxNmU3MzY2NjU3MjU2NjE2Yzc1NjU0NTc4NjU2Mzc1NzQ2NTAwMDkwMzY1NmU3NjA5NjI2OTY3NDk2ZTc0NDE2NDY0MDAwNjAzNjU2ZTc2MGE2MjY5Njc0OTZlNzQ1MzY5Njc2ZTAwMDEwMzY1NmU3NjEyNmQ0Mjc1NjY2NjY1NzI0MTcwNzA2NTZlNjQ0Mjc5NzQ2NTczMDAwODAzNjU2ZTc2MjI2ZDYxNmU2MTY3NjU2NDRkNzU2Yzc0Njk1NDcyNjE2ZTczNjY2NTcyNDU1MzQ0NTQ0ZTQ2NTQ0NTc4NjU2Mzc1NzQ2NTAwMDkwMzY1NmU3NjBkNmQ2MTZlNjE2NzY1NjQ0MzYxNmM2YzY1NzIwMDA1MDM2NTZlNzYxMzZkNjE2ZTYxNjc2NTY0NGY3NzZlNjU3MjQxNjQ2NDcyNjU3MzczMDAwNTAzNjU2ZTc2MTM2NzY1NzQ0ZTc1NmQ0NTUzNDQ1NDU0NzI2MTZlNzM2NjY1NzI3MzAwMDQwMzY1NmU3NjEyNjI2OTY3NDk2ZTc0NDc2NTc0NDM2MTZjNmM1NjYxNmM3NTY1MDAwNTAzNjU2ZTc2MTY2MjY5Njc0OTZlNzQ0NzY1NzQ0NTUzNDQ1NDQzNjE2YzZjNTY2MTZjNzU2NTAwMDUwMzY1NmU3NjEwNjc2NTc0NDU1MzQ0NTQ1NDZmNmI2NTZlNGU2MTZkNjUwMDAxMDM2NTZlNzYwZjZkNDI3NTY2NjY2NTcyNTM2NTc0NDI3OTc0NjU3MzAwMDgwMzY1NmU3NjEyNmQ0Mjc1NjY2NjY1NzI0NzY1NzQ0MTcyNjc3NTZkNjU2ZTc0MDAwMjAzNjU2ZTc2MTI2ZDYxNmU2MTY3NjU2NDUzNjk2NzZlNjE2YzQ1NzI3MjZmNzIwMDA1MDM2NTZlNzYxMDZkNDI3NTY2NjY2NTcyNDc2NTc0NGM2NTZlNjc3NDY4MDAwMTAzNjU2ZTc2MTk2MjY5Njc0OTZlNzQ0NzY1NzQ1NTZlNzM2OTY3NmU2NTY0NDE3MjY3NzU2ZDY1NmU3NDAwMDMwMzY1NmU3NjBmNjc2NTc0NGU3NTZkNDE3MjY3NzU2ZDY1NmU3NDczMDAwNDAzNjU2ZTc2MDk2MjY5Njc0OTZlNzQ1Mzc1NjIwMDA2MDM2NTZlNzYxOTZkNDI3NTY2NjY2NTcyNDY3MjZmNmQ0MjY5Njc0OTZlNzQ1NTZlNzM2OTY3NmU2NTY0MDAwMjAzNjU2ZTc2MTc2ZDQyNzU2NjY2NjU3MjU0NmY0MjY5Njc0OTZlNzQ1NTZlNzM2OTY3NmU2NTY0MDAwMjAzNjU2ZTc2MTI2ZDQyNzU2NjY2NjU3MjUzNzQ2ZjcyNjE2NzY1NGM2ZjYxNjQwMDAyMDM2NTZlNzYxMzZkNDI3NTY2NjY2NTcyNTM3NDZmNzI2MTY3NjU1Mzc0NmY3MjY1MDAwMjAzNjU2ZTc2MGU2MjY5Njc0OTZlNzQ1MzY1NzQ0OTZlNzQzNjM0MDAwYTAzNjU2ZTc2MDk2MjY5Njc0OTZlNzQ0MzZkNzAwMDAyMDM2NTZlNzYwZjZkNjE2ZTYxNjc2NTY0NTc3MjY5NzQ2NTRjNmY2NzAwMDMwMzY1NmU3NjExNjc2NTc0NDE3MjY3NzU2ZDY1NmU3NDRjNjU2ZTY3NzQ2ODAwMDEwMzY1NmU3NjFiNzM2ZDYxNmM2YzQ5NmU3NDQ3NjU3NDU1NmU3MzY5Njc2ZTY1NjQ0MTcyNjc3NTZkNjU2ZTc0MDAwYjAzNjU2ZTc2MGU2MzY4NjU2MzZiNGU2ZjUwNjE3OTZkNjU2ZTc0MDAwMDAzNjU2ZTc2MTE2NzY1NzQ0MjZjNmY2MzZiNTQ2OTZkNjU3Mzc0NjE2ZDcwMDAwZTAzNjU2ZTc2MTQ3MzZkNjE2YzZjNDk2ZTc0NDY2OTZlNjk3MzY4NTM2OTY3NmU2NTY0MDAwYzAzNjU2ZTc2MTQ2MjY5Njc0OTZlNzQ0NjY5NmU2OTczNjg1NTZlNzM2OTY3NmU2NTY0MDAwNTAzNjU2ZTc2MTY3MzZkNjE2YzZjNDk2ZTc0NDY2OTZlNjk3MzY4NTU2ZTczNjk2NzZlNjU2NDAwMGMwMzY1NmU3NjBkNmQ0Mjc1NjY2NjY1NzI0NjY5NmU2OTczNjgwMDAxMDM2NTZlNzYwNjY2Njk2ZTY5NzM2ODAwMDMwMzY1NmU3NjEzNmQ0Mjc1NjY2NjY1NzI0NzY1NzQ0Mjc5NzQ2NTUzNmM2OTYzNjUwMDBkMDM1YjVhMGYwMDAxMDIwMzA2MDIwNDAxMDQwNTA0MDQwMDA1MDEwNzA4MDEwMTAzMDQwMTA1MDUwMDAwMDMwMjAyMGQwMzAzMDIwMjAxMDEwNDEwMDcwNzA1MDEwMzAxMDEwMTBiMDMwYTExMDYwMzAzMDEwMTAzMDMwMjA2MTIwMTAzMDYwNjAyMDIwNzA2MDYwNDAyMDIwNDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDUwMzAxMDAxMTA2MTkwMzdmMDE0MTgwODBjMDAwMGI3ZjAwNDFiY2QzYzAwMDBiN2YwMDQxYzBkM2MwMDAwYjA3ZWQwMTEyMDY2ZDY1NmQ2ZjcyNzkwMjAwMDQ2OTZlNjk3NDAwNzEwODYxNjQ2NDQzNmM2MTY5NmQwMDcyMDk2MTY0NjQ0MzZjNjE2OTZkNzMwMDczMDU2MzZjNjE2OTZkMDA3NDA4Njk3MzUwNjE3NTczNjU2NDAwNzUwNTcwNjE3NTczNjUwMDc2MGI3MjY1NmQ2Zjc2NjU0MzZjNjE2OTZkMDA3NzBjNzI2NTZkNmY3NjY1NDM2YzYxNjk2ZDczMDA3ODBlNzM2NTc0NTI2NTc3NjE3MjY0NTQ2ZjZiNjU2ZTAwNzkwOTc2Njk2NTc3NDM2YzYxNjk2ZDAwN2ExMDc2Njk2NTc3NDM2YzYxNjk2ZDQxNjQ2NDQ0NjE3NDY1MDA3YjExNzY2OTY1Nzc0MzZjNjE2OTZkNTc2OTc0Njg0NDYxNzQ2NTAwN2MwYTc2Njk2NTc3NDM2YzYxNjk2ZDczMDA3ZDEzNzY2OTY1Nzc1NDZmNmI2NTZlNDk2NDY1NmU3NDY5NjY2OTY1NzIwMDdlMDg2MzYxNmM2YzQyNjE2MzZiMDA3ZjBhNWY1ZjY0NjE3NDYxNWY2NTZlNjQwMzAxMGI1ZjVmNjg2NTYxNzA1ZjYyNjE3MzY1MDMwMjBhOWQzMDVhMmUwMDAyNDAyMDAxMjAwMjRkMDQ0MDIwMDIyMDA0NGQwZDAxMTAyODAwMGIxMDI4MDAwYjIwMDAyMDAyMjAwMTZiMzYwMjA0MjAwMDIwMDEyMDAzNmEzNjAyMDAwYjA2MDAxMDgwMDEwMDBiMGYwMTAxN2YxMDAxMjIwMTIwMDAxMDAyMWEyMDAxMGIwYjAwMjAwMDIwMDExMDAzNDEwMDRhMGIwOTAwMjAwMDIwMDExMDAwMDAwYmNiMDEwMTA1N2YyMzAwNDExMDZiMjIwMzI0MDA0MWRjODRjMDAwNDEwMDEwMmQyMTA1MTAyZTIxMDYwMjQwMjAwMTEwMmYwNDQwMjAwMDIwMDI0MjAwMjAwNTIwMDYxMDA0MWEwYzAxMGIxMDJlMjEwNzIwMDExMDI5MjEwNDEwMzAyMjAxMTAzMTIwMDEyMDAxMjAwMjEwMDUyMDAxMTAwNjA0NDAyMDA0MTAyZjFhMGIyMDAzNDIwMDM3MDIwNDIwMDMyMDAxNDEwODc0NDE4MDgwZmMwNzcxMjAwMTQxMTg3NDcyMjAwMTQxMDg3NjQxODBmZTAzNzEyMDAxNDExODc2NzI3MjM2MDIwYzIwMDMyMDA0NDEwODc0NDE4MDgwZmMwNzcxMjAwNDQxMTg3NDcyMjAwNDQxMDg3NjQxODBmZTAzNzEyMDA0NDExODc2NzI3MjM2MDIwMDIwMDcyMDAzNDExMDEwMDcxYTIwMDAyMDA3NDIwMDIwMDUyMDA2MTAwODFhMGIyMDAzNDExMDZhMjQwMDBiMTEwMTAxN2YxMDMwMjIwMjIwMDAyMDAxMTAwZjFhMjAwMjBiMTQwMTAxN2YxMDMwMjIwMDQxZGM4NGMwMDA0MTAwMTAwZjFhMjAwMDBiMDcwMDIwMDAxMDEyNDUwYjFiMDEwMTdmNDE5Yzg1YzAwMDQxOWM4NWMwMDAyODAyMDA0MTAxNmIyMjAwMzYwMjAwMjAwMDBiMDgwMDIwMDA0MjAwMTAxYTBiMGMwMTAxN2YxMDMwMjIwMDEwMDkyMDAwMGIwYzAxMDE3ZjEwMzAyMjAwMTAwYTIwMDAwYjE2MDAxMDMzMTAzMjEwMmEwNDQwMGYwYjQxODA4MGMwMDA0MTI0MTAwMDAwMGJhOTAxMDEwMzdmMjMwMDQxMjA2YjIyMDEyNDAwMDI0MDAyNDAwMjQwMDI0MDEwMGI0NTA0NDA0MTc1MjEwMjQxNzUxMDBjMGMwMTBiNDE3MzIxMDI0MTczMTAwZDEwMGIwZDAxMGIxMDJlMjEwMzBjMDEwYjIwMDE0MTE4NmE0MjAwMzcwMzAwMjAwMTQxMTA2YTQyMDAzNzAzMDAyMDAxNDEwODZhNDIwMDM3MDMwMDIwMDE0MjAwMzcwMzAwMDI0MDIwMDExMDBlMjIwMjQ1MDQ0MDEwMzAyMjAzNDFkYzg0YzAwMDQxMDAxMDBmMWEwYzAxMGIyMDAyNDEyMTRmMGQwMjIwMDEyMDAyMTAyZDIxMDMwYjQxNzMyMTAyMGIyMDAwMjAwMzM2MDIwNDIwMDAyMDAyMzYwMjAwMjAwMTQxMjA2YTI0MDAwZjBiMTAyODAwMGIwZDAwMjAwMDEwMzAyMjAwMTAxMDFhMjAwMDBiMzAwMTAxN2Y0MWE0ODBjMDAwNDExNzEwMmQyMjA0MjAwMDIwMDExMDA3MWEyMDA0NDFiYjgwYzAwMDQxMDMxMDA3MWEyMDA0MjAwMjIwMDMxMDA3MWEyMDA0MTAxMTAwMGIzMTAxMDE3ZjIwMDAyODAyMDAyMjAzNDFiOGQzYzAwMDI4MDIwMDRlMDQ0MDIwMDEyMDAyNDFiZTgwYzAwMDQxMTExMDM3MDAwYjIwMDAyMDAzNDEwMTZhMzYwMjAwMjAwMzBiNTIwMTAxN2UyMDAwMTAxZDQ1MDQ0MDQxMDAwZjBiMDI0MDIwMDAxMDFlMjIwMTQyODAwMjU0MDQ0MDIwMDFhNzIyMDA0MWZmMDE3MTQxMDM0ZjBkMDEyMDAwMGYwYjQxYWI4NGMwMDA0MTBhNDFmYTgwYzAwMDQxMGUxMDM3MDAwYjQxYWI4NGMwMDA0MTBhNDFjMjg0YzAwMDQxMGQxMDM3MDAwYjMyMDEwMTdmMTAyZTIxMDEwMzQwMjAwMDI4MDIwMDQxYjhkM2MwMDAyODAyMDA0ZTQ1MDQ0MDIwMDEyMDAwNDFiYzg0YzAwMDQxMDYxMDM4MTAzNjEwM2IwYzAxMGIwYjIwMDEwYjQ3MDEwMTdmMjMwMDQxMTA2YjIyMDIyNDAwMjAwMjIwMDE0MTA4NzQ0MTgwODBmYzA3NzEyMDAxNDExODc0NzIyMDAxNDEwODc2NDE4MGZlMDM3MTIwMDE0MTE4NzY3MjcyMzYwMjBjMjAwMDIwMDI0MTBjNmE0MTA0MTAwNzFhMjAwMjQxMTA2YTI0MDAwYjI1MDEwMTdmNDEwMDEwMzYyMjAwMTAxMjQxMjA0NzA0NDA0MWI1ODRjMDAwNDEwNzQxYjQ4MWMwMDA0MTEwMTAzNzAwMGIyMDAwMGI1MTAxMDE3ZjIzMDA0MTEwNmIyMjAxMjQwMDAyNDAyMDAwMTAxMjQxMDQ0NzBkMDAyMDAxNDEwMDM2MDIwYzIwMDA0MTAwMjAwMTQxMGM2YTQxMDQxMDQ1MWEyMDAxMjgwMjBjNDFjNThlYjFhMjA0NDcwZDAwMjAwMDQxZGM4NGMwMDA0MTAwMTAwZjFhMGIyMDAxNDExMDZhMjQwMDIwMDAwYjFhMDAyMDAwNDFiOGQzYzAwMDI4MDIwMDQ4MDQ0MDQxY2Y4MGMwMDA0MTEyMTAwMDAwMGIwYjE1MDAxMDE0MjAwMDQ2MDQ0MDBmMGI0MWUxODBjMDAwNDExOTEwMDAwMDBiMWIwMDQxYjhkM2MwMDAyODAyMDA0MTAwNGUwNDQwMGYwYjQxYmU4MGMwMDA0MTExMTAwMDAwMGIwYzAwNDFiOGQzYzAwMDEwMTQzNjAyMDAwYjIyMDEwMTdmMjAwMTEwMTIyMTAyMjAwMDQxMDAzNjAyMDgyMDAwMjAwMTM2MDIwMDIwMDAyMDAyNDEwMjc2MzYwMjA0MGIwODAwMjAwMDIwMDExMDQ0MGIxNTAwNDE3ZjIwMDAyMDAxMTAxYjIyMDA0MTAwNDcyMDAwNDEwMDQ4MWIwYjBmMDAyMDAwMjAwMTIwMDMyMDAyMTAyNjQxMDA0NzBiMDkwMDIwMDAyMDAxMTAwMjFhMGIwYTAwMjAwMDIwMDAyMDAxMTAwNTBiMGMwMDIwMDAyMDAwMjAwMTEwMDUyMDAwMGIyMDAwMjAwMDIwMDAyMDAxMTAxNTIwMDAxMDA2NDEwMDQ4MDQ0MDQxZGM4NGMwMDA0MTMwMTAwMDAwMGIyMDAwMGIwZjAxMDE3ZjEwMzAyMjAxMjAwMDEwMTYxYTIwMDEwYjBkMDAyMDAwMTAzMDIyMDAxMDE3MWEyMDAwMGIwYzAxMDE3ZjEwMzAyMjAwMTAzMTIwMDAwYmZlMDEwMTAyN2YyMzAwNDEzMDZiMjIwMzI0MDAyMDAzNDEyMDZhNDEwMDM2MDIwMDIwMDM0MjAwMzcwMzE4MjAwMzIwMDI0MTA4NzQ0MTgwODBmYzA3NzEyMDAyNDExODc0NzIyMDAyNDEwODc2NDE4MGZlMDM3MTIwMDI0MTE4NzY3MjcyMzYwMjI4MjAwMzQxMTA2YTIwMDM0MTE4NmEyMjAyNDEwMDQxMDQxMDRlMjAwMzI4MDIxMDIwMDMyODAyMTQyMDAzNDEyODZhMjIwNDQxMDQxMDRmMjAwMzIwMDE0MjI4ODY0MjgwODA4MDgwODA4MGMwZmYwMDgzMjAwMTQyMzg4Njg0MjAwMTQyMTg4NjQyODA4MDgwODA4MGUwM2Y4MzIwMDE0MjA4ODY0MjgwODA4MDgwZjAxZjgzODQ4NDIwMDE0MjA4ODg0MjgwODA4MGY4MGY4MzIwMDE0MjE4ODg0MjgwODBmYzA3ODM4NDIwMDE0MjI4ODg0MjgwZmUwMzgzMjAwMTQyMzg4ODg0ODQ4NDM3MDMyODIwMDM0MTA4NmEyMDAyNDEwNDQxMGMxMDRlMjAwMzI4MDIwODIwMDMyODAyMGMyMDA0NDEwODEwNGYyMDAwMjgwMjAwMjAwMjQxMGMxMDA3MWEyMDAzNDEzMDZhMjQwMDBiM2IwMTAxN2YyMzAwNDExMDZiMjIwNDI0MDAyMDA0NDEwODZhMjAwMjIwMDMyMDAxNDEwYzEwMjcyMDA0MjgwMjBjMjEwMTIwMDAyMDA0MjgwMjA4MzYwMjAwMjAwMDIwMDEzNjAyMDQyMDA0NDExMDZhMjQwMDBiYjUwMjAxMDU3ZjIwMDEyMDAzNDYwNDQwMjAwMTIyMDM0MTBmNGIwNDQwMjAwMDQxMDAyMDAwNmI0MTAzNzEyMjA0NmEyMTA1MjAwNDA0NDAyMDAyMjEwMTAzNDAyMDAwMjAwMTJkMDAwMDNhMDAwMDIwMDE0MTAxNmEyMTAxMjAwMDQxMDE2YTIyMDAyMDA1NDkwZDAwMGIwYjIwMDUyMDAzMjAwNDZiMjIwMzQxN2M3MTIyMDY2YTIxMDAwMjQwMjAwMjIwMDQ2YTIyMDQ0MTAzNzEwNDQwMjAwNjQxMDA0YzBkMDEyMDA0NDEwMzc0MjIwMTQxMTg3MTIxMDc0MTAwMjAwMTZiNDExODcxMjEwODIwMDQ0MTdjNzEyMjAxNDEwNDZhMjEwMjIwMDEyODAyMDAyMTAxMDM0MDIwMDUyMDAxMjAwNzc2MjAwMjI4MDIwMDIyMDEyMDA4NzQ3MjM2MDIwMDIwMDI0MTA0NmEyMTAyMjAwNTQxMDQ2YTIyMDUyMDAwNDkwZDAwMGIwYzAxMGIyMDA2NDEwMDRjMGQwMDIwMDQyMTAyMDM0MDIwMDUyMDAyMjgwMjAwMzYwMjAwMjAwMjQxMDQ2YTIxMDIyMDA1NDEwNDZhMjIwNTIwMDA0OTBkMDAwYjBiMjAwMzQxMDM3MTIxMDMyMDA0MjAwNjZhMjEwMjBiMjAwMzA0NDAyMDAwMjAwMzZhMjEwMTAzNDAyMDAwMjAwMjJkMDAwMDNhMDAwMDIwMDI0MTAxNmEyMTAyMjAwMDQxMDE2YTIyMDAyMDAxNDkwZDAwMGIwYjBmMGIxMDI4MDAwYjU1MDEwMTdmMjAwMDJkMDAwNDIxMDEyMDAwNDEwMDNhMDAwNDAyNDAyMDAxNDEwMTcxMDQ0MDQxYTA4NWMwMDAyODAyMDAyMjAxNDE5MWNlMDA0ZjBkMDEyMDAwMjgwMjAwNDFhNDg1YzAwMDIwMDExMDA3MWE0MWEwODVjMDAwNDEwMDM2MDIwMDQxYjRkM2MwMDA0MTAwM2EwMDAwMGIwZjBiMTAyODAwMGIwZDAwMjAwMDEwMzAyMjAwMTAxODFhMjAwMDBiM2EwMTAxN2YyMzAwNDExMDZiMjIwMjI0MDAyMDAyMjAwMTQxZmYwMTcxNDEwMjc0NDFkMDg0YzAwMDZhMjgwMjAwMmQwMDAwM2EwMDBmMjAwMDIwMDI0MTBmNmE0MTAxMTAwNzFhMjAwMjQxMTA2YTI0MDAwYjA4MDAyMDAwMTA1MTEwM2QwYjA4MDAyMDAwMTA1MTEwNGIwYjJmMDEwMTdlMDI3ZjAyNDAyMDAwMTA1NjIyMDE0MjAxNTgwNDQwNDEwMDIwMDFhNzQxMDE2YjBkMDIxYTBjMDEwYjQxOGM4MWMwMDA0MTEyMTA1NzAwMGI0MTAxMGIwYjVkMDIwMjdmMDE3ZTIzMDA0MTEwNmIyMjAxMjQwMDIwMDE0MjAwMzcwMzA4MjAwMDEwNTEyMjAwMTAxMjIyMDI0MTA5NGYwNDQwNDFmYTgwYzAwMDQxMGUxMDU3MDAwYjIwMDEyMDAxNDEwODZhMjAwMjEwNjIyMDAwNDEwMDIwMDEyODAyMDAyMjAwMjAwMTI4MDIwNDIyMDIxMDQ1MWEyMDAwMjAwMjEwNjMyMDAxNDExMDZhMjQwMDBiMWIwMTAxN2Y0MTllODFjMDAwNDExNjEwMmQyMjAyMjAwMDIwMDExMDA3MWEyMDAyMTAxMTAwMGIzNjAxMDE3ZjIzMDA0MTEwNmIyMjAyMjQwMDIwMDI0MjAwMzcwMzA4MjAwMjIwMDE0MTAwMjAwMjQxMDg2YTEwNTkyMDAwMjAwMjI4MDIwMDIwMDIyODAyMDQxMDVhMjAwMjQxMTA2YTI0MDAwYjljMDIwMjA0N2YwMTdlMjAwMzIwMDE0MjI4ODY0MjgwODA4MDgwODA4MGMwZmYwMDgzMjAwMTQyMzg4Njg0MjAwMTQyMTg4NjQyODA4MDgwODA4MGUwM2Y4MzIwMDE0MjA4ODY0MjgwODA4MDgwZjAxZjgzODQ4NDIwMDE0MjA4ODg0MjgwODA4MGY4MGY4MzIwMDE0MjE4ODg0MjgwODBmYzA3ODM4NDIwMDE0MjI4ODg0MjgwZmUwMzgzMjAwMTQyMzg4ODg0ODQ4NDIyMDgzNzAwMDAwMjQwMDI0MDIwMDAwMjdmMjAwMTUwMDQ0MDQxZGM4NGMwMDAyMTAzNDEwMDBjMDEwYjIwMDI0MTAwMjAwMTQyN2Y1MTFiNDUwNDQwNDEwMDIwMDIyMDA4NDI4MDAxODM0MjA3ODhhNzcxMjIwNTZiNDFmZjAxNzEyMTA2MDM0MDIwMDQ0MTA4NDYwZDAzMjAwNjIwMDMyMDA0NmEyZDAwMDAyMjA3NDcwNDQwMjAwNDIwMDIyMDA3NDEwNzc2MjAwNTQ3NzEyMjAyNmI0MTA5NGYwZDA1MjAwMzQxMDAyMDAyNmIyMjAyNmEyMDA0NmEyMTAzNDEwODIwMDIyMDA0NmE2YjBjMDMwNTIwMDQ0MTAxNmEyMTA0MGMwMTBiMDAwYjAwMGIyMDAzNDEwNzZhMjEwMzQxMDEwYjM2MDIwNDIwMDAyMDAzMzYwMjAwMGYwYjEwODAwMTAwMGIxMDgwMDEwMDBiMGQwMDIwMDAyMDAxMjAwMjEwMmQxMDE5MWEwYjM3MDEwMTdmMjMwMDQxMTA2YjIyMDIyNDAwMjAwMjQyMDAzNzAzMDgyMDAyMjAwMWFkNDEwMTIwMDI0MTA4NmExMDU5MjAwMDIwMDIyODAyMDAyMDAyMjgwMjA0MTA1YTIwMDI0MTEwNmEyNDAwMGIwYjAwMjAwMDIwMDExMDRhMTAxOTFhMGIwZTAwMjAwMDQxNjcxMDE4MWE0MTY3MTAxMjQ1MGIwOTAwMTAyZTFhMjAwMDEwNGEwYjBkMDAxMDJlMWEyMDAwMjAwMTEwMjkxMDNiMGI1YTAyMDI3ZjAxN2UyMzAwNDExMDZiMjIwMjI0MDAxMDJlMjEwMzIwMDE0MWZmMDE3MTQxMDI3NDQxZDA4NGMwMDA2YTI4MDIwMDMxMDAwMDIxMDQyMDAyNDIwMDM3MDMwODIwMDIyMDA0NDEwMDIwMDI0MTA4NmExMDU5MjAwMzIwMDIyODAyMDAyMDAyMjgwMjA0MTAwZjFhMjAwMDIwMDMxMDNiMjAwMjQxMTA2YTI0MDAwYjEyMDEwMTdmMTAyZTIyMDIyMDAwMjAwMTEwMmQxMDNiMjAwMjBiMzkwMTAxN2YyMzAwNDExMDZiMjIwMzI0MDAyMDAzNDEwODZhMjAwMTQxMDgyMDAyMTA2YTIwMDMyODAyMGMyMTAxMjAwMDIwMDMyODAyMDgzNjAyMDAyMDAwMjAwMTM2MDIwNDIwMDM0MTEwNmEyNDAwMGIzNDAxMDE3ZTAyNDAyMDAxNDUwZDAwMDM0MDIwMDE0NTBkMDEyMDAxNDEwMTZiMjEwMTIwMDAzMTAwMDAyMDAyNDIwODg2ODQyMTAyMjAwMDQxMDE2YTIxMDAwYzAwMGIwMDBiMjAwMjBiODAwMTAxMDM3ZjIzMDA0MTEwNmIyMjAxMjQwMDIwMDAyODAyMDgyMTAzMjAwMTQxMDAzNjAyMGMyMDAwMjgwMjAwMjAwMzQxMDI3NDIwMDE0MTBjNmE0MTA0MTA0NTQ1MDQ0MDIwMDEyODAyMGMyMTAyMjAwMDIwMDM0MTAxNmEzNjAyMDgyMDAyNDEwODc0NDE4MDgwZmMwNzcxMjAwMjQxMTg3NDcyMjAwMjQxMDg3NjQxODBmZTAzNzEyMDAyNDExODc2NzI3MjEwMjkyMDAxNDExMDZhMjQwMDBmMGI0MWM0ODFjMDAwNDEwODQxYmU4MGMwMDA0MTExMTAzNzAwMGJmYjAxMDIwNDdmMDE3ZTIzMDA0MTEwNmIyMjAzMjQwMDAyNDAwMjQwMDI0MDAyNDAyMDAxMjgwMjA4MjAwMTI4MDIwNDRmMDQ0MDIwMDA0MTAzM2EwMDA0MGMwMTBiMjAwMTEwNjQxMDI5MjIwNTEwMTI0MTIwNDcwZDAxMDI0MDIwMDExMDY0MjIwMjEwMTI0NTA0NDA0MTAwMjEwMjBjMDEwYjIwMDM0MjAwMzcwMzA4MjAwMjEwMTIyMjA0NDEwOTRmMGQwNDIwMDMyMDAzNDEwODZhMjAwNDEwNjIyMDAyNDEwMDIwMDMyODAyMDAyMjAyMjAwMzI4MDIwNDIyMDQxMDQ1MWEyMDAyMjAwNDEwNjMyMjA2NDI4MDAyNWEwZDA0MjAwNmE3MjIwMjQxZmYwMTcxNDEwMzRmMGQwMzBiMjAwMDQxMDg2YTIwMDExMDY0MTA0YjM2MDIwMDIwMDAyMDAyM2EwMDA0MjAwMDIwMDUzNjAyMDAwYjIwMDM0MTEwNmEyNDAwMGYwYjQxYzQ4MWMwMDA0MTA4NDFiNDgxYzAwMDQxMTAxMDM3MDAwYjQxYzQ4MWMwMDA0MTA4NDFjMjg0YzAwMDQxMGQxMDM3MDAwYjQxYzQ4MWMwMDA0MTA4NDFmYTgwYzAwMDQxMGUxMDM3MDAwYjgwMDEwMTAyN2YyMzAwNDExMDZiMjIwMzI0MDAwMjQwMDI0MDIwMDAyZDAwMDQwNDQwNDE5MGNlMDA0MWEwODVjMDAwMjgwMjAwMjIwNDZiMjAwMjQ5MGQwMTIwMDM0MTA4NmEyMDA0MjAwMjIwMDQ2YTIyMDAxMDY3MjAwMzI4MDIwODIwMDMyODAyMGMyMDAxMjAwMjEwNGY0MWEwODVjMDAwMjAwMDM2MDIwMDBjMDIwYjIwMDAyODAyMDAyMDAxMjAwMjEwMDcxYTBjMDEwYjIwMDAxMDUwMjAwMDI4MDIwMDIwMDEyMDAyMTAwNzFhMGIyMDAzNDExMDZhMjQwMDBiNDAwMTAxN2YyMzAwNDExMDZiMjIwMzI0MDAyMDAzNDEwODZhMjAwMTIwMDI0MWE0ODVjMDAwNDE5MGNlMDAxMDI3MjAwMzI4MDIwYzIxMDEyMDAwMjAwMzI4MDIwODM2MDIwMDIwMDAyMDAxMzYwMjA0MjAwMzQxMTA2YTI0MDAwYjBmMDAyMDAwMjAwMTEwNDM0MWZmMDE3MTQxMDI0OTBiMGYwMDIwMDAyMDAxMTA0MzQxZmYwMTcxNDEwMTQ2MGIzYjAxMDE3ZjIzMDA0MTEwNmIyMjA0MjQwMDIwMDQ0MTA4NmE0MTAwMjAwMzIwMDEyMDAyMTAyNzIwMDQyODAyMGMyMTAxMjAwMDIwMDQyODAyMDgzNjAyMDAyMDAwMjAwMTM2MDIwNDIwMDQ0MTEwNmEyNDAwMGIyMTAxMDE3ZjQxY2M4MWMwMDA0MTBhMTA2MTIyMDMyMDAwMTA1ZjIwMDMyMDAxMTA2MDIwMDMyMDAyMTA1ZTEwMWMwYjIxMDEwMTdmNDFkNjgxYzAwMDQxMGMxMDYxMjIwMzIwMDAxMDVmMjAwMzIwMDExMDYwMjAwMzIwMDIxMDVlMTAxYzBiMGIwMDQxODI4MmMwMDA0MTBmMTAyZDBiMWUwMTAxN2Y0MTkxODJjMDAwNDEwOTEwMmQyMjAyMjAwMDEwNDYyMDAyMjAwMTJkMDAwMDEwNTIyMDAyMGIxZTAxMDE3ZjQxOWE4MmMwMDA0MTA1MTAyZDIyMDIyMDAwMTA0NjIwMDIyMDAxMmQwMDAwMTA1MjIwMDIwYjBiMDA0MTlmODJjMDAwNDEwODEwMmQwYjBlMDAxMDFmNDEwMDEwM2YxMDcwNDEwMTEwNWIwYmIwMDEwMjA3N2YwMTdlMjMwMDQxMTA2YjIyMDAyNDAwMTAzNDQxMDIxMDNmMTAzYzIxMDEyMDAwNDEwMTEwMzkyMjA0M2EwMDBmMDI0MDAyNDAxMDZkMTA1ZDQ1MDQ0MDIwMDAxMDM1MjAwMDI4MDIwMDIxMDIyMDAwMjgwMjA0MjAwMTIwMDA0MTBmNmExMDZmMTA1NDIxMDUxMDZkMTA1MzEwMjAyMTA3MTAyYTQ1MGQwMTIwMDIxMDRjMTA2OTQ1MGQwMjIwMDEyMDAwNDEwZjZhMjIwMzEwNmYyMDA1MjAwMjEwNDgxMDVjMjAwMTIwMDMxMDZlMjAwNzEwNTgyMDAxMjAwNDIwMDIxMDZiMjAwMDQxMTA2YTI0MDAwZjBiNDFhNzgyYzAwMDQxMTcxMDJiMDAwYjQxYmU4MmMwMDA0MTFkMTAyYjAwMGI0MWRiODJjMDAwNDExYjEwMmIwMDBiYzcwMjAyMDc3ZjAyN2UyMzAwNDFkMDAwNmIyMjAwMjQwMDEwMzQxMDQxMTA0MDIwMDA0MTAwMzYwMjQwMjAwMDQxNDA2YjEwM2EyMTAxMjAwMDI4MDI0MDEwM2UxMDZkMTA1ZDQ1MDQ0MDIwMDA0MTA4NmExMDM1MjAwMDI4MDIwODIxMDUyMDAwMjgwMjBjMTA2ZDEwNTMxMDIwMjEwNzEwMmEwNDQwMjAwNTEwNGMxMDY5MDQ0MDEwNGMyMTAzMjAwMDQxMTA2YTIwMDExMDQyMjAwMDQxMjg2YTIwMDA0MTE4NmEyODAyMDAzNjAyMDAyMDAwMjAwMDI5MDMxMDM3MDMyMDIwMDA0MTQwNmI0MTA0NzIyMTAxMDM0MDAyNDAyMDAwNDEzMDZhMjAwMDQxMjA2YTEwNjUyMDAwMmQwMDM0NDEwMzQ2MDQ0MDIwMDMyMDA1MTA0NDQxZmYwMTcxMGQwMTIwMDA0MWQwMDA2YTI0MDAwZjA1MjAwMDQxYzgwMDZhMjIwNDIwMDA0MTM4NmEyODAyMDAzNjAyMDAyMDAwMjAwMDI5MDMzMDIyMDgzNzAzNDAyMDA4YTcyMjAyMjAwMTEwNmYxMDU0MjEwNjIwMDIyMDAxMTA2ZjIwMDYyMDA0MjgwMjAwMjIwNDEwNDgxMDVjMjAwMjIwMDExMDZlMjAwNzEwNTgyMDAzMjAwNDEwNDcyMDAyMjAwMDJkMDA0NDIwMDQxMDZiMGMwMjBiMDAwYjBiNDFmNjgyYzAwMDQxMjYxMDJiMDAwYjQxZGI4MmMwMDA0MTFiMTAyYjAwMGI0MWJlODJjMDAwNDExZDEwMmIwMDBiNDFhNzgyYzAwMDQxMTcxMDJiMDAwYmY0MDIwMTA1N2YyMzAwNDExMDZiMjIwMjI0MDAxMDFmMTA0MTEwNDAyMDAyNDEwMDM2MDIwODIwMDI0MTA4NmEyMjAwMjgwMjAwNDFiOGQzYzAwMDI4MDIwMDQ4MDQ3ZjIwMDA0MWFiODRjMDAwNDEwYTEwMzgxMDM5MDU0MTAzMGIyMTAxMjAwMjI4MDIwODEwM2UwMjQwMDI0MDEwNmQxMDVkNDUwNDQwMTA2ZDEwNTMyMTAzMTAzMjIxMDAxMDcwMTA1NTBkMDEwMjQwMjAwMTQxZmYwMTcxNDEwMzQ3MDQ0MDIwMDIyMDAxM2EwMDBmMjAwMDIwMDI0MTBmNmExMDZmMTA1NDIyMDQxMDRjMTA2OTQ1MGQwMTIwMDAyMDAzMjAwNDEwMmMyMDAwMjAwMjQxMGY2YTEwNmYxMDRjMTA1YzQxZTI4MWMwMDA0MTBlMTA2MTIyMDMyMDAwMTA1ZjIwMDMyMDAxMTA2MDIwMDMyMDA0MTA1ZTEwMWMwYzA0MGIxMDRjMjIwMTIwMDA0MWU4ODNjMDAwMTA2ZjEwNTQxMDQ3MjAwMTIwMDA0MWU5ODNjMDAwMTA2ZjEwNTQxMDQ3MjAwMTIwMDA0MWVhODNjMDAwMTA2ZjEwNTQxMDQ3MjAwMTEwNGMxMDY5MDQ0MDIwMDAyMDAzMjAwMTEwMmMyMDAwNDFlODgzYzAwMDEwNmYxMDRjMTA1YzIwMDA0MWU5ODNjMDAwMTA2ZjEwNGMxMDVjMjAwMDQxZWE4M2MwMDAxMDZmMTA0YzEwNWM0MWYwODFjMDAwNDExMjEwNjEyMjAzMjAwMDEwNWYyMDAzMjAwMTEwNWUxMDFjMGMwNDBiNDFkMzgzYzAwMDQxMTUxMDJiMDAwYjQxZDM4M2MwMDA0MTE1MTAyYjAwMGI0MWE3ODJjMDAwNDExNzEwMmIwMDBiNDFjMTgzYzAwMDQxMTIxMDJiMDAwYjIwMDI0MTEwNmEyNDAwMGIxNjAwMTAxZjQxMDAxMDNmNDE5ZjgyYzAwMDQxMDgxMDJkMTA1NWFkMTAyMTBiMTUwMDEwMWYxMDM0NDEwMDEwM2YxMDcwMTA3MDEwNTU0MTAxNzMxMDViMGJhMDAxMDIwODdmMDE3ZTIzMDA0MTEwNmIyMjAxMjQwMDEwMWYxMDM0NDEwMzEwM2YxMDNjMjEwMjQxMDExMDM5MjEwMzQxMDIxMDMwMjIwMDEwMTMyMDAxMjAwMzNhMDAwZjAyNDAxMDZkMTA1ZDQ1MDQ0MDIwMDIyMDAxNDEwZjZhMTA2ZjEwNTQyMTA0MTAzMzEwNmQxMDUzMTAyMDIxMDgyMDA0MjAwMDEwNjg0NTBkMDEyMDAyMjAwMTQxMGY2YTIyMDcxMDZmMjAwNDIwMDAxMDQ5MTA1YzIwMDIyMDA3MTA2ZTIwMDgxMDU4MjAwMDEwMmMyMDAyMjAwMzIwMDAxMDZjMjAwMTQxMTA2YTI0MDAwZjBiNDFhNzgyYzAwMDQxMTcxMDJiMDAwYjQxOWM4M2MwMDA0MTI1MTAyYjAwMGJhMTAyMDIwNjdmMDI3ZTIzMDA0MTQwNmEyMjAwMjQwMDEwMWYxMDM0MTA0MTEwNDAyMDAwNDEwMDM2MDIzMDIwMDA0MTMwNmExMDNhMjEwMTIwMDAyODAyMzAxMDNlMTA2ZDEwNWQ0NTA0NDAxMDRjMjEwMzEwMjAyMTA2MjAwMDIwMDExMDQyMjAwMDQxMTg2YTIwMDA0MTA4NmEyODAyMDAzNjAyMDAyMDAwMjAwMDI5MDMwMDM3MDMxMDIwMDA0MTMwNmE0MTA0NzIyMTAxMDI0MDAzNDAyMDAwNDEyMDZhMjAwMDQxMTA2YTEwNjUyMDAwMmQwMDI0NDEwMzQ2MDQ0MDIwMDMxMDRjMTA2OTBkMDI0MWViODNjMDAwNDEyNTEwMmIwMDBiMjAwMDQxMzg2YTIyMDIyMDAwNDEyODZhMjgwMjAwMzYwMjAwMjAwMDIwMDAyOTAzMjAyMjA3MzcwMzMwMjAwN2E3MjIwNDIwMDExMDZmMTA1NDIxMDUyMDA0MjAwMTEwNmUyMDA2MTA1ODIwMDUyMDAyMjgwMjAwMjIwMjEwNjgwNDQwMjAwMzIwMDIxMDQ3MjAwNDIwMDExMDZmMjAwNTIwMDIxMDQ5MTA1YzIwMDQyMDAwMmQwMDM0MjAwMjEwNmMwYzAxMGIwYjQxOWM4M2MwMDA0MTI1MTAyYjAwMGIxMDMzMTA2ZDEwNTMyMDAzMTAyYzIwMDA0MTQwNmIyNDAwMGYwYjQxYTc4MmMwMDA0MTE3MTAyYjAwMGI0NTAxMDI3ZjEwMWYxMDM0NDEwMTEwM2Y0MTAwMTAzNjEwM2QyMTAwMDI0MDEwNmQxMDVkMDQ0MDEwNmQyMTAxMjAwMDEwMmYwZDAxMjAwMTIwMDAxMDE5MWEwZjBiNDE5MDg0YzAwMDQxMWIxMDJiMDAwYjIwMDE0MTg4ODFjMDAwNDEwNDEwNWEwYjMwMDEwMzdmMTAxZjQxMDIxMDNmMTAzYzIxMDE0MTAxMTAzOTIxMDI0MTlhODJjMDAwNDEwNTEwMmQyMjAwMjAwMTEwMDIxYTIwMDAyMDAyMTA1MjIwMDAxMDU0MTAyMjBiMzAwMTAzN2YxMDFmNDEwMjEwM2YxMDNjMjEwMTQxMDExMDM5MjEwMjQxOTE4MmMwMDA0MTA5MTAyZDIyMDAyMDAxMTAwMjFhMjAwMDIwMDIxMDUyMjAwMDEwNTYxMDIzMGJhNTA1MDIwODdmMDE3ZTIzMDA0MTMwNmIyMjAwMjQwMDEwMWY0MTAxMjEwNDQxMDExMDNmMTAzYzIxMDEyMDAwMTAyZTIyMDUzNjAyMjAyMDAxNDFlODgzYzAwMDEwNmYxMDU0MjEwMjIwMDA0MTIwNmEyMjAzMjAwMTQxZTg4M2MwMDAxMDZlMTA1NjIwMDIxMDRkMjAwMTQxZTk4M2MwMDAxMDZmMTA1NDIxMDIyMDAzMjAwMTQxZTk4M2MwMDAxMDZlMTA1NjIwMDIxMDRkMjAwMTQxZWE4M2MwMDAxMDZmMTA1NDIxMDIyMDAzMjAwMTQxZWE4M2MwMDAxMDZlMTA1NjIwMDIxMDRkMDI3ZjQxYjRkM2MwMDAyZDAwMDA0NTA0NDA0MWI0ZDNjMDAwNDEwMTNhMDAwMDQxYTA4NWMwMDA0MTAwMzYwMjAwMjAwMDQxMTA2YTQxYTQ4NWMwMDA0MTkwY2UwMDQxMDAxMDZhMjAwMDI4MDIxMDIwMDAyODAyMTQ0MWRjODRjMDAwNDEwMDEwNGYxMDJlMGMwMTBiNDEwMDIxMDQ0MWRjODRjMDAwNDEwMDEwMmQwYjIxMDEyMDAwMjAwNDNhMDAxYzIwMDAyMDAxMzYwMjE4MjAwNTEwMTIyMTA2NDEwMDIxMDEwMzQwMjAwMTQxMGM2YTIyMDQyMDA2NGI0NTA0NDAyMDAwNDEyODZhNDEwMDM2MDIwMDIwMDA0MjAwMzcwMzIwMjAwNTIwMDEyMDAwNDEyMDZhMjIwMzQxMGMxMDQ1MWEyMDAwMjkwMjI0MjEwODIwMDAyMDAwMjgwMjIwMjIwMTQxMTg3NDIwMDE0MTA4NzQ0MTgwODBmYzA3NzE3MjIwMDE0MTA4NzY0MTgwZmUwMzcxMjAwMTQxMTg3NjcyNzIxMDRhMjIwMTEwMTIyMjAyNDExODc0MjAwMjQxMDg3NDQxODA4MGZjMDc3MTcyMjAwMjQxMDg3NjQxODBmZTAzNzEyMDAyNDExODc2NzI3MjM2MDIyMDIwMDA0MTE4NmEyMDAzNDEwNDEwNjYyMDAwMmQwMDFjMjEwMjIwMDA0MTAwM2EwMDFjMDI0MDAyNDAwMjQwMjAwMjQxMDE3MTIyMDIwNDQwMjAwMTEwMTIyMjA3NDE5MGNlMDA0MWEwODVjMDAwMjgwMjAwMjIwMzZiNGIwZDAyMjAwMDQxMDg2YTIwMDMyMDAzMjAwNzZhMjIwMzEwNjcyMDAxNDEwMDIwMDAyODAyMDgyMDAwMjgwMjBjMTA0NTFhNDFhMDg1YzAwMDIwMDMzNjAyMDAwYzAxMGIyMDAwMjgwMjE4MjAwMTEwMDIxYTBiMjAwMDIwMDIzYTAwMWMwYzAxMGIyMDAwNDExODZhMTA1MDIwMDAyODAyMTgyMDAxMTAwMjFhMjAwMDJkMDAxYzIwMDAyMDAyM2EwMDFjNDEwMTcxNDUwZDAwNDFhMDg1YzAwMDQxMDAzNjAyMDA0MWI0ZDNjMDAwNDEwMDNhMDAwMDBiMjAwMDIwMDgzNzAzMjAyMDAwNDExODZhMjAwMDQxMjA2YTQxMDgxMDY2MjAwNDIxMDEwYzAxMGIwYjIwMDAyODAyMTgyMTAxMjAwMDIwMDAyZDAwMWMzYTAwMjQyMDAwMjAwMTM2MDIyMDIwMDA0MTIwNmExMDUwMjAwMDI4MDIyMDIwMDAyZDAwMjQwNDQwNDFhMDg1YzAwMDQxMDAzNjAyMDA0MWI0ZDNjMDAwNDEwMDNhMDAwMDBiMTAyNDFhMjAwMDQxMzA2YTI0MDAwYjQxMDEwMjdmMTAxZjQxMDExMDNmMTAzYzIxMDAxMDRjMjIwMTIwMDA0MWU4ODNjMDAwMTA2ZjEwNTQxMDQ3MjAwMTIwMDA0MWU5ODNjMDAwMTA2ZjEwNTQxMDQ3MjAwMTIwMDA0MWVhODNjMDAwMTA2ZjEwNTQxMDQ3MjAwMTEwMjIwYjJjMDEwMTdmMTAxZjQxMDAxMDNmNDE4MjgyYzAwMDQxMGYxMDJkMTA1MzIyMDAxMDJmNDUwNDQwMjAwMDEwMjQxYTBmMGI0MTg4ODFjMDAwNDEwNDEwMjUwYjAzMDAwMTBiMGMwMDQxOGM4NWMwMDA0MTBlMTAwMDAwMGIwYmIwMDUwMjAwNDE4MDgwYzAwMDBiOWEwNTQ1NmU2NDcwNmY2OTZlNzQyMDYzNjE2ZTIwNmY2ZTZjNzkyMDYyNjUyMDYzNjE2YzZjNjU2NDIwNjI3OTIwNmY3NzZlNjU3MjYxNzI2Nzc1NmQ2NTZlNzQyMDY0NjU2MzZmNjQ2NTIwNjU3MjcyNmY3MjIwMjgyOTNhMjA3NDZmNmYyMDY2NjU3NzIwNjE3MjY3NzU2ZDY1NmU3NDczNzQ2ZjZmMjA2ZDYxNmU3OTIwNjE3MjY3NzU2ZDY1NmU3NDczNzc3MjZmNmU2NzIwNmU3NTZkNjI2NTcyMjA2ZjY2MjA2MTcyNjc3NTZkNjU2ZTc0NzM2OTZlNzA3NTc0MjA3NDZmNmYyMDZjNmY2ZTY3NDU0NzRjNDQ2OTZlNzA3NTc0MjA2Zjc1NzQyMDZmNjYyMDcyNjE2ZTY3NjU3Mzc0NmY3MjYxNjc2NTIwNjQ2NTYzNmY2NDY1MjA2NTcyNzI2ZjcyM2EyMDYyNjE2NDIwNjE3MjcyNjE3OTIwNmM2NTZlNjc3NDY4NzY2MTcyMjA2MTcyNjc3MzYzNmM2MTY5NmQ0MTY0NjQ2NTY0NjM2YzYxNjk2ZDUyNjU2ZDZmNzY2NTY0NjM2YzYxNjk2ZDQzNmY2YzZjNjU2Mzc0NjU2NDYxNmM2YzQzNmM2MTY5NmQ3MzQzNmY2YzZjNjU2Mzc0NjU2NDc0NmY2YjY1NmU0OTY0NjU2ZTc0Njk2NjY5NjU3MjYzNmM2MTY5NmQ0NDYxNzQ2NTYzNmM2MTY5NmQ2OTczNTA2MTc1NzM2NTY0NTI2NTc3NjE3MjY0MjA3NDZmNmI2NTZlMjA2OTczMjA2ZTZmNzQyMDczNjU3NDQzNjE2ZTIwNmY2ZTZjNzkyMDYxNjQ2NDIwNjQ2NTczNjk2NzZlNjE3NDY1NjQyMDc0NmY2YjY1NmU0ZDc1NzM3NDIwNjE2NDY0MjA2ZDZmNzI2NTIwNzQ2ODYxNmUyMDMwMjA3NDZmNmI2NTZlNzM0MzZjNjE2OTZkNzMyMDYxNjQ2NDY1NjQyMDZkNzU3Mzc0MjA2NTcxNzU2MTZjMjA3MDYxNzk2ZDY1NmU3NDIwNjE2ZDZmNzU2ZTc0NDM2MTZlNmU2Zjc0MjA3MjY1NmQ2Zjc2NjUyMDZkNmY3MjY1MjA3NDY4NjE2ZTIwNjM3NTcyNzI2NTZlNzQyMDYzNmM2MTY5NmQ0MzZmNmU3NDcyNjE2Mzc0MjA2OTczMjA3MDYxNzU3MzY1NjQ0MzYxNmU2ZTZmNzQyMDYzNmM2MTY5NmQyMDMwMjA3NDZmNmI2NTZlNzMwMDAxMDI0MzZjNjE2OTZkNzMyMDcyNjU2ZDZmNzY2NTY0MjA2ZDc1NzM3NDIwNjI2NTIwNjc3MjY1NjE3NDY1NzIyMDc0Njg2MTZlMjAzMDUyNjU3NzYxNzI2NDIwNzQ2ZjZiNjU2ZTIwNjk3MzIwNjE2YzcyNjU2MTY0NzkyMDczNjU3NDYzNmM2MTY5NmQ1Zjc0Nzk3MDY1NjE2NDY0NzI2NTczNzM2MzZjNjE2OTZkNzM2OTZlNzY2MTZjNjk2NDIwNzY2MTZjNzU2NTAwZTgwMTEwMDBlOTAxMTAwMGVhMDExMDAwNjM2MTZlNmU2Zjc0MjA3Mzc1NjI3NDcyNjE2Mzc0MjA2MjY1NjM2MTc1NzM2NTIwNzI2NTczNzU2Yzc0MjA3NzZmNzU2YzY0MjA2MjY1MjA2ZTY1Njc2MTc0Njk3NjY1NzA2MTZlNjk2MzIwNmY2MzYzNzU3MjcyNjU2NDAwNDE5Yzg1YzAwMDBiMDQ5Y2ZmZmZmZkAwNTAwQDA1MDI=", - "chainID": "D", - "version": 1, - "signature": "c98aaed578a089e4a034fbc75e8ccae200d0f489923cb441ea943da755e99b7e69889d7509ca489fc3de10d190378d0c887e8b804c09e57a224723f634d75509" - }, - "hash": "10ce205dd38daf38cd03ca2dc0682519c7f26f90cc971b82d0c5077764336288", - "data": "0061736d01000000016e1360000060017f017f60027f7f017f60027f7f006000017f60017f0060037f7f7f0060047f7f7f7f0060037f7f7f017f60057f7f7e7f7f017f60027f7e0060017f017e60017e0060047f7f7f7f017f6000017e60057f7f7f7f7f0060037f7e7f0060047f7e7f7f0060027f7f017e02a8072703656e760b7369676e616c4572726f72000303656e760a6d4275666665724e6577000403656e760d6d427566666572417070656e64000203656e76096d4275666665724571000203656e761b6d616e616765645472616e7366657256616c756545786563757465000903656e7609626967496e74416464000603656e760a626967496e745369676e000103656e76126d427566666572417070656e644279746573000803656e76226d616e616765644d756c74695472616e73666572455344544e465445786563757465000903656e760d6d616e6167656443616c6c6572000503656e76136d616e616765644f776e657241646472657373000503656e76136765744e756d455344545472616e7366657273000403656e7612626967496e7447657443616c6c56616c7565000503656e7616626967496e744765744553445443616c6c56616c7565000503656e761067657445534454546f6b656e4e616d65000103656e760f6d4275666665725365744279746573000803656e76126d427566666572476574417267756d656e74000203656e76126d616e616765645369676e616c4572726f72000503656e76106d4275666665724765744c656e677468000103656e7619626967496e74476574556e7369676e6564417267756d656e74000303656e760f6765744e756d417267756d656e7473000403656e7609626967496e74537562000603656e76196d42756666657246726f6d426967496e74556e7369676e6564000203656e76176d427566666572546f426967496e74556e7369676e6564000203656e76126d42756666657253746f726167654c6f6164000203656e76136d42756666657253746f7261676553746f7265000203656e760e626967496e74536574496e743634000a03656e7609626967496e74436d70000203656e760f6d616e6167656457726974654c6f67000303656e7611676574417267756d656e744c656e677468000103656e761b736d616c6c496e74476574556e7369676e6564417267756d656e74000b03656e760e636865636b4e6f5061796d656e74000003656e7611676574426c6f636b54696d657374616d70000e03656e7614736d616c6c496e7446696e6973685369676e6564000c03656e7614626967496e7446696e697368556e7369676e6564000503656e7616736d616c6c496e7446696e697368556e7369676e6564000c03656e760d6d42756666657246696e697368000103656e760666696e697368000303656e76136d42756666657247657442797465536c696365000d035b5a0f00010203060204010405040400050107080101030401050500000302020d030302020101041007070501030101010b030a1106030301010303020612010306060202070606040202040000000000000000000000000000000005030100110619037f01418080c0000b7f0041bcd3c0000b7f0041c0d3c0000b07ed0112066d656d6f7279020004696e6974007108616464436c61696d007209616464436c61696d73007305636c61696d0074086973506175736564007505706175736500760b72656d6f7665436c61696d00770c72656d6f7665436c61696d7300780e736574526577617264546f6b656e00790976696577436c61696d007a1076696577436c61696d41646444617465007b1176696577436c61696d5769746844617465007c0a76696577436c61696d73007d1376696577546f6b656e4964656e746966696572007e0863616c6c4261636b007f0a5f5f646174615f656e6403010b5f5f686561705f6261736503020a9d305a2e000240200120024d0440200220044d0d011028000b1028000b2000200220016b3602042000200120036a3602000b0600108001000b0f01017f10012201200010021a20010b0b0020002001100341004a0b0900200020011000000bcb0101057f230041106b2203240041dc84c0004100102d2105102e210602402001102f04402000200242002005200610041a0c010b102e210720011029210410302201103120012001200210052001100604402004102f1a0b2003420037020420032001410874418080fc077120014118747220014108764180fe03712001411876727236020c20032004410874418080fc077120044118747220044108764180fe03712004411876727236020020072003411010071a2000200742002005200610081a0b200341106a24000b1101017f1030220220002001100f1a20020b1401017f1030220041dc84c0004100100f1a20000b070020001012450b1b01017f419c85c000419c85c00028020041016b220036020020000b080020004200101a0b0c01017f10302200100920000b0c01017f10302200100a20000b160010331032102a04400f0b418080c00041241000000ba90101037f230041206b220124000240024002400240100b450440417521024175100c0c010b417321024173100d100b0d010b102e21030c010b200141186a4200370300200141106a4200370300200141086a42003703002001420037030002402001100e22024504401030220341dc84c0004100100f1a0c010b200241214f0d0220012002102d21030b417321020b2000200336020420002002360200200141206a24000f0b1028000b0d0020001030220010101a20000b3001017f41a480c0004117102d22042000200110071a200441bb80c000410310071a20042002200310071a20041011000b3101017f2000280200220341b8d3c0002802004e04402001200241be80c00041111037000b2000200341016a36020020030b5201017e2000101d45044041000f0b02402000101e22014280025404402001a7220041ff017141034f0d0120000f0b41ab84c000410a41fa80c000410e1037000b41ab84c000410a41c284c000410d1037000b3201017f102e21010340200028020041b8d3c0002802004e4504402001200041bc84c000410610381036103b0c010b0b20010b4701017f230041106b2202240020022001410874418080fc077120014118747220014108764180fe03712001411876727236020c20002002410c6a410410071a200241106a24000b2501017f4100103622001012412047044041b584c000410741b481c00041101037000b20000b5101017f230041106b220124000240200010124104470d002001410036020c200041002001410c6a410410451a200128020c41c58eb1a204470d00200041dc84c0004100100f1a0b200141106a240020000b1a00200041b8d3c00028020048044041cf80c00041121000000b0b1500101420004604400f0b41e180c00041191000000b1b0041b8d3c00028020041004e04400f0b41be80c00041111000000b0c0041b8d3c00010143602000b2201017f2001101221022000410036020820002001360200200020024102763602040b08002000200110440b1500417f20002001101b220041004720004100481b0b0f00200020012003200210264100470b09002000200110021a0b0a0020002000200110050b0c00200020002001100520000b2000200020002001101520001006410048044041dc84c00041301000000b20000b0f01017f10302201200010161a20010b0d0020001030220010171a20000b0c01017f10302200103120000bfe0101027f230041306b22032400200341206a41003602002003420037031820032002410874418080fc077120024118747220024108764180fe037120024118767272360228200341106a200341186a220241004104104e20032802102003280214200341286a22044104104f2003200142288642808080808080c0ff00832001423886842001421886428080808080e03f8320014208864280808080f01f838484200142088842808080f80f832001421888428080fc07838420014228884280fe03832001423888848484370328200341086a20024104410c104e2003280208200328020c20044108104f20002802002002410c10071a200341306a24000b3b01017f230041106b22042400200441086a200220032001410c1027200428020c21012000200428020836020020002001360204200441106a24000bb50201057f2001200346044020012203410f4b04402000410020006b41037122046a210520040440200221010340200020012d00003a0000200141016a2101200041016a22002005490d000b0b2005200320046b2203417c7122066a21000240200220046a22044103710440200641004c0d01200441037422014118712107410020016b41187121082004417c71220141046a2102200128020021010340200520012007762002280200220120087472360200200241046a2102200541046a22052000490d000b0c010b200641004c0d0020042102034020052002280200360200200241046a2102200541046a22052000490d000b0b20034103712103200420066a21020b20030440200020036a21010340200020022d00003a0000200241016a2102200041016a22002001490d000b0b0f0b1028000b5501017f20002d00042101200041003a000402402001410171044041a085c00028020022014191ce004f0d01200028020041a485c000200110071a41a085c000410036020041b4d3c00041003a00000b0f0b1028000b0d0020001030220010181a20000b3a01017f230041106b220224002002200141ff017141027441d084c0006a2802002d00003a000f20002002410f6a410110071a200241106a24000b080020001051103d0b080020001051104b0b2f01017e027f0240200010562201420158044041002001a741016b0d021a0c010b418c81c00041121057000b41010b0b5d02027f017e230041106b22012400200142003703082000105122001012220241094f044041fa80c000410e1057000b2001200141086a2002106220004100200128020022002001280204220210451a200020021063200141106a24000b1b01017f419e81c0004116102d22022000200110071a20021011000b3601017f230041106b2202240020024200370308200220014100200241086a1059200020022802002002280204105a200241106a24000b9c0202047f017e2003200142288642808080808080c0ff00832001423886842001421886428080808080e03f8320014208864280808080f01f838484200142088842808080f80f832001421888428080fc07838420014228884280fe038320014238888484842208370000024002402000027f200150044041dc84c000210341000c010b200241002001427f511b45044041002002200842800183420788a77122056b41ff01712106034020044108460d032006200320046a2d000022074704402004200220074107762005477122026b41094f0d052003410020026b22026a20046a21034108200220046a6b0c0305200441016a21040c010b000b000b200341076a210341010b360204200020033602000f0b108001000b108001000b0d00200020012002102d10191a0b3701017f230041106b220224002002420037030820022001ad4101200241086a1059200020022802002002280204105a200241106a24000b0b0020002001104a10191a0b0e002000416710181a41671012450b0900102e1a2000104a0b0d00102e1a200020011029103b0b5a02027f017e230041106b22022400102e2103200141ff017141027441d084c0006a280200310000210420024200370308200220044100200241086a1059200320022802002002280204100f1a20002003103b200241106a24000b1201017f102e220220002001102d103b20020b3901017f230041106b22032400200341086a200141082002106a200328020c21012000200328020836020020002001360204200341106a24000b3401017e02402001450d0003402001450d01200141016b210120003100002002420886842102200041016a21000c000b000b20020b800101037f230041106b22012400200028020821032001410036020c200028020020034102742001410c6a41041045450440200128020c21022000200341016a3602082002410874418080fc077120024118747220024108764180fe0371200241187672721029200141106a24000f0b41c481c000410841be80c00041111037000bfb0102047f017e230041106b220324000240024002400240200128020820012802044f0440200041033a00040c010b200110641029220510124120470d0102402001106422021012450440410021020c010b2003420037030820021012220441094f0d042003200341086a2004106220024100200328020022022003280204220410451a20022004106322064280025a0d042006a7220241ff017141034f0d030b200041086a20011064104b360200200020023a0004200020053602000b200341106a24000f0b41c481c000410841b481c00041101037000b41c481c000410841c284c000410d1037000b41c481c000410841fa80c000410e1037000b800101027f230041106b220324000240024020002d000404404190ce0041a085c00028020022046b2002490d01200341086a2004200220046a220010672003280208200328020c20012002104f41a085c00020003602000c020b20002802002001200210071a0c010b2000105020002802002001200210071a0b200341106a24000b4001017f230041106b22032400200341086a2001200241a485c0004190ce001027200328020c21012000200328020836020020002001360204200341106a24000b0f0020002001104341ff01714102490b0f0020002001104341ff01714101460b3b01017f230041106b22042400200441086a41002003200120021027200428020c21012000200428020836020020002001360204200441106a24000b2101017f41cc81c000410a106122032000105f20032001106020032002105e101c0b2101017f41d681c000410c106122032000105f20032001106020032002105e101c0b0b00418282c000410f102d0b1e01017f419182c0004109102d220220001046200220012d0000105220020b1e01017f419a82c0004105102d220220001046200220012d0000105220020b0b00419f82c0004108102d0b0e00101f4100103f10704101105b0bb00102077f017e230041106b2200240010344102103f103c210120004101103922043a000f02400240106d105d4504402000103520002802002102200028020420012000410f6a106f10542105106d105310202107102a450d012002104c1069450d0220012000410f6a2203106f200520021048105c20012003106e20071058200120042002106b200041106a24000f0b41a782c0004117102b000b41be82c000411d102b000b41db82c000411b102b000bc70202077f027e230041d0006b2200240010341041104020004100360240200041406b103a21012000280240103e106d105d450440200041086a103520002802082105200028020c106d105310202107102a04402005104c10690440104c2103200041106a20011042200041286a200041186a28020036020020002000290310370320200041406b410472210103400240200041306a200041206a106520002d0034410346044020032005104441ff01710d01200041d0006a24000f05200041c8006a2204200041386a2802003602002000200029033022083703402008a722022001106f1054210620022001106f2006200428020022041048105c20022001106e20071058200320041047200220002d00442004106b0c020b000b0b41f682c0004126102b000b41db82c000411b102b000b41be82c000411d102b000b41a782c0004117102b000bf40201057f230041106b22022400101f1041104020024100360208200241086a220028020041b8d3c00028020048047f200041ab84c000410a103810390541030b21012002280208103e02400240106d105d450440106d1053210310322100107010550d010240200141ff01714103470440200220013a000f20002002410f6a106f10542204104c1069450d01200020032004102c20002002410f6a106f104c105c41e281c000410e106122032000105f20032001106020032004105e101c0c040b104c2201200041e883c000106f105410472001200041e983c000106f105410472001200041ea83c000106f105410472001104c10690440200020032001102c200041e883c000106f104c105c200041e983c000106f104c105c200041ea83c000106f104c105c41f081c0004112106122032000105f20032001105e101c0c040b41d383c0004115102b000b41d383c0004115102b000b41a782c0004117102b000b41c183c0004112102b000b200241106a24000b1600101f4100103f419f82c0004108102d1055ad10210b1500101f10344100103f107010701055410173105b0ba00102087f017e230041106b22012400101f10344103103f103c21024101103921034102103022001013200120033a000f0240106d105d45044020022001410f6a106f105421041033106d105310202108200420001068450d0120022001410f6a2207106f200420001049105c20022007106e200810582000102c200220032000106c200141106a24000f0b41a782c0004117102b000b419c83c0004125102b000ba10202067f027e230041406a22002400101f10341041104020004100360230200041306a103a21012000280230103e106d105d450440104c210310202106200020011042200041186a200041086a28020036020020002000290300370310200041306a410472210102400340200041206a200041106a106520002d002441034604402003104c10690d0241eb83c0004125102b000b200041386a2202200041286a2802003602002000200029032022073703302007a722042001106f1054210520042001106e200610582005200228020022021068044020032002104720042001106f200520021049105c200420002d00342002106c0c010b0b419c83c0004125102b000b1033106d10532003102c200041406b24000f0b41a782c0004117102b000b4501027f101f10344101103f41001036103d21000240106d105d0440106d21012000102f0d012001200010191a0f0b419084c000411b102b000b2001418881c0004104105a0b3001037f101f4102103f103c2101410110392102419a82c0004105102d2200200110021a2000200210522000105410220b3001037f101f4102103f103c2101410110392102419182c0004109102d2200200110021a2000200210522000105610230ba50502087f017e230041306b22002400101f410121044101103f103c21012000102e2205360220200141e883c000106f10542102200041206a2203200141e883c000106e10562002104d200141e983c000106f105421022003200141e983c000106e10562002104d200141ea83c000106f105421022003200141ea83c000106e10562002104d027f41b4d3c0002d000045044041b4d3c00041013a000041a085c0004100360200200041106a41a485c0004190ce004100106a2000280210200028021441dc84c0004100104f102e0c010b4100210441dc84c0004100102d0b2101200020043a001c200020013602182005101221064100210103402001410c6a220420064b450440200041286a41003602002000420037032020052001200041206a2203410c10451a200029022421082000200028022022014118742001410874418080fc07717220014108764180fe037120014118767272104a2201101222024118742002410874418080fc07717220024108764180fe037120024118767272360220200041186a20034104106620002d001c2102200041003a001c0240024002402002410171220204402001101222074190ce0041a085c00028020022036b4b0d02200041086a2003200320076a22031067200141002000280208200028020c10451a41a085c00020033602000c010b2000280218200110021a0b200020023a001c0c010b200041186a10502000280218200110021a20002d001c200020023a001c410171450d0041a085c000410036020041b4d3c00041003a00000b20002008370320200041186a200041206a41081066200421010c010b0b20002802182101200020002d001c3a002420002001360220200041206a1050200028022020002d0024044041a085c000410036020041b4d3c00041003a00000b10241a200041306a24000b4101027f101f4101103f103c2100104c2201200041e883c000106f105410472001200041e983c000106f105410472001200041ea83c000106f10541047200110220b2c01017f101f4100103f418282c000410f102d10532200102f450440200010241a0f0b418881c000410410250b0300010b0c00418c85c000410e1000000b0bb0050200418080c0000b9a05456e64706f696e742063616e206f6e6c792062652063616c6c6564206279206f776e6572617267756d656e74206465636f6465206572726f722028293a20746f6f2066657720617267756d656e7473746f6f206d616e7920617267756d656e747377726f6e67206e756d626572206f6620617267756d656e7473696e70757420746f6f206c6f6e6745474c44696e707574206f7574206f662072616e676573746f72616765206465636f6465206572726f723a20626164206172726179206c656e6774687661722061726773636c61696d4164646564636c61696d52656d6f766564636c61696d436f6c6c6563746564616c6c436c61696d73436f6c6c6563746564746f6b656e4964656e746966696572636c61696d44617465636c61696d697350617573656452657761726420746f6b656e206973206e6f742073657443616e206f6e6c79206164642064657369676e6174656420746f6b656e4d75737420616464206d6f7265207468616e203020746f6b656e73436c61696d73206164646564206d75737420657175616c207061796d656e7420616d6f756e7443616e6e6f742072656d6f7665206d6f7265207468616e2063757272656e7420636c61696d436f6e74726163742069732070617573656443616e6e6f7420636c61696d203020746f6b656e73000102436c61696d732072656d6f766564206d7573742062652067726561746572207468616e203052657761726420746f6b656e20697320616c726561647920736574636c61696d5f7479706561646472657373636c61696d73696e76616c69642076616c756500e8011000e9011000ea01100063616e6e6f74207375627472616374206265636175736520726573756c7420776f756c64206265206e6567617469766570616e6963206f6363757272656400419c85c0000b049cffffff@0500@0502", - "address": "erd1qqqqqqqqqqqqqpgqtywnp7z0war94rpzk00p2n2wjwaws2xr7yqsejxy7f" - } -} diff --git a/interaction/devnet.snippets.sh b/interaction/devnet.snippets.sh new file mode 100644 index 0000000..99acd7c --- /dev/null +++ b/interaction/devnet.snippets.sh @@ -0,0 +1,154 @@ +PROXY=https://devnet-gateway.elrond.com +CHAIN_ID="D" + +WALLET="./wallet.pem" + +ADDRESS=$(erdpy data load --key=address-devnet) +DEPLOY_TRANSACTION=$(erdpy data load --key=deployTransaction-devnet) + +TOKEN="ITHEUM-a61317" +TOKEN_HEX="0x$(echo -n ${TOKEN} | xxd -p -u | tr -d '\n')" + +deploy(){ + erdpy --verbose contract deploy \ + --bytecode output/claims.wasm \ + --outfile deployOutput \ + --metadata-not-readable \ + --pem wallet.pem \ + --proxy ${PROXY} \ + --chain ${CHAIN_ID} \ + --gas-limit 150000000 \ + --send \ + --recall-nonce \ + --outfile="./interaction/deploy-devnet.interaction.json" || return + + TRANSACTION=$(erdpy data parse --file="./interaction/deploy-devnet.interaction.json" --expression="data['emittedTransactionHash']") + ADDRESS=$(erdpy data parse --file="./interaction/deploy-devnet.interaction.json" --expression="data['contractAddress']") + + erdpy data store --key=address-devnet --value=${ADDRESS} + erdpy data store --key=deployTransaction-devnet --value=${TRANSACTION} +} + +setClaimToken(){ + erdpy --verbose contract call ${ADDRESS} \ + --recall-nonce \ + --pem=${WALLET} \ + --gas-limit=6000000 \ + --function "setClaimToken" \ + --arguments ${TOKEN_HEX} \ + --proxy ${PROXY} \ + --chain ${CHAIN_ID} \ + --send || return +} + +pause(){ + erdpy --verbose contract call ${ADDRESS} \ + --recall-nonce \ + --pem=${WALLET} \ + --gas-limit=6000000 \ + --function "pause" \ + --proxy ${PROXY} \ + --chain ${CHAIN_ID} \ + --send || return +} + +unpause(){ + erdpy --verbose contract call ${ADDRESS} \ + --recall-nonce \ + --pem=${WALLET} \ + --gas-limit=6000000 \ + --function "unpause" \ + --proxy ${PROXY} \ + --chain ${CHAIN_ID} \ + --send || return +} + +addPrivilegedAddress(){ + # $1 = address to which to give privileges + + address="0x$(erdpy wallet bech32 --decode ${1})" + erdpy --verbose contract call ${ADDRESS} \ + --recall-nonce \ + --pem=${WALLET} \ + --gas-limit=10000000 \ + --function "addPrivilegedAddress" \ + --arguments $address \ + --proxy ${PROXY} \ + --chain ${CHAIN_ID} \ + --send || return +} + +removePrivilegedAddress(){ + # $1 = address to which to remove privileges + + address="0x$(erdpy wallet bech32 --decode ${1})" + erdpy --verbose contract call ${ADDRESS} \ + --recall-nonce \ + --pem=${WALLET} \ + --gas-limit=10000000 \ + --function "removePrivilegedAddress" \ + --arguments $address \ + --proxy ${PROXY} \ + --chain ${CHAIN_ID} \ + --send || return +} + +addClaim(){ + # $1 = amount to add to claim + # $2 = address to which to attribute the claim + # $3 = claim type (0 = reward, 1 = aidrop, 2 = allocation) + + method="0x$(echo -n 'addClaim' | xxd -p -u | tr -d '\n')" + address="0x$(erdpy wallet bech32 --decode ${2})" + erdpy --verbose contract call ${ADDRESS} \ + --recall-nonce \ + --pem=${WALLET} \ + --gas-limit=6000000 \ + --function "ESDTTransfer" \ + --arguments ${TOKEN_HEX} $1 $method $address $3 \ + --proxy ${PROXY} \ + --chain ${CHAIN_ID} \ + --send || return +} + +removeClaim(){ + # $1 = address from which to remove the claim + # $2 = claim type (0 = reward, 1 = aidrop, 2 = allocation) + # $3 = amount to remove from claim + + address="0x$(erdpy wallet bech32 --decode ${1})" + erdpy --verbose contract call ${ADDRESS} \ + --recall-nonce \ + --pem=${WALLET} \ + --gas-limit=6000000 \ + --function "removeClaim" \ + --arguments $address $2 $3 \ + --proxy ${PROXY} \ + --chain ${CHAIN_ID} \ + --send || return +} + +harvestAllClaims(){ + erdpy --verbose contract call ${ADDRESS} \ + --recall-nonce \ + --pem=${WALLET} \ + --gas-limit=6000000 \ + --function "claim" \ + --proxy ${PROXY} \ + --chain ${CHAIN_ID} \ + --send || return +} + +harvestClaim(){ + # $1 = claim type (0 = reward, 1 = aidrop, 3 = allocation) + + erdpy --verbose contract call ${ADDRESS} \ + --recall-nonce \ + --pem=${WALLET} \ + --gas-limit=6000000 \ + --function "claim" \ + --arguments $1 \ + --proxy ${PROXY} \ + --chain ${CHAIN_ID} \ + --send || return +} \ No newline at end of file diff --git a/src/events.rs b/src/events.rs index b074aa5..15ac1ce 100644 --- a/src/events.rs +++ b/src/events.rs @@ -3,32 +3,50 @@ elrond_wasm::derive_imports!(); use crate::storage::ClaimType; +//Module that handles event emitting for important smart contract events in order to facilitate logging, debugging and monitoring with ease #[elrond_wasm::module] pub trait EventsModule { + //Emitted whenever a privileged address pauses claim harvesting + #[event("harvestPaused")] + fn harvest_paused_event(&self, #[indexed] operator: &ManagedAddress); + + //Emitted whenever the owner unpauses claim harvesting + #[event("harvestUnpaused")] + fn harvest_unpaused_event(&self); + + //Emitted whenever the owner adds a privileged address + #[event("privilegedAddressAdded")] + fn privileged_address_added_event(&self, #[indexed] address: &ManagedAddress); + + //Emitted whenever the owner removes a privileged address + #[event("privledgedAddressRemoved")] + fn privileged_address_removed_event(&self, #[indexed] address: &ManagedAddress); + + //Emitted whenever a new claim is added to the smart contract #[event("claimAdded")] fn claim_added_event( &self, + #[indexed] operator: &ManagedAddress, #[indexed] address: &ManagedAddress, #[indexed] claim_type: &ClaimType, - amount: BigUint, + #[indexed] amount: &BigUint, ); + //Emitted whenever a claim is removed from the smart contract #[event("claimRemoved")] fn claim_removed_event( &self, #[indexed] address: &ManagedAddress, #[indexed] claim_type: &ClaimType, - amount: BigUint, + #[indexed] amount: &BigUint, ); + //Emitted whenever an address harvests a claim from the smart contract #[event("claimCollected")] fn claim_collected_event( &self, #[indexed] address: &ManagedAddress, #[indexed] claim_type: &ClaimType, - amount: BigUint, + #[indexed] amount: &BigUint, ); - - #[event("allClaimsCollected")] - fn all_claims_collected_event(&self, #[indexed] address: &ManagedAddress, amount: BigUint); } diff --git a/src/lib.rs b/src/lib.rs index ac14a87..333493f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,164 +6,244 @@ elrond_wasm::imports!(); use crate::storage::ClaimType; pub mod events; +pub mod requirements; pub mod storage; pub mod views; #[elrond_wasm::contract] pub trait ClaimsContract: - storage::StorageModule + events::EventsModule + views::ViewsModule + storage::StorageModule + + events::EventsModule + + views::ViewsModule + + requirements::RequirementsModule { + //When the smart contract is deployed claim harvesting is paused #[init] fn init(&self) { self.is_paused().set(true); } + //Endpoint available for the owner of the smart contract to set the token used by the smart contract for claims. Can only be called once successfully. #[only_owner] - #[endpoint(setRewardToken)] - fn set_reward_token(&self, token: TokenIdentifier) { - require!( - self.reward_token().is_empty(), - "Reward token is already set" - ); - self.reward_token().set(&token); + #[endpoint(setClaimToken)] + fn set_claim_token(&self, token: TokenIdentifier) { + require!(self.claim_token().is_empty(), "Claim token is already set"); + self.claim_token().set(&token); } - #[only_owner] + //Endpoint available for privileged addresses of the smart contract to pause claim harvesting. Cannot be called while harvesting is already paused. #[endpoint(pause)] fn pause(&self) { - self.is_paused().set(!self.is_paused().get()); + require!(!self.is_paused().get(), "Contract is already paused"); + let caller = self.blockchain().get_caller(); + self.require_address_is_privileged(&caller); + self.is_paused().set(true); + self.harvest_paused_event(&caller); + } + + //Endpoint avbailable for the owner of the smart contract to resume claim harvesting. Cannot be called while harvesting is already unpaused. + #[only_owner] + #[endpoint(unpause)] + fn unpause(&self) { + require!(self.is_paused().get(), "Contract is already unpaused"); + self.is_paused().set(false); + self.harvest_unpaused_event(); } + //Endpoint available for owner in order to add an address to the list of privileged addresses #[only_owner] + #[endpoint(addPrivilegedAddress)] + fn add_privileged_address(&self, address: ManagedAddress) { + let privileged_addresses = self.privileged_addresses(); + require!( + !privileged_addresses.contains(&address), + "Address is already privileged" + ); + require!( + privileged_addresses.len() < 2usize, + "Maximum number of priviledged addresses reached" + ); + let owner = self.blockchain().get_owner_address(); + require!( + owner != address, + "Owner cannot be added to priviledged addresses" + ); + self.privileged_address_added_event(&address); + self.privileged_addresses().insert(address); + } + + //Endpoint available for owner in order to remove an address from the list of privileged addresses + #[only_owner] + #[endpoint(removePrivilegedAddress)] + fn remove_privileged_address(&self, address: ManagedAddress) { + let privileged_addresses = self.privileged_addresses(); + require!( + privileged_addresses.contains(&address), + "Address is not privileged" + ); + self.privileged_address_removed_event(&address); + self.privileged_addresses().remove(&address); + } + + //Endpoint available for privileged addresses of the smart contract to add a claim of a specific claim type for a specific address. #[payable("*")] #[endpoint(addClaim)] fn add_claim(&self, address: &ManagedAddress, claim_type: ClaimType) { - require!(!self.reward_token().is_empty(), "Reward token is not set"); + self.require_claim_token_is_set(); let (payment_amount, payment_token) = self.call_value().payment_token_pair(); + self.require_token_is_correct(payment_token); + self.require_value_not_zero(&payment_amount); + let caller = self.blockchain().get_caller(); + self.require_address_is_privileged(&caller); let current_claim = self.claim(address, &claim_type).get(); - let reward_token = self.reward_token().get(); let timestamp = self.blockchain().get_block_timestamp(); - require!( - payment_token == reward_token, - "Can only add designated token" - ); - require!( - payment_amount > BigUint::zero(), - "Must add more than 0 tokens" - ); + //Add the amount of the tokens sent to the current claim reservation self.claim(address, &claim_type) .set(current_claim + &payment_amount); - self.claim_add_date(address, &claim_type).set(timestamp); - self.claim_added_event(address, &claim_type, payment_amount); + //Update the last modification date of the claim to the current timestamp + self.claim_modify_date(address, &claim_type).set(timestamp); + self.claim_added_event(&caller, &address, &claim_type, &payment_amount); } - #[only_owner] + //Endpoint available for privileged addresses of the smart contract to add a bulk of claims of different claim types for different specific addresses. #[payable("*")] #[endpoint(addClaims)] fn add_claims( &self, claims: MultiValueEncoded>, ) { - require!(!self.reward_token().is_empty(), "Reward token is not set"); + self.require_claim_token_is_set(); + self.require_number_of_claims_in_bulk_is_valid(&claims.len()); let (payment_amount, payment_token) = self.call_value().payment_token_pair(); - let reward_token = self.reward_token().get(); + self.require_token_is_correct(payment_token); + self.require_value_not_zero(&payment_amount); + let caller = self.blockchain().get_caller(); + self.require_address_is_privileged(&caller); let timestamp = self.blockchain().get_block_timestamp(); - require!( - payment_token == reward_token, - "Can only add designated token" - ); - require!( - payment_amount > BigUint::zero(), - "Must add more than 0 tokens" - ); + //Initialize the sum of claims to be added to zero let mut sum_of_claims = BigUint::zero(); + //Iterate over the claims provided as argument and proceeds similarly to the add_claim endpoint for each one for item in claims.into_iter() { - let tuple = item.into_tuple(); - let current_claim = self.claim(&tuple.0, &tuple.1).get(); - self.claim(&tuple.0, &tuple.1).set(current_claim + &tuple.2); - self.claim_add_date(&tuple.0, &tuple.1).set(timestamp); - sum_of_claims += &tuple.2; - self.claim_added_event(&tuple.0, &tuple.1, tuple.2); + let (address, claim_type, amount) = item.into_tuple(); + self.require_value_not_zero(&amount); + let current_claim = self.claim(&address, &claim_type).get(); + self.claim(&address, &claim_type) + .set(current_claim + &amount); + self.claim_modify_date(&address, &claim_type).set(timestamp); + sum_of_claims += &amount; + self.claim_added_event(&caller, &address, &claim_type, &amount); } + //Panic if the amount of tokens sent by the owner to the endpoint are not equal to the sum of the claims added to the contract require!( sum_of_claims == payment_amount, "Claims added must equal payment amount" ); } + //Endpoint available for the owner of the smart contract to remove a claim of a specific claim type for a specific address. #[only_owner] #[endpoint(removeClaim)] fn remove_claim(&self, address: &ManagedAddress, claim_type: ClaimType, amount: BigUint) { - require!(!self.reward_token().is_empty(), "Reward token is not set"); + self.require_claim_token_is_set(); + self.require_value_not_zero(&amount); let current_claim = self.claim(address, &claim_type).get(); + self.require_remove_claim_is_valid(¤t_claim, &amount); let owner = self.blockchain().get_owner_address(); - let reward_token = self.reward_token().get(); + let claim_token = self.claim_token().get(); let timestamp = self.blockchain().get_block_timestamp(); - require!( - current_claim >= amount, - "Cannot remove more than current claim" - ); + //Remove the amount of tokens given as argument from the current claim reservation self.claim(address, &claim_type) .set(current_claim - &amount); - self.claim_add_date(address, &claim_type).set(timestamp); - self.send().direct(&owner, &reward_token, 0, &amount, &[]); - self.claim_removed_event(address, &claim_type, amount); + //Update the modification date of the claim to the current timestamp + self.claim_modify_date(address, &claim_type).set(timestamp); + self.claim_removed_event(&address, &claim_type, &amount); + //Send the removed tokens from the claim back to the owner of the smart contract + self.send().direct(&owner, &claim_token, 0, &amount, &[]); } + //Endpoint available for the owner of the smart contract to remove a bulk of claims of different claim types for different specific addresses. #[only_owner] #[endpoint(removeClaims)] fn remove_claims( &self, claims: MultiValueEncoded>, ) { - require!(!self.reward_token().is_empty(), "Reward token is not set"); + self.require_claim_token_is_set(); + //Panics if the user tries to add more than 200 claims per operation. Implemented in order to ensure + self.require_number_of_claims_in_bulk_is_valid(&claims.len()); + //Initialize the sum of claims to be removed to zero let mut sum_of_claims = BigUint::zero(); let timestamp = self.blockchain().get_block_timestamp(); + //Iterate over the claims provided as argument and proceeds similarly to the remove_claim endpoint for each one for item in claims.into_iter() { - let tuple = item.into_tuple(); - let current_claim = self.claim(&tuple.0, &tuple.1).get(); - self.claim_add_date(&tuple.0, &tuple.1).set(timestamp); - require!( - current_claim >= tuple.2, - "Cannot remove more than current claim" - ); - sum_of_claims += &tuple.2; - self.claim(&tuple.0, &tuple.1).set(current_claim - &tuple.2); - self.claim_removed_event(&tuple.0, &tuple.1, tuple.2); + let (address, claim_type, amount) = item.into_tuple(); + self.require_value_not_zero(&amount); + let current_claim = self.claim(&address, &claim_type).get(); + self.require_remove_claim_is_valid(¤t_claim, &amount); + self.claim_modify_date(&address, &claim_type).set(timestamp); + sum_of_claims += &amount; + self.claim(&address, &claim_type) + .set(current_claim - &amount); + self.claim_removed_event(&address, &claim_type, &amount); } - require!( - sum_of_claims > BigUint::zero(), - "Claims removed must be greater than 0" - ); let owner = self.blockchain().get_owner_address(); - let reward_token = self.reward_token().get(); + let claim_token = self.claim_token().get(); + //Send the removed tokens from the claim back to the owner of the smart contract self.send() - .direct(&owner, &reward_token, 0, &sum_of_claims, &[]); + .direct(&owner, &claim_token, 0, &sum_of_claims, &[]); } + //Endpoint available for the public to claim tokens reserved for the calling address. Cannot be called while contract is paused for the public/(harvesting is paused). + //Can be given an argument as a claim type to harvest only specific claim type. If the claim_type argument is not provided, all claim types for the calling addresses will be harvested. #[endpoint(claim)] fn harvest_claim(&self, claim_type: OptionalValue) { - require!(!self.reward_token().is_empty(), "Reward token is not set"); - let reward_token = self.reward_token().get(); - let caller = self.blockchain().get_caller(); require!(!self.is_paused().get(), "Contract is paused"); + self.require_claim_token_is_set(); + let claim_token = self.claim_token().get(); + let caller = self.blockchain().get_caller(); + //Initializes the amount of tokens to be harvested to zero. + let mut claim = BigUint::zero(); + //Checks whether the claim type argument is provided. if let OptionalValue::Some(what_type_to_claim) = claim_type { - let claim = self.claim(&caller, &what_type_to_claim).get(); - require!(claim > BigUint::zero(), "Cannot claim 0 tokens"); - self.send().direct(&caller, &reward_token, 0, &claim, &[]); + //Sets claim to the given amount of tokens reserved for the calling address and the given claim type. + claim = self.claim(&caller, &what_type_to_claim).get(); + self.require_value_not_zero(&claim); + //Resets the reserved tokens for the given claim type of the calling address to zero. self.claim(&caller, &what_type_to_claim) .set(BigUint::zero()); - self.claim_collected_event(&caller, &what_type_to_claim, claim); + self.claim_collected_event(&caller, &what_type_to_claim, &claim); } else { - let claim = self.view_claims(&caller); - require!(claim > BigUint::zero(), "Cannot claim 0 tokens"); - self.send().direct(&caller, &reward_token, 0, &claim, &[]); - self.claim(&caller, &ClaimType::Reward).set(BigUint::zero()); - self.claim(&caller, &ClaimType::Airdrop) - .set(BigUint::zero()); - self.claim(&caller, &ClaimType::Allocation) - .set(BigUint::zero()); - self.all_claims_collected_event(&caller, claim); + //Sets claim to the sum of all reserved tokens for the calling address. + + //Checks claims of the reward type and adds them to the sum if they are not zero. + let reward_claim = self.claim(&caller, &ClaimType::Reward).get(); + if reward_claim > BigUint::zero() { + claim += &reward_claim; + self.claim_collected_event(&caller, &ClaimType::Reward, &reward_claim); + self.claim(&caller, &ClaimType::Reward).set(BigUint::zero()); + } + + //Checks claims of the airdrop type and adds them to the sum if they are not zero. + let airdrop_claim = self.claim(&caller, &ClaimType::Airdrop).get(); + if airdrop_claim > BigUint::zero() { + claim += &airdrop_claim; + self.claim_collected_event(&caller, &ClaimType::Airdrop, &airdrop_claim); + self.claim(&caller, &ClaimType::Airdrop) + .set(BigUint::zero()); + } + + //Checks claims of the allocation type and adds them to the sum if they are not zero. + let allocation_claim = self.claim(&caller, &ClaimType::Allocation).get(); + if allocation_claim > BigUint::zero() { + claim += &allocation_claim; + self.claim_collected_event(&caller, &ClaimType::Allocation, &allocation_claim); + self.claim(&caller, &ClaimType::Allocation) + .set(BigUint::zero()); + } + + self.require_value_not_zero(&claim); } + //Send the amount of tokens harvested (all tokens of a given claim type or the sum for all claim types) to the calling address. + self.send().direct(&caller, &claim_token, 0, &claim, &[]); } } diff --git a/src/requirements.rs b/src/requirements.rs new file mode 100644 index 0000000..236bdb4 --- /dev/null +++ b/src/requirements.rs @@ -0,0 +1,52 @@ +elrond_wasm::imports!(); +elrond_wasm::derive_imports!(); + +//Module that handles generic (commonly used, which are not specific to one function) requirements which should stop execution and rollback if not met +#[elrond_wasm::module] +pub trait RequirementsModule: crate::storage::StorageModule { + // Checks whether the owner of the smart contract designated a token to be used by the smart contract for all the claims + fn require_claim_token_is_set(&self) { + require!(!self.claim_token().is_empty(), "Claims token is not set"); + } + + //Checks whether a given token identifier is equal to the token identifier of the token used by the smart contract claims + fn require_token_is_correct(&self, token: TokenIdentifier) { + require!( + token == self.claim_token().get(), + "Can only add designated token" + ); + } + + //Checks whether a value is not zero + fn require_value_not_zero(&self, value: &BigUint) { + require!( + value > &BigUint::zero(), + "Operations must have non-zero value" + ); + } + + //Checks whether a claim that is intended to be removed is smaller than the amount reserved in the claim + fn require_remove_claim_is_valid(&self, current_claim: &BigUint, amount: &BigUint) { + require!( + current_claim >= amount, + "Cannot remove more than current claim" + ); + } + + //Checks whether the number of claims added or removed is smaller than 200. Implemented in order to ensure no call will fail due to consuming more than the maxium gas allowed per transaciton on Elrond. + fn require_number_of_claims_in_bulk_is_valid(&self, number_of_claims: &usize) { + require!( + number_of_claims <= &200usize, + "Exceeded maximum number of claims per operation (200)" + ); + } + + //Checks whether the address has the special rights needed in case of some special operations + fn require_address_is_privileged(&self, address: &ManagedAddress) { + require!( + self.privileged_addresses().contains(address) + || &self.blockchain().get_owner_address() == address, + "Address doesn't have the privilege to use this operation" + ); + } +} diff --git a/src/storage.rs b/src/storage.rs index ca3a1e0..33522c0 100644 --- a/src/storage.rs +++ b/src/storage.rs @@ -1,6 +1,7 @@ elrond_wasm::imports!(); elrond_wasm::derive_imports!(); +// Enumeration used to define claim types and increase readability of the code #[derive(TopEncode, TopDecode, NestedEncode, NestedDecode, PartialEq, Clone, Debug, TypeAbi)] pub enum ClaimType { Reward, @@ -8,26 +9,35 @@ pub enum ClaimType { Allocation, } +// Module that handles the common storage of the smart contract #[elrond_wasm::module] pub trait StorageModule { + // Stores the token identifier of the token that is used for claims in the smart contract #[view(viewTokenIdentifier)] #[storage_mapper("tokenIdentifier")] - fn reward_token(&self) -> SingleValueMapper; + fn claim_token(&self) -> SingleValueMapper; + // Stores the amount available to claim for each address and claim type #[view(viewClaim)] #[storage_mapper("claim")] fn claim(&self, address: &ManagedAddress, claim_type: &ClaimType) -> SingleValueMapper; - #[view(viewClaimAddDate)] + // Stores the last timestamp at which the claim has been modified by the owner for each address and claim type + #[view(viewClaimModifyDate)] #[storage_mapper("claimDate")] - fn claim_add_date( + fn claim_modify_date( &self, address: &ManagedAddress, claim_type: &ClaimType, ) -> SingleValueMapper; + // Stores whether claim harvesting is paused or not #[view(isPaused)] #[storage_mapper("isPaused")] fn is_paused(&self) -> SingleValueMapper; + + #[view(viewPrivilegedAddresses)] + #[storage_mapper("privilegedAddresses")] + fn privileged_addresses(&self) -> SetMapper; } diff --git a/src/views.rs b/src/views.rs index 9df217c..020875c 100644 --- a/src/views.rs +++ b/src/views.rs @@ -3,14 +3,17 @@ elrond_wasm::derive_imports!(); use crate::storage::ClaimType; +//Structure that is used in order to return claims with their last modification timestamp #[derive(ManagedVecItem, Clone, NestedEncode, NestedDecode, TopEncode, TopDecode, TypeAbi)] pub struct Claim { pub amount: BigUint, pub date: u64, } +//Module that implements views, by which we understand read-only endpoints #[elrond_wasm::module] pub trait ViewsModule: crate::storage::StorageModule { + //View that returns the sum of all claims, from all claim types, for a given address #[view(viewClaims)] fn view_claims(&self, address: &ManagedAddress) -> BigUint { let mut claim = BigUint::zero(); @@ -20,20 +23,23 @@ pub trait ViewsModule: crate::storage::StorageModule { claim } + //View that returns all claims with the last timestamp at which the claims have been modified by the owner for a given address #[view(viewClaimWithDate)] fn view_claims_with_date(&self, address: &ManagedAddress) -> ManagedVec> { let mut claims = ManagedVec::new(); claims.push(Claim { amount: self.claim(address, &ClaimType::Reward).get(), - date: self.claim_add_date(address, &ClaimType::Reward).get(), + date: self.claim_modify_date(address, &ClaimType::Reward).get(), }); claims.push(Claim { amount: self.claim(address, &ClaimType::Airdrop).get(), - date: self.claim_add_date(address, &ClaimType::Airdrop).get(), + date: self.claim_modify_date(address, &ClaimType::Airdrop).get(), }); claims.push(Claim { amount: self.claim(address, &ClaimType::Allocation).get(), - date: self.claim_add_date(address, &ClaimType::Allocation).get(), + date: self + .claim_modify_date(address, &ClaimType::Allocation) + .get(), }); claims } diff --git a/tests/empty_mandos_go_test.rs b/tests/empty_mandos_go_test.rs deleted file mode 100644 index b989e39..0000000 --- a/tests/empty_mandos_go_test.rs +++ /dev/null @@ -1,4 +0,0 @@ -#[test] -fn claims_go() { - elrond_wasm_debug::mandos_go("mandos/claims.scen.json"); -} diff --git a/tests/empty_mandos_rs_test.rs b/tests/empty_mandos_rs_test.rs deleted file mode 100644 index 9622fbc..0000000 --- a/tests/empty_mandos_rs_test.rs +++ /dev/null @@ -1,13 +0,0 @@ -use elrond_wasm_debug::*; - -fn world() -> BlockchainMock { - let mut blockchain = BlockchainMock::new(); - - blockchain.register_contract_builder("file:output/claims.wasm", claims::ContractBuilder); - blockchain -} - -#[test] -fn claims_rs() { - elrond_wasm_debug::mandos_rs("mandos/claims.scen.json", world()); -} diff --git a/tests/empty_rust_test.rs b/tests/empty_rust_test.rs deleted file mode 100644 index 5aced4b..0000000 --- a/tests/empty_rust_test.rs +++ /dev/null @@ -1,464 +0,0 @@ -use claims::storage::StorageModule; -use claims::*; -use elrond_wasm::{ - elrond_codec::multi_types::{MultiValue3, OptionalValue}, - types::{Address, MultiValueEncoded}, -}; - -use elrond_wasm_debug::{ - managed_address, managed_biguint, managed_token_id, rust_biguint, testing_framework::*, - DebugApi, -}; -pub const WASM_PATH: &'static str = "../output/claims.wasm"; -pub const TOKEN_ID: &[u8] = b"ITHEUM-df6f26"; -pub const WRONG_TOKEN_ID: &[u8] = b"WRONG-123456"; -pub const OWNER_EGLD_BALANCE: u64 = 100_000_000; - -struct ContractSetup -where - ContractObjBuilder: 'static + Copy + Fn() -> claims::ContractObj, -{ - pub blockchain_wrapper: BlockchainStateWrapper, - pub owner_address: Address, - pub contract_wrapper: ContractObjWrapper, ContractObjBuilder>, - pub first_user_address: Address, - pub second_user_address: Address, -} - -fn setup_contract( - cf_builder: ContractObjBuilder, -) -> ContractSetup -where - ContractObjBuilder: 'static + Copy + Fn() -> claims::ContractObj, -{ - let rust_zero = rust_biguint!(0u64); - let mut blockchain_wrapper = BlockchainStateWrapper::new(); - let first_user_address = blockchain_wrapper.create_user_account(&rust_zero); - let second_user_address = blockchain_wrapper.create_user_account(&rust_zero); - let owner_address = blockchain_wrapper.create_user_account(&rust_biguint!(OWNER_EGLD_BALANCE)); - let cf_wrapper = blockchain_wrapper.create_sc_account( - &rust_zero, - Some(&owner_address), - cf_builder, - WASM_PATH, - ); - blockchain_wrapper.set_esdt_balance(&owner_address, TOKEN_ID, &rust_biguint!(5_000_000)); - blockchain_wrapper.set_esdt_balance(&owner_address, WRONG_TOKEN_ID, &rust_biguint!(1_000_000)); - blockchain_wrapper.set_esdt_balance(&first_user_address, TOKEN_ID, &rust_biguint!(1_000)); - blockchain_wrapper.set_esdt_balance(&second_user_address, TOKEN_ID, &rust_biguint!(0)); - - blockchain_wrapper - .execute_tx(&owner_address, &cf_wrapper, &rust_zero, |sc| { - sc.init(); - }) - .assert_ok(); - blockchain_wrapper - .execute_tx(&owner_address, &cf_wrapper, &rust_zero, |sc| { - sc.set_reward_token(managed_token_id!(TOKEN_ID)); - }) - .assert_ok(); - - blockchain_wrapper - .execute_query(&cf_wrapper, |sc| { - assert_eq!(sc.is_paused().get(), true); - }) - .assert_ok(); - - blockchain_wrapper - .execute_tx(&owner_address, &cf_wrapper, &rust_zero, |sc| { - sc.pause(); - }) - .assert_ok(); - - blockchain_wrapper.add_mandos_set_account(cf_wrapper.address_ref()); - - ContractSetup { - blockchain_wrapper, - owner_address, - first_user_address, - second_user_address, - contract_wrapper: cf_wrapper, - } -} - -#[test] //Tests whether the contrat is deployed and initialized after deployment correctly -fn deploy_test() { - let mut setup = setup_contract(claims::contract_obj); - setup - .blockchain_wrapper - .execute_tx( - &setup.owner_address, - &setup.contract_wrapper, - &rust_biguint!(0u64), - |sc| { - sc.init(); - }, - ) - .assert_ok(); -} - -#[test] //Tests wether adding and removing singular claims works and also if removing returns an error if trying to remove more than the available claim -fn add_and_remove_claim_test() { - let mut setup = setup_contract(claims::contract_obj); - let b_wrapper = &mut setup.blockchain_wrapper; - let owner_address = &setup.owner_address; - let user_addr = &setup.first_user_address; - - b_wrapper - .execute_esdt_transfer( - owner_address, - &setup.contract_wrapper, - TOKEN_ID, - 0, - &rust_biguint!(1_000_000), - |sc| { - sc.add_claim(&managed_address!(user_addr), storage::ClaimType::Airdrop); - }, - ) - .assert_ok(); - b_wrapper - .execute_esdt_transfer( - owner_address, - &setup.contract_wrapper, - TOKEN_ID, - 0, - &rust_biguint!(0), - |sc| { - sc.remove_claim( - &managed_address!(user_addr), - storage::ClaimType::Airdrop, - managed_biguint!(500_000), - ); - }, - ) - .assert_ok(); - b_wrapper - .execute_esdt_transfer( - owner_address, - &setup.contract_wrapper, - TOKEN_ID, - 0, - &rust_biguint!(0), - |sc| { - sc.remove_claim( - &managed_address!(user_addr), - storage::ClaimType::Airdrop, - managed_biguint!(700_000), - ); - }, - ) - .assert_user_error("Cannot remove more than current claim"); -} - -#[test] //Same tests as the ones for singular claims, but for multiple claims + testing whether adding claims, but not sending enough tokens returns an error -fn add_and_remove_claims_test() { - let mut setup = setup_contract(claims::contract_obj); - let b_wrapper = &mut setup.blockchain_wrapper; - let owner_address = &setup.owner_address; - let first_user_addr = &setup.first_user_address; - let second_user_addr = &setup.second_user_address; - - b_wrapper - .execute_esdt_transfer( - owner_address, - &setup.contract_wrapper, - TOKEN_ID, - 0, - &rust_biguint!(2_000_000), - |sc| { - let mut args = MultiValueEncoded::new(); - args.push(MultiValue3( - ( - managed_address!(first_user_addr), - storage::ClaimType::Airdrop, - managed_biguint!(1_000_000), - ) - .into(), - )); - args.push(MultiValue3( - ( - managed_address!(second_user_addr), - storage::ClaimType::Allocation, - managed_biguint!(1_000_000), - ) - .into(), - )); - sc.add_claims(args); - }, - ) - .assert_ok(); - b_wrapper - .execute_esdt_transfer( - owner_address, - &setup.contract_wrapper, - TOKEN_ID, - 0, - &rust_biguint!(1_700_000), - |sc| { - let mut args = MultiValueEncoded::new(); - args.push(MultiValue3( - ( - managed_address!(first_user_addr), - storage::ClaimType::Airdrop, - managed_biguint!(1_000_000), - ) - .into(), - )); - args.push(MultiValue3( - ( - managed_address!(second_user_addr), - storage::ClaimType::Allocation, - managed_biguint!(1_000_000), - ) - .into(), - )); - sc.add_claims(args); - }, - ) - .assert_user_error("Claims added must equal payment amount"); - b_wrapper - .execute_esdt_transfer( - owner_address, - &setup.contract_wrapper, - TOKEN_ID, - 0, - &rust_biguint!(0), - |sc| { - let mut args = MultiValueEncoded::new(); - args.push(MultiValue3( - ( - managed_address!(first_user_addr), - storage::ClaimType::Airdrop, - managed_biguint!(1_000_000), - ) - .into(), - )); - args.push(MultiValue3( - ( - managed_address!(second_user_addr), - storage::ClaimType::Allocation, - managed_biguint!(500_000), - ) - .into(), - )); - sc.remove_claims(args); - }, - ) - .assert_ok(); - b_wrapper - .execute_esdt_transfer( - owner_address, - &setup.contract_wrapper, - TOKEN_ID, - 0, - &rust_biguint!(0), - |sc| { - let mut args = MultiValueEncoded::new(); - args.push(MultiValue3( - ( - managed_address!(first_user_addr), - storage::ClaimType::Airdrop, - managed_biguint!(300_000), - ) - .into(), - )); - args.push(MultiValue3( - ( - managed_address!(second_user_addr), - storage::ClaimType::Allocation, - managed_biguint!(500_000), - ) - .into(), - )); - sc.remove_claims(args); - }, - ) - .assert_user_error("Cannot remove more than current claim"); -} - -#[test] //Tests whether the transaction to add a token fails in the case in which a different token than the reward token is sent -fn add_claim_wrong_token_test() { - let mut setup = setup_contract(claims::contract_obj); - let b_wrapper = &mut setup.blockchain_wrapper; - let owner_address = &setup.owner_address; - let user_addr = &setup.first_user_address; - - b_wrapper - .execute_esdt_transfer( - owner_address, - &setup.contract_wrapper, - WRONG_TOKEN_ID, - 0, - &rust_biguint!(1_000_000), - |sc| { - sc.add_claim(&managed_address!(user_addr), storage::ClaimType::Airdrop); - }, - ) - .assert_user_error("Can only add designated token"); -} - -#[test] //Tests whether one can set the reward token only once -fn reset_reward_token_test() { - let mut setup = setup_contract(claims::contract_obj); - let b_wrapper = &mut setup.blockchain_wrapper; - let owner_address = &setup.owner_address; - - b_wrapper - .execute_tx( - &owner_address, - &setup.contract_wrapper, - &rust_biguint!(0), - |sc| { - sc.set_reward_token(managed_token_id!(TOKEN_ID)); - }, - ) - .assert_user_error("Reward token is already set"); -} - -#[test] //Tests whether claiming is impossible in pause state -fn harvest_claim_in_pause_test() { - let mut setup = setup_contract(claims::contract_obj); - let b_wrapper = &mut setup.blockchain_wrapper; - let owner_address = &setup.owner_address; - let user_addr = &setup.second_user_address; - - b_wrapper - .execute_tx( - &owner_address, - &setup.contract_wrapper, - &rust_biguint!(0), - |sc| { - sc.pause(); - }, - ) - .assert_ok(); - - b_wrapper - .execute_esdt_transfer( - user_addr, - &setup.contract_wrapper, - TOKEN_ID, - 0, - &rust_biguint!(0), - |sc| { - sc.harvest_claim(OptionalValue::Some(storage::ClaimType::Airdrop)); - }, - ) - .assert_user_error("Contract is paused"); -} - -#[test] //Tests whether users can claim -fn harvest_claim_test() { - let mut setup = setup_contract(claims::contract_obj); - let b_wrapper = &mut setup.blockchain_wrapper; - let owner_address = &setup.owner_address; - let user_addr = &setup.second_user_address; - - b_wrapper - .execute_esdt_transfer( - owner_address, - &setup.contract_wrapper, - TOKEN_ID, - 0, - &rust_biguint!(1_000_000), - |sc| { - sc.add_claim(&managed_address!(user_addr), storage::ClaimType::Airdrop); - }, - ) - .assert_ok(); - - b_wrapper - .execute_esdt_transfer( - user_addr, - &setup.contract_wrapper, - TOKEN_ID, - 0, - &rust_biguint!(0), - |sc| { - sc.harvest_claim(OptionalValue::Some(storage::ClaimType::Airdrop)); - }, - ) - .assert_ok(); -} - -#[test] //Test wether the transaction to claim returns an error if no claims are present for the user for the type he tries to claim -fn harvest_wrong_claim_type_test() { - let mut setup = setup_contract(claims::contract_obj); - let b_wrapper = &mut setup.blockchain_wrapper; - let owner_address = &setup.owner_address; - let user_addr = &setup.second_user_address; - - b_wrapper - .execute_esdt_transfer( - owner_address, - &setup.contract_wrapper, - TOKEN_ID, - 0, - &rust_biguint!(1_000_000), - |sc| { - sc.add_claim(&managed_address!(user_addr), storage::ClaimType::Airdrop); - }, - ) - .assert_ok(); - - b_wrapper - .execute_esdt_transfer( - user_addr, - &setup.contract_wrapper, - TOKEN_ID, - 0, - &rust_biguint!(0), - |sc| { - sc.harvest_claim(OptionalValue::Some(storage::ClaimType::Reward)); - }, - ) - .assert_user_error("Cannot claim 0 tokens"); -} - -#[test] //Tests whether claiming all claim types at once works -fn harvest_all_claims_test() { - let mut setup = setup_contract(claims::contract_obj); - let b_wrapper = &mut setup.blockchain_wrapper; - let owner_address = &setup.owner_address; - let user_addr = &setup.second_user_address; - - b_wrapper - .execute_esdt_transfer( - owner_address, - &setup.contract_wrapper, - TOKEN_ID, - 0, - &rust_biguint!(1_000_000), - |sc| { - sc.add_claim(&managed_address!(user_addr), storage::ClaimType::Airdrop); - }, - ) - .assert_ok(); - - b_wrapper - .execute_esdt_transfer( - owner_address, - &setup.contract_wrapper, - TOKEN_ID, - 0, - &rust_biguint!(1_000_000), - |sc| { - sc.add_claim(&managed_address!(user_addr), storage::ClaimType::Reward); - }, - ) - .assert_ok(); - - b_wrapper - .execute_esdt_transfer( - user_addr, - &setup.contract_wrapper, - TOKEN_ID, - 0, - &rust_biguint!(0), - |sc| { - sc.harvest_claim(OptionalValue::None); - }, - ) - .assert_ok(); - b_wrapper.check_esdt_balance(user_addr, TOKEN_ID, &rust_biguint!(2_000_000)); -} diff --git a/tests/rust_tests.rs b/tests/rust_tests.rs new file mode 100644 index 0000000..929778e --- /dev/null +++ b/tests/rust_tests.rs @@ -0,0 +1,1200 @@ +use claims::storage::StorageModule; +use claims::*; +use elrond_wasm::{ + elrond_codec::multi_types::{MultiValue3, OptionalValue}, + types::{Address, MultiValueEncoded}, +}; + +use elrond_wasm_debug::{ + managed_address, managed_biguint, managed_token_id, rust_biguint, testing_framework::*, + DebugApi, +}; +pub const WASM_PATH: &'static str = "../output/claims.wasm"; +pub const TOKEN_ID: &[u8] = b"ITHEUM-df6f26"; +pub const WRONG_TOKEN_ID: &[u8] = b"WRONG-123456"; +pub const OWNER_EGLD_BALANCE: u64 = 100_000_000; + +struct ContractSetup +where + ContractObjBuilder: 'static + Copy + Fn() -> claims::ContractObj, +{ + pub blockchain_wrapper: BlockchainStateWrapper, + pub owner_address: Address, + pub contract_wrapper: ContractObjWrapper, ContractObjBuilder>, + pub first_user_address: Address, + pub second_user_address: Address, + pub third_user_address: Address, +} + +fn setup_contract( + cf_builder: ContractObjBuilder, +) -> ContractSetup +where + ContractObjBuilder: 'static + Copy + Fn() -> claims::ContractObj, +{ + let rust_zero = rust_biguint!(0u64); + let mut blockchain_wrapper = BlockchainStateWrapper::new(); + let first_user_address = blockchain_wrapper.create_user_account(&rust_zero); + let second_user_address = blockchain_wrapper.create_user_account(&rust_zero); + let third_user_address = blockchain_wrapper.create_user_account(&rust_zero); + let owner_address = blockchain_wrapper.create_user_account(&rust_biguint!(OWNER_EGLD_BALANCE)); + let cf_wrapper = blockchain_wrapper.create_sc_account( + &rust_zero, + Some(&owner_address), + cf_builder, + WASM_PATH, + ); + blockchain_wrapper.set_esdt_balance(&owner_address, TOKEN_ID, &rust_biguint!(5_000_000)); + blockchain_wrapper.set_esdt_balance(&owner_address, WRONG_TOKEN_ID, &rust_biguint!(1_000_000)); + blockchain_wrapper.set_esdt_balance(&first_user_address, TOKEN_ID, &rust_biguint!(1_000)); + blockchain_wrapper.set_esdt_balance(&second_user_address, TOKEN_ID, &rust_biguint!(0)); + blockchain_wrapper.set_esdt_balance(&third_user_address, TOKEN_ID, &rust_biguint!(1_000)); + + blockchain_wrapper + .execute_tx(&owner_address, &cf_wrapper, &rust_zero, |sc| { + sc.init(); + }) + .assert_ok(); + blockchain_wrapper + .execute_tx(&owner_address, &cf_wrapper, &rust_zero, |sc| { + sc.set_claim_token(managed_token_id!(TOKEN_ID)); + }) + .assert_ok(); + + blockchain_wrapper + .execute_tx(&owner_address, &cf_wrapper, &rust_zero, |sc| { + sc.add_privileged_address(managed_address!(&first_user_address)); + }) + .assert_ok(); + + blockchain_wrapper + .execute_query(&cf_wrapper, |sc| { + assert_eq!(sc.is_paused().get(), true); + }) + .assert_ok(); + + blockchain_wrapper + .execute_tx(&owner_address, &cf_wrapper, &rust_zero, |sc| { + sc.unpause(); + }) + .assert_ok(); + + blockchain_wrapper.add_mandos_set_account(cf_wrapper.address_ref()); + + ContractSetup { + blockchain_wrapper, + owner_address, + first_user_address, + second_user_address, + third_user_address, + contract_wrapper: cf_wrapper, + } +} + +#[test] //Tests whether the contrat is deployed and initialized correctly after deployment +fn deploy_test() { + let mut setup = setup_contract(claims::contract_obj); + setup + .blockchain_wrapper + .execute_tx( + &setup.owner_address, + &setup.contract_wrapper, + &rust_biguint!(0u64), + |sc| { + sc.init(); + }, + ) + .assert_ok(); +} + +#[test] //Tests wether pausing and unpausing the contract works correctly + //Tests wether trying to change the pause state to the already set state returns an error + //Tests wether privileged addresses can pause harvesting, but normal addresses cannnot +fn pause_unpause_test() { + let mut setup = setup_contract(claims::contract_obj); + let b_wrapper = &mut setup.blockchain_wrapper; + let owner_address = &setup.owner_address; + let first_user_address = &setup.first_user_address; + let second_user_address = &setup.second_user_address; + + b_wrapper + .execute_query(&setup.contract_wrapper, |sc| { + assert_eq!(sc.is_paused().get(), false); + }) + .assert_ok(); + + b_wrapper + .execute_tx( + &owner_address, + &setup.contract_wrapper, + &rust_biguint!(0), + |sc| { + sc.unpause(); + }, + ) + .assert_user_error("Contract is already unpaused"); + + b_wrapper + .execute_tx( + &owner_address, + &setup.contract_wrapper, + &rust_biguint!(0), + |sc| { + sc.pause(); + }, + ) + .assert_ok(); + + b_wrapper + .execute_query(&setup.contract_wrapper, |sc| { + assert_eq!(sc.is_paused().get(), true); + }) + .assert_ok(); + + b_wrapper + .execute_tx( + &owner_address, + &setup.contract_wrapper, + &rust_biguint!(0), + |sc| { + sc.pause(); + }, + ) + .assert_user_error("Contract is already paused"); + + b_wrapper + .execute_tx( + &owner_address, + &setup.contract_wrapper, + &rust_biguint!(0), + |sc| { + sc.unpause(); + }, + ) + .assert_ok(); + + b_wrapper + .execute_query(&setup.contract_wrapper, |sc| { + assert_eq!(sc.is_paused().get(), false); + }) + .assert_ok(); + + b_wrapper + .execute_tx( + &first_user_address, + &setup.contract_wrapper, + &rust_biguint!(0), + |sc| { + sc.pause(); + }, + ) + .assert_ok(); + + b_wrapper + .execute_query(&setup.contract_wrapper, |sc| { + assert_eq!(sc.is_paused().get(), true); + }) + .assert_ok(); + + b_wrapper + .execute_tx( + &owner_address, + &setup.contract_wrapper, + &rust_biguint!(0), + |sc| { + sc.unpause(); + }, + ) + .assert_ok(); + + b_wrapper + .execute_tx( + &second_user_address, + &setup.contract_wrapper, + &rust_biguint!(0), + |sc| { + sc.pause(); + }, + ) + .assert_user_error("Address doesn't have the privilege to use this operation"); + + b_wrapper + .execute_query(&setup.contract_wrapper, |sc| { + assert_eq!(sc.is_paused().get(), false); + }) + .assert_ok(); +} + +#[test] //Tests wether adding and removing privileged addresses works as expected + //Tests if trying give privileges to an address that already has them returns an error + //Tests if trying to offer privileges to the owner of the smart contract returns an error + //Tests if trying to remove the privileges that is not privileged returns an error +fn add_and_remove_privileged_addresses_test() { + let mut setup = setup_contract(claims::contract_obj); + let b_wrapper = &mut setup.blockchain_wrapper; + let owner_address = &setup.owner_address; + let first_user_addr = &setup.first_user_address; + let second_user_addr = &setup.second_user_address; + let third_user_addr = &setup.third_user_address; + + b_wrapper + .execute_tx( + &owner_address, + &setup.contract_wrapper, + &rust_biguint!(0), + |sc| { + sc.add_privileged_address(managed_address!(second_user_addr)); + }, + ) + .assert_ok(); + b_wrapper + .execute_tx( + &owner_address, + &setup.contract_wrapper, + &rust_biguint!(0), + |sc| { + sc.add_privileged_address(managed_address!(third_user_addr)); + }, + ) + .assert_user_error("Maximum number of priviledged addresses reached"); + b_wrapper + .execute_tx( + &owner_address, + &setup.contract_wrapper, + &rust_biguint!(0), + |sc| { + sc.remove_privileged_address(managed_address!(third_user_addr)); + }, + ) + .assert_user_error("Address is not privileged"); + b_wrapper + .execute_query(&setup.contract_wrapper, |sc| { + assert_eq!( + sc.privileged_addresses() + .contains(&managed_address!(first_user_addr)) + && sc + .privileged_addresses() + .contains(&managed_address!(second_user_addr)), + true + ); + }) + .assert_ok(); + b_wrapper + .execute_tx( + &owner_address, + &setup.contract_wrapper, + &rust_biguint!(0), + |sc| { + sc.remove_privileged_address(managed_address!(second_user_addr)); + }, + ) + .assert_ok(); + b_wrapper + .execute_tx( + &owner_address, + &setup.contract_wrapper, + &rust_biguint!(0), + |sc| { + sc.add_privileged_address(managed_address!(owner_address)); + }, + ) + .assert_user_error("Owner cannot be added to priviledged addresses"); + b_wrapper + .execute_query(&setup.contract_wrapper, |sc| { + assert_eq!( + sc.privileged_addresses() + .contains(&managed_address!(first_user_addr)) + && !sc + .privileged_addresses() + .contains(&managed_address!(second_user_addr)), + true + ); + }) + .assert_ok(); +} + +#[test] //Tests wether adding and removing singular claims works as expected + //Tests if adding and removing a zero value claim returns an error + //Tests if removing more than the amount reserved in claims returns an error +fn add_and_remove_claim_test() { + let mut setup = setup_contract(claims::contract_obj); + let b_wrapper = &mut setup.blockchain_wrapper; + let owner_address = &setup.owner_address; + let user_addr = &setup.first_user_address; + + b_wrapper + .execute_esdt_transfer( + owner_address, + &setup.contract_wrapper, + TOKEN_ID, + 0, + &rust_biguint!(1_000_000), + |sc| { + sc.add_claim(&managed_address!(user_addr), storage::ClaimType::Airdrop); + }, + ) + .assert_ok(); + + b_wrapper + .execute_query(&setup.contract_wrapper, |sc| { + assert_eq!( + sc.claim(&managed_address!(user_addr), &storage::ClaimType::Airdrop) + .get(), + 1_000_000 + ); + }) + .assert_ok(); + + b_wrapper + .execute_esdt_transfer( + owner_address, + &setup.contract_wrapper, + TOKEN_ID, + 0, + &rust_biguint!(0), + |sc| { + sc.add_claim(&managed_address!(user_addr), storage::ClaimType::Airdrop); + }, + ) + .assert_user_error("Operations must have non-zero value"); + + b_wrapper + .execute_query(&setup.contract_wrapper, |sc| { + assert_eq!( + sc.claim(&managed_address!(user_addr), &storage::ClaimType::Airdrop) + .get(), + 1_000_000 + ); + }) + .assert_ok(); + + b_wrapper + .execute_esdt_transfer( + owner_address, + &setup.contract_wrapper, + TOKEN_ID, + 0, + &rust_biguint!(0), + |sc| { + sc.remove_claim( + &managed_address!(user_addr), + storage::ClaimType::Airdrop, + managed_biguint!(500_000), + ); + }, + ) + .assert_ok(); + + b_wrapper + .execute_query(&setup.contract_wrapper, |sc| { + assert_eq!( + sc.claim(&managed_address!(user_addr), &storage::ClaimType::Airdrop) + .get(), + 500_000 + ); + }) + .assert_ok(); + + b_wrapper + .execute_esdt_transfer( + owner_address, + &setup.contract_wrapper, + TOKEN_ID, + 0, + &rust_biguint!(0), + |sc| { + sc.remove_claim( + &managed_address!(user_addr), + storage::ClaimType::Airdrop, + managed_biguint!(0), + ); + }, + ) + .assert_user_error("Operations must have non-zero value"); + + b_wrapper + .execute_esdt_transfer( + owner_address, + &setup.contract_wrapper, + TOKEN_ID, + 0, + &rust_biguint!(0), + |sc| { + sc.remove_claim( + &managed_address!(user_addr), + storage::ClaimType::Airdrop, + managed_biguint!(700_000), + ); + }, + ) + .assert_user_error("Cannot remove more than current claim"); + + b_wrapper + .execute_query(&setup.contract_wrapper, |sc| { + assert_eq!( + sc.claim(&managed_address!(user_addr), &storage::ClaimType::Airdrop) + .get(), + 500_000 + ); + }) + .assert_ok(); +} + +#[test] //Same tests as the ones for singular claims, but for multiple claims + //Tests if adding multiple claims, but not sending the right amount of tokens for it returns an error + //Tests if adding or removing zero valued claims returns an error + //Tests if removing more than the amount reserved in claims returns an error +fn add_and_remove_claims_test() { + let mut setup = setup_contract(claims::contract_obj); + let b_wrapper = &mut setup.blockchain_wrapper; + let owner_address = &setup.owner_address; + let first_user_addr = &setup.first_user_address; + let second_user_addr = &setup.second_user_address; + + b_wrapper + .execute_esdt_transfer( + owner_address, + &setup.contract_wrapper, + TOKEN_ID, + 0, + &rust_biguint!(2_000_000), + |sc| { + let mut args = MultiValueEncoded::new(); + args.push(MultiValue3( + ( + managed_address!(first_user_addr), + storage::ClaimType::Airdrop, + managed_biguint!(1_000_000), + ) + .into(), + )); + args.push(MultiValue3( + ( + managed_address!(second_user_addr), + storage::ClaimType::Allocation, + managed_biguint!(1_000_000), + ) + .into(), + )); + sc.add_claims(args); + }, + ) + .assert_ok(); + + b_wrapper + .execute_query(&setup.contract_wrapper, |sc| { + assert_eq!( + sc.claim( + &managed_address!(first_user_addr), + &storage::ClaimType::Airdrop + ) + .get(), + 1_000_000 + ); + }) + .assert_ok(); + + b_wrapper + .execute_query(&setup.contract_wrapper, |sc| { + assert_eq!( + sc.claim( + &managed_address!(second_user_addr), + &storage::ClaimType::Allocation + ) + .get(), + 1_000_000 + ); + }) + .assert_ok(); + + b_wrapper + .execute_esdt_transfer( + owner_address, + &setup.contract_wrapper, + TOKEN_ID, + 0, + &rust_biguint!(1_700_000), + |sc| { + let mut args = MultiValueEncoded::new(); + args.push(MultiValue3( + ( + managed_address!(first_user_addr), + storage::ClaimType::Airdrop, + managed_biguint!(1_000_000), + ) + .into(), + )); + args.push(MultiValue3( + ( + managed_address!(second_user_addr), + storage::ClaimType::Allocation, + managed_biguint!(1_000_000), + ) + .into(), + )); + sc.add_claims(args); + }, + ) + .assert_user_error("Claims added must equal payment amount"); + + b_wrapper + .execute_esdt_transfer( + owner_address, + &setup.contract_wrapper, + TOKEN_ID, + 0, + &rust_biguint!(201_000), + |sc| { + let mut args = MultiValueEncoded::new(); + for _i in 0..201 { + args.push(MultiValue3( + ( + managed_address!(first_user_addr), + storage::ClaimType::Airdrop, + managed_biguint!(1_000), + ) + .into(), + )); + } + sc.add_claims(args); + }, + ) + .assert_user_error("Exceeded maximum number of claims per operation (200)"); + + b_wrapper + .execute_esdt_transfer( + owner_address, + &setup.contract_wrapper, + TOKEN_ID, + 0, + &rust_biguint!(1_700_000), + |sc| { + let mut args = MultiValueEncoded::new(); + args.push(MultiValue3( + ( + managed_address!(first_user_addr), + storage::ClaimType::Airdrop, + managed_biguint!(1_700_000), + ) + .into(), + )); + args.push(MultiValue3( + ( + managed_address!(second_user_addr), + storage::ClaimType::Allocation, + managed_biguint!(0), + ) + .into(), + )); + sc.add_claims(args); + }, + ) + .assert_user_error("Operations must have non-zero value"); + + b_wrapper + .execute_query(&setup.contract_wrapper, |sc| { + assert_eq!( + sc.claim( + &managed_address!(first_user_addr), + &storage::ClaimType::Airdrop + ) + .get(), + 1_000_000 + ); + }) + .assert_ok(); + + b_wrapper + .execute_query(&setup.contract_wrapper, |sc| { + assert_eq!( + sc.claim( + &managed_address!(second_user_addr), + &storage::ClaimType::Allocation + ) + .get(), + 1_000_000 + ); + }) + .assert_ok(); + + b_wrapper + .execute_esdt_transfer( + owner_address, + &setup.contract_wrapper, + TOKEN_ID, + 0, + &rust_biguint!(0), + |sc| { + let mut args = MultiValueEncoded::new(); + args.push(MultiValue3( + ( + managed_address!(first_user_addr), + storage::ClaimType::Airdrop, + managed_biguint!(700_000), + ) + .into(), + )); + args.push(MultiValue3( + ( + managed_address!(second_user_addr), + storage::ClaimType::Allocation, + managed_biguint!(500_000), + ) + .into(), + )); + sc.remove_claims(args); + }, + ) + .assert_ok(); + + b_wrapper + .execute_query(&setup.contract_wrapper, |sc| { + assert_eq!( + sc.claim( + &managed_address!(first_user_addr), + &storage::ClaimType::Airdrop + ) + .get(), + 300_000 + ); + }) + .assert_ok(); + + b_wrapper + .execute_query(&setup.contract_wrapper, |sc| { + assert_eq!( + sc.claim( + &managed_address!(second_user_addr), + &storage::ClaimType::Allocation + ) + .get(), + 500_000 + ); + }) + .assert_ok(); + + b_wrapper + .execute_esdt_transfer( + owner_address, + &setup.contract_wrapper, + TOKEN_ID, + 0, + &rust_biguint!(0), + |sc| { + let mut args = MultiValueEncoded::new(); + args.push(MultiValue3( + ( + managed_address!(first_user_addr), + storage::ClaimType::Airdrop, + managed_biguint!(200_000), + ) + .into(), + )); + args.push(MultiValue3( + ( + managed_address!(second_user_addr), + storage::ClaimType::Allocation, + managed_biguint!(0), + ) + .into(), + )); + sc.remove_claims(args); + }, + ) + .assert_user_error("Operations must have non-zero value"); + + b_wrapper + .execute_esdt_transfer( + owner_address, + &setup.contract_wrapper, + TOKEN_ID, + 0, + &rust_biguint!(0), + |sc| { + let mut args = MultiValueEncoded::new(); + args.push(MultiValue3( + ( + managed_address!(first_user_addr), + storage::ClaimType::Airdrop, + managed_biguint!(400_000), + ) + .into(), + )); + args.push(MultiValue3( + ( + managed_address!(second_user_addr), + storage::ClaimType::Allocation, + managed_biguint!(500_000), + ) + .into(), + )); + sc.remove_claims(args); + }, + ) + .assert_user_error("Cannot remove more than current claim"); + + b_wrapper + .execute_esdt_transfer( + owner_address, + &setup.contract_wrapper, + TOKEN_ID, + 0, + &rust_biguint!(0), + |sc| { + let mut args = MultiValueEncoded::new(); + for _i in 0..201 { + args.push(MultiValue3( + ( + managed_address!(first_user_addr), + storage::ClaimType::Airdrop, + managed_biguint!(1_000), + ) + .into(), + )); + } + sc.remove_claims(args); + }, + ) + .assert_user_error("Exceeded maximum number of claims per operation (200)"); + + b_wrapper + .execute_query(&setup.contract_wrapper, |sc| { + assert_eq!( + sc.claim( + &managed_address!(first_user_addr), + &storage::ClaimType::Airdrop + ) + .get(), + 300_000 + ); + }) + .assert_ok(); + + b_wrapper + .execute_query(&setup.contract_wrapper, |sc| { + assert_eq!( + sc.claim( + &managed_address!(second_user_addr), + &storage::ClaimType::Allocation + ) + .get(), + 500_000 + ); + }) + .assert_ok(); +} + +#[test] //Tests wether privileged addresses can add a claim, but a non-priviledged address cannot +fn add_claim_privileged_test() { + let mut setup = setup_contract(claims::contract_obj); + let b_wrapper = &mut setup.blockchain_wrapper; + let user_addr = &setup.first_user_address; + let user_addr_3 = &setup.third_user_address; + b_wrapper + .execute_esdt_transfer( + user_addr, + &setup.contract_wrapper, + TOKEN_ID, + 0, + &rust_biguint!(1_000), + |sc| { + sc.add_claim(&managed_address!(user_addr), storage::ClaimType::Airdrop); + }, + ) + .assert_ok(); + b_wrapper + .execute_esdt_transfer( + user_addr_3, + &setup.contract_wrapper, + TOKEN_ID, + 0, + &rust_biguint!(1_000), + |sc| { + sc.add_claim(&managed_address!(user_addr), storage::ClaimType::Airdrop); + }, + ) + .assert_user_error("Address doesn't have the privilege to use this operation"); +} + +#[test] //Tests wether privileged addresses can add claims, but a non-priviledged address cannot +fn add_claims_privileged_test() { + let mut setup = setup_contract(claims::contract_obj); + let b_wrapper = &mut setup.blockchain_wrapper; + let user_addr = &setup.first_user_address; + let user_addr_3 = &setup.third_user_address; + b_wrapper + .execute_esdt_transfer( + user_addr, + &setup.contract_wrapper, + TOKEN_ID, + 0, + &rust_biguint!(1_000), + |sc| { + let mut args = MultiValueEncoded::new(); + args.push(MultiValue3( + ( + managed_address!(user_addr), + storage::ClaimType::Airdrop, + managed_biguint!(600), + ) + .into(), + )); + args.push(MultiValue3( + ( + managed_address!(user_addr_3), + storage::ClaimType::Allocation, + managed_biguint!(400), + ) + .into(), + )); + sc.add_claims(args); + }, + ) + .assert_ok(); + b_wrapper + .execute_esdt_transfer( + user_addr_3, + &setup.contract_wrapper, + TOKEN_ID, + 0, + &rust_biguint!(100), + |sc| { + let mut args = MultiValueEncoded::new(); + args.push(MultiValue3( + ( + managed_address!(user_addr), + storage::ClaimType::Airdrop, + managed_biguint!(600), + ) + .into(), + )); + args.push(MultiValue3( + ( + managed_address!(user_addr_3), + storage::ClaimType::Allocation, + managed_biguint!(400), + ) + .into(), + )); + sc.add_claims(args); + }, + ) + .assert_user_error("Address doesn't have the privilege to use this operation"); +} + +#[test] //Tests whether the transaction to add a token fails in the case in which a different token than the claim token is sent +fn add_claim_wrong_token_test() { + let mut setup = setup_contract(claims::contract_obj); + let b_wrapper = &mut setup.blockchain_wrapper; + let owner_address = &setup.owner_address; + let user_addr = &setup.first_user_address; + + b_wrapper + .execute_esdt_transfer( + owner_address, + &setup.contract_wrapper, + WRONG_TOKEN_ID, + 0, + &rust_biguint!(1_000_000), + |sc| { + sc.add_claim(&managed_address!(user_addr), storage::ClaimType::Airdrop); + }, + ) + .assert_user_error("Can only add designated token"); + + b_wrapper + .execute_query(&setup.contract_wrapper, |sc| { + assert_eq!( + sc.claim(&managed_address!(user_addr), &storage::ClaimType::Airdrop) + .get(), + 0 + ); + }) + .assert_ok(); +} + +#[test] //Tests whether the transaction to add tokens fails in the case in which a different token than the claim token is sent +fn add_claims_wrong_token_test() { + let mut setup = setup_contract(claims::contract_obj); + let b_wrapper = &mut setup.blockchain_wrapper; + let owner_address = &setup.owner_address; + let first_user_addr = &setup.first_user_address; + let second_user_addr = &setup.second_user_address; + + b_wrapper + .execute_esdt_transfer( + owner_address, + &setup.contract_wrapper, + WRONG_TOKEN_ID, + 0, + &rust_biguint!(500_000), + |sc| { + let mut args = MultiValueEncoded::new(); + args.push(MultiValue3( + ( + managed_address!(first_user_addr), + storage::ClaimType::Airdrop, + managed_biguint!(200_000), + ) + .into(), + )); + args.push(MultiValue3( + ( + managed_address!(second_user_addr), + storage::ClaimType::Allocation, + managed_biguint!(300_000), + ) + .into(), + )); + sc.add_claims(args); + }, + ) + .assert_user_error("Can only add designated token"); + + b_wrapper + .execute_query(&setup.contract_wrapper, |sc| { + assert_eq!( + sc.claim( + &managed_address!(first_user_addr), + &storage::ClaimType::Airdrop + ) + .get(), + 0 + ); + }) + .assert_ok(); + + b_wrapper + .execute_query(&setup.contract_wrapper, |sc| { + assert_eq!( + sc.claim( + &managed_address!(second_user_addr), + &storage::ClaimType::Allocation + ) + .get(), + 0 + ); + }) + .assert_ok(); +} + +#[test] //Tests whether one can set the claim token only once +fn reset_claim_token_test() { + let mut setup = setup_contract(claims::contract_obj); + let b_wrapper = &mut setup.blockchain_wrapper; + let owner_address = &setup.owner_address; + + b_wrapper + .execute_tx( + &owner_address, + &setup.contract_wrapper, + &rust_biguint!(0), + |sc| { + sc.set_claim_token(managed_token_id!(TOKEN_ID)); + }, + ) + .assert_user_error("Claim token is already set"); +} + +#[test] //Tests whether claiming is impossible in pause state +fn harvest_claim_in_pause_test() { + let mut setup = setup_contract(claims::contract_obj); + let b_wrapper = &mut setup.blockchain_wrapper; + let owner_address = &setup.owner_address; + let user_addr = &setup.second_user_address; + + b_wrapper + .execute_tx( + &owner_address, + &setup.contract_wrapper, + &rust_biguint!(0), + |sc| { + sc.pause(); + }, + ) + .assert_ok(); + + b_wrapper + .execute_esdt_transfer( + user_addr, + &setup.contract_wrapper, + TOKEN_ID, + 0, + &rust_biguint!(0), + |sc| { + sc.harvest_claim(OptionalValue::Some(storage::ClaimType::Airdrop)); + }, + ) + .assert_user_error("Contract is paused"); +} + +#[test] //Tests whether users can claim +fn harvest_claim_test() { + let mut setup = setup_contract(claims::contract_obj); + let b_wrapper = &mut setup.blockchain_wrapper; + let owner_address = &setup.owner_address; + let user_addr = &setup.second_user_address; + + b_wrapper + .execute_esdt_transfer( + owner_address, + &setup.contract_wrapper, + TOKEN_ID, + 0, + &rust_biguint!(1_000_000), + |sc| { + sc.add_claim(&managed_address!(user_addr), storage::ClaimType::Airdrop); + }, + ) + .assert_ok(); + + b_wrapper + .execute_esdt_transfer( + user_addr, + &setup.contract_wrapper, + TOKEN_ID, + 0, + &rust_biguint!(0), + |sc| { + sc.harvest_claim(OptionalValue::Some(storage::ClaimType::Airdrop)); + }, + ) + .assert_ok(); + + b_wrapper + .execute_query(&setup.contract_wrapper, |sc| { + assert_eq!( + sc.claim(&managed_address!(user_addr), &storage::ClaimType::Airdrop) + .get(), + 0 + ); + }) + .assert_ok(); + + b_wrapper.check_esdt_balance(user_addr, TOKEN_ID, &rust_biguint!(1_000_000)); +} + +#[test] //Test wether the transaction to claim returns an error if no claims are present for the user for the type he tries to claim +fn harvest_wrong_claim_type_test() { + let mut setup = setup_contract(claims::contract_obj); + let b_wrapper = &mut setup.blockchain_wrapper; + let owner_address = &setup.owner_address; + let user_addr = &setup.second_user_address; + + b_wrapper + .execute_esdt_transfer( + owner_address, + &setup.contract_wrapper, + TOKEN_ID, + 0, + &rust_biguint!(1_000_000), + |sc| { + sc.add_claim(&managed_address!(user_addr), storage::ClaimType::Airdrop); + }, + ) + .assert_ok(); + + b_wrapper + .execute_esdt_transfer( + user_addr, + &setup.contract_wrapper, + TOKEN_ID, + 0, + &rust_biguint!(0), + |sc| { + sc.harvest_claim(OptionalValue::Some(storage::ClaimType::Reward)); + }, + ) + .assert_user_error("Operations must have non-zero value"); + + b_wrapper + .execute_query(&setup.contract_wrapper, |sc| { + assert_eq!( + sc.claim(&managed_address!(user_addr), &storage::ClaimType::Airdrop) + .get(), + 1_000_000 + ); + }) + .assert_ok(); + + b_wrapper + .execute_query(&setup.contract_wrapper, |sc| { + assert_eq!( + sc.claim(&managed_address!(user_addr), &storage::ClaimType::Reward) + .get(), + 0 + ); + }) + .assert_ok(); + + b_wrapper.check_esdt_balance(user_addr, TOKEN_ID, &rust_biguint!(0)); +} + +#[test] //Tests whether claiming all claim types at once works +fn harvest_all_claims_test() { + let mut setup = setup_contract(claims::contract_obj); + let b_wrapper = &mut setup.blockchain_wrapper; + let owner_address = &setup.owner_address; + let user_addr = &setup.second_user_address; + + b_wrapper + .execute_esdt_transfer( + owner_address, + &setup.contract_wrapper, + TOKEN_ID, + 0, + &rust_biguint!(1_000_000), + |sc| { + sc.add_claim(&managed_address!(user_addr), storage::ClaimType::Airdrop); + }, + ) + .assert_ok(); + + b_wrapper + .execute_esdt_transfer( + owner_address, + &setup.contract_wrapper, + TOKEN_ID, + 0, + &rust_biguint!(1_000_000), + |sc| { + sc.add_claim(&managed_address!(user_addr), storage::ClaimType::Reward); + }, + ) + .assert_ok(); + + b_wrapper + .execute_esdt_transfer( + user_addr, + &setup.contract_wrapper, + TOKEN_ID, + 0, + &rust_biguint!(0), + |sc| { + sc.harvest_claim(OptionalValue::None); + }, + ) + .assert_ok(); + + b_wrapper + .execute_query(&setup.contract_wrapper, |sc| { + assert_eq!( + sc.claim(&managed_address!(user_addr), &storage::ClaimType::Airdrop) + .get(), + 0 + ); + }) + .assert_ok(); + + b_wrapper + .execute_query(&setup.contract_wrapper, |sc| { + assert_eq!( + sc.claim(&managed_address!(user_addr), &storage::ClaimType::Reward) + .get(), + 0 + ); + }) + .assert_ok(); + + b_wrapper.check_esdt_balance(user_addr, TOKEN_ID, &rust_biguint!(2_000_000)); +} diff --git a/wasm/src/lib.rs b/wasm/src/lib.rs index 3029c7d..4e0869a 100644 --- a/wasm/src/lib.rs +++ b/wasm/src/lib.rs @@ -9,16 +9,20 @@ elrond_wasm_node::wasm_endpoints! { ( addClaim addClaims + addPrivilegedAddress claim isPaused pause removeClaim removeClaims - setRewardToken + removePrivilegedAddress + setClaimToken + unpause viewClaim - viewClaimAddDate + viewClaimModifyDate viewClaimWithDate viewClaims + viewPrivilegedAddresses viewTokenIdentifier ) }