├── .gitignore ├── LICENSE ├── README.md ├── SECURITY.md ├── Tinyman_AMM_Contracts_V1-1_Internal_Review.pdf ├── asc.json ├── contracts ├── pool_logicsig.teal.tmpl ├── validator_approval.teal └── validator_clear_state.teal └── docs ├── .DS_Store ├── README.md ├── bootstrap.md ├── burn.md ├── create.md ├── mint.md ├── optin.md ├── redeem.md ├── redeem_fees.md └── swap.md /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # TypeScript v1 declaration files 45 | typings/ 46 | 47 | # TypeScript cache 48 | *.tsbuildinfo 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Microbundle cache 57 | .rpt2_cache/ 58 | .rts2_cache_cjs/ 59 | .rts2_cache_es/ 60 | .rts2_cache_umd/ 61 | 62 | # Optional REPL history 63 | .node_repl_history 64 | 65 | # Output of 'npm pack' 66 | *.tgz 67 | 68 | # Yarn Integrity file 69 | .yarn-integrity 70 | 71 | # dotenv environment variables file 72 | .env 73 | .env.test 74 | 75 | # parcel-bundler cache (https://parceljs.org/) 76 | .cache 77 | 78 | # Next.js build output 79 | .next 80 | 81 | # Nuxt.js build / generate output 82 | .nuxt 83 | dist 84 | 85 | # Gatsby files 86 | .cache/ 87 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 88 | # https://nextjs.org/blog/next-9-1#public-directory-support 89 | # public 90 | 91 | # vuepress build output 92 | .vuepress/dist 93 | 94 | # Serverless directories 95 | .serverless/ 96 | 97 | # FuseBox cache 98 | .fusebox/ 99 | 100 | # DynamoDB Local files 101 | .dynamodb/ 102 | 103 | # TernJS port file 104 | .tern-port 105 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Business Source License 1.1 2 | 3 | License text copyright (c) 2017 MariaDB Corporation Ab, All Rights Reserved. 4 | "Business Source License" is a trademark of MariaDB Corporation Ab. 5 | 6 | ----------------------------------------------------------------------------- 7 | 8 | Parameters 9 | 10 | Licensor: TM Distributed Development Ltd 11 | 12 | Licensed Work: Tinyman V1 Contracts 13 | The Licensed Work is (c) 2021 TM Distributed Development Ltd 14 | Additional Use Grant: None 15 | 16 | Change Date: 2023-10-01 17 | 18 | Change License: GNU General Public License v2.0 or later 19 | 20 | ----------------------------------------------------------------------------- 21 | 22 | Terms 23 | 24 | The Licensor hereby grants you the right to copy, modify, create derivative 25 | works, redistribute, and make non-production use of the Licensed Work. The 26 | Licensor may make an Additional Use Grant, above, permitting limited 27 | production use. 28 | 29 | Effective on the Change Date, or the fourth anniversary of the first publicly 30 | available distribution of a specific version of the Licensed Work under this 31 | License, whichever comes first, the Licensor hereby grants you rights under 32 | the terms of the Change License, and the rights granted in the paragraph 33 | above terminate. 34 | 35 | If your use of the Licensed Work does not comply with the requirements 36 | currently in effect as described in this License, you must purchase a 37 | commercial license from the Licensor, its affiliated entities, or authorized 38 | resellers, or you must refrain from using the Licensed Work. 39 | 40 | All copies of the original and modified Licensed Work, and derivative works 41 | of the Licensed Work, are subject to this License. This License applies 42 | separately for each version of the Licensed Work and the Change Date may vary 43 | for each version of the Licensed Work released by Licensor. 44 | 45 | You must conspicuously display this License on each original or modified copy 46 | of the Licensed Work. If you receive the Licensed Work in original or 47 | modified form from a third party, the terms and conditions set forth in this 48 | License apply to your use of that work. 49 | 50 | Any use of the Licensed Work in violation of this License will automatically 51 | terminate your rights under this License for the current and all other 52 | versions of the Licensed Work. 53 | 54 | This License does not grant you any right in any trademark or logo of 55 | Licensor or its affiliates (provided that you may use a trademark or logo of 56 | Licensor as expressly required by this License). 57 | 58 | TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE LICENSED WORK IS PROVIDED ON 59 | AN "AS IS" BASIS. LICENSOR HEREBY DISCLAIMS ALL WARRANTIES AND CONDITIONS, 60 | EXPRESS OR IMPLIED, INCLUDING (WITHOUT LIMITATION) WARRANTIES OF 61 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, AND 62 | TITLE. 63 | 64 | MariaDB hereby grants you permission to use this License’s text to license 65 | your works, and to refer to it using the trademark "Business Source License", 66 | as long as you comply with the Covenants of Licensor below. 67 | 68 | ----------------------------------------------------------------------------- 69 | 70 | Covenants of Licensor 71 | 72 | In consideration of the right to use this License’s text and the "Business 73 | Source License" name and trademark, Licensor covenants to MariaDB, and to all 74 | other recipients of the licensed work to be provided by Licensor: 75 | 76 | 1. To specify as the Change License the GPL Version 2.0 or any later version, 77 | or a license that is compatible with GPL Version 2.0 or a later version, 78 | where "compatible" means that software provided under the Change License can 79 | be included in a program with software provided under GPL Version 2.0 or a 80 | later version. Licensor may specify additional Change Licenses without 81 | limitation. 82 | 83 | 2. To either: (a) specify an additional grant of rights to use that does not 84 | impose any additional restriction on the right granted in this License, as 85 | the Additional Use Grant; or (b) insert the text "None". 86 | 87 | 3. To specify a Change Date. 88 | 89 | 4. Not to modify this License in any other way. 90 | 91 | ----------------------------------------------------------------------------- 92 | 93 | Notice 94 | 95 | The Business Source License (this document, or the "License") is not an Open 96 | Source license. However, the Licensed Work will eventually be made available 97 | under an Open Source License, as stated in this License. 98 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # tinyman-contracts-v1.1 2 | Tinyman AMM Contracts V1.1 3 | 4 | Tinyman is an automated market maker (AMM) implementation on Algorand. 5 | 6 | 7 | ### Docs 8 | 9 | Docs describing the transactions for each operation are located in the [docs](docs/) folder. 10 | 11 | Further documentation is available at [docs.tinyman.org](https://docs.tinyman.org) 12 | 13 | ### Bug Bounty Program 14 | Please see details in the blog post announcing the program: 15 | https://tinymanorg.medium.com/tinyman-bug-bounty-campaign-b6c5e1ba7d6c 16 | 17 | Reports of potential flaws must be responsibly disclosed to `security@tinyman.org`. Do not share details with anyone else until notified to do so by the team. 18 | 19 | ### Audit 20 | An audit of these contracts has been completed by [Runtime Verification](https://runtimeverification.com/). It can be found in [their Github repo](https://github.com/runtimeverification/publications/blob/main/reports/smart-contracts/Tinyman.pdf). 21 | 22 | 23 | ### Internal Review 24 | The Tinyman team conducted an internal review of the V1.1 contracts in January 2022. This is published in this repo [here](Tinyman_AMM_Contracts_V1-1_Internal_Review.pdf). 25 | 26 | 27 | ### Acknowledgements 28 | The Tinyman team would like to thank [@jasonpaulos](https://github.com/jasonpaulos) for his help and guidance with the initial design and development of the Tinyman AMM contracts. 29 | 30 | The Tinyman team would also like to thank [Runtime Verification](https://runtimeverification.com/) and particularly [@malturki](https://github.com/malturki) for their insightful comments and code improvement suggestions. 31 | 32 | ### Licensing 33 | 34 | The contents of this repository are licensed under the Business Source License 1.1 (BUSL-1.1), see [LICENSE](LICENSE). 35 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Reporting a Vulnerability 4 | 5 | Reports of potential flaws must be responsibly disclosed to `security@tinyman.org`. Do not share details with anyone else until notified to do so by the team. 6 | 7 | ### Bug Bounty Program 8 | Please see details in the blog post announcing the program: 9 | https://tinymanorg.medium.com/tinyman-bug-bounty-campaign-b6c5e1ba7d6c 10 | -------------------------------------------------------------------------------- /Tinyman_AMM_Contracts_V1-1_Internal_Review.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tinymanorg/tinyman-contracts-v1/a055a31ab4e70d21c4ae5fe3e192bd48d37fbb71/Tinyman_AMM_Contracts_V1-1_Internal_Review.pdf -------------------------------------------------------------------------------- /asc.json: -------------------------------------------------------------------------------- 1 | { 2 | "repo": "https://github.com/tinymanorg/tinyman-contracts-v1", 3 | "ref": "13acadd1a619d0fcafadd6f6c489a906bf347484", 4 | "contracts": { 5 | "pool_logicsig": { 6 | "type": "logicsig", 7 | "logic": { 8 | "bytecode": "BCAIAQCBgICAgICAgPABgICAgICAgIDwAQMEBQYlJA1EMQkyAxJEMRUyAxJEMSAyAxJEMgQiDUQzAQAxABJEMwEQIQcSRDMBGIGCgICAgICAgPABEkQzARkiEjMBGyEEEhA3ARoAgAlib290c3RyYXASEEAAXDMBGSMSRDMBG4ECEjcBGgCABHN3YXASEEACOzMBGyISRDcBGgCABG1pbnQSQAE7NwEaAIAEYnVybhJAAZg3ARoAgAZyZWRlZW0SQAJbNwEaAIAEZmVlcxJAAnkAIQYhBSQjEk0yBBJENwEaARclEjcBGgIXJBIQRDMCADEAEkQzAhAhBBJEMwIhIxJEMwIiIxwSRDMCIyEHEkQzAiQjEkQzAiWACFRNUE9PTDExEkQzAiZRAA+AD1RpbnltYW5Qb29sMS4xIBJEMwIngBNodHRwczovL3RpbnltYW4ub3JnEkQzAikyAxJEMwIqMgMSRDMCKzIDEkQzAiwyAxJEMwMAMQASRDMDECEFEkQzAxElEkQzAxQxABJEMwMSIxJEJCMTQAAQMwEBMwIBCDMDAQg1AUIBsTMEADEAEkQzBBAhBRJEMwQRJBJEMwQUMQASRDMEEiMSRDMBATMCAQgzAwEIMwQBCDUBQgF8MgQhBhJENwEcATEAE0Q3ARwBMwQUEkQzAgAxABNEMwIUMQASRDMDADMCABJEMwIRJRJEMwMUMwMHMwMQIhJNMQASRDMDESMzAxAiEk0kEkQzBAAxABJEMwQUMwIAEkQzAQEzBAEINQFCAREyBCEGEkQ3ARwBMQATRDcBHAEzAhQSRDMDFDMDBzMDECISTTcBHAESRDMCADEAEkQzAhQzBAASRDMCESUSRDMDADEAEkQzAxQzAwczAxAiEk0zBAASRDMDESMzAxAiEk0kEkQzBAAxABNEMwQUMQASRDMBATMCAQgzAwEINQFCAJAyBCEFEkQ3ARwBMQATRDMCADcBHAESRDMCADEAE0QzAwAxABJEMwIUMwIHMwIQIhJNMQASRDMDFDMDBzMDECISTTMCABJEMwEBMwMBCDUBQgA+MgQhBBJENwEcATEAE0QzAhQzAgczAhAiEk03ARwBEkQzAQEzAgEINQFCABIyBCEEEkQzAQEzAgEINQFCAAAzAAAxABNEMwAHMQASRDMACDQBD0M=", 9 | "address": "ABUKAXTANWR6K6ZYV75DWJEPVWWOU6SFUVRI6QHO44E4SIDLHBTD2CZ64A", 10 | "size": 881, 11 | "variables": [ 12 | { 13 | "name": "TMPL_ASSET_ID_1", 14 | "type": "int", 15 | "index": 15, 16 | "length": 10 17 | }, 18 | { 19 | "name": "TMPL_ASSET_ID_2", 20 | "type": "int", 21 | "index": 5, 22 | "length": 10 23 | }, 24 | { 25 | "name": "TMPL_VALIDATOR_APP_ID", 26 | "type": "int", 27 | "index": 74, 28 | "length": 10 29 | } 30 | ], 31 | "source": "https://github.com/tinymanorg/tinyman-contracts-v1/tree/13acadd1a619d0fcafadd6f6c489a906bf347484/contracts/pool_logicsig.teal.tmpl" 32 | }, 33 | "name": "pool_logicsig" 34 | }, 35 | "validator_app": { 36 | "type": "app", 37 | "approval_program": { 38 | "bytecode": "BCAHAAHoB+UHBf///////////wHAhD0mDQFvAWUBcAJhMQJhMgJsdARzd2FwBG1pbnQBdAJjMQJwMQJjMgJwMjEZgQQSMRkhBBIRMRmBAhIRQATxMRkjEjEbIhIQQATjNhoAgAZjcmVhdGUSQATUMRkjEjYaAIAJYm9vdHN0cmFwEhBAA/MzAhIzAggINTQiK2I1ZSI0ZXAARDUBIicEYjVmNGZAABEiYCJ4CTEBCDMACAk1AkIACCI0ZnAARDUCIicFYjVnKDRlFlA1byI0b2I1PSg0ZhZQNXAiNHBiNT4oNGcWUDVxIjRxYjU/IipiNUA0ATQ9CTVHNAI0Pgk1SDEAKVA0ZRZQNXkxAClQNGYWUDV6MQApUDRnFlA1ezYaAIAGcmVkZWVtEkAAWjYaAIAEZmVlcxJAABw2GgAnBhI2GgAnBxIRNhoAgARidXJuEhFAAG0ANGdJRDMCERJEMwISRDMCFDIJEkQ0PzMCEgk1PzRAMwISCTVAIio0QGYiNHE0P2YjQzMCFDMCBzMCECMSTTYcARJENDREIigzAhEWUEpiNDQJZiMxAClQMwIRFlBKYjQ0CUlBAANmI0NIaCNDMgciJwhiCUk1+kEARiInCWIiJwpiNPodTEAANx4hBSMeHzX7SEhIIicLYiInDGI0+h1MQAAdHiEFIx4fNfxISEgiJwk0+2YiJws0/GYiJwgyB2YzAxIzAwgINTU2HAExABNENGdBACIiNGdwAEQ1BiIcNAYJND8INQQ2GgAnBhJAASA0ZzMEERJENhoAJwcSQABVNhwBMwQAEkQzBBI0Rx00BCMdH0hITEhJNRA0NAk1yTMEEjRIHTQEIx0fSEhMSEk1ETQ1CTXKNBA0ERBENEc0EAk1UTRINBEJNVI0BDMEEgk1U0ICCjYcATMCABJENEc0NAg1UTRINDUINVI0BCISQAAuNDQ0BB00RyMdH0hITEg0NTQEHTRIIx0fSEhMSEoNTUk0BAg1UzMEEgk1y0IBvyInBTMEEUk1Z2YoNGcWUDVxIjRncABERDRnNGUTRDRnNGYTRDMEEiQISR018DQ0NDUdNfFKDEAACBJENPA08Q5EMwQSJAgjCEkdNfA0NDQ1HTXxSg1AAAgSRDTwNPENRCQ1PzQEMwQSJAgINVNCAU82HAEzAgASRDMCETRlEjMDETRmEhBJNWRAABkzAhE0ZhIzAxE0ZRIQRDRINRI0RzUTQgAINEc1EjRINRM2GgGAAmZpEkAAWjYaAYACZm8SRDQ1JAs0Eh00EzQ1CSUdH0hITEgjCEk1FSINNDU0EwwQRDQ0NBUJNGRBABM1yTRHNBUINVE0SDQ1CTVSQgBnNco0SDQVCDVSNEc0NQk1UUIAVDQ0STUVJQs0Ex00EiQLNDQlCx4fSEhMSEk1FCINNBQ0EwwQRDQUNDUJNGRBABM1yjRHNDQINVE0SDQUCTVSQgATNck0RzQUCTVRNEg0NAg1UkIAADQVIQQLNAQdgaCcATQSHR9ISExISTUqNAQINVNCADsiKzYaARdJNWVmIicENhoCF0k1ZmY0ZXEDRIABLVCABEFMR080ZkEABkg0ZnEDRFAzAiZJFYEPTFISQyIqNEA0KghmIjRxND80Kgg0ywhmIjRvND00yQhmIjRwND40yghmIoACczE0UWYigAJzMjRSZiInCjRSIQYdNFEjHR9ISExIZiInDDRRIQYdNFIjHR9ISExIZiKAA2lsdDRTZjTLQQAJIzR7SmI0ywhmNMlBAAkjNHlKYjTJCGY0ykEACSM0ekpiNMoIZiNDI0MiQw==", 39 | "address": "BUQHXHPLMYUVS3P2INJ2EUJFCSNT6LNUGXVM6T2SZ27TDRDYLUMWCFYW3E", 40 | "size": 1351, 41 | "variables": [], 42 | "source": "https://github.com/tinymanorg/tinyman-contracts-v1/tree/13acadd1a619d0fcafadd6f6c489a906bf347484/contracts/validator_approval.teal" 43 | }, 44 | "clear_program": { 45 | "bytecode": "BIEB", 46 | "address": "P7GEWDXXW5IONRW6XRIRVPJCT2XXEQGOBGG65VJPBUOYZEJCBZWTPHS3VQ", 47 | "size": 3, 48 | "variables": [], 49 | "source": "https://github.com/tinymanorg/tinyman-contracts-v1/tree/13acadd1a619d0fcafadd6f6c489a906bf347484/contracts/validator_clear_state.teal" 50 | }, 51 | "global_state_schema": { 52 | "num_uints": 0, 53 | "num_byte_slices": 0 54 | }, 55 | "local_state_schema": { 56 | "num_uints": 16, 57 | "num_byte_slices": 0 58 | }, 59 | "name": "validator_app" 60 | } 61 | } 62 | } -------------------------------------------------------------------------------- /contracts/pool_logicsig.teal.tmpl: -------------------------------------------------------------------------------- 1 | #pragma version 4 2 | 3 | // Tinyman Pool LogicSig 4 | // Documentation: https://docs.tinyman.org 5 | 6 | // This code should be read in conjunction with validator_approval.teal. 7 | // The validation logic is split between these two programs. 8 | 9 | // ensure ASSET_ID_1 > ASSET_ID_2 10 | int TMPL_ASSET_ID_1 11 | int TMPL_ASSET_ID_2 12 | > 13 | assert 14 | 15 | txn CloseRemainderTo 16 | global ZeroAddress 17 | == 18 | assert 19 | 20 | txn AssetCloseTo 21 | global ZeroAddress 22 | == 23 | assert 24 | 25 | txn RekeyTo 26 | global ZeroAddress 27 | == 28 | assert 29 | 30 | global GroupSize 31 | int 1 32 | > 33 | assert 34 | 35 | // ensure gtxn 1 is ApplicationCall to Validator App 36 | gtxn 1 Sender 37 | txn Sender 38 | == 39 | assert 40 | 41 | gtxn 1 TypeEnum 42 | int appl // ApplicationCall 43 | == 44 | assert 45 | 46 | gtxn 1 ApplicationID 47 | int TMPL_VALIDATOR_APP_ID 48 | == 49 | assert 50 | 51 | // Bootstrap? 52 | gtxn 1 OnCompletion 53 | int OptIn 54 | == 55 | gtxn 1 NumAppArgs 56 | int 3 57 | == 58 | && 59 | gtxna 1 ApplicationArgs 0 60 | byte "bootstrap" 61 | == 62 | && 63 | bnz bootstrap 64 | 65 | 66 | // The remaining operations (Mint/Burn/Swap/Redeem/Fees) must all have OnCompletion=NoOp 67 | gtxn 1 OnCompletion 68 | int NoOp 69 | == 70 | assert 71 | 72 | // Swap? 73 | gtxn 1 NumAppArgs 74 | int 2 75 | == 76 | gtxna 1 ApplicationArgs 0 77 | byte "swap" 78 | == 79 | && 80 | bnz swap 81 | 82 | 83 | // The remaining operations (Mint/Burn/Redeem/Fees) must all have NumAppArgs=1 84 | gtxn 1 NumAppArgs 85 | int 1 86 | == 87 | assert 88 | 89 | // Mint? 90 | gtxna 1 ApplicationArgs 0 91 | byte "mint" 92 | == 93 | bnz mint 94 | 95 | 96 | // Burn? 97 | gtxna 1 ApplicationArgs 0 98 | byte "burn" 99 | == 100 | bnz burn 101 | 102 | // Redeem? 103 | gtxna 1 ApplicationArgs 0 104 | byte "redeem" 105 | == 106 | bnz redeem 107 | 108 | // Fees? 109 | gtxna 1 ApplicationArgs 0 110 | byte "fees" 111 | == 112 | bnz redeem_fees 113 | 114 | err 115 | 116 | 117 | bootstrap: 118 | // Ensure group size is correct 4 or 5: 119 | // 0: Pay Fees (signed by Pooler) 120 | // 1: Call App (signed by Pool LogicSig) 121 | // 2: Asset Creation (signed by Pool LogicSig) 122 | // 3: Asset Optin (signed by Pool LogicSig) 123 | // If asset 2 is an ASA: 124 | // (4): Asset Optin (signed by Pool LogicSig) 125 | int 5 // 5 if asset 2 is an ASA 126 | int 4 // 4 if asset 2 is Algo 127 | int TMPL_ASSET_ID_2 128 | int 0 // Algo 129 | == 130 | select 131 | global GroupSize 132 | == 133 | assert 134 | 135 | gtxna 1 ApplicationArgs 1 136 | btoi 137 | int TMPL_ASSET_ID_1 138 | == 139 | gtxna 1 ApplicationArgs 2 140 | btoi 141 | int TMPL_ASSET_ID_2 142 | == 143 | && 144 | assert 145 | 146 | // ensure sender (signer) of AssetConfig tx is same as sender of app call 147 | gtxn 2 Sender 148 | txn Sender 149 | == 150 | assert 151 | 152 | // ensure gtxn 2 is type AssetConfig 153 | gtxn 2 TypeEnum 154 | int acfg 155 | == 156 | assert 157 | 158 | // ensure a new asset is being created 159 | gtxn 2 ConfigAsset 160 | int 0 161 | == 162 | assert 163 | 164 | // ensure asset total amount is max int 165 | gtxn 2 ConfigAssetTotal 166 | int 0 167 | ~ // inverse of 0 is max int 168 | == 169 | assert 170 | 171 | // ensure decimals is 6 172 | gtxn 2 ConfigAssetDecimals 173 | int 6 174 | == 175 | assert 176 | 177 | // ensure default frozen is false 178 | gtxn 2 ConfigAssetDefaultFrozen 179 | int 0 180 | == 181 | assert 182 | 183 | // ensure unit name is 'TMPOOL11' 184 | gtxn 2 ConfigAssetUnitName 185 | byte "TMPOOL11" 186 | == 187 | assert 188 | 189 | // ensure asset name begins with 'TinymanPool1.1 ' 190 | // the Validator app ensures the name ends with "{asset1_unit_name}-{asset2_unit_name}" 191 | gtxn 2 ConfigAssetName 192 | substring 0 15 193 | byte "TinymanPool1.1 " 194 | == 195 | assert 196 | 197 | // ensure asset url is 'https://tinyman.org' 198 | gtxn 2 ConfigAssetURL 199 | byte "https://tinyman.org" 200 | == 201 | assert 202 | 203 | // ensure no asset manager address is set 204 | gtxn 2 ConfigAssetManager 205 | global ZeroAddress 206 | == 207 | assert 208 | 209 | // ensure no asset reserve address is set 210 | gtxn 2 ConfigAssetReserve 211 | global ZeroAddress 212 | == 213 | assert 214 | 215 | // ensure no asset freeze address is set 216 | gtxn 2 ConfigAssetFreeze 217 | global ZeroAddress 218 | == 219 | assert 220 | 221 | // ensure no asset clawback address is set 222 | gtxn 2 ConfigAssetClawback 223 | global ZeroAddress 224 | == 225 | assert 226 | 227 | // Asset 1 optin 228 | // Ensure optin txn is signed by the same sig as this txn 229 | gtxn 3 Sender 230 | txn Sender 231 | == 232 | assert 233 | 234 | // ensure txn type is AssetTransfer 235 | gtxn 3 TypeEnum 236 | int axfer 237 | == 238 | assert 239 | 240 | // ensure the asset id is the same as asset 1 241 | gtxn 3 XferAsset 242 | int TMPL_ASSET_ID_1 243 | == 244 | assert 245 | 246 | // ensure the receiver is the sender 247 | gtxn 3 AssetReceiver 248 | txn Sender 249 | == 250 | assert 251 | 252 | // ensure the amount is 0 for Optin 253 | gtxn 3 AssetAmount 254 | int 0 255 | == 256 | assert 257 | 258 | // if asset 2 is not 0 (Algo), it needs an optin 259 | int TMPL_ASSET_ID_2 260 | int 0 261 | != 262 | bnz bootstrap__non_algo 263 | 264 | gtxn 1 Fee 265 | gtxn 2 Fee 266 | + 267 | gtxn 3 Fee 268 | + 269 | store 1 // fee_total 270 | b check_fees 271 | 272 | 273 | bootstrap__non_algo: 274 | // verify 5th txn is asset 2 optin txn 275 | gtxn 4 Sender 276 | txn Sender 277 | == 278 | assert 279 | gtxn 4 TypeEnum 280 | int axfer 281 | == 282 | assert 283 | 284 | // ensure the asset id is the same as asset 2 285 | gtxn 4 XferAsset 286 | int TMPL_ASSET_ID_2 287 | == 288 | assert 289 | 290 | // ensure the receiver is the sender 291 | gtxn 4 AssetReceiver 292 | txn Sender 293 | == 294 | assert 295 | 296 | // ensure the amount is 0 for Optin 297 | gtxn 4 AssetAmount 298 | int 0 299 | == 300 | assert 301 | 302 | gtxn 1 Fee 303 | gtxn 2 Fee 304 | + 305 | gtxn 3 Fee 306 | + 307 | gtxn 4 Fee 308 | + 309 | store 1 // fee_total 310 | b check_fees 311 | 312 | mint: 313 | // Mint Checks: 314 | // 315 | // # ensure group size is 5 316 | // global GroupSize == 5 317 | 318 | // # ensure transaction fees are covered by txn 0 319 | // # ensure Pool is not paying the fee 320 | // gtxn 0 Sender != txn Sender 321 | // gtxn 0 Receiver == txn Sender 322 | // gtxn 0 Amount >= (gtxn 1 Fee + gtxn 4 Fee) 323 | 324 | // # verify the receiver of the liquidity token asset is the one whose local state is updated 325 | // gtxna 1 Accounts 1 != txn Sender 326 | // gtxna 1 Accounts 1 == gtxn 4 AssetReceiver 327 | 328 | // # from Pooler to Pool asset 1 329 | // gtxn 2 Sender (Pooler) != txn Sender (Pool) 330 | // gtxn 2 AssetReceiver (Pool) == txn Sender (Pool) 331 | // gtxn 2 Sender (Pooler) == gtxn 3 Sender (Pooler) 332 | // gtxn 2 XferAsset == TMPL_ASSET_ID_1 333 | 334 | // # from Pooler to Pool asset 2 335 | // txn Sender (Pool) == (gtxn 3 AssetReceiver or gtxn 3 Receiver) (Pool) 336 | // gtxn 3 XferAsset == TMPL_ASSET_ID_2 337 | 338 | 339 | // # from Pool to Pooler liquidity token 340 | // gtxn 4 AssetReceiver (Pooler) == gtxn 2 Sender (Poooler) 341 | // gtxn 4 Sender (Pool) == txn Sender (Pool) 342 | 343 | 344 | // ensure group size is 5: 345 | // 0: Pay Fees (signed by Pooler) 346 | // 1: Call App (signed by Pool LogicSig) 347 | // 2: Asset Transfer/Pay (signed by Pooler) 348 | // 3: Asset Transfer/Pay (signed by Pooler) 349 | // 4: Asset Transfer/Pay (signed by Pool LogicSig) 350 | global GroupSize 351 | int 5 352 | == 353 | assert 354 | 355 | // verify the receiver of the asset is the one whose local state is updated 356 | gtxna 1 Accounts 1 357 | txn Sender 358 | != 359 | assert 360 | 361 | gtxna 1 Accounts 1 362 | gtxn 4 AssetReceiver 363 | == 364 | assert 365 | 366 | // verify txn 2 is AssetTransfer from Pooler to Pool 367 | gtxn 2 Sender 368 | txn Sender 369 | != 370 | assert 371 | 372 | gtxn 2 AssetReceiver 373 | txn Sender 374 | == 375 | assert 376 | 377 | gtxn 3 Sender 378 | gtxn 2 Sender 379 | == 380 | assert 381 | 382 | // ensure asset id is asset 1 383 | gtxn 2 XferAsset 384 | int TMPL_ASSET_ID_1 385 | == 386 | assert 387 | 388 | // verify txn 3 is AssetTransfer from Pooler to Pool 389 | gtxn 3 AssetReceiver 390 | gtxn 3 Receiver 391 | gtxn 3 TypeEnum 392 | int pay 393 | == // check if Algo 394 | select 395 | txn Sender 396 | == 397 | assert 398 | 399 | // ensure asset id is asset 2 400 | gtxn 3 XferAsset 401 | int 0 402 | gtxn 3 TypeEnum 403 | int pay 404 | == // check if Algo 405 | select 406 | int TMPL_ASSET_ID_2 407 | == 408 | assert 409 | 410 | // verify txn 4 is AssetTransfer from Pool to Pooler 411 | gtxn 4 Sender 412 | txn Sender 413 | == 414 | assert 415 | 416 | gtxn 4 AssetReceiver 417 | gtxn 2 Sender 418 | == 419 | assert 420 | 421 | gtxn 1 Fee 422 | gtxn 4 Fee 423 | + 424 | store 1 // fee_total 425 | b check_fees 426 | 427 | 428 | burn: 429 | // Burn Checks: 430 | // 431 | // # ensure group size is 5 432 | // global GroupSize == 5 433 | 434 | // # ensure transaction fees are covered by txn 0 435 | // # ensure Pool is not paying the fee 436 | // gtxn 0 Sender != txn Sender 437 | // gtxn 0 Receiver == txn Sender 438 | // gtxn 0 Amount >= (gtxn 1 Fee + gtxn 2 Fee gtxn 3 Fee) 439 | 440 | // # ensure the calculated amounts are not 0 441 | // calculated_asset1_out != 0 442 | // calculated_asset2_out != 0 443 | 444 | // # verify the receiver of the assets is the one whose local state is updated 445 | // gtxna 1 Accounts 1 != txn Sender 446 | // gtxna 1 Accounts 1 == gtxn 2 AssetReceiver 447 | // gtxna 1 Accounts 1 == (gtxn 3 AssetReceiver or gtxn 3 Receiver) 448 | 449 | // # from Pool to Pooler asset 1 450 | // gtxn 2 Sender (Pooler) == txn Sender (Pool) 451 | // gtxn 2 AssetReceiver (Pool) == gtxn 4 Sender (Pool) 452 | // gtxn 2 XferAsset == TMPL_ASSET_ID_1 453 | 454 | // # from Pool to Pooler asset 2 455 | // gtxn 3 Sender (Pool) == txn Sender (Pool) 456 | // gtxn 4 Sender (Pooler) == (gtxn 3 AssetReceiver or gtxn 3 Receiver) (Pool) 457 | // gtxn 3 XferAsset == TMPL_ASSET_ID_2 458 | 459 | 460 | // # from Pooler to Pool liquidity token 461 | // gtxn 4 Sender (Pooler) != txn Sender (Pool) 462 | // gtxn 4 AssetReceiver == txn Sender (Pool) 463 | 464 | // ensure group size is 5: 465 | // 0: Pay Fees (signed by Pooler) 466 | // 1: Call App (signed by Pool LogicSig) 467 | // 2: Asset Transfer/Pay (signed by Pool LogicSig) 468 | // 3: Asset Transfer/Pay (signed by Pool LogicSig) 469 | // 4: Asset Transfer/Pay (signed by Pooler) 470 | global GroupSize 471 | int 5 472 | == 473 | assert 474 | 475 | // verify the receiver of the assets is the one whose local state is updated 476 | gtxna 1 Accounts 1 477 | txn Sender 478 | != 479 | assert 480 | 481 | gtxna 1 Accounts 1 482 | gtxn 2 AssetReceiver 483 | == 484 | assert 485 | 486 | gtxn 3 AssetReceiver 487 | gtxn 3 Receiver 488 | gtxn 3 TypeEnum 489 | int pay 490 | == 491 | select 492 | gtxna 1 Accounts 1 493 | == 494 | assert 495 | 496 | // 2: AssetTransfer - from Pool to Pooler asset 1 497 | gtxn 2 Sender 498 | txn Sender 499 | == 500 | assert 501 | 502 | gtxn 2 AssetReceiver 503 | gtxn 4 Sender 504 | == 505 | assert 506 | 507 | // ensure asset id is asset 1 508 | gtxn 2 XferAsset 509 | int TMPL_ASSET_ID_1 510 | == 511 | assert 512 | 513 | 514 | // 3: AssetTransfer - from Pool to Pooler asset 2 515 | gtxn 3 Sender 516 | txn Sender 517 | == 518 | assert 519 | 520 | gtxn 3 AssetReceiver 521 | gtxn 3 Receiver 522 | gtxn 3 TypeEnum 523 | int pay 524 | == // if algo 525 | select 526 | gtxn 4 Sender 527 | == 528 | assert 529 | 530 | // ensure asset id is asset 2 531 | gtxn 3 XferAsset 532 | int 0 533 | gtxn 3 TypeEnum 534 | int pay 535 | == // check if Algo 536 | select 537 | int TMPL_ASSET_ID_2 538 | == 539 | assert 540 | 541 | // 4: AssetTransfer - from Pooler to Pool liquidity token 542 | gtxn 4 Sender 543 | txn Sender 544 | != 545 | assert 546 | 547 | gtxn 4 AssetReceiver 548 | txn Sender 549 | == 550 | assert 551 | 552 | gtxn 1 Fee 553 | gtxn 2 Fee 554 | + 555 | gtxn 3 Fee 556 | + 557 | store 1 // fee_total 558 | b check_fees 559 | 560 | 561 | swap: 562 | // ensure group size is 4: 563 | // 0: Pay Fees (signed by Swapper) 564 | // 1: Call App (signed by Pool LogicSig) 565 | // 2: Asset Transfer/Pay (signed by Swapper) 566 | // 3: Asset Transfer/Pay (signed by Pool LogicSig) 567 | global GroupSize 568 | int 4 569 | == 570 | assert 571 | 572 | // ensure accounts[1] is not Pool 573 | gtxna 1 Accounts 1 574 | txn Sender 575 | != 576 | assert 577 | 578 | // ensure the sender of asset in is the one whose local state is updated 579 | gtxn 2 Sender 580 | gtxna 1 Accounts 1 581 | == 582 | assert 583 | 584 | // ensure txn 2 sender is not the Pool 585 | gtxn 2 Sender 586 | txn Sender 587 | != 588 | assert 589 | 590 | // ensure txn 3 sender is the Pool 591 | gtxn 3 Sender 592 | txn Sender 593 | == 594 | assert 595 | 596 | // ensure txn 2 receiver is Pool 597 | gtxn 2 AssetReceiver 598 | gtxn 2 Receiver 599 | gtxn 2 TypeEnum 600 | int pay 601 | == // if Algo 602 | select 603 | txn Sender 604 | == 605 | assert 606 | 607 | // ensure txn 3 receiver is Swapper (sender of txn 2) 608 | gtxn 3 AssetReceiver 609 | gtxn 3 Receiver 610 | gtxn 3 TypeEnum 611 | int pay 612 | == // if Algo 613 | select 614 | gtxn 2 Sender 615 | == 616 | assert 617 | 618 | gtxn 1 Fee 619 | gtxn 3 Fee 620 | + 621 | store 1 // fee_total 622 | b check_fees 623 | 624 | 625 | redeem: 626 | // ensure group size is 3: 627 | // 0: Pay Fees (signed by Swapper) 628 | // 1: Call App (signed by Pool LogicSig) 629 | // 2: Asset Transfer/Pay (signed by Pool LogicSig) 630 | global GroupSize 631 | int 3 632 | == 633 | assert 634 | 635 | // ensure accounts[1] is not Pool 636 | gtxna 1 Accounts 1 637 | txn Sender 638 | != 639 | assert 640 | 641 | // ensure the receiver of the asset is the one whose local state is updated 642 | gtxn 2 AssetReceiver 643 | gtxn 2 Receiver 644 | gtxn 2 TypeEnum 645 | int pay 646 | == // if algo 647 | select 648 | gtxna 1 Accounts 1 649 | == 650 | assert 651 | 652 | gtxn 1 Fee 653 | gtxn 2 Fee 654 | + 655 | store 1 // fee_total 656 | b check_fees 657 | 658 | 659 | redeem_fees: 660 | // ensure group size is 3: 661 | // 0: Pay Fees (signed by User) 662 | // 1: Call App (signed by Pool LogicSig) 663 | // 2: Asset Transfer/Pay (signed by Pool LogicSig) 664 | global GroupSize 665 | int 3 666 | == 667 | assert 668 | 669 | gtxn 1 Fee 670 | gtxn 2 Fee 671 | + 672 | store 1 // fee_total 673 | b check_fees 674 | 675 | 676 | 677 | check_fees: 678 | // ensure gtxn 0 amount covers all fees 679 | // ensure Pool is not paying the fee 680 | gtxn 0 Sender 681 | txn Sender 682 | != 683 | assert 684 | 685 | // ensure Pool is receiving the fee 686 | gtxn 0 Receiver 687 | txn Sender 688 | == 689 | assert 690 | 691 | gtxn 0 Amount 692 | load 1 // fee_total 693 | >= 694 | return -------------------------------------------------------------------------------- /contracts/validator_approval.teal: -------------------------------------------------------------------------------- 1 | #pragma version 4 2 | 3 | // Tinyman Validator App 4 | // Documentation: https://docs.tinyman.org 5 | 6 | // Note: This code is written targeting TEAL 3 but using divmodw from TEAL4. 7 | // There are no back jumps, callsubs or other TEAL4 features used. 8 | 9 | // This code should be read in conjunction with pool_logicsig.teal.tmpl. 10 | // The validation logic is split between these two programs. 11 | 12 | // Slots: 13 | // 1: asset1_balance 14 | // 2: asset2_balance 15 | // 4: issued_liquidity_tokens 16 | // 6: liquidity_token_balance 17 | // 7: calculated_liquidity_token_out 18 | // 16: calculated_asset1_out 19 | // 17: calculated_asset2_out 20 | // 18: input_supply 21 | // 19: output_supply 22 | // 20: calculated_amount_out 23 | // 21: calculated_amount_in 24 | // 42: protocol_fee_liquidity_tokens 25 | // 52: gtxn_2_amount 26 | // 53: gtxn_3_amount 27 | // 61: outstanding_asset1_amount 28 | // 62: outstanding_asset2_amount 29 | // 63: outstanding_liquidity_token 30 | // 64: unclaimed_protocol_fee_token_amount 31 | // 71: asset1_supply 32 | // 72: asset2_supply 33 | // 81: final_asset1_supply 34 | // 82: final_asset2_supply 35 | // 83: final_issued_liquidity_tokens 36 | // 100: is_asset_in_asset1 37 | // 101: asset1_id 38 | // 102: asset2_id 39 | // 103: liquidity_token_id 40 | // 111: outstanding_asset1_key 41 | // 112: outstanding_asset2_key 42 | // 113: outstanding_liquidity_token_key 43 | // 121: excess_asset1_key 44 | // 122: excess_asset2_key 45 | // 123: excess_liquidity_token_key 46 | // 201: excess_asset_1 47 | // 202: excess_asset_2 48 | // 203: excess_liquidity_token 49 | 50 | 51 | // Deny Update, Delete, CloseOut 52 | txn OnCompletion 53 | int UpdateApplication 54 | == 55 | txn OnCompletion 56 | int DeleteApplication 57 | == 58 | || 59 | txn OnCompletion 60 | int CloseOut 61 | == 62 | || 63 | bnz fail 64 | 65 | // Pooler/Swapper Optin 66 | txn OnCompletion 67 | int OptIn 68 | == 69 | txn NumAppArgs 70 | int 0 71 | == 72 | && 73 | bnz success 74 | 75 | // Create 76 | txna ApplicationArgs 0 77 | byte "create" // create 78 | == 79 | bnz success 80 | 81 | // Pool Optin & Bootstrap 82 | txn OnCompletion 83 | int OptIn 84 | == 85 | txna ApplicationArgs 0 86 | byte "bootstrap" // bootstrap 87 | == 88 | && 89 | bnz bootstrap 90 | 91 | 92 | // Common normalisation used in many places for the remaining operations 93 | gtxn 2 AssetAmount // asset amount 94 | gtxn 2 Amount // or algo amount 95 | + 96 | store 52 // gtxn_2_amount 97 | 98 | 99 | int 0 100 | byte "a1" // asset1 key 101 | app_local_get // get asset1 id 102 | store 101 // asset1_id 103 | 104 | int 0 105 | load 101 // asset1_id 106 | asset_holding_get AssetBalance // get asset1 balance 107 | assert // assert asset exists 108 | store 1 // store asset1_balance 109 | 110 | int 0 111 | byte "a2" // asset2 key 112 | app_local_get // get asset2 id 113 | store 102 // asset2_id 114 | 115 | // store asset2_balance 116 | load 102 // asset2_id 117 | bnz common_setup__if_not_algo 118 | 119 | // common_setup__if_algo: 120 | // store calculate and store asset2_balance 121 | int 0 122 | balance // algo balance 123 | int 0 124 | min_balance // min balance 125 | - 126 | txn Fee 127 | + 128 | gtxn 0 Amount 129 | - // ((balance - min_balance) + fee) - gtxn0 amount 130 | store 2 // store asset2_balance 131 | b common_setup__1 132 | 133 | common_setup__if_not_algo: 134 | int 0 135 | load 102 // asset2_id 136 | asset_holding_get AssetBalance 137 | assert // assert asset exists 138 | store 2 // store asset2_balance 139 | 140 | common_setup__1: 141 | 142 | // liquidity_token_id 143 | int 0 144 | byte "lt" // liquidity_token key 145 | app_local_get // get liquidity_token id 146 | store 103 // liquidity_token_id 147 | 148 | 149 | // outstanding_asset1_key 150 | byte "o" 151 | load 101 // asset1ID 152 | itob 153 | concat 154 | store 111 // outstanding_asset1_key 155 | 156 | // load outstanding asset 1 from Pool account state 157 | int 0 158 | load 111 // outstanding_asset1_key 159 | app_local_get // outstanding_asset1_amount 160 | store 61 161 | 162 | // outstanding_asset2_key 163 | byte "o" 164 | load 102 // asset2ID 165 | itob 166 | concat 167 | store 112 // outstanding_asset2_key 168 | 169 | // load outstanding asset 2 from Pool account state 170 | int 0 171 | load 112 // outstanding_asset2_key 172 | app_local_get // outstanding_asset2_amount 173 | store 62 174 | 175 | 176 | // outstanding_liquidity_token_key 177 | byte "o" 178 | load 103 // liquidity_token_id 179 | itob 180 | concat 181 | store 113 // outstanding_liquidity_token_key 182 | 183 | // load outstanding liquidity token from Pool account state 184 | int 0 185 | load 113 // outstanding_liquidity_token_key 186 | app_local_get // outstanding_liquidity_token_amount 187 | store 63 188 | 189 | // load unclaimed_protocol_fee_token_amount from Pool account state 190 | int 0 191 | byte "p" // unclaimed_protocol_fee_token_key 192 | app_local_get // unclaimed_protocol_fee_token_amount 193 | store 64 // unclaimed_protocol_fee_token_amount 194 | 195 | // asset1_supply 196 | load 1 197 | load 61 198 | - 199 | store 71 // asset1_supply 200 | 201 | 202 | // asset2_supply 203 | load 2 204 | load 62 205 | - 206 | store 72 // asset2_supply 207 | 208 | 209 | // excess_asset1_key 210 | txn Sender 211 | byte "e" 212 | concat 213 | load 101 // asset1ID 214 | itob 215 | concat 216 | store 121 // excess_asset1_key 217 | 218 | // excess_asset2_key 219 | txn Sender 220 | byte "e" 221 | concat 222 | load 102 // asset2ID 223 | itob 224 | concat 225 | store 122 // excess_asset2_key 226 | 227 | 228 | // excess_liquidity_token_key 229 | txn Sender 230 | byte "e" 231 | concat 232 | load 103 // liquidity_token_id 233 | itob 234 | concat 235 | store 123 // excess_liquidity_token_key 236 | 237 | // Redeem 238 | txna ApplicationArgs 0 239 | byte "redeem" // redeem 240 | == 241 | bnz redeem 242 | 243 | // Redeem Fees 244 | txna ApplicationArgs 0 245 | byte "fees" // fees 246 | == 247 | bnz redeem_fees 248 | 249 | // Swap/Mint/Burn 250 | txna ApplicationArgs 0 251 | byte "swap" // swap 252 | == 253 | txna ApplicationArgs 0 254 | byte "mint" //mint 255 | == 256 | || 257 | txna ApplicationArgs 0 258 | byte "burn" //burn 259 | == 260 | || 261 | bnz swap_mint_burn 262 | 263 | 264 | // else 265 | err 266 | 267 | 268 | // Redeem Fees 269 | redeem_fees: 270 | load 103 // liquidity_token_id 271 | dup 272 | assert 273 | gtxn 2 XferAsset 274 | == // ensure the asset in gtxn2 really is the liquidity token 275 | assert 276 | 277 | gtxn 2 AssetAmount // redeemed_token_amount 278 | assert 279 | 280 | gtxn 2 AssetReceiver 281 | global CreatorAddress 282 | == // only the creator (of the validator app) can redeem fees 283 | assert 284 | 285 | // outstanding_liquidity_token_amount -= redeemed_token_amount 286 | load 63 // outstanding_liquidity_token_amount 287 | gtxn 2 AssetAmount // redeemed_token_amount 288 | - 289 | store 63 290 | 291 | // unclaimed_protocol_fee_token_amount -= redeemed_token_amount 292 | load 64 // unclaimed_protocol_fee_token_amount 293 | gtxn 2 AssetAmount // redeemed_token_amount 294 | // NOTE: This operation (-) will cause an error if redeeming amount is > unclaimed_protocol_fee_token_amount 295 | - 296 | store 64 297 | 298 | // store liquidity_tokens in unclaimed_protocol_fee_token state var 299 | int 0 300 | byte "p" // unclaimed_protocol_fee_token_key 301 | load 64 // unclaimed_protocol_fee_token_amount 302 | app_local_put // unclaimed_protocol_fee_token_amount 303 | 304 | // record outstanding liquidity token amount in Pool state 305 | int 0 306 | load 113 // outstanding_liquidity_token_key 307 | load 63 // outstanding_liquidity_token 308 | app_local_put // outstanding_liquidity_token 309 | 310 | int 1 311 | return 312 | 313 | // Redeem 314 | redeem: 315 | // ensure Accounts[1] is the Pooler/Swapper (receiver of txn 2) 316 | gtxn 2 AssetReceiver 317 | gtxn 2 Receiver 318 | gtxn 2 TypeEnum 319 | int pay 320 | == // if algo 321 | select 322 | txna Accounts 1 323 | == 324 | assert 325 | // ensure redeeming amount > 0 326 | load 52 // gtxn_2_amount 327 | assert 328 | 329 | // update value of outstanding_asset_i in local state of Pool account 330 | // outstanding_asset_i_amount = outstanding_asset_i_amount - redeeming_amount 331 | int 0 // account 0, Pool account 332 | byte "o" // 'o' 333 | gtxn 2 XferAsset 334 | itob 335 | concat // key = 'o' + assetID 336 | dup2 // duplicate 0 and key for use with app_local_put below 337 | app_local_get // current outstanding asset 338 | load 52 // gtxn_2_amount 339 | - // outstanding_asset_amount - redeeming_amount 340 | app_local_put // store outstanding asset amount in Pool account 341 | 342 | // update value of excess_asset_i in local state of Swapper/Pooler account 343 | // excess_asset_i_amount = excess_asset_i_amount - redeeming_amount 344 | int 1 345 | txn Sender 346 | byte "e" // 'e' 347 | concat // pool_address + 'e' 348 | gtxn 2 XferAsset 349 | itob 350 | concat // key = pool_address + 'e' + assetID 351 | dup2 // duplicate 1 and key for use with app_local_put below 352 | app_local_get 353 | load 52 // gtxn_2_amount 354 | // NOTE: This operation (-) will cause an error if redeeming amount is > excess_asset_i_amount 355 | - // excess_asset_i_amount - redeeming_amount 356 | dup 357 | bz redeem__zero_excess 358 | app_local_put // store excess asset amount in User account if it is > 0 359 | int 1 360 | return 361 | redeem__zero_excess: 362 | pop // pop the 0 amount 363 | app_local_del // delete the local state for excess amount 364 | int 1 365 | return 366 | 367 | swap_mint_burn: 368 | 369 | // In the first transaction of a block we update state used for oracles: 370 | // * cumulative_price1 (c1) 371 | // * cumulative_price2 (c2) 372 | // * last_updated_timestamp (t) 373 | // 374 | // cumulative_price1 and cumulative_price2 accumulate the price scaled by 1e6 375 | // They are uint64s and wrap around by design 376 | // Recorded scaled prices in state (p1, p2) from the previous swap/mint/burn are used 377 | // p1 is (asset2_supply/asset1_supply) * 1e6 378 | // p2 is (asset2_supply/asset1_supply) * 1e6 379 | // time_delta is (LatestTimestamp - last_updated_timestamp) 380 | 381 | // If (p1 * time_delta) > 0xffffffffffffffff we can't record a cumulative price. 382 | // In this case we skip the update of c1, c2, t. 383 | 384 | global LatestTimestamp 385 | int 0 386 | byte "t" 387 | app_local_get // last_updated_timestamp 388 | - // time_delta 389 | dup 390 | store 250 // time_delta 391 | // if time_delta is 0 it means cumulative prices have already been updated for this round 392 | bz skip_oracle_update 393 | 394 | // this must be the first operation of the round 395 | // record cumulative prices: 396 | 397 | // cumulative_price1 = cumulative_price1 + (price1 * time_delta) 398 | int 0 399 | byte "c1" 400 | app_local_get // cumulative_price1 401 | int 0 402 | byte "p1" 403 | app_local_get // price1 404 | load 250 // time_delta 405 | mulw 406 | // high 407 | // low 408 | swap 409 | // low 410 | // high 411 | // if high > 0 then (price1 * time_delta) is > uint64. skip the oracle update in this case 412 | bnz skip_oracle_update 413 | // (price1 * time_delta) on stack 414 | // now calculate: (cumulative_price1 + (price1 * time_delta)) % (MAX_UINT64 + 1) 415 | addw 416 | int 0xffffffffffffffff 417 | int 1 418 | addw 419 | divmodw 420 | store 251 421 | pop 422 | pop 423 | pop 424 | 425 | // cumulative_price2 = cumulative_price2 + (price2 * time_delta) 426 | int 0 427 | byte "c2" 428 | app_local_get // cumulative_price2 429 | int 0 430 | byte "p2" 431 | app_local_get // price2 432 | load 250 // time_delta 433 | mulw 434 | // high 435 | // low 436 | swap 437 | // low 438 | // high 439 | // if high > 0 then (price2 * time_delta) is > uint64. skip the oracle update in this case 440 | bnz skip_oracle_update 441 | // (price2 * time_delta) on stack 442 | // now calculate: (cumulative_price2 + (price2 * time_delta)) % (MAX_UINT64 + 1) 443 | addw 444 | int 0xffffffffffffffff 445 | int 1 446 | addw 447 | divmodw 448 | store 252 449 | pop 450 | pop 451 | pop 452 | 453 | 454 | int 0 455 | byte "c1" 456 | load 251 457 | app_local_put // cumulative_price1 458 | 459 | int 0 460 | byte "c2" 461 | load 252 462 | app_local_put // cumulative_price2 463 | 464 | int 0 465 | byte "t" 466 | global LatestTimestamp 467 | app_local_put // update last_updated_timestamp 468 | 469 | 470 | skip_oracle_update: 471 | gtxn 3 AssetAmount // asset amount 472 | gtxn 3 Amount // or algo amount 473 | + 474 | store 53 // gtxn_3_amount 475 | 476 | txna Accounts 1 477 | txn Sender 478 | != 479 | assert 480 | 481 | // if liquidity_token_id is 0 it means it hasn't been set yet so this must be before first mint 482 | load 103 // liquidity_token_id 483 | bz mint_burn__1 484 | 485 | // get liquidity_token_balance 486 | int 0 487 | load 103 488 | asset_holding_get AssetBalance 489 | assert // assert asset exists 490 | store 6 // liquidity_token_balance 491 | 492 | // calculate issued liquidity 493 | int 0 494 | ~ // TOTAL_LIQUIDITY 495 | load 6 // liquidity_token_balance 496 | - // TOTAL_LIQUIDITY - liquidity_token_balance 497 | load 63 // outstanding_liquidity_token_amount 498 | + // (TOTAL_LIQUIDITY - liquidity_token_balance) + outstanding_liquidity_token_amount 499 | store 4 // issued_liquidity_tokens 500 | 501 | 502 | txna ApplicationArgs 0 503 | byte "swap" // 'swap' 504 | == 505 | bnz swap 506 | 507 | // ensure the asset in gtxn4 really is the liquidity token 508 | load 103 509 | gtxn 4 XferAsset 510 | == 511 | assert 512 | 513 | mint_burn__1: 514 | 515 | // Mint 516 | txna ApplicationArgs 0 517 | byte "mint" // 'mint' 518 | == 519 | bnz mint 520 | 521 | // Burn 522 | 523 | // burn_amount = gtxn 4 AssetAmount 524 | // asset1_amount = gtxn 2 AssetAmount 525 | // asset2_amount = gtxn 3 AssetAmount 526 | 527 | // calculated_asset1_out = asset1_supply * (burn_amount / issued_liquidity_tokens) 528 | // calculated_asset2_out = asset2_supply * (burn_amount / issued_liquidity_tokens) 529 | 530 | // excess_asset_1 = calculated_asset1_out - asset1_out 531 | // excess_asset_2 = calculated_asset2_out - asset2_out 532 | // # if the excess amount is negative an error will occur 533 | 534 | 535 | // # record outstanding assets in Pool account state 536 | // outstanding_asset1_amount += excess_asset_1 537 | // outstanding_asset2_amount += excess_asset_2 538 | 539 | // # record excess assets in Pooler account state 540 | // excess_asset1_amount += excess_asset_1 541 | // excess_asset2_amount += excess_asset_2 542 | 543 | // burn 544 | // ensure Accounts[1] is the Pooler (sender of txn 4) 545 | txna Accounts 1 546 | gtxn 4 Sender 547 | == 548 | assert 549 | 550 | gtxn 4 AssetAmount // burn_amount 551 | load 71 // asset1_supply 552 | mulw // burn_amount * asset1_supply 553 | load 4 // issued_liquidity_tokens 554 | int 1 555 | mulw // uint128 issued_liquidity_tokens 556 | divmodw // (burn_amount * asset1_supply) / issued_liquidity_tokens 557 | pop 558 | pop 559 | swap 560 | pop 561 | // only low quotient remains on stack 562 | // 563 | dup // calculated_asset1_out 564 | store 16 // calculated_asset1_out 565 | load 52 // gtxn 2 AssetAmount 566 | // Note: the next line will fail if asset1_out > calculated_asset1_out 567 | - // excess_asset_1 = calculated_asset1_out - asset1_out 568 | store 201 // excess_asset_1 569 | 570 | 571 | gtxn 4 AssetAmount // burn_amount 572 | load 72 // asset2_supply 573 | mulw // burn_amount * asset1_supply 574 | load 4 // issued_liquidity_tokens 575 | int 1 576 | mulw // uint128 issued_liquidity_tokens 577 | 578 | divmodw // (burn_amount * asset2_supply) / issued_liquidity_tokens 579 | pop 580 | pop 581 | swap 582 | pop 583 | // only low quotient remains on stack 584 | dup 585 | store 17 // calculated_asset2_out 586 | load 53 // gtxn_3_amount ASA or Algo asset2_out 587 | // Note: the next line will fail if asset2_out > calculated_asset2_out 588 | - // excess_asset_2 = calculated_asset2_out - asset2_out 589 | store 202 // excess_asset_2 590 | 591 | // ensure the calculated amounts are not 0 592 | load 16 593 | load 17 594 | && 595 | assert 596 | 597 | load 71 // asset1_supply 598 | load 16 // calculated_asset1_out 599 | - 600 | store 81 // final_asset1_supply 601 | 602 | load 72 // asset2_supply 603 | load 17 // calculated_asset2_out 604 | - 605 | store 82 // final_asset2_supply 606 | 607 | load 4 // issued_liquidity_tokens 608 | gtxn 4 AssetAmount // burn_amount 609 | - 610 | store 83 // final_issued_liquidity_tokens 611 | 612 | 613 | b update_local_state 614 | 615 | 616 | // Mint 617 | // asset1_amount = gtxn 2 AssetAmount 618 | // asset2_amount = gtxn 3 AssetAmount 619 | // liquidity_token_amount = gtxn 4 AssetAmount 620 | 621 | 622 | // if first minting: 623 | // assert(liquidity_token_amount + MINIMUM_LIQUIDITY <= sqrt(asset1_amount * asset2_amount)) 624 | // # lock up the first MINIMUM_LIQUIDITY tokens 625 | // put outstanding_liquidity_token_amount = MINIMUM_LIQUIDITY 626 | // calculated_liquidity_token_out = liquidity_token_amount 627 | // else: 628 | // calculated_liquidity_token_out = Min( 629 | // asset1_amount * issued_liquidity_tokens / asset1_supply, 630 | // asset2_amount * issued_liquidity_tokens / asset2_supply 631 | // ) 632 | 633 | 634 | // excess_liquidity_token = calculated_liquidity_token_out - liquidity_token_amount 635 | // # record outstanding liquidity in Pool account state 636 | // put outstanding_liquidity_token_amount += excess_liquidity_token 637 | // # record excess liquidity in Pooler account state 638 | // put excess_liquidity_token_amount += excess_liquidity_token 639 | 640 | mint: 641 | // ensure Accounts[1] is the Pooler (sender of txn 2) 642 | txna Accounts 1 643 | gtxn 2 Sender 644 | == 645 | assert 646 | 647 | load 71 // asset1_supply 648 | load 52 // gtxn_2_amount 649 | + 650 | store 81 // final_asset1_supply 651 | 652 | load 72 // asset2_supply 653 | load 53 // gtxn_3_amount 654 | + 655 | store 82 // final_asset2_supply 656 | 657 | 658 | load 4 // issued_liquidity_tokens 659 | int 0 660 | == // is it first minting? 661 | bnz mint__first_minting 662 | 663 | // subsequent minting: 664 | load 52 // gtxn_2_amount 665 | load 4 // issued_liquidity_tokens 666 | mulw // asset1_amount * issued_liquidity_tokens 667 | load 71 // asset1_supply 668 | int 1 669 | mulw // uint128 asset1_supply 670 | divmodw // asset1_amount * issued_liquidity_tokens / asset1_supply 671 | pop 672 | pop 673 | swap 674 | pop 675 | // only low quotient remains on stack 676 | // A 677 | 678 | load 53 // gtxn_3_amount 679 | load 4 // issued_liquidity_tokens 680 | mulw // asset2_amount * issued_liquidity_tokens 681 | load 72 // asset2_supply 682 | int 1 683 | mulw // uint128 asset2_supply 684 | divmodw // asset2_amount * issued_liquidity_tokens / asset2_supply 685 | pop 686 | pop 687 | swap 688 | pop 689 | // only low quotient remains on stack 690 | // B 691 | 692 | // Min(A, B) 693 | dup2 694 | // A 695 | // B 696 | > // A > B 697 | select // B 698 | // calculated_liquidity_token_out = Min(A, B) 699 | dup 700 | load 4 // issued_liquidity_tokens 701 | + 702 | store 83 // final_issued_liquidity_tokens 703 | 704 | gtxn 4 AssetAmount // liquidity_token_amount 705 | - // excess_liquidity_token 706 | store 203 // excess_liquidity_token 707 | b update_local_state 708 | 709 | mint__first_minting: 710 | // minted_liquidity_token = liquidity_token_amount + MINIMUM_LIQUIDITY 711 | // assert(minted_liquidity_token == floor(sqrt(asset1_amount * asset2_amount))) 712 | // Same as: 713 | // assert((minted_liquidity_token * minted_liquidity_token) <= (asset1_amount * asset2_amount)) 714 | // && 715 | // assert(((minted_liquidity_token + 1) * (minted_liquidity_token + 1)) > (asset1_amount * asset2_amount)) 716 | 717 | // the liquidity_token_id is not in local state at this point 718 | // put the liquidity_token_id in a scratch slot & the Pool local state 719 | int 0 720 | byte "lt" // 'lt' liquidity token 721 | gtxn 4 XferAsset 722 | dup 723 | store 103 // liquidity_token_id 724 | app_local_put // liquidity_token_id 725 | 726 | byte "o" 727 | load 103 // liquidity_token_id 728 | itob 729 | concat 730 | store 113 // outstanding_liquidity_token_key 731 | 732 | // ensure gtxn 4 XferAsset (slot 103) is really the liquidity token 733 | // ensure the Pool holds it 734 | int 0 735 | load 103 // liquidity_token_id 736 | asset_holding_get AssetBalance // liquidity_token_balance 737 | assert // assert asset exists 738 | assert 739 | 740 | // ensure it is NOT asset1 741 | // gtxn 4 XferAsset on stack 742 | load 103 // liquidity_token_id 743 | load 101 // asset1_id 744 | != 745 | assert 746 | 747 | // ensure it is NOT asset2 748 | load 103 // liquidity_token_id 749 | load 102 // asset2_id 750 | != 751 | assert 752 | 753 | // assert minted_liquidity_token * minted_liquidity_token <= asset1_amount * asset2_amount 754 | gtxn 4 AssetAmount // liquidity_token_amount 755 | int 1000 // MINIMUM_LIQUIDITY 756 | + // minted_liquidity_token 757 | dup 758 | mulw // minted_liquidity_token * minted_liquidity_token 759 | store 240 // store minted_squared_lo, leave high on stack 760 | 761 | load 52 // gtxn 2 AssetAmount: asset1_amount 762 | load 53 // gtxn_3_amount: assset2_amount 763 | mulw // asset1_amount * asset2_amount 764 | store 241 // store amount_product_lo, leave high on stack 765 | dup2 766 | < // compare highs 767 | // if minted_squared_hi < amount_product_hi don't check lows 768 | bnz mint__first_minting_1 769 | == 770 | assert 771 | load 240 // minted_squared_lo 772 | load 241 // amount_product_lo 773 | <= // compare lows: minted_squared_lo <= amount_product_lo 774 | assert 775 | 776 | mint__first_minting_1: 777 | // assert (minted_liquidity_token + 1) * (minted_liquidity_token + 1) > asset1_amount * asset2_amount 778 | gtxn 4 AssetAmount // liquidity_token_amount 779 | int 1000 // MINIMUM_LIQUIDITY 780 | + // minted_liquidity_token 781 | int 1 782 | + 783 | dup 784 | mulw // minted_liquidity_token * minted_liquidity_token 785 | store 240 // store minted_squared_lo, leave high on stack 786 | 787 | load 52 // gtxn 2 AssetAmount: asset1_amount 788 | load 53 // gtxn_3_amount: assset2_amount 789 | mulw // asset1_amount * asset2_amount 790 | store 241 // store amount_product_lo, leave high on stack 791 | dup2 792 | > // compare highs 793 | // if minted_squared_hi > amount_product_hi don't check lows 794 | bnz mint__first_minting_2 795 | == 796 | assert // assert highs are equal 797 | load 240 // minted_squared_lo 798 | load 241 // amount_product_lo 799 | > // compare lows: minted_squared_lo > amount_product_lo 800 | assert 801 | 802 | 803 | mint__first_minting_2: 804 | // lock up the first MINIMUM_LIQUIDITY tokens 805 | int 1000 806 | store 63 // outstanding_liquidity_token_amount 807 | 808 | load 4 // issued_liquidity_tokens 809 | gtxn 4 AssetAmount // liquidity_token_amount 810 | int 1000 // MINIMUM_LIQUIDITY 811 | + // minted_liquidity_token 812 | + 813 | store 83 // final_issued_liquidity_tokens 814 | 815 | b update_local_state 816 | // End Mint 817 | 818 | 819 | 820 | // Swap 821 | 822 | // k = input_supply * output_supply 823 | // ignoring fees, k must remain constant 824 | // (input_supply + asset_in) * (output_supply - amount_out) = k 825 | 826 | // if fixed input: 827 | // asset_in_amount_minus_fee = asset_in_amount * 997/1000 828 | // calculated_amount_out = output_supply - (k / (input_supply + asset_in_amount_minus_fee)) 829 | 830 | // excess_asset_out = calculated_amount_out - asset_out_amount 831 | // # record outstanding asset out amount in Pool state 832 | // put outstanding_asset_out_amount += excess_asset_out 833 | 834 | // # record excess asset out amount in Pooler state 835 | // put excess_asset_out_amount += excess_asset_out 836 | 837 | // # ensure calculated_amount_out > 0 838 | // # ensure calculated_amount_out < output_supply 839 | 840 | // else: 841 | 842 | // k = input_supply * output_supply 843 | // calculated_amount_in_without_fee = (k / (output_supply - asset_out_amount)) - input_supply 844 | // calculated_amount_in = calculated_amount_in * 1000/997 845 | 846 | // excess_asset_in = asset_in_amount - calculated_amount_in 847 | // # record outstanding asset in amount in Pool state 848 | // put outstanding_asset_in_amount += excess_asset_in 849 | 850 | // # record excess asset in amount in Pooler state 851 | // put excess_asset_in_amount += excess_asset_in 852 | 853 | // # ensure calculated_amount_in > 0 854 | // # ensure asset_out_amount < output_supply 855 | 856 | 857 | swap: 858 | // ensure Accounts[1] is the Swapper (sender of txn 2) 859 | txna Accounts 1 860 | gtxn 2 Sender 861 | == 862 | assert 863 | 864 | // Is asset_in == asset1 and asset_out == asset2? 865 | gtxn 2 XferAsset 866 | load 101 // asset1_id 867 | == 868 | gtxn 3 XferAsset 869 | load 102 // asset2_id 870 | == 871 | && 872 | dup 873 | store 100 // is_asset_in_asset1 874 | bnz swap__asset1_in 875 | 876 | // Is asset_in == asset2 and asset_out == asset1? 877 | gtxn 2 XferAsset 878 | load 102 // asset2_id 879 | == 880 | gtxn 3 XferAsset 881 | load 101 // asset1_id 882 | == 883 | && 884 | assert // else a combination of unexpected assets 885 | 886 | // asset_in == asset2 and asset_out == asset1 887 | load 72 // asset2_supply 888 | store 18 // input_supply 889 | 890 | load 71 // asset1_supply 891 | store 19 // output_supply 892 | b swap__fi_or_fo 893 | 894 | 895 | // asset_in == asset1 and asset_out == asset2 896 | swap__asset1_in: 897 | load 71 // asset1_supply 898 | store 18 // input_supply 899 | load 72 // asset2_supply 900 | store 19 // output_supply 901 | 902 | swap__fi_or_fo: 903 | txna ApplicationArgs 1 904 | byte "fi" // 'fi' 905 | == 906 | bnz swap__fi 907 | txna ApplicationArgs 1 908 | byte "fo" // 'fo' 909 | == 910 | assert 911 | 912 | // Fixed Output Swap 913 | // k = input_supply * output_supply 914 | // calculated_amount_in = (k / (output_supply - asset_out_amount)) - input_supply 915 | // calculated_amount_in_with_fee = calculated_amount_in * 1000/997 916 | // This can be rewritten to reduce precision loss with integer division: 917 | // calculated_amount_in = ((asset_out_amount * 1000 * input_suppply) / ((output_supply - asset_out_amount) * 997)) + 1 918 | 919 | load 53 // gtxn_3_amount: asset_out_amount 920 | int 1000 921 | * 922 | load 18 // input_supply 923 | mulw // (asset_out_amount * 1000 * input_suppply) 924 | 925 | load 19 // output_supply 926 | load 53 // gtxn_3_amount: asset_out_amount 927 | - 928 | int 997 929 | mulw // ((output_supply - asset_out_amount) * 997)) 930 | 931 | divmodw 932 | pop 933 | pop 934 | swap 935 | pop 936 | // only low quotient remains on stack 937 | // ((asset_out_amount * 1000 * input_suppply) / ((output_supply - asset_out_amount) * 997)) 938 | int 1 939 | + 940 | // calculated_amount_in = ((asset_out_amount * 1000 * input_suppply) / ((output_supply - asset_out_amount) * 997)) + 1 941 | dup 942 | store 21 // calculated_amount_in 943 | 944 | // ensure calculated_amount_in > 0 945 | int 0 946 | > 947 | 948 | // ensure asset_out_amount < output_supply 949 | load 53 // gtxn_3_amount: asset_out_amount 950 | load 19 // output_supply 951 | < 952 | && 953 | assert 954 | 955 | // calculate excess_asset_in 956 | // excess_asset_in = asset_in_amount - calculated_amount_in 957 | load 52 // gtxn_2_amount: asset_in_amount 958 | load 21 // calculated_amount_in 959 | // Note: the next line will fail if asset_in_amount < calculated_amount_in 960 | - // excess_asset_in 961 | 962 | load 100 // is_asset_in_asset1 963 | bz swap_fo__1 964 | //asset_in == asset1 965 | store 201 // excess_asset_1 966 | 967 | load 71 // asset1_supply 968 | load 21 // calculated_amount_in 969 | + 970 | store 81 // final_asset1_supply 971 | 972 | load 72 // asset2_supply 973 | load 53 // gtxn_3_amount: asset_out_amount 974 | - 975 | store 82 // final_asset2_supply 976 | 977 | b calculate_protocol_fee 978 | //asset_in == asset2 979 | swap_fo__1: 980 | store 202 // excess_asset_2 981 | 982 | load 72 // asset2_supply 983 | load 21 // calculated_amount_in 984 | + 985 | store 82 // final_asset2_supply 986 | 987 | load 71 // asset1_supply 988 | load 53 // gtxn_3_amount: asset_out_amount 989 | - 990 | store 81 // final_asset1_supply 991 | 992 | b calculate_protocol_fee 993 | 994 | // Fixed Input Swap 995 | swap__fi: 996 | // k = input_supply * output_supply 997 | // asset_in_amount_minus_fee = asset_in_amount * 997/1000 998 | // calculated_amount_out = output_supply - (k / (input_supply + asset_in_amount_minus_fee)) 999 | // This can be rewritten to reduce precision loss with integer division: 1000 | // calculated_amount_out = (asset_in_amount * 997 * output_supply) / ((input_supply * 1000) + (asset_in_amount * 997)) 1001 | 1002 | load 52 // gtxn_2_amount: asset_in_amount 1003 | dup 1004 | store 21 // calculated_amount_in == asset_in_amount 1005 | int 997 1006 | * 1007 | load 19 // output_supply 1008 | mulw // (asset_in_amount * 997 * output_supply) 1009 | 1010 | load 18 // input_supply 1011 | int 1000 1012 | * 1013 | 1014 | load 52 // gtxn_2_amount: asset_in_amount 1015 | int 997 1016 | * 1017 | addw // ((input_supply * 1000) + (asset_in_amount * 997)) 1018 | 1019 | divmodw 1020 | pop 1021 | pop 1022 | swap 1023 | pop 1024 | // only low quotient remains on stack 1025 | // calculated_amount_out = (asset_in_amount * 997 * output_supply) / ((input_supply * 1000) + (asset_in_amount * 997)) 1026 | dup 1027 | store 20 // calculated_amount_out 1028 | // ensure calculated_amount_out > 0 1029 | int 0 1030 | > 1031 | 1032 | // ensure calculated_amount_out < output_supply 1033 | load 20 1034 | load 19 1035 | < 1036 | && 1037 | assert 1038 | 1039 | // calculate excess_asset_out 1040 | // excess_asset_out = calculated_amount_out - asset_out_amount 1041 | load 20 // calculated_amount_out 1042 | load 53 // gtxn_3_amount: asset_out_amount 1043 | // Note: the next line will fail if asset_out_amount > calculated_amount_out 1044 | - // excess_asset_out = calculated_amount_out - asset_out_amount 1045 | 1046 | load 100 // is_asset_in_asset1 aka is_asset_out_asset2 1047 | bz swap_fi__1 1048 | //asset_out == asset2 1049 | store 202 // excess_asset_2 1050 | 1051 | load 71 // asset1_supply 1052 | load 52 // gtxn_2_amount: asset_in_amount 1053 | + 1054 | store 81 // final_asset1_supply 1055 | 1056 | load 72 // asset2_supply 1057 | load 20 // calculated_amount_out 1058 | - 1059 | store 82 // final_asset2_supply 1060 | 1061 | b calculate_protocol_fee 1062 | //asset_in == asset2 1063 | swap_fi__1: 1064 | store 201 // excess_asset_1 1065 | 1066 | load 71 // asset1_supply 1067 | load 20 // calculated_amount_out 1068 | - 1069 | store 81 // final_asset1_supply 1070 | 1071 | load 72 // asset2_supply 1072 | load 52 // gtxn_2_amount: asset_in_amount 1073 | + 1074 | store 82 // final_asset2_supply 1075 | 1076 | b calculate_protocol_fee 1077 | 1078 | calculate_protocol_fee: 1079 | // calculate amount owed as protocol fees and issue corresponding liquidity_tokens: 1080 | // protocol_fee_portion = asset_in_amount * 0.05% 1081 | // protocol_fee_portion = (asset_in_amount * 5) / 10000 1082 | 1083 | // the protocol is granted a share of the pool for half of this amount because it's a 50/50 pool: 1084 | // protocol_fee_asset_in_amount = (asset_in_amount * 5) / 20000 1085 | // protocol_fee_liquidity_tokens = issued_liquidity_tokens * (protocol_fee_asset_in_amount / input_supply) 1086 | 1087 | // refactor to reduce rounding errors: 1088 | // protocol_fee_liquidity_tokens = (issued_liquidity_tokens * (asset_in_amount * 5)) / (20000 * input_supply) 1089 | 1090 | load 21 // asset_in_amount 1091 | int 5 1092 | * 1093 | load 4 // issued_liquidity_tokens 1094 | mulw 1095 | int 20000 1096 | load 18 // input_supply 1097 | mulw 1098 | divmodw 1099 | pop 1100 | pop 1101 | swap 1102 | pop 1103 | // only low quotient remains on stack 1104 | 1105 | dup 1106 | store 42 // protocol_fee_liquidity_tokens 1107 | 1108 | load 4 // issued_liquidity_tokens 1109 | + 1110 | store 83 // final_issued_liquidity_tokens 1111 | 1112 | b update_local_state 1113 | 1114 | 1115 | // Bootstrap 1116 | bootstrap: 1117 | int 0 1118 | byte "a1" // 'a1' 1119 | txna ApplicationArgs 1 1120 | btoi 1121 | dup 1122 | store 101 1123 | app_local_put // asset1ID 1124 | int 0 1125 | byte "a2" // 'a2' 1126 | txna ApplicationArgs 2 1127 | btoi 1128 | dup 1129 | store 102 1130 | app_local_put // asset2ID 1131 | 1132 | // Ensure that the AssetName of the liquidity asset ends with "{asset1_unit_name}-{asset2_unit_name}" 1133 | // The Pool LogicSig ensures that it starts with "TinymanPool1.1 " 1134 | load 101 1135 | asset_params_get AssetUnitName 1136 | assert // Note: This will fail if the asset has no unit name 1137 | // asset1_unit_name on stack 1138 | pushbytes "-" 1139 | concat 1140 | 1141 | pushbytes "ALGO" 1142 | load 102 1143 | bz bootstrap__1 1144 | pop 1145 | load 102 1146 | asset_params_get AssetUnitName 1147 | assert // Note: This will fail if the asset has no unit name 1148 | // asset2_unit_name on stack 1149 | 1150 | bootstrap__1: 1151 | concat // "{asset1_unit_name}-{asset2_unit_name}" 1152 | gtxn 2 ConfigAssetName 1153 | dup 1154 | len 1155 | int 15 1156 | swap 1157 | substring3 1158 | == 1159 | return 1160 | 1161 | update_local_state: 1162 | 1163 | // store liquidity_tokens in unclaimed_protocol_fee_token state var 1164 | int 0 1165 | byte "p" // unclaimed_protocol_fee_token_key 1166 | load 64 // unclaimed_protocol_fee_token_amount 1167 | load 42 1168 | + // unclaimed_protocol_fee_token_amount += protocol_fee_liquidity_tokens 1169 | app_local_put // unclaimed_protocol_fee_token_amount 1170 | 1171 | // record outstanding liquidity token amount in Pool state 1172 | int 0 1173 | load 113 // outstanding_liquidity_token_key 1174 | load 63 // outstanding_liquidity_token 1175 | load 42 // protocol_fee_liquidity_tokens 1176 | + // outstanding_liquidity_token_amount += protocol_fee_liquidity_tokens 1177 | load 203 // excess_liquidity_token 1178 | + // outstanding_liquidity_token_amount += excess_liquidity_token 1179 | app_local_put // outstanding_liquidity_token 1180 | 1181 | int 0 1182 | load 111 // outstanding_asset1_key 1183 | load 61 // outstanding_asset1_amount 1184 | load 201 // excess_asset_1 1185 | + 1186 | app_local_put // outstanding_asset1_amount 1187 | 1188 | int 0 1189 | load 112 // outstanding_asset2_key 1190 | load 62 // outstanding_asset2_amount 1191 | load 202 // excess_asset_2 1192 | + 1193 | app_local_put // outstanding_asset2_amount 1194 | 1195 | 1196 | // store final_asset_1_supply and final_asset_2_supply in pool state 1197 | int 0 1198 | byte "s1" 1199 | load 81 // final_asset_1_supply 1200 | app_local_put // asset_1_supply 1201 | 1202 | int 0 1203 | byte "s2" 1204 | load 82 // final_asset_2_supply 1205 | app_local_put // asset_2_supply 1206 | 1207 | // calculate and store price1 1208 | int 0 1209 | byte "p1" 1210 | load 82 // final_asset_2_supply 1211 | int 1000000 // 1e6 1212 | mulw 1213 | load 81 // final_asset_1_supply 1214 | int 1 1215 | mulw 1216 | divmodw 1217 | pop 1218 | pop 1219 | swap 1220 | pop 1221 | // price1 on stack 1222 | app_local_put // price1 1223 | 1224 | // calculate and store price2 1225 | int 0 1226 | byte "p2" 1227 | load 81 // final_asset_1_supply 1228 | int 1000000 // 1e6 1229 | mulw 1230 | load 82 // final_asset_2_supply 1231 | int 1 1232 | mulw 1233 | divmodw 1234 | pop 1235 | pop 1236 | swap 1237 | pop 1238 | // price2 on stack 1239 | app_local_put // price2 1240 | 1241 | // store final_issued_liquidity_tokens in pool state 1242 | int 0 1243 | byte "ilt" 1244 | load 83 // final_issued_liquidity_tokens 1245 | app_local_put // final_issued_liquidity_tokens 1246 | 1247 | // record excess liquidity in Pooler account state 1248 | load 203 1249 | bz update_local_state__1 // don't record if amount is 0 1250 | int 1 1251 | load 123 // excess_liquidity_token_key 1252 | dup2 1253 | app_local_get 1254 | load 203 // excess_liquidity_token 1255 | + // excess_liquidity_token_amount += excess_liquidity_token 1256 | app_local_put // excess_liquidity_token_amount 1257 | 1258 | update_local_state__1: 1259 | // record excess asset 1 in Pooler account state 1260 | load 201 1261 | bz update_local_state__2 // don't record if amount is 0 1262 | int 1 1263 | load 121 // excess_asset1_key 1264 | dup2 1265 | app_local_get 1266 | load 201 // excess_asset_1 1267 | +// excess_asset1_amount += excess_asset_1 1268 | app_local_put // excess_asset1_amount 1269 | 1270 | update_local_state__2: 1271 | // record excess asset 2 in Pooler account state 1272 | load 202 1273 | bz update_local_state__3 // don't record if amount is 0 1274 | int 1 1275 | load 122 // excess_asset2_key 1276 | dup2 1277 | app_local_get 1278 | load 202 // excess_asset_2 1279 | +// excess_asset2_amount += excess_asset_2 1280 | app_local_put // excess_asset2_amount 1281 | 1282 | update_local_state__3: 1283 | int 1 1284 | return 1285 | 1286 | 1287 | success: 1288 | int 1 1289 | return 1290 | 1291 | fail: 1292 | int 0 1293 | return 1294 | -------------------------------------------------------------------------------- /contracts/validator_clear_state.teal: -------------------------------------------------------------------------------- 1 | #pragma version 4 2 | int 1 3 | -------------------------------------------------------------------------------- /docs/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tinymanorg/tinyman-contracts-v1/a055a31ab4e70d21c4ae5fe3e192bd48d37fbb71/docs/.DS_Store -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | 2 | ## Overview 3 | The Tinyman contracts consist of a single stateful smart contract (the Validator) and a template for a stateless smart contract for each Pool. 4 | 5 | The Validator app is created/deployed to the network by the Tinyman team. The Pool contract accounts are created from the Pool LogicSig template by the first Pooler who wishes to provide liquidity. 6 | 7 | Pool contract account addresses are discovered or verified by Swappers/Poolers using the Pool LogicSig template to generate a specific Pool LogicSig and retrieving the address. 8 | 9 | ## Operations 10 | The following sections describe each of the possible operations that can be performed with the Tinyman smart contract app. 11 | 12 | ### Create 13 | Create/deploy the Validator App 14 | 15 | [Create Docs](create.md) 16 | 17 | ### Bootstrap 18 | Setup a Pool for a pair of assets. 19 | 20 | [Bootstrap Docs](bootstrap.md) 21 | 22 | ### OptIn 23 | Swappers/Poolers must OptIn to the Validator App 24 | 25 | [OptIn Docs](optin.md) 26 | 27 | ### Mint 28 | Mint Pool assets in exchange for transferring assets to the Pool account. 29 | 30 | [Mint Docs](mint.md) 31 | 32 | ### Burn 33 | Burn Pool liquidity assets in exchange for removing assets from the Pool. 34 | 35 | [Burn Docs](burn.md) 36 | 37 | ### Swap 38 | Swap one asset (ASA or Algo) for another with the Pool. 39 | 40 | [Swap Docs](swap.md) 41 | 42 | ### Redeem 43 | Claim back 'change' due to slippage in Mint/Burn/Swap process. 44 | 45 | [Redeem Docs](redeem.md) 46 | 47 | 48 | ### Redeem Fees 49 | Redeem Protocol Fees to the Creator account 50 | 51 | [Redeem Fees Docs](redeem_fees.md) 52 | -------------------------------------------------------------------------------- /docs/bootstrap.md: -------------------------------------------------------------------------------- 1 | ## Bootstrap 2 | Setup a Pool for a pair of assets. The Pool account should be a LogicSig contract account. 3 | 4 | ### Transaction Group: 5 | 0. Pay - pay fees from Pooler to Pool 6 | - fees to cover Tx 1,2,3,4 7 | - Signed by Pooler 8 | ``` 9 | { 10 | "txn": { 11 | "type": "pay", 12 | "rcv": "{POOL_ADDRESS}", 13 | "snd": "{POOLER_ADDRESS}", 14 | "amt": 961000, // or 860000 if asset 2 is Algo 15 | "fee": 1000, 16 | "note": "", 17 | ... 18 | }, 19 | "sig": "{POOLER_SIG}", 20 | } 21 | ``` 22 | 1. App Call - OptIn call to Validator App with args ['bootstrap', asset1ID, asset2ID] 23 | - signed by Pool LogicSig 24 | 25 | ``` 26 | { 27 | "txn": { 28 | "type": "appl", 29 | "snd": "{POOL_ADDRESS}", 30 | "apid": {VALIDATOR_APP_ID}, 31 | "apan": 1, // OnComplete: OptIn 32 | "apaa": ['Ym9vdHN0cmFw', '{ASSET1_ID}', '{ASSET2_ID}'] // ['bootstrap', asset1ID, asset2ID] 33 | "apas": [{ASSET1_ID}, {ASSET1_ID}] // or just [{ASSET1_ID}] if asset 2 is Algo 34 | "fee": 1000, 35 | ... 36 | }, 37 | "lsig": "{POOL_LOGICSIG}", 38 | } 39 | ``` 40 | 41 | 2. AssetConfig - create asset for liquidity token 42 | - signed by Pool LogicSig 43 | 44 | ``` 45 | { 46 | "txn": { 47 | "type": "acfg" 48 | "snd": "{POOL_ADDRESS}", 49 | "fee": 1000, 50 | "apar": { 51 | "an": "TinymanPool1.1 USDC-ALGO", 52 | "un": "TMPOOL11" 53 | "au": "https://tinyman.org", 54 | "t": 18446744073709551615, // 0xFFFFFFFFFFFFFFFF 55 | "dc": 6, 56 | }, 57 | ... 58 | }, 59 | "lsig": "{POOL_LOGICSIG}", 60 | } 61 | ``` 62 | 63 | 3. Asset OptIn - Pool opt in to Asset 1 64 | - signed by Pool LogicSig 65 | 66 | ``` 67 | { 68 | "txn": { 69 | "type": "axfer", 70 | "arcv": "{POOL_ADDRESS}", 71 | "snd": "{POOL_ADDRESS}", 72 | "fee": 1000, 73 | "xaid": {ASSET_1_ID}, 74 | ... 75 | }, 76 | "lsig": "{POOL_LOGICSIG}", 77 | } 78 | ``` 79 | 4. (Optional) Asset OptIn - Pool opt in to Asset 2 80 | - Only if Asset 2 is not Algo 81 | - signed by Pool LogicSig 82 | 83 | ``` 84 | { 85 | "txn": { 86 | "type": "axfer", 87 | "snd": "{POOL_ADDRESS}", 88 | "arcv": "{POOL_ADDRESS}", 89 | "fee": 1000, 90 | "xaid": {ASSET_2_ID}, 91 | ... 92 | }, 93 | "lsig": "{POOL_LOGICSIG}", 94 | } 95 | ``` 96 | 97 | ### Validator App State 98 | #### Global State 99 | None 100 | #### Pool Account Local State 101 | * `a1: {ASSET1_ID` 102 | * `a2: {ASSET2_ID` 103 | -------------------------------------------------------------------------------- /docs/burn.md: -------------------------------------------------------------------------------- 1 | ## Burn 2 | Burn Pool liquidity assets in exchange for removing assets from the Pool. 3 | 4 | ### Transaction Group: 5 | 6 | 0. Pay - pay fees in Algo from Pooler to Pool 7 | - fees to cover Tx 1,2,3 8 | - Signed by Pooler 9 | ``` 10 | { 11 | "txn": { 12 | "type": "pay", 13 | "rcv": "{POOL_ADDRESS}", 14 | "snd": "{POOLER_ADDRESS}", 15 | "amt": 3000, 16 | "fee": 1000, 17 | ... 18 | }, 19 | "sig": "{POOLER_SIG}", 20 | } 21 | ``` 22 | 1. App Call - NoOp call to Validator App with args ['burn'], with Pooler account 23 | 24 | ``` 25 | { 26 | "txn": { 27 | "type": "appl", 28 | "snd": "{POOL_ADDRESS}", 29 | "apid": {VALIDATOR_APP_ID}, 30 | "apan": 0, // OnComplete: NoOp 31 | "apaa": ['YnVybg=='], // ['burn'] 32 | "apat": [{POOLER_ADDRESS}], 33 | "apas": [{ASSET1_ID}, {ASSET1_ID}, {LIQUIDITY_ASSET_ID}] // or just [{ASSET1_ID}, {LIQUIDITY_ASSET_ID}] if asset 2 is Algo 34 | "fee": 1000, 35 | ... 36 | }, 37 | "lsig": "{POOL_LOGICSIG}", 38 | } 39 | ``` 40 | 41 | 2. AssetTransfer - Transfer of asset 1 from Pool to Pooler 42 | - Signed by Pool 43 | - Amount is minimum expected amount allowing for slippage 44 | 45 | ``` 46 | { 47 | "txn": { 48 | "type": "axfer", 49 | "arcv": "{POOLER_ADDRESS}", 50 | "snd": "{POOL_ADDRESS}", 51 | "xaid": {ASSET_1_ID}, 52 | "aamt": {ASSET_1_AMOUNT}, 53 | "fee": 1000, 54 | ... 55 | }, 56 | "lsig": "{POOL_LOGICSIG}", 57 | } 58 | ``` 59 | 60 | 3. (a) AssetTransfer - Transfer of asset 2 from Pool to Pooler 61 | - If asset 2 is an ASA 62 | - Signed by Pool 63 | - Amount is minimum expected amount allowing for slippage 64 | 65 | ``` 66 | { 67 | "txn": { 68 | "type": "axfer", 69 | "arcv": "{POOLER_ADDRESS}", 70 | "snd": "{POOL_ADDRESS}", 71 | "xaid": {ASSET_2_ID}, 72 | "aamt": {ASSET_2_AMOUNT}, 73 | "fee": 1000, 74 | ... 75 | }, 76 | "lsig": "{POOL_LOGICSIG}", 77 | } 78 | ``` 79 | 80 | 3. (b) Pay - Transfer of Algo from Pool to Pooler 81 | - If asset 2 is Algo 82 | - Signed by Pool 83 | - Amount is minimum expected amount allowing for slippage 84 | 85 | ``` 86 | { 87 | "txn": { 88 | "type": "pay", 89 | "rcv": "{POOLER_ADDRESS}", 90 | "snd": "{POOL_ADDRESS"}, 91 | "amt": {ASSET_2_AMOUNT}, 92 | "fee": 1000, 93 | ... 94 | }, 95 | "lsig": "{POOL_LOGICSIG}", 96 | } 97 | ``` 98 | 99 | 4. AssetTransfer - Transfer of liquidity token asset from Pooler to Pool 100 | - Signed by Pooler 101 | 102 | ``` 103 | { 104 | "txn": { 105 | "type": "axfer", 106 | "arcv": "{POOL_ADDRESS}", 107 | "snd": "{POOLER_ADDRESS}", 108 | "xaid": {LIQUIDITY_ASSET_ID}, 109 | "aamt": {LIQUIDITY_ASSET_AMOUNT}, 110 | "fee": 1000, 111 | ... 112 | }, 113 | "sig": "{POOLER_SIG}", 114 | } 115 | ``` 116 | 117 | 118 | 119 | ### Validator App State 120 | #### Global State 121 | None 122 | #### Pool Account Local State 123 | * `a1: {ASSET1_ID}` 124 | * `a2: {ASSET2_ID}` 125 | * `o{LIQUIDITY_ASSET_ID}: {o}` // total outstanding unredeemed liquidity asset amount 126 | * `o{ASSET1_ID}: {o}` // total outstanding unredeemed asset 1 amount 127 | * `o{ASSET2_ID}: {o}` // total outstanding unredeemed asset 2 amount 128 | 129 | #### Pooler Account Local State 130 | * `{POOL_ADDRESS}e{LIQUIDITY_ASSET_ID}: {e}` // excess liquidity asset amount available for redemption 131 | * `{POOL_ADDRESS}e{ASSET1_ID}: {e}` // excess asset 1 amount available for redemption 132 | * `{POOL_ADDRESS}e{ASSET2_ID}: {e}` // excess asset 2 amount available for redemption -------------------------------------------------------------------------------- /docs/create.md: -------------------------------------------------------------------------------- 1 | ## Create 2 | Create/deploy the Validator App. 3 | 4 | ### Transaction: 5 | 0. App Call - Create Validator App 6 | - signed by Creator 7 | 8 | ``` 9 | { 10 | "txn": { 11 | "type": "appl", 12 | "snd": "{CREATOR_ADDRESS}", 13 | "apid": 0, 14 | "apan": 0, // OnComplete: NoOp 15 | "apaa": ['Y3JlYXRl'] // ['create'] 16 | "apls": {"nui": 16}, // Local State Schema: num_uints=16 17 | "apap": {APPROVAL_PROGRAM_BYTES}, 18 | "apsu": {CLEAR_STATE_PROGRAM_BYTES}, 19 | ... 20 | }, 21 | } 22 | ``` 23 | 24 | 25 | ### Validator App State Changes 26 | #### Global State 27 | None 28 | #### Creator Account Local State 29 | None 30 | -------------------------------------------------------------------------------- /docs/mint.md: -------------------------------------------------------------------------------- 1 | ## Mint 2 | Mint Pool assets in exchange for transferring assets to the Pool account. 3 | 4 | ### Transaction Group: 5 | 0. Pay - pay fees in Algo from Pooler to Pool 6 | - fees to cover Tx 1,4 7 | - Signed by Pooler 8 | ``` 9 | { 10 | "txn": { 11 | "type": "pay", 12 | "rcv": "{POOL_ADDRESS}", 13 | "snd": "{POOLER_ADDRESS}", 14 | "amt": 2000, 15 | "fee": 1000, 16 | ... 17 | }, 18 | "sig": "{POOLER_SIG}", 19 | } 20 | ``` 21 | 1. App Call - NoOp call to Validator App with args ['mint'], with Pooler account 22 | - Signed by Pool LogicSig 23 | 24 | ``` 25 | { 26 | "txn": { 27 | "type": "appl", 28 | "snd": "{POOL_ADDRESS}", 29 | "apid": {VALIDATOR_APP_ID}, 30 | "apan": 0, // OnComplete: NoOp 31 | "apaa": ['bWludA=='], // ['mint'] 32 | "apat": [{POOLER_ADDRESS}], 33 | "apas": [{ASSET1_ID}, {ASSET1_ID}, {LIQUIDITY_ASSET_ID}] // or just [{ASSET1_ID}, {LIQUIDITY_ASSET_ID}] if asset 2 is Algo 34 | "fee": 1000, 35 | ... 36 | }, 37 | "lsig": "{POOL_LOGICSIG}", 38 | } 39 | ``` 40 | 41 | 2. AssetTransfer - Transfer of asset 1 from Pooler to Pool 42 | - Signed by Pooler 43 | 44 | ``` 45 | { 46 | "txn": { 47 | "type": "axfer", 48 | "arcv": "{POOL_ADDRESS}", 49 | "snd": "{POOLER_ADDRESS}", 50 | "xaid": {ASSET_1_ID}, 51 | "aamt": {ASSET_1_AMOUNT}, 52 | "fee": 1000, 53 | ... 54 | }, 55 | "sig": "{POOLER_SIG}", 56 | } 57 | ``` 58 | 59 | 3. (a) AssetTransfer - Transfer of asset 2 from Pooler to Pool 60 | - If asset 2 is an ASA 61 | - Signed by Pooler 62 | 63 | ``` 64 | { 65 | "txn": { 66 | "type": "axfer", 67 | "arcv": "{POOL_ADDRESS}", 68 | "snd": "{POOLER_ADDRESS}", 69 | "xaid": {ASSET_2_ID}, 70 | "aamt": {ASSET_2_AMOUNT}, 71 | "fee": 1000, 72 | ... 73 | }, 74 | "sig": "{POOLER_SIG}", 75 | } 76 | ``` 77 | 78 | 3. (b) Pay - Transfer of Algo from Pooler to Pool 79 | - If asset 2 is Algo 80 | - Signed by Pooler 81 | 82 | ``` 83 | { 84 | "txn": { 85 | "type": "pay", 86 | "rcv": "{POOL_ADDRESS}", 87 | "snd": "{POOLER_ADDRESS}", 88 | "amt": {ASSET_2_AMOUNT}, 89 | "fee": 1000, 90 | ... 91 | }, 92 | "sig": "{POOLER_SIG}", 93 | } 94 | ``` 95 | 96 | 4. AssetTransfer - Transfer of liquidity token asset from Pool to Pooler 97 | - Signed by Pool LogicSig 98 | - Amount is minimum expected amount of liquidity token allowing for slippage 99 | 100 | ``` 101 | { 102 | "txn": { 103 | "type": "axfer", 104 | "arcv": "{POOLER_ADDRESS}", 105 | "snd": "{POOL_ADDRESS}", 106 | "xaid": {LIQUIDITY_ASSET_ID}, 107 | "aamt": {LIQUIDITY_ASSET_AMOUNT}, 108 | "fee": 1000, 109 | ... 110 | }, 111 | "lsig": "{POOL_LOGICSIG}", 112 | } 113 | ``` 114 | 115 | 116 | 117 | ### Validator App State 118 | #### Global State 119 | None 120 | #### Pool Account Local State 121 | * `a1: {ASSET1_ID}` 122 | * `a2: {ASSET2_ID}` 123 | * `o{LIQUIDITY_ASSET_ID}: {o}` // total outstanding unredeemed liquidity asset amount 124 | 125 | #### Pooler Account Local State 126 | * `{POOL_ADDRESS}e{LIQUIDITY_ASSET_ID}: {e}` // excess liquidity asset amount available for redemption 127 | -------------------------------------------------------------------------------- /docs/optin.md: -------------------------------------------------------------------------------- 1 | ## OptIn 2 | OptIn to the Validator App for Poolers/Swappers. 3 | The user must OptIn to the app once before doing any mint/burn/swap/redeem transactions. 4 | 5 | ### Transaction: 6 | 0. App Call - OptIn to the Validator App 7 | - signed by Pooler/Swapper 8 | 9 | ``` 10 | { 11 | "txn": { 12 | "type": "appl", 13 | "snd": "{POOOLER/SWAPPER_ADDRESS}", 14 | "apid": {VALIDATOR_APP_ID}, 15 | "apan": 1, // OnComplete: OptIn 16 | "apaa": [] 17 | ... 18 | }, 19 | } 20 | ``` 21 | 22 | 23 | ### Validator App State Changes 24 | #### Global State 25 | None 26 | #### User Account Local State 27 | None 28 | -------------------------------------------------------------------------------- /docs/redeem.md: -------------------------------------------------------------------------------- 1 | ## Redeem 2 | Claim back 'change' due to slippage in Mint/Burn/Swap process. 3 | 4 | ### Transaction Group: 5 | 6 | 0. Pay - pay fees in Algo from Pooler to Pool 7 | - fees to cover Tx 1,2 8 | - Signed by Pooler 9 | ``` 10 | { 11 | "txn": { 12 | "type": "pay", 13 | "rcv": "{POOL_ADDRESS}", 14 | "snd": "{POOLER_ADDRESS}", 15 | "amt": 3000, 16 | "fee": 1000, 17 | ... 18 | }, 19 | "sig": "{POOLER_SIG}", 20 | } 21 | ``` 22 | 1. App Call - NoOp call to Validator App with args ['redeem'], with Pooler account 23 | - Signed by Pool LogicSig 24 | 25 | ``` 26 | { 27 | "txn": { 28 | "type": "appl", 29 | "snd": "{POOL_ADDRESS}", 30 | "apid": {VALIDATOR_APP_ID}, 31 | "apan": 0, // OnComplete: NoOp 32 | "apaa": ['cmVkZWVt'] // ['redeem'] 33 | "apat": [{POOLER/SWAPPER_ADDRESS}], 34 | "apas": [{ASSET1_ID}, {ASSET1_ID}, {LIQUIDITY_ASSET_ID}] // or just [{ASSET1_ID}, {LIQUIDITY_ASSET_ID}] if asset 2 is Algo 35 | "fee": 1000, 36 | ... 37 | }, 38 | "lsig": "{POOL_LOGICSIG}", 39 | } 40 | ``` 41 | 42 | 2. (a) AssetTransfer - Transfer of asset from Pool to Pooler/Swapper 43 | - If asset is an ASA 44 | - Signed by Pool 45 | 46 | ``` 47 | { 48 | "txn": { 49 | "type": "axfer", 50 | "arcv": "{POOLER/SWAPPER_ADDRESS}", 51 | "snd": "{POOL_ADDRESS}", 52 | "xaid": {ASSET_ID}, 53 | "aamt": {ASSET_AMOUNT}, 54 | "fee": 1000, 55 | ... 56 | }, 57 | "lsig": "{POOL_LOGICSIG}", 58 | } 59 | ``` 60 | 61 | 2. (b) Pay - Transfer of Algo from Pool to Pooler/Swapper 62 | - If asset is Algo 63 | - Signed by Pool 64 | 65 | ``` 66 | { 67 | "txn": { 68 | "type": "pay", 69 | "rcv": "{POOLER/SWAPPER_ADDRESS}", 70 | "snd": "{POOL_ADDRESS"}, 71 | "amt": {ASSET_AMOUNT}, 72 | "fee": 1000, 73 | ... 74 | }, 75 | "lsig": "{POOL_LOGICSIG}", 76 | } 77 | ``` 78 | 79 | 80 | 81 | 82 | ### Validator App State 83 | #### Global State 84 | None 85 | #### Pool Account Local State 86 | * `a1: {ASSET1_ID}` 87 | * `a2: {ASSET2_ID}` 88 | * `o{LIQUIDITY_ASSET_ID}: {o}` // total outstanding unredeemed liquidity asset amount 89 | * `o{ASSET1_ID}: {o}` // total outstanding unredeemed asset 1 amount 90 | * `o{ASSET2_ID}: {o}` // total outstanding unredeemed asset 2 amount 91 | 92 | #### Pooler Account Local State 93 | * `{POOL_ADDRESS}e{LIQUIDITY_ASSET_ID}: {e}` // excess liquidity asset amount available for redemption 94 | * `{POOL_ADDRESS}e{ASSET1_ID}: {e}` // excess asset 1 amount available for redemption 95 | * `{POOL_ADDRESS}e{ASSET2_ID}: {e}` // excess asset 2 amount available for redemption -------------------------------------------------------------------------------- /docs/redeem_fees.md: -------------------------------------------------------------------------------- 1 | ## Redeem Fees 2 | Transfer collected protocol fees to the CREATOR account. 3 | Note: The CREATOR must opt-in to the liquidity assets separately before receiving them. 4 | 5 | ### Transaction Group: 6 | 7 | 0. Pay - pay fees in Algo from User to Pool 8 | - fees to cover Tx 1,2 9 | - Signed by User 10 | ``` 11 | { 12 | "txn": { 13 | "type": "pay", 14 | "rcv": "{POOL_ADDRESS}", 15 | "snd": "{USER_ADDRESS}", 16 | "amt": 2000, 17 | ... 18 | }, 19 | "sig": "{USER_SIG}", 20 | } 21 | ``` 22 | 1. App Call - NoOp call to Validator App with args ['fees'] 23 | - Signed by Pool LogicSig 24 | 25 | ``` 26 | { 27 | "txn": { 28 | "type": "appl", 29 | "snd": "{POOL_ADDRESS}", 30 | "apid": {VALIDATOR_APP_ID}, 31 | "apan": 0, // OnComplete: NoOp 32 | "apaa": ['ZmVlcw=='], // ['fees'] 33 | "apas": [{ASSET1_ID}, {ASSET1_ID}, {LIQUIDITY_ASSET_ID}] // or just [{ASSET1_ID}, {LIQUIDITY_ASSET_ID}] if asset 2 is Algo 34 | ... 35 | }, 36 | "lsig": "{POOL_LOGICSIG}", 37 | } 38 | ``` 39 | 40 | 2. AssetTransfer - Transfer of liquidity asset from Pool to CREATOR account 41 | - Signed by Pool 42 | 43 | ``` 44 | { 45 | "txn": { 46 | "type": "axfer", 47 | "arcv": "{CREATOR_ADDRESS}", 48 | "snd": "{POOL_ADDRESS}", 49 | "xaid": {LIQUIDITY_ASSET_ID}, 50 | "aamt": {ASSET_AMOUNT}, 51 | ... 52 | }, 53 | "lsig": "{POOL_LOGICSIG}", 54 | } 55 | ``` 56 | 57 | 58 | 59 | 60 | ### Validator App State Changes 61 | #### Global State 62 | None 63 | #### Pool Account Local State 64 | * `o{LIQUIDITY_ASSET_ID}: {o}` // total outstanding unredeemed liquidity asset amount 65 | * `p: {p}` // unclaimed protocol fee liquidity asset amount 66 | 67 | #### Creator Local State 68 | None -------------------------------------------------------------------------------- /docs/swap.md: -------------------------------------------------------------------------------- 1 | ## Swap 2 | Swap one asset (ASA or Algo) for another with the Pool. 3 | 4 | ### Transaction Group: 5 | 6 | 0. Pay - pay fees in Algo from Swapper to Pool 7 | - fees to cover Tx 1,2 8 | - Signed by Swapper 9 | ``` 10 | { 11 | "txn": { 12 | "type": "pay", 13 | "rcv": "{POOL_ADDRESS}", 14 | "snd": "{SWAPPER_ADDRESS}", 15 | "amt": 2000, 16 | "fee": 1000, 17 | ... 18 | }, 19 | "sig": "{SWAPPER_SIG}", 20 | } 21 | ``` 22 | 1. App Call - NoOp call to Validator App with args ['swap', (fixed-input) or 'fo' (fixed-output)], with Swapper account 23 | - Signed by Pool LogicSig 24 | - Argument 1 'fi' specifies that the input (sell) is fixed but the output may vary with slippage 25 | - Argument 1 'fo' specifies that the output (buy) is fixed but the input may vary with slippage 26 | 27 | ``` 28 | { 29 | "txn": { 30 | "type": "appl", 31 | "snd": "{POOL_ADDRESS}", 32 | "apid": {VALIDATOR_APP_ID}, 33 | "apan": 0, // OnComplete: NoOp 34 | "apaa": ['c3dhcA==', 'Zmk=' or 'Zm8='], // ['swap', 'fi' (fixed-input) or 'fo' (fixed-output)] 35 | "apat": [{SWAPPER_ADDRESS}], 36 | "apas": [{ASSET1_ID}, {ASSET1_ID}, {LIQUIDITY_ASSET_ID}] // or just [{ASSET1_ID}, {LIQUIDITY_ASSET_ID}] if asset 2 is Algo 37 | "fee": 1000, 38 | ... 39 | }, 40 | "lsig": "{POOL_LOGICSIG}", 41 | } 42 | ``` 43 | 44 | 2. (a) AssetTransfer - Transfer of sell asset from Swapper to Pool 45 | - If sell asset is an ASA 46 | - Signed by Swapper 47 | 48 | ``` 49 | { 50 | "txn": { 51 | "type": "axfer", 52 | "arcv": "{POOL_ADDRESS}", 53 | "snd": "{SWAPPER_ADDRESS}", 54 | "xaid": {SELL_ASSET_ID}, 55 | "aamt": {SELL_ASSET_AMOUNT}, 56 | "fee": 1000, 57 | ... 58 | }, 59 | "sig": "{SWAPPER_SIG}", 60 | } 61 | ``` 62 | 63 | 2. (b) Pay - Transfer of Algo from Swapper to Pool 64 | - If sell asset is Algo 65 | - Signed by Swapper 66 | 67 | ``` 68 | { 69 | "txn": { 70 | "type": "pay", 71 | "rcv": "{POOL_ADDRESS}", 72 | "snd": "{SWAPPER_ADDRESS}", 73 | "amt": {SELL_ASSET_AMOUNT}, 74 | "fee": 1000, 75 | ... 76 | }, 77 | "sig": "{SWAPPER_SIG}", 78 | } 79 | ``` 80 | 81 | 3. (a) AssetTransfer - Transfer of buy asset from Pool to Swapper 82 | - If buy asset is an ASA 83 | - Signed by Pool 84 | 85 | ``` 86 | { 87 | "txn": { 88 | "type": "axfer", 89 | "arcv": "{SWAPPER_ADDRESS}", 90 | "snd": "{POOL_ADDRESS}", 91 | "xaid": {BUY_ASSET_ID}, 92 | "aamt": {BUY_ASSET_AMOUNT}, 93 | "fee": 1000, 94 | ... 95 | }, 96 | "lsig": "{POOL_LOGICSIG}", 97 | } 98 | ``` 99 | 100 | 3. (b) Pay - Transfer of buy asset from Pool to Swapper 101 | - If buy asset is Algo 102 | - Signed by Pool 103 | 104 | ``` 105 | { 106 | "txn": { 107 | "type": "pay", 108 | "rcv": "{SWAPPER_ADDRESS}", 109 | "snd": "{POOL_ADDRESS}", 110 | "amt": {BUY_ASSET_AMOUNT}, 111 | "fee": 1000, 112 | ... 113 | }, 114 | "lsig": "{POOL_LOGICSIG}", 115 | } 116 | ``` 117 | 118 | 119 | 120 | ### Validator App State 121 | #### Global State 122 | None 123 | #### Pool Account Local State 124 | * `a1: {ASSET1_ID}` 125 | * `a2: {ASSET2_ID}` 126 | * `o{ASSET1_ID}: {o}` // total outstanding unredeemed asset 1 amount 127 | * `o{ASSET2_ID}: {o}` // total outstanding unredeemed asset 2 amount 128 | 129 | #### Swapper Account Local State 130 | * `{POOL_ADDRESS}e{ASSET1_ID}: {e}` // excess asset 1 amount available for redemption 131 | * `{POOL_ADDRESS}e{ASSET2_ID}: {e}` // excess asset 2 amount available for redemption 132 | 133 | --------------------------------------------------------------------------------