diff --git a/docs/execution-flow-collection.png b/docs/execution-flow-collection.png
new file mode 100644
index 0000000..ae007af
Binary files /dev/null and b/docs/execution-flow-collection.png differ
diff --git a/docs/execution-flow-item.png b/docs/execution-flow-item.png
new file mode 100644
index 0000000..792a96b
Binary files /dev/null and b/docs/execution-flow-item.png differ
diff --git a/docs/execution-flow.drawio b/docs/execution-flow.drawio
new file mode 100644
index 0000000..4c523aa
--- /dev/null
+++ b/docs/execution-flow.drawio
@@ -0,0 +1 @@
+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
\ No newline at end of file
diff --git a/docs/storage-datas.png b/docs/storage-datas.png
new file mode 100644
index 0000000..2610751
Binary files /dev/null and b/docs/storage-datas.png differ
diff --git a/docs/storage-structs.png b/docs/storage-structs.png
new file mode 100644
index 0000000..131ec90
Binary files /dev/null and b/docs/storage-structs.png differ
diff --git a/docs/storage.drawio b/docs/storage.drawio
new file mode 100644
index 0000000..6c63e8b
--- /dev/null
+++ b/docs/storage.drawio
@@ -0,0 +1 @@
+7V1dl6I4E/41fTl7lPDl5UzPzO7ZM7277zv7NXvjiRKVGSQejKPur9+gxAYrTaSFENDui5YYaKinnqKqUkke0ONy92OCV4snGpDowRoEuwf0/sGyhsgZ8D9py/7Y4nv+sWGehEHW6bnhc/gvyRqz8+abMCDrQkdGacTCVbFxSuOYTFmhDScJ3Ra7zWhU/K8rPCeg4fMUR7D1rzBgi6zVdeznL34i4Xwh/vXQHR2/WWLRO3uU9QIHdJtrQh8e0GNCKTt+Wu4eSZRKTwjmeN7HF7493VlCYnbJCd5g88+Hx+3u56/k+yPZfvtpTp7eZFf5jqNN9sQhI8txgBnO7prthSzW23AZ4ZgfvZuFUfRII5ocvkHk8MPb1yyh30juG+SiEeI38A7erfjXJGFkl2vK7v5HQpeEJXveJfsWWcczMlUSct3mcHFGWeMih4kjlBBnyjA/XfpZXPxDJrEK0rOAjEjA1Sc7pAlb0DmNcfThufVdQjdxQNKrDvjRc59PlK5445A3fiWM7TMu4A2jvGnBllH2LdmF7O/c5y/ppX5wsqP3u+zKh4O9OIj54/6dP8idlR4+n3Y4EufNaMxyaPqD9PfQK3ibsos3x/SgEbzlY5hK73AeRLtM+9Z0k0xJST+UMR8nc1J2PffYLwWhVKESEmEWfi9yvHbtQIBbQF1YEuJ4ftCL7YIz7/MKHwSx5da0iHqEJyTi6hKQRACSSf4ljKphcDkNhWnOaIgcCQ0hCa2mOGi3ycFn3n3JfaPi4DPtvhRYZzQH3Qs56BnFQeduoY3SDt82Sj1cYKITMoOOzwKv0o8LssNcE7j0ViQJ+R2Q5Ln1N9FkqY35LNwR4eymx+vs40DiWg0Dh3gy12rkegi7TRp698zSQ4fLlrhbqClL78l9Ve76z8J5mbeq6U3oQfkgmYC8pgTkywUUcmu2A/IpWjmFwqYyCXlU9DYK51z/308oY3QJtXU2I+50KtPWwBtNBhUMj3clGBJlPemzFm0dATCmNIp4jBrSeIyDICHrtR5QAkz8mRQUd+qTyaxJUDzDUBFaUnDKXbxMRRtP1umfTRgzy3EBNvyRWRGAojyFO54TftaEM4AiMmMS3JZhELwYAhQ1oj3yyFBqzKcfwrTEOUopg24LIjWV9GJkyd82a4YZaeNtfMo1vvw6Hp6klheR3dgLeYiAIO7hR6XwQ6RMVPHHMIsrlAHIMHOSDAlAxH3nWES3MUm66iGcrEJn/TaRvOrxu6cJkPS+e6B3dk/6NWNX/QvtqrCQpthV7/7mNU1DBmZpCMyW9Dj3V9niW4Yl/4aju8nXQ2jBUyWhkVm5fAumdlJCo49GcXrmT4k8QzrxHdtp1ItzRo5RnLZgmkdk9Fn67DUkEa71dCVJBEcmo2FzNSYwzxLP2IsyaiQ+rO9FVL3qx7T40IJlHUGsKU5vFQfTUvkWTJ2cB+rTw2W6G6g3wRatgbrVTv2FmSHVqYBV6Vihuh2r7NTfaJi+Mk4Zc1TUFtspXuHoKmYnnWnC6S6uUA5YfXFO4CBMa3rf9mBIrhGDq5fLsP6CcVnH4zCe0Rp8tVdIyPdUzpreAgwL5hRivISjYQ3VXsysF2ov3InrVHEOOl98YcHqi4AucRjfABSm+WniwiVmnospjGEJV5fMe9dLLpC65OImUTLsJSzmwNzToFc64eo5J5dOOrHMqnhHMD/R33GNV0wiQ0Wfse0cKIJZDLw5FIlCyPSnP6Xew6lTweI1JyFYkJFJqLZasxMINaaJXUsipebSxAgG0xFes/GEn1UioHPH1Ul/oRU+/sgY6x5+KojaqV/UzkBnkIdgHMyFHHS3ZuukOp0N9xCMu2V63wwODvEDW4aDb01QJWpUx8G4WA/G3X0rnmuCLVpDCOHelGA0T/ASmrF+g2RYoGfDeJybtDHT9HapbyJXdSjOhyVat2q2BbCQTRqSxOa9pswFOOmlTKuzLXqQGxG+uTI3IgYK1SOZZq3IYbe6WEQdVcFey2PY9auIbdaEHBtmG3pdRVg9Hj/lxwzJoNkw87EMY2niw/QAsDoWI9NcJZgc4XZmzEJ9o+C1Oa6V0bAd09CAeZH+hXpNkEav36pOmvQgvGiCTVphErfTXeexK76jc2mhpHDWDPEdHVkNxTll21/x7+ohmLaX/HOgWO9MrMREEQcrmXjpsgoie2oKE++pINM0BBlmq2HdRX/LZKrXN6CBWUG+e5/+q4nQ9Vty+eST0bA4GfW8XuNoUBqbfCL+3Z3/Uv77hk0V9oYAmrvL1wz/rQtf6I5Z0//FfZsefF1baqgx+Prz6dF9Cr6M7J/Rm0+/rv7AH9zfJTtGiGrM2hbirS4j92woWlYk6EnsVS31mFIxQW2ckJjMwmmIk30HKgVLse9CoaD0AWBtfBiHLMTR2Pwho5oQabFkUPoE6ln8HaoYbJU1jZl89YqIXRpF0sYjrRi1uiJixypbyjQg7x9L+2UD3Hn3WH692hdEPJzKHxXvcx1WaYC8zl35LMx2zoxJNmTy8cLu/qC0u3tN7+GwvLtf2p1/OMritWmBMnTztSR4dxOOQZtVt9In6FftgjaQtL51YOVC5keP14ys9HDmVRU/9cBxbqEGbXNGsr1FCghnRAeqsOrB5Lw80QBQ1EsrpPU9/k1Zsguoo9WSSbaz6HQRlj4y6YVJsvriJsHS+fJ9s2z2wDjLpk7e3CBpLsFJa+WiB0mTLiGb0D2O2H7MJSDzo3UsN+GevQZan9zvQY2ON0vCLQyFOUhzDEw57F1I18ufAGYeA3JLeJg2v99TL9nJ7b1kexYz7b022ug19zCJdQfJuAn+HsxvBWTNwlijM1vjzqCV8TBulr/X/7VLGkFJK2uEv9hSHejDK8a5TiuSG1EHdvE2MH7WsWv7OvmtVgr2oFK4fg3xal/P/joNgam+/pb+Vo/JR2p3Vmvpr3+fy3MloS/l88V76Fpm8Vk+lae3S3ZUTkiO7NaK+dlmP/nfL5++/sv+/8+vxN2+mXpTSQnxlEYROVYRB5hhCNyLK5aSw49MrMhFIxRIxVp93N0qCNDxJUXYtojoal8WVypE+BJjdDNdcJtnblqsVBsuHzpUKXNjcaP0/mFyXZZtAaiYGTZqQqipmFF6+/D1sN5MtphbHDbWVdjVIl2GrmGEUZcTd2rQUBtKWkkDE/10G5Nk/I3se88YZNorRp3Q79YOadpg0koZmNB//W629UjIsosS0rqbrfQRYI69G5vZlgLehfF1uULBclFte9m+Kh6uCYYWh9XlOKgrRKeHy3TXujfAFa3G/ZQVv7GdbMvAzCcK5TmK2oeGslNb28hWrhvqFEAnN7LVZm31MhnmA167j21NAmpxG1u5hGCEbvg2tuVAd9c/g1G48dvY1gWFcT6aOgjv1Aap2iij17irZ43eJEqmvYPvy/nV4oHnx/RLPXClqz5sbTWv0vvuW41OPR5ji5vYytFqt+YuR2bzqzLLMrtqOktKdMqGb0xhczvJk/6Y+9r1o7VNl+S33WqJX8dWL9KjIO3tal963zfhD1SfcTxSjoTq9QccgEzH+NwVOrsX0llU05hCZ5jYysbKA01pxhbZ6tiGlZdY6tRW14cfmwBJa8bEgomt2SaKxh3NB1eFwxspK1k0c0Y9W7QHicYGYNLKGgH/PfBs2BER/oU6rjAr8kT3fSUMUxBkViJa3Pc98JRO9HDMCjzRfa6oJjpfmkdqb9u30vu+01k6TDw0LJGEJJVIV65cV4+kRq66sr6phevkkoIZlS4slFaOemcLtxDMqHRj4bq68DCtegup64K6tCaaNtrojdbVOZVbBMmw2i3hAHRu4bqa8Ghz4To5IOrZQx1auE4fSjWxhh8mlLL8lBL+pIsnGpC0x38=7V1bl5s4Ev41/Tg5SFyMH3s6k+Rhc3Z2s3uSPPnQRraZwdAHq9P2/voVbcCGks3FSEiYTh4MxtjUV1Wqq+rBfNruPyfey+Zr7JPwARv+/sH8+IAxsjB+SP8b/uF4xp2ZxxPrJPCzi04nvgX/I9lJIzv7GvhkV7qQxnFIg5fyyWUcRWRJS+e8JInfypet4rD8rS/emoAT35ZeCM9+D3y6yc46tnV64wsJ1pv8q5EzP76z9fKrs0fZbTw/fjs7Zf7xYD4lcUyPr7b7JxKm1MsJc/zcpwvvFr8sIRFt8gFG++TRtZzVLzv82wy+f/3Xv3/8lqHxywtfsyem8d8kWgTRKs5+Nj3kxNi9BdvQi9jR7/DLs9/ziySU7M9OZT/mM4m3hCYHdkn2rpnRJeMMKzt8O5HZzM9tzig8y855GbLr4sanZ2cvssdvQQoLkCLytgQQIYlfI5+kNzIYHd42ASXfXrxl+u4bkwF2bkO37Is/IvYypUbAuOkxDNYRO/ccUxpv2RurIAyf4jBO3m9qrlYrvFyy8zuaMPqfveM7z47tcCl+FdDGMKAyDCaEATkcGExRMNgABj/eekE0fiBmiiGBMIDiATveNiVt9Lx7OVIpiNYAGvbEtEz/Mjmj+F2JnNM+O+Vl+IRkRTmwbQPfT7+EC3eZIYaTFx5IWBhIUINPIDWRJbkozQFKjDTbBTNbVgGEpt+F1rbKxEB46KU2/wFVcgSMNfayFD1xLij62fzZaMGcBbbaLrkFS53gWMZhyOzpII4Wnu8nZLeTA4vvEXfFhcVZuuR5JRQW1Rbg3Hm6ottfg4hiZpZorNxFyI9U5Y7rl+BUhu4MI8VWYOxeXIFp+uxSfV1sQGrYvBUY2cLoAS2SaEUvkkOIsifIt8mMp+znzsz0WjhbBbrarsEmNIn8SNKiOygOqi26JjSGqtp8+X4bfbW5CGmR607NEKA/8dfkW3YYJ3QTr+PIC/84na3Q6nTNP+L4JYPsL0LpIYtCe680LgPKqJocfmSffz/4mR58sPPDj/vzNz8esqMVU6pnQuUa6b/3q/zHNEZ9YgV25lOQ0qILnNRL1uTahU4WW0tJdRX1hIQeDX6VY+A8FLOP/hkH6ZpRrHbV0K5dvsMufk2WJPtQhRWKX3GDBNebzX6Q5goeR2A9C9G4UoXZhnB1zUH0RSF3VqLQDFJIbrzEhh6GjrmJAmltrTQbJon0zE50gEI1Q82GmaLxRb5FiIxc7e5MKGmwCCNnBgCQYVHvA/rjZEOzo59n75zM6fQgt6ZPVvjJ8P5ZsrsHtcKPxm0DK7zWXM+5qj9z/SYWcaC+TcgKGmgb7yV9uSF7jzEDI+ALSQL2E0hyOvtnfgrXL46rYE/yyqB3LZC9NNQKZJhm2WjkSDTPZhS2PM6g4k0DfEl88EJ6WDAKeFsYXxIa9uQlHm1DpiE9m0GivG5J4lHGKJJMuL7yjgXA2lrTMxiT98k94aGaST2DSYGqsRZEFGkdMBEhNlJNNRdmCiaQlDOoXZhG8MmOBpGXFlXI0W39FVO0x6MaAx5ct7kwzDi2LL0QlORKDQx0ht6OLp7ZpwSbqm6ZEpxydLmmqgtDjYwKPkl0LchytS9Nd6EHzGNMMTjYxPUtHg4ufjadFq5tBxxUs1Pd+qCi7rpchLTIVeXQ1a5itOaGIMYNkmpmKnTBmUpbUEmrS3/ed3so1LNQ673vtGAhL2+4F5FRzUadQ//bez2Wyu+oR2FWvl9D1Z3XGqoO5tBDXCkpMqBd1MRqr+oCO/0HEzXHP64F+v7XnPcK5LquqZxCELlOATKg5aO3V3BiHm3dAmRAU0dDv6ALEqo5BsiA9szYPAMhEiO32MCot3S09w3EiJNcnBA0dfT0DjqAoZx7gFB9Z4L+/oEYpCTXUrkAgamWqg0T5IZ6bS0VMrIoeW0xVcFXilRTFbp+GCZBZyxyYpg6JimxyIfZwH0vArhkbqrFJAZMAqU1d+YnwDtDlt2t3CXhr9PPrm3ZHSBtrvyxVav8pZbdzeEivQ0ibihEcX+wAxZzxUymOQZgME2zoIG83pm+LNj2aFi2amjU75OgvdcnRGjkhrfhkjM+L0OINMl1MjCMBGdpCGgbiCzqNjlBcVREnEq0EOcac1p+b8jJ1BC8x+yB7EyN2a2+Snqm5gSovqkac2wFXCfu0TdVY46ihKsLEsqlaszxF3EJkRjJ24veQRmXGHGSjNNYCrk6gKFeqsa8h1IuMUhJTtXANqkpVdOOCXIero3C53HY+lyNqVgYPh9aMeVqlGITSzE2yX/5vWRrOnjpqqVrkDWafE0XNFRL2CALBvJ0zdh0wEO5lE2hYsft/okQHLl2rHUPWRsxEiXZ4dC+7GdoSzLPwzSwJJvutIXsuVqWpGMNySVjcEtFcEmuflThEuuedmTrkH81DdWcjYs5+06ToPqik1OJyvFypzMOoZBASnFaqkhEVsEy8JKDthlUS/s925DFGaQSBTTwwoWujnMHVJTLplr1GQfts6nSpEec+Z+7I6P2p4XIk+QBI4Ma4KOI+FuNLXA7n+xd76eJmWDCntc7nF3wko4m2Z3duTLgxK7olcwQ/9Tw8ozZL13u3HL1vPRT2Ivjs51EoYcBKwVg50Fzb38na7961QacCSrToqJgsQFnjEtmMi92lLzIkZ0eExztIalssugOLzowbp5CwuRC27RTe1SqSVkFYKmf7ZKmM2Cf9bg1Wr34SFZo9SW8I8g6CREoyUDBcJr/mkjcinpQ/WYZyum3+m2K7lJwGiAlOV1rAwSmRJygRFyehG1Q+JfdU5VEnA2Dr1MirrRnpV0S68ETcc4w84NzsW4R3usqnjsmRbRyBQQ4J0OdbOI8bi16dPC8ugBURgcfdYOw0cHIgSvuxBhXGQPLYYyKRW0h2YwxaAuAjoxhyWGMSoGfJVtjzGCogq3ZWyl7QVcCnA7iODbFRecrqyWu7d0dtKNqDAWOOUfVW8y5e1RvMbuK7X7HGWIWv0Ua740w038ba87IstGVkIiASWpEAhvDWqgjiEi00K+NS4Pz2R2K6FecN6BPq7BSXOIqxSWIM9BsxHGr9rq/WlwzdNwKm9PO0vLE2m0o1thQrA+dM9Rr3H3o7SXbVi0izRn+9R5GWDJ5IFno4sbGkJttX15fCI9KAjcw5Azfilb0IpWEeI09LkkF6vp6jZwNr/1Ikv8+LBLK9YBwtruuOvDL9/to7MCLkBi5JQXzYTIJirpZOZ4NrCzUt5XFTyhUy4atSkLhaDmKSyg02IDbD5aMVI9piRC2Ybx95PKsWq/QHHrRlFE7WgTRKu7DcOtAo3z68mXLzeRZbuLSQnNYKhp58qqrV/hC9aHz7Nht7IS5/nH+OacYNN56gbRS0CHBUM9oqy8FZYQKIrg3gE5qXoTYSNby9Q3W94mTYssxzivIphjprTZ5fZFPbpPXpz7mahUgFL/8PlIf7S1I0yxbkEMHSIsg+63zbgTFRrmWhOQxOJgzLrDvMTgnHPQdg4MN6LEpOQbnBKi2Y3CwAR0/vcfgYBHz1+U6HdjgbK+l3+YNXZBQzf/DRr3/p3vVnRCJkexU1Ht/2m+xIUac5OKU8432Y3A6gKHcxjQY1VcU69/mLAYpyWIDEZhiJu2YIN8wrjZmgpoOoy8YS5WYCZrG4KjIJmimGJvAKMSoyw87eOmqjcHBCEZENN3Ntwsaqo3BYV8H4NB1P7IOeCg3BqdQseN2/0QIjmQ79h72VRIjUXKBsgdtZBlBf1oLSzLf/K2BJdn7ng03cglP7VYFNwm8aH1Rws7QD71nEjK28UlSleoLWIkTyGp804YrnC1VcU79orfKY+4c18ojblo0gU3FPLuc/6YwkUpcYiimtfE9ldZ0qIdQbSwVNqch6NLEun+Vzu9imaNyg+vM+GCc/WGzfEPBu2QVjz3pBD12yMT2ZBBK0wl5C1f9Up/rAVWWeu4G8Co6aLcWLUp00H6a3z4/Pn0P97//5+d//0m//PhiWb9x4vO9T47sQKXOkyP7qO3kE4pTbqjV4Mjr8OtQcsh/AmgA6DM2si9MBiw+5D/CqGoPh5Uccap/XKWH8mRJKkrDhsY0q425ygPn9jKf1JzIGP/CUUyLzLbKbTj/sd3VCF2/3L16+cOt0yWvwqvVcMmetNqQJbx8MPC0+ChVv8tHSdvBkj0BUtVTxuByo/NcyZ5AqRY5KoDKuMZKyhMeudpsZFMlJYqTXJy0HCrZExrVSYUKKLeR1T5KBEpu7WMux5U9bJP44IX0sGA04BnUUja1cCprwfDbB1jQG4xet4SpmRjGJdXRMjXI6xDGv/AI0CH0yV0hotwOAhb0/qqKn+l9pPU+oUJER7Lar9/X9S5hUm0LAQs6iT7Z0SCSaNn2uFtNe0TU20fAqvcINcpTSsRJsuQMO53moUMOrNgaXYmasebTabDVtKlHtaFT2Bq0snAE1cYiuKRQP8pwyT0VDHfw1Of15q3kgmEY1YDrsdRS0NbjlKvD52dDd+XZvP2T9Fhh+9OBTSeOI6Pxjil278qOXz5RZaiGs8nBfRAyr99I8EwSbMOoj27CXcmeWMbgws0L20zCfUG4c6+8gXBjOcJdZSjUUbjBvKHqjYQLNy8wpZdwV7a3sIZfuXmVBpNwXxJuo7Fw997rfKEIs8JQXVfuanEFuFF/ws3PlwI2vKVtqL1cVh4fc5KvkhuEoCUzhv6gtsCo1h4EbSHdu4NaI6Jac1B9ukqjmPugUiOsAqu+nFH/4mwBciQVo0H31hhFXxC0IrnXcULd/PuJmSM7dQUJ7QqC6QBdm4LaKjTleoLq60zvb9VRrSMI9gxr3RDUFg7l+oE4c0l07wdqi4l67UANx5Po3Q4kQHTkbn8AAzYjbGsQIUxyYYIRHG2bgdqCoV4vEGcExyQ0MluB2GESx/TcB2JPuvka+yS94v8=
\ No newline at end of file
diff --git a/func/common.fc b/func/common.fc
index 98947f9..ddf0570 100644
--- a/func/common.fc
+++ b/func/common.fc
@@ -1,65 +1,91 @@
-const int one_ton = 1000000000;
+const int tons = 1000000000;
+const int days = 60 * 60 * 24;
+
const int dns_next_resolver_prefix = 0xba93; ;; dns_next_resolver prefix - https://github.com/ton-blockchain/ton/blob/7e3df93ca2ab336716a230fceb1726d81bac0a06/crypto/block/block.tlb#L819
-const int op::fill_up = 0x370fec51;
-const int op::outbid_notification = 0x557cea20;
-const int op::change_dns_record = 0x4eb1f0f9;
-const int op::dns_balance_release = 0x4ed14b65;
+const int op::simple_transfer_0 = 0;
+
+const int op::fill_up = 0x370fec51;
+const int op::outbid_notification = 0x557cea20;
+const int op::change_dns_record = 0x4eb1f0f9;
+const int op::dns_balance_release = 0x4ed14b65; ;; CONSTANT NOT USED
-const int op::telemint_msg_deploy = 0x4637289a;
-const int op::teleitem_msg_deploy = 0x299a3e15;
-const int op::teleitem_start_auction = 0x487a8e81;
+const int op::telemint_msg_deploy = 0x4637289a;
+const int op::teleitem_msg_deploy = 0x299a3e15;
+const int op::teleitem_start_auction = 0x487a8e81;
const int op::teleitem_cancel_auction = 0x371638ae;
-const int op::teleitem_bid_info = 0x38127de1;
-const int op::teleitem_return_bid = 0xa43227e1;
-const int op::teleitem_ok = 0xa37a0983;
-
-const int op::nft_cmd_transfer = 0x5fcc3d14;
-const int op::nft_cmd_get_static_data = 0x2fcb26a2;
-const int op::nft_cmd_edit_content = 0x1a0b9d51;
-const int op::nft_answer_ownership_assigned = 0x05138d91;
-const int op::nft_answer_excesses = 0xd53276db;
-
-const int op::ownership_assigned = 0x05138d91;
-const int op::excesses = 0xd53276db;
-const int op::get_static_data = 0x2fcb26a2;
-const int op::report_static_data = 0x8b771735;
-const int op::get_royalty_params = 0x693d3950;
+const int op::teleitem_bid_info = 0x38127de1;
+const int op::teleitem_return_bid = 0xa43227e1;
+const int op::teleitem_ok = 0xa37a0983;
+
+const int op::nft_cmd_transfer = 0x5fcc3d14;
+const int op::nft_cmd_get_static_data = 0x2fcb26a2;
+const int op::nft_cmd_edit_content = 0x1a0b9d51; ;; CONSTANT NOT USED
+const int op::nft_answer_ownership_assigned = 0x05138d91; ;; CONSTANT NOT USED
+const int op::nft_answer_excesses = 0xd53276db; ;; CONSTANT NOT USED
+
+const int op::ownership_assigned = 0x05138d91;
+const int op::excesses = 0xd53276db;
+const int op::get_static_data = 0x2fcb26a2;
+const int op::report_static_data = 0x8b771735;
+const int op::get_royalty_params = 0x693d3950;
const int op::report_royalty_params = 0xa8cb00ad;
-const int err::invalid_length = 201;
-const int err::invalid_signature = 202;
-const int err::wrong_subwallet_id = 203;
-const int err::not_yet_valid_signature = 204;
-const int err::expired_signature = 205;
-const int err::not_enough_funds = 206;
-const int err::wrong_topup_comment = 207;
-const int err::unknown_op = 208;
-const int err::uninited = 210;
-const int err::too_small_stake = 211;
+const int err::invalid_string_length = 70;
+
+const int err::invalid_length = 201;
+const int err::invalid_signature = 202;
+const int err::wrong_subwallet_id = 203;
+const int err::not_yet_valid_signature = 204;
+const int err::expired_signature = 205;
+const int err::not_enough_funds = 206;
+const int err::wrong_topup_comment = 207;
+const int err::unknown_op = 208;
+const int err::uninited = 210;
+const int err::too_small_stake = 211;
const int err::expected_onchain_content = 212;
-const int err::forbidden_not_deploy = 213;
-const int err::forbidden_not_stake = 214;
-const int err::forbidden_topup = 215;
-const int err::forbidden_transfer = 216;
-const int err::forbidden_change_dns = 217;
-const int err::forbidden_touch = 218;
-const int err::no_auction = 219;
-const int err::forbidden_auction = 220;
-const int err::already_has_stakes = 221;
-const int err::auction_already_started = 222;
-const int err::invalid_auction_config = 223;
+const int err::forbidden_not_deploy = 213;
+const int err::forbidden_not_stake = 214;
+const int err::forbidden_topup = 215;
+const int err::forbidden_transfer = 216;
+const int err::forbidden_change_dns = 217;
+const int err::forbidden_touch = 218;
+const int err::no_auction = 219;
+const int err::forbidden_auction = 220;
+const int err::already_has_stakes = 221;
+const int err::auction_already_started = 222;
+const int err::invalid_auction_config = 223;
+
+const int err::invalid_workchain = 333;
+
+const int err::doesnt_start_with_zero_byte = 413;
+
+const slice comm::topup = "#topup";
+
+const int cat::dns_next_resolver = "dns_next_resolver"H;
+
+;; MF category: Message Flags
+const int mf::paying_fees = 1;
+const int mf::ignore_errors = 2;
+;; const int mf::self_destruct = 32;
+const int mf::add_inbound_value = 64;
+;; const int mf::add_remaining_balance = 128;
+
+const int need_to_save_item_data = -1;
+
+;; Warning: some constants exist here (and even in TLB) and are not used in code!
+;; This may be a dire mistake or yet missing logic that is to be implemented.
int mod(int x, int y) asm "MOD";
int equal_slices(slice a, slice b) asm "SDEQ";
int builder_null?(builder b) asm "ISNULL";
builder store_builder(builder to, builder from) asm "STBR";
-int workchain() asm "0 PUSHINT";
+const workchain = 0;
() force_chain(slice addr) impure inline {
(int wc, _) = parse_std_addr(addr);
- throw_unless(333, wc == workchain());
+ throw_unless(err::invalid_workchain, wc == workchain);
}
slice zero_address() asm "b{00} PUSHSLICE";
@@ -72,21 +98,25 @@ int get_top_domain_bits(slice domain) inline {
i += 8;
char = domain~load_uint(8); ;; we do not check domain.length because it MUST contains \0 character
}
- throw_unless(201, i); ;; should not start with \0
+ throw_unless(err::invalid_length, i); ;; should not start with \0
return i;
}
-_ load_text(slice cs) inline {
+;; ( cs) -> ( cs, text)
+(slice, slice) load_text(slice cs) inline {
int len = cs~load_uint(8);
slice text = cs~load_bits(len * 8);
return (cs, text);
}
-_ load_text_ref(slice cs) inline {
+
+;; ( cs) -> ( cs, text)
+(slice, slice) load_text_ref(slice cs) inline {
slice text_cs = cs~load_ref().begin_parse();
slice text = text_cs~load_text();
return (cs, text);
}
+;; ( b, text) -> ( b')
builder store_text(builder b, slice text) inline {
(int len, int rem) = slice_bits(text) /% 8;
throw_if(err::invalid_length, rem);
@@ -94,6 +124,7 @@ builder store_text(builder b, slice text) inline {
.store_slice(text);
}
+;; -> ( name, domain)
(slice, slice) unpack_token_info(cell c) inline {
slice cs = c.begin_parse();
var res = (
@@ -104,6 +135,7 @@ builder store_text(builder b, slice text) inline {
return res;
}
+;; ( name, domain) ->
cell pack_token_info(slice name, slice domain) {
return begin_cell()
.store_text(name)
@@ -121,6 +153,24 @@ cell pack_state_init(cell code, cell data) inline {
}
cell pack_init_int_message(slice dest, cell state_init, cell body) inline {
+ ;; 0x18 = [ 01 1000]
+ ;; 0 | int_msg_info$0
+ ;; 1 | ihr_disabled:Bool = True
+ ;; 1 | bounce:Bool = True
+ ;; 0 | bounced:Bool = False
+ ;; 00 | src:MsgAddress = addr_none$00
+ ;; dest:MsgAddressInt <- dest
+ ;; value:CurrencyCollection
+ ;; grams:Grams <- 0
+ ;; other:ExtraCurrencyCollection <- 1 zero bit
+ ;; ihr_fee:Grams <- 0 (4 zero bits)
+ ;; fwd_fee:Grams <- 0 (4 zero bits)
+ ;; created_lt:uint64 <- 0 (64 zero bits)
+ ;; created_at:uint32 <- 0 (32 zero bits)
+ ;; init:(Maybe (Either StateInit ^StateInit))
+ ;; 1 ^ yes 1 ^ ref <- 2 one bits
+ ;; body:(Either X ^X)
+ ;; 1^ ref <- 1 one bit
return begin_cell()
.store_uint(0x18, 6) ;; 011000 tag=0, ihr_disabled=1, allow_bounces=1, bounced=0, add_none
.store_slice(dest)
@@ -132,6 +182,24 @@ cell pack_init_int_message(slice dest, cell state_init, cell body) inline {
}
() send_msg(slice to_address, int amount, int op, int query_id, builder payload, int mode) impure inline {
+ ;; 0x10 = [ 01 0000]
+ ;; 0 | int_msg_info$0
+ ;; 1 | ihr_disabled:Bool = True
+ ;; 0 | bounce:Bool = False
+ ;; 0 | bounced:Bool = False
+ ;; 00 | src:MsgAddress = addr_none$00
+ ;; dest:MsgAddressInt <- to_address
+ ;; value:CurrencyCollection
+ ;; grams:Grams <- amount
+ ;; other:ExtraCurrencyCollection <- 1 zero bit
+ ;; ihr_fee:Grams <- 0 (4 zero bits)
+ ;; fwd_fee:Grams <- 0 (4 zero bits)
+ ;; created_lt:uint64 <- 0 (64 zero bits)
+ ;; created_at:uint32 <- 0 (32 zero bits)
+ ;; init:(Maybe (Either StateInit ^StateInit))
+ ;; 0 ^ no <- 1 zero bit
+ ;; body:(Either X ^X)
+ ;; 0^embed <- 1 zero bit
var msg = begin_cell()
.store_uint(0x10, 6) ;; nobounce - int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool src:MsgAddress -> 010000
.store_slice(to_address)
@@ -149,14 +217,15 @@ cell pack_init_int_message(slice dest, cell state_init, cell body) inline {
slice calculate_address(int wc, cell state_init) inline {
slice res = begin_cell()
- .store_uint(4, 3)
- .store_int(wc, 8)
- .store_uint(cell_hash(state_init), 256)
- .end_cell()
+ .store_uint(4, 3) ;; addr_std$10 anycast:(Maybe Anycast) nothing$0
+ .store_int(wc, 8) ;; workchain_id:int8
+ .store_uint(cell_hash(state_init), 256) ;; address:uint256
+ .end_cell() ;; = MsgAddressInt
.begin_parse();
return res;
}
+;; -> ( item_index, collection_address)
(int, slice) unpack_item_config(cell c) inline {
slice cs = c.begin_parse();
var res = (
@@ -167,6 +236,7 @@ slice calculate_address(int wc, cell state_init) inline {
return res;
}
+;; ( item_index, collection_address) ->
cell pack_item_config(int item_index, slice collection_address) inline {
return begin_cell()
.store_uint(item_index, 256)
@@ -174,6 +244,7 @@ cell pack_item_config(int item_index, slice collection_address) inline {
.end_cell();
}
+;; -> ( config, state)
(cell, cell) unpack_item_data() inline {
var cs = get_data().begin_parse();
var res = (cs~load_ref(), cs~load_dict());
@@ -181,6 +252,10 @@ cell pack_item_config(int item_index, slice collection_address) inline {
return res;
}
+;; nft_royalty_params#_ numerator:uint16 denominator:uint16
+;; The items should be uint16! They were int16, that would cause slight loss in precision and large loss in logic.
+
+;; ( numerator, denominator, destination) ->
cell pack_nft_royalty_params(int numerator, int denominator, slice destination) inline {
return begin_cell()
.store_uint(numerator, 16)
@@ -189,6 +264,7 @@ cell pack_nft_royalty_params(int numerator, int denominator, slice destination)
.end_cell();
}
+;; -> ( numerator, denumerator, destination)
(int, int, slice) unpack_nft_royalty_params(cell c) inline {
var cs = c.begin_parse();
var res = (
@@ -200,6 +276,7 @@ cell pack_nft_royalty_params(int numerator, int denominator, slice destination)
return res;
}
+;; ( config, state) ->
cell pack_item_data(cell config, cell state) inline {
return begin_cell()
.store_ref(config)
@@ -207,6 +284,7 @@ cell pack_item_data(cell config, cell state) inline {
.end_cell();
}
+;; ( nft_content, dns, token_info) ->
cell pack_item_content(cell nft_content, cell dns, cell token_info) inline {
return begin_cell()
.store_ref(nft_content)
@@ -215,6 +293,7 @@ cell pack_item_content(cell nft_content, cell dns, cell token_info) inline {
.end_cell();
}
+;; -> ( nft_content, dns, token_info)
(cell, cell, cell) unpack_item_content(cell c) inline {
var cs = c.begin_parse();
var res = (
@@ -226,6 +305,7 @@ cell pack_item_content(cell nft_content, cell dns, cell token_info) inline {
return res;
}
+;; -> ( owner_address, content, auction, royalty_params)
(slice, cell, cell, cell) unpack_item_state(cell c) inline {
var cs = c.begin_parse();
var res = (
@@ -238,6 +318,7 @@ cell pack_item_content(cell nft_content, cell dns, cell token_info) inline {
return res;
}
+;; ( owner_address, content, auction, royalty_params) ->
cell pack_item_state(slice owner_address, cell content, cell auction, cell royalty_params) inline {
return begin_cell()
.store_slice(owner_address)
@@ -247,7 +328,8 @@ cell pack_item_state(slice owner_address, cell content, cell auction, cell royal
.end_cell();
}
-_ save_item_data(config, state) impure inline {
+;; ( config, state) -> ()
+() save_item_data(config, state) impure inline {
set_data(pack_item_data(config, state));
}
@@ -257,6 +339,7 @@ cell pack_item_state_init(int item_index, cell item_code) inline {
return pack_state_init(item_code, item_data);
}
+;; ( sender_address, bid, info, content, auction_config, royalty_params) ->
cell pack_teleitem_msg_deploy(slice sender_address, int bid, cell info, cell content, cell auction_config, cell royalty_params) inline {
return begin_cell()
.store_uint(op::teleitem_msg_deploy, 32)
@@ -269,6 +352,7 @@ cell pack_teleitem_msg_deploy(slice sender_address, int bid, cell info, cell con
.end_cell();
}
+;; -> ( sender_address, bid, info, content, auction_config, royalty_params)
(slice, int, cell, cell, cell, cell) unpack_teleitem_msg_deploy(slice cs) inline {
return (cs~load_msg_addr(),
cs~load_grams(),
@@ -278,6 +362,7 @@ cell pack_teleitem_msg_deploy(slice sender_address, int bid, cell info, cell con
cs~load_ref());
}
+;; -> ( touched, subwallet_id, owner_key, content, item_code, full_domain, royalty_params)
(int, int, int, cell, cell, slice, cell) unpack_collection_data() inline {
var cs = get_data().begin_parse();
var res = (
@@ -293,7 +378,8 @@ cell pack_teleitem_msg_deploy(slice sender_address, int bid, cell info, cell con
return res;
}
-_ save_collection_data(int touched, int subwallet_id, int owner_key, cell content, cell item_code, slice full_domain, cell royalty_params) impure inline {
+;; ( touched, subwallet_id, owner_key, content, item_code, full_domain, royalty_params) ->
+() save_collection_data(int touched, int subwallet_id, int owner_key, cell content, cell item_code, slice full_domain, cell royalty_params) impure inline {
cell data = begin_cell()
.store_int(touched, 1)
.store_uint(subwallet_id, 32)
@@ -306,7 +392,8 @@ _ save_collection_data(int touched, int subwallet_id, int owner_key, cell conten
set_data(data);
}
-_ unpack_signed_cmd(slice cs) inline {
+;; -> ( signature, *slice_hash, subwallet_id, valid_since, valid_till, cmd)
+(slice, int, int, int, int, slice) unpack_signed_cmd(slice cs) inline {
return (
cs~load_bits(512), ;; signature
slice_hash(cs), ;; hash
@@ -317,7 +404,8 @@ _ unpack_signed_cmd(slice cs) inline {
);
}
-_ unpack_deploy_msg(slice cs) inline {
+;; -> ( token_name, content, auction_config, royalty)
+(slice, cell, cell, cell) unpack_deploy_msg(slice cs) inline {
var res = (
cs~load_text(), ;; token_name
cs~load_ref(), ;; content
@@ -329,6 +417,8 @@ _ unpack_deploy_msg(slice cs) inline {
}
;;teleitem_last_bid bidder_address:MsgAddressInt bid:Grams bid_ts:uint32 = TeleitemLastBid;
+
+;; -> ( bidder_address, bid, bid_ts)
(slice, int, int) unpack_last_bid(cell c) inline {
slice cs = c.begin_parse();
var res = (
@@ -339,6 +429,8 @@ _ unpack_deploy_msg(slice cs) inline {
cs.end_parse();
return res;
}
+
+;; ( bidder_address, bid, bid_ts) ->
cell pack_last_bid(slice bidder_address, int bid, int bid_ts) inline {
return begin_cell()
.store_slice(bidder_address)
@@ -348,6 +440,8 @@ cell pack_last_bid(slice bidder_address, int bid, int bid_ts) inline {
}
;;teleitem_auction_state$_ last_bid:(Maybe ^TeleitemLastBid) min_bid:Grams end_time:uint32 = TeleitemAuctionState;
+
+;; -> ( last_bid, min_bid, end_time)
(cell, int, int) unpack_auction_state(cell c) inline {
slice cs = c.begin_parse();
var res = (
@@ -358,6 +452,8 @@ cell pack_last_bid(slice bidder_address, int bid, int bid_ts) inline {
cs.end_parse();
return res;
}
+
+;; ( last_bid, min_bid, end_time) ->
cell pack_auction_state(cell last_bid, int min_bid, int end_time) inline {
return begin_cell()
.store_maybe_ref(last_bid)
@@ -366,6 +462,8 @@ cell pack_auction_state(cell last_bid, int min_bid, int end_time) inline {
.end_cell();
}
+;; -> ( beneficiary_address, initial_min_bid, max_bid,
+;; min_bid_step, min_extend_time, duration)
(slice, int, int, int, int, int) unpack_auction_config(cell c) inline {
slice cs = c.begin_parse();
var res = (
@@ -381,6 +479,8 @@ cell pack_auction_state(cell last_bid, int min_bid, int end_time) inline {
}
;;teleitem_auction$_ state:^TeleitemAuctionState config:^TeleitemConfig = TeleitemAuction;
+
+;; -> ( state, config)
(cell, cell) unpack_auction(cell c) inline {
slice cs = c.begin_parse();
var res = (
@@ -391,6 +491,7 @@ cell pack_auction_state(cell last_bid, int min_bid, int end_time) inline {
return res;
}
+;; ( state, config) ->
cell pack_auction(cell state, cell config) inline {
if (null?(state)) {
return null();
@@ -401,6 +502,7 @@ cell pack_auction(cell state, cell config) inline {
.end_cell();
}
+;; -> ( query_id, new_owner_address, response_destination, custom_payload, forward_amount, forward_payload)
(int, slice, slice, cell, int, slice) unpack_nft_cmd_transfer(slice cs) inline {
return (
cs~load_uint(64),
diff --git a/func/nft-collection.fc b/func/nft-collection.fc
index 5e80f32..8b0a824 100644
--- a/func/nft-collection.fc
+++ b/func/nft-collection.fc
@@ -1,13 +1,17 @@
-_ unwrap_signed_cmd(slice signed_cmd, int public_key, int subwallet_id, int msg_value) {
+#include "stdlib.fc";
+#include "common.fc";
+
+(slice) unwrap_signed_cmd(slice signed_cmd, int public_key, int subwallet_id, int msg_value) {
(slice signature, int hash, int got_subwallet_id, int valid_since, int valid_till, slice cmd)
= unpack_signed_cmd(signed_cmd);
throw_unless(err::invalid_signature, check_signature(hash, signature, public_key));
throw_unless(err::wrong_subwallet_id, subwallet_id == got_subwallet_id);
int ts = now();
- throw_unless(err::not_yet_valid_signature, valid_since < ts);
- throw_unless(err::expired_signature, ts < valid_till);
+ ;; In theory comparisons should be not so strict because variables are valid since and valid till, not valid after and before
+ throw_unless(err::not_yet_valid_signature, valid_since <= ts);
+ throw_unless(err::expired_signature, ts <= valid_till);
return cmd;
-
+ ;; SAFE: considering cmd containing valid data since it is signed by remote server that is out of scope of this research
}
() deploy_item(slice sender_address, int bid, cell item_code, slice cmd, slice full_domain, cell default_royalty_params) impure {
@@ -16,14 +20,14 @@ _ unwrap_signed_cmd(slice signed_cmd, int public_key, int subwallet_id, int msg_
throw_unless(err::not_enough_funds, bid >= initial_min_bid);
int item_index = string_hash(token_name);
cell state_init = pack_item_state_init(item_index, item_code);
- slice item_address = calculate_address(workchain(), state_init);
+ slice item_address = calculate_address(workchain, state_init);
if (null?(royalty)) {
royalty = default_royalty_params;
}
cell token_info = pack_token_info(token_name, full_domain);
cell deploy_msg = pack_teleitem_msg_deploy(sender_address, bid, token_info, content, auction_config, royalty);
cell msg = pack_init_int_message(item_address, state_init, deploy_msg);
- send_raw_message(msg, 64); ;; carry all the remaining value of the inbound message, fee deducted from amount
+ send_raw_message(msg, mf::add_inbound_value); ;; carry all the remaining value of the inbound message, fee deducted from amount
}
() recv_internal(int msg_value, cell in_msg_full, slice in_msg_body) impure {
@@ -37,10 +41,10 @@ _ unwrap_signed_cmd(slice signed_cmd, int public_key, int subwallet_id, int msg_
int op = in_msg_body.slice_empty?() ? 0 : in_msg_body~load_uint(32);
- if (op == 0) { ;; regular money transfer
+ if (op == op::simple_transfer_0) { ;; regular money transfer
;; NB: it is not possible to recover any money transferred to this account
;; so we return back all transfers except ones with comment #topup in it
- throw_unless(err::wrong_topup_comment, equal_slices(in_msg_body, "#topup") & (in_msg_body.slice_refs() == 0));
+ throw_unless(err::wrong_topup_comment, equal_slices(in_msg_body, comm::topup) & (in_msg_body.slice_refs() == 0));
return ();
}
@@ -60,33 +64,41 @@ _ unwrap_signed_cmd(slice signed_cmd, int public_key, int subwallet_id, int msg_
;; Get methods
+;; -> ( success, content, ???)
(int, cell, slice) get_collection_data() method_id {
var (_, _, _, content, _, _, _) = unpack_collection_data();
return (-1, content, zero_address());
}
+;; -> ( full_domain)
slice get_full_domain() method_id {
var (_, _, _, _, _, full_domain, _) = unpack_collection_data();
return full_domain;
}
+;; ( index) -> ( nft_address)
slice get_nft_address_by_index(int index) method_id {
var (_, _, _, _, item_code, _, _) = unpack_collection_data();
cell state_init = pack_item_state_init(index, item_code);
- return calculate_address(workchain(), state_init);
+ return calculate_address(workchain, state_init);
}
+
+;; -> ( numerator, denumerator, destination)
(int, int, slice) royalty_params() method_id {
var (_, _, _, _, _, _, royalty_params) = unpack_collection_data();
(int numerator, int denominator, slice destination) = unpack_nft_royalty_params(royalty_params);
return (numerator, denominator, destination);
}
+;; ( index, individual_nft_content) -> ( individual_nft_content)
+;; This functions looks strange to say the last. Some mistake or inheritable loopback.
cell get_nft_content(int index, cell individual_nft_content) method_id {
return individual_nft_content;
}
+;; ( subdomain, category) -> ( prefix_len, result)
(int, cell) dnsresolve(slice subdomain, int category) method_id {
- throw_unless(70, mod(slice_bits(subdomain), 8) == 0);
+ throw_unless(err::invalid_string_length, mod(slice_bits(subdomain), 8) == 0);
int starts_with_zero_byte = subdomain.preload_int(8) == 0;
diff --git a/func/nft-item.fc b/func/nft-item.fc
index cf8b1c9..b7e1d16 100644
--- a/func/nft-item.fc
+++ b/func/nft-item.fc
@@ -1,9 +1,12 @@
-int min_tons_for_storage() asm "1000000000 PUSHINT"; ;; 1 TON
+#include "stdlib.fc";
+#include "common.fc";
+
+const int min_tons_for_storage = 1 * tons;
int send_money(int my_balance, slice address, int value) impure {
- int amount_to_send = min(my_balance - min_tons_for_storage(), value);
+ int amount_to_send = min(my_balance - min_tons_for_storage, value);
if (amount_to_send > 0) {
- send_msg(address, amount_to_send, op::fill_up, cur_lt(), null(), 2); ;; ignore errors
+ send_msg(address, amount_to_send, op::fill_up, cur_lt(), null(), mf::ignore_errors);
my_balance -= amount_to_send;
}
return my_balance;
@@ -35,7 +38,7 @@ int send_money(int my_balance, slice address, int value) impure {
.store_int(op::teleitem_bid_info, 32)
.store_grams(bid)
.store_uint(bid_ts, 32),
- 1); ;; paying fees, revert on errors
+ mf::paying_fees); ;; revert on errors
if ((royalty_num > 0) & (royalty_denom > 0) & ~ equal_slices(royalty_address, beneficiary_address)) {
int royalty_value = min(bid, muldiv(bid, royalty_num, royalty_denom));
@@ -48,7 +51,7 @@ int send_money(int my_balance, slice address, int value) impure {
return (my_balance, bidder_address, null());
}
-cell process_new_bid(int my_balance, slice new_bid_address, int new_bid, cell auction) impure {
+(int, cell) process_new_bid(int my_balance, slice new_bid_address, int new_bid, cell auction, int fwd_fees) impure {
(cell auction_state, cell auction_config) = unpack_auction(auction);
(cell old_last_bid, int min_bid, int end_time) = unpack_auction_state(auction_state);
throw_if(err::too_small_stake, new_bid < min_bid);
@@ -59,24 +62,30 @@ cell process_new_bid(int my_balance, slice new_bid_address, int new_bid, cell au
new_end_time = 0;
}
;; step is at least GR$1
- int new_min_bid = max(new_bid + 1000000000, (new_bid * (100 + min_bid_step) + 99) / 100);
+ int new_min_bid = max(new_bid + 1 * tons, (new_bid * (100 + min_bid_step) + 99) / 100);
if (~ cell_null?(old_last_bid)) {
(slice old_bidder_address, int old_bid, _) = unpack_last_bid(old_last_bid);
- int to_send = min(my_balance - min_tons_for_storage(), old_bid);
+ int to_send = min(my_balance - min_tons_for_storage, old_bid);
if (to_send > 0) {
- send_msg(old_bidder_address, to_send, op::outbid_notification, cur_lt(), null(), 1);
+ send_msg(old_bidder_address, to_send, op::outbid_notification, cur_lt(), null(), mf::paying_fees);
+ my_balance -= (to_send + fwd_fees);
}
}
cell new_auction_state = pack_auction_state(new_last_bid, new_min_bid, new_end_time);
- return pack_auction(new_auction_state, auction_config);
+ return (my_balance, pack_auction(new_auction_state, auction_config));
}
cell prepare_auction(cell auction_config) {
(slice beneficiary_address, int initial_min_bid, int max_bid, int min_bid_step, int min_extend_time, int duration) = unpack_auction_config(auction_config);
- if ((initial_min_bid < 2 * min_tons_for_storage()) | ((max_bid != 0) & (max_bid < initial_min_bid)) |
- (min_bid_step <= 0) | (min_extend_time > 60 * 60 * 24 * 7) | (duration > 60 * 60 * 24 * 365)) {
+ if ((initial_min_bid < 2 * min_tons_for_storage) | ((max_bid != 0) & (max_bid < initial_min_bid)) |
+ (min_bid_step <= 0) | (min_extend_time > 7 * days) | (duration > 365 * days)) {
return null();
}
+ ;; WARNING: beneficiary_address is obtained from cs~load_msg_addr that is LDMSGADDR and it by documentation loads MsgAddress
+ ;; While in tlb, teleitem_auction_config$_ beneficiary_address:MsgAddressInt, and MsgAddress can be also MsgAddressExt, including addr_none$00
+ ;; Therefore it is possible to abrubtly push in such address violating the spec and possibly causing problems in send_money in maybe_end_auction
+ ;; Need to check that address is MsgAddressInt, the easiest way is to use parse_std_addr (REWRITESTDADDR) that will fail for non MsgAddressInt
+ (_, _) = parse_std_addr(beneficiary_address);
cell auction_state = pack_auction_state(null(), initial_min_bid, now() + duration);
return pack_auction(auction_state, auction_config);
}
@@ -88,11 +97,11 @@ cell deploy_item(int my_balance, slice msg) {
if (cell_null?(auction)) {
return null();
}
- cell new_auction = process_new_bid(my_balance, bidder_address, bid, auction);
+ ;; The change should not affect this execution flow at all since old_bid cell is null here after creation
+ (my_balance, cell new_auction) = process_new_bid(my_balance, bidder_address, bid, auction, 0);
(my_balance, slice owner, new_auction) = maybe_end_auction(my_balance, zero_address(), new_auction, royalty_params, 0);
cell content = pack_item_content(nft_content, null(), token_info);
return pack_item_state(owner, content, new_auction, royalty_params);
-
}
slice transfer_ownership(int my_balance, slice owner_address, slice in_msg_body, int fwd_fees) impure inline {
@@ -101,7 +110,7 @@ slice transfer_ownership(int my_balance, slice owner_address, slice in_msg_body,
force_chain(new_owner_address);
- int rest_amount = my_balance - min_tons_for_storage();
+ int rest_amount = my_balance - min_tons_for_storage;
if (forward_amount) {
rest_amount -= (forward_amount + fwd_fees);
}
@@ -114,12 +123,12 @@ slice transfer_ownership(int my_balance, slice owner_address, slice in_msg_body,
if (forward_amount) {
send_msg(new_owner_address, forward_amount, op::ownership_assigned, query_id,
- begin_cell().store_slice(owner_address).store_slice(forward_payload), 1); ;; paying fees, revert on errors
+ begin_cell().store_slice(owner_address).store_slice(forward_payload), mf::paying_fees); ;; revert on errors
}
if (need_response) {
force_chain(response_destination);
- send_msg(response_destination, rest_amount, op::excesses, query_id, null(), 1); ;; paying fees, revert on errors
+ send_msg(response_destination, rest_amount, op::excesses, query_id, null(), mf::paying_fees); ;; revert on errors
}
return new_owner_address;
@@ -155,7 +164,7 @@ cell change_dns_record(cell dns, slice in_msg_body) {
cs~load_grams(); ;; skip ihr_fee
int fwd_fee = cs~load_grams(); ;; we use message fwd_fee for estimation of forward_payload costs
- int op = in_msg_body.slice_empty?() ? 0 : in_msg_body~load_uint(32);
+ int op = in_msg_body.slice_empty?() ? op::simple_transfer_0 : in_msg_body~load_uint(32);
(cell config, cell state) = unpack_item_data();
(int index, slice collection_address) = unpack_item_config(config);
@@ -169,32 +178,41 @@ cell change_dns_record(cell dns, slice in_msg_body) {
}
}
slice bidder_address = in_msg_body~load_msg_addr(); ;; first field in teleitem_msg_deploy
- send_msg(bidder_address, 0, op::teleitem_return_bid, cur_lt(), null(), 64); ;; carry all the remaining value of the inbound message
+ send_msg(bidder_address, 0, op::teleitem_return_bid, cur_lt(), null(), mf::add_inbound_value); ;; carry all the remaining value of the inbound message
return ();
}
throw_if(err::uninited, cell_null?(state));
(slice owner_address, cell content, cell auction, cell royalty_params) = unpack_item_state(state);
- if (~ cell_null?(auction)) {
- ;; sender do not pay for auction with its message
- my_balance -= msg_value;
- (my_balance, owner_address, auction) = maybe_end_auction(my_balance, owner_address, auction, royalty_params, 0);
- cell new_state = pack_item_state(owner_address, content, auction, royalty_params);
- save_item_data(config, new_state);
- my_balance += msg_value;
- }
+
+ ;; Those getters do not care for value of (my_balance, owner_address, auction) and therefore should not call maybe_end_auction whose result is discarded
if (op == op::get_royalty_params) {
int query_id = in_msg_body~load_uint(64);
- send_msg(sender_address, 0, op::report_royalty_params, query_id, begin_cell().store_slice(royalty_params.begin_parse()), 64); ;; carry all the remaining value of the inbound message
+ send_msg(sender_address, 0, op::report_royalty_params, query_id, begin_cell().store_slice(royalty_params.begin_parse()), mf::add_inbound_value); ;; carry all the remaining value of the inbound message
return ();
}
+
if (op == op::nft_cmd_get_static_data) {
int query_id = in_msg_body~load_uint(64);
- send_msg(sender_address, 0, op::report_static_data, query_id, begin_cell().store_uint(index, 256).store_slice(collection_address), 64); ;; carry all the remaining value of the inbound message
+ send_msg(sender_address, 0, op::report_static_data, query_id, begin_cell().store_uint(index, 256).store_slice(collection_address), mf::add_inbound_value); ;; carry all the remaining value of the inbound message
return ();
}
+ if (~ cell_null?(auction)) {
+ ;; sender do not pay for auction with its message
+ my_balance -= msg_value;
+ (my_balance, owner_address, auction) = maybe_end_auction(my_balance, owner_address, auction, royalty_params, 0);
+ my_balance += msg_value;
+ ;; It seems that state persisting is missing. However, closer look of execution flow points out that save_item_data is called in all branches except for the one.
+ ;; Therefore for for all those executions it is return save_item_data that persists or throw that reverts everything.
+ ;; NB: Obvious idea of persisting here is bad because then extra pack_item_state and save_item_data will be called for each bid in the auction, and that's undesriable.
+ ;; I will reuse existing flags variable to try to decrease the amount of variables and correspondingly required gas.
+ if (cell_null?(auction)) {
+ flags = need_to_save_item_data; ;; Such value of flags (-1) variable is normally impossible because it is uint4.
+ }
+ }
+
if (op == op::teleitem_cancel_auction) {
throw_if(err::no_auction, cell_null?(auction));
throw_unless(err::forbidden_auction, equal_slices(sender_address, owner_address));
@@ -204,26 +222,46 @@ cell change_dns_record(cell dns, slice in_msg_body) {
throw_unless(err::already_has_stakes, cell_null?(last_bid));
cell new_state = pack_item_state(owner_address, content, null(), royalty_params);
if (query_id) {
- send_msg(sender_address, 0, op::teleitem_ok, query_id, null(), 64); ;; carry all the remaining value of the inbound message
+ send_msg(sender_address, 0, op::teleitem_ok, query_id, null(), mf::add_inbound_value); ;; carry all the remaining value of the inbound message
}
return save_item_data(config, new_state);
}
+ ;; NB: if maybe_end_auction did not finish auction, op == 0 will be rerouted to this condition (and only it is allowed actually)
if (~ cell_null?(auction)) {
- throw_unless(err::forbidden_not_stake, op == 0);
- auction = process_new_bid(my_balance, sender_address, msg_value, auction);
+ throw_unless(err::forbidden_not_stake, op == op::simple_transfer_0);
+ ;; During analysis of execution and data flow diagram I discovered that here may be an issue because my_balance is not updated by process_new_bid
+ ;; despite the fact that actually an old bid message may be sent that really changes the balance.
+ ;; In order to fix that I return new balance from process_new_bid and update it accordingly inside that function.
+ (my_balance, auction) = process_new_bid(my_balance, sender_address, msg_value, auction, fwd_fee);
(my_balance, owner_address, auction) = maybe_end_auction(my_balance, owner_address, auction, royalty_params, 0);
cell new_state = pack_item_state(owner_address, content, auction, royalty_params);
return save_item_data(config, new_state);
}
- if (op == 0) {
- int is_topup = equal_slices(in_msg_body, "#topup") & (in_msg_body.slice_refs() == 0);
+ if (op == op::simple_transfer_0) {
+ int is_topup = equal_slices(in_msg_body, comm::topup) & (in_msg_body.slice_refs() == 0);
throw_unless(err::forbidden_topup, is_topup | equal_slices(sender_address, owner_address)); ;; only owner can fill-up balance, prevent coins lost right after the auction
;; if owner send bid right after auction he can restore it by transfer response message
+ ;;
+ ;; This flow causes specific problems. If auction end conditions are met and the new owner sends #topup then end auction ceremonies will be carried out.
+ ;; HOWEVER, no data will be updated since save_item_data is not called in this flow.
+ ;; Therefore, send_msg and send_money in maybe_end_auction will be called multiple times that is not desirable to say the least.
+ ;; On the other hand, calling pack_item_state and save_item_data each time for simple #topup may be also unwanted behaviour.
+ ;;
+ ;; Update: It seems even worse, not just owner but anyone can send #topup to trigger this issue.
+ if (flags == need_to_save_item_data) {
+ ;; flags equals -1 if, and only if, auction was running when #topup message was received from the new owner.
+ ;; Why? If auction keeps running the previous to (op == 0) condition (~ cell_null?(auction)) will take precedence.
+ ;; If auction was not running flags cannot be -1 because it is read as uint4.
+ ;; If auction was running and was ended in first (~ cell_null?(auction)) condition then owner_address will be updated there.
+ cell new_state = pack_item_state(owner_address, content, auction, royalty_params);
+ return save_item_data(config, new_state);
+ ;; Now each flow where (my_balance, owner_address, auction) may be updated ends either in save_item_data or exception and reversal.
+ }
return ();
}
-
+
if (op == op::teleitem_start_auction) {
throw_unless(err::auction_already_started, cell_null?(auction));
throw_unless(err::forbidden_auction, equal_slices(sender_address, owner_address));
@@ -233,7 +271,7 @@ cell change_dns_record(cell dns, slice in_msg_body) {
throw_if(err::invalid_auction_config, cell_null?(new_auction));
cell new_state = pack_item_state(owner_address, content, new_auction, royalty_params);
if (query_id) {
- send_msg(sender_address, 0, op::teleitem_ok, query_id, null(), 64); ;; carry all the remaining value of the inbound message
+ send_msg(sender_address, 0, op::teleitem_ok, query_id, null(), mf::add_inbound_value); ;; carry all the remaining value of the inbound message
}
return save_item_data(config, new_state);
}
@@ -251,7 +289,7 @@ cell change_dns_record(cell dns, slice in_msg_body) {
cell new_dns = change_dns_record(dns, in_msg_body);
cell new_content = pack_item_content(nft_content, new_dns, token_info);
cell new_state = pack_item_state(owner_address, new_content, auction, royalty_params);
- send_msg(sender_address, 0, op::teleitem_ok, cur_lt(), null(), 64); ;; carry all the remaining value of the inbound message
+ send_msg(sender_address, 0, op::teleitem_ok, cur_lt(), null(), mf::add_inbound_value); ;; carry all the remaining value of the inbound message
return save_item_data(config, new_state);
}
throw(err::unknown_op);
@@ -270,6 +308,7 @@ cell change_dns_record(cell dns, slice in_msg_body) {
;; GET Methods
;;
+;; -> ( has_state, item_index, collection_address, owner_address, nft_content)
(int, int, slice, slice, cell) get_nft_data() method_id {
(cell config, cell state) = unpack_item_data();
(int item_index, slice collection_address) = unpack_item_config(config);
@@ -281,6 +320,7 @@ cell change_dns_record(cell dns, slice in_msg_body) {
return (-1, item_index, collection_address, owner_address, nft_content);
}
+;; -> ( bidder_address, bid, end_time)
(slice, int, int) get_auction_info() method_id {
(cell config, cell state) = unpack_item_data();
(slice owner_address, cell content, cell auction, cell royalty_params) = unpack_item_state(state);
@@ -291,12 +331,16 @@ cell change_dns_record(cell dns, slice in_msg_body) {
(cell last_bid, int min_bid, int end_time) = unpack_auction_state(auction_state);
slice bidder_address = null();
int bid = 0;
- if (cell_null?(last_bid)) {
+ if (~ cell_null?(last_bid)) {
+ ;; It seems that the condition had logic error, tried to unpack last bid only if it is null.
+ ;; Strange enough, in get_telemint_auction_state the logic is correct.
(bidder_address, bid, _) = unpack_last_bid(last_bid);
}
return (bidder_address, bid, end_time);
}
+;; -> ( full_domain) = domain + token_name + \0
+;; NB: Shouldn't there be \0 between domain and token_name? Or is it guaranteed that domain has \0 at end? Or is it not needed?
slice get_full_domain() method_id {
(cell config, cell state) = unpack_item_data();
(slice owner_address, cell content, cell auction, cell royalty_params) = unpack_item_state(state);
@@ -305,6 +349,7 @@ slice get_full_domain() method_id {
return begin_cell().store_slice(domain).store_slice(token_name).store_int(0, 8).end_cell().begin_parse();
}
+;; -> ( token_name)
slice get_telemint_token_name() method_id {
(cell config, cell state) = unpack_item_data();
(slice owner_address, cell content, cell auction, cell royalty_params) = unpack_item_state(state);
@@ -313,10 +358,12 @@ slice get_telemint_token_name() method_id {
return token_name;
}
+;; -> ( token_name)
slice get_username() method_id {
return get_telemint_token_name();
}
+;; -> ( bidder_address, bid, bid_ts, min_bid, end_time)
(slice, int, int, int, int) get_telemint_auction_state() method_id {
(cell config, cell state) = unpack_item_data();
(slice owner_address, cell content, cell auction, cell royalty_params) = unpack_item_state(state);
@@ -330,7 +377,8 @@ slice get_username() method_id {
return (bidder_address, bid, bid_ts, min_bid, end_time);
}
-
+;; -> ( beneficiary_address, initial_min_bid, max_bid,
+;; min_bid_step, min_extend_time, duration)
(slice, int, int, int, int, int) get_telemint_auction_config() method_id {
(cell config, cell state) = unpack_item_data();
(slice owner_address, cell content, cell auction, cell royalty_params) = unpack_item_state(state);
@@ -344,6 +392,7 @@ slice get_username() method_id {
return (beneficiary_address, initial_min_bid, max_bid, min_bid_step, min_extend_time, duration);
}
+;; -> ( numerator, denumerator, destination)
(int, int, slice) royalty_params() method_id {
(cell config, cell state) = unpack_item_data();
(slice owner_address, cell content, cell auction, cell royalty_params) = unpack_item_state(state);
@@ -351,19 +400,20 @@ slice get_username() method_id {
return (numerator, denominator, destination);
}
+;; ( subdomain, category) -> ( prefix_len, result)
(int, cell) dnsresolve(slice subdomain, int category) method_id {
(cell config, cell state) = unpack_item_data();
(slice owner_address, cell content, cell auction, cell royalty_params) = unpack_item_state(state);
(cell nft_content, cell dns, cell token_info) = unpack_item_content(content);
int subdomain_bits = slice_bits(subdomain);
- throw_unless(70, mod(subdomain_bits, 8) == 0);
+ throw_unless(err::invalid_string_length, mod(subdomain_bits, 8) == 0);
int starts_with_zero_byte = subdomain.preload_int(8) == 0;
- throw_unless(413, starts_with_zero_byte);
+ throw_unless(err::doesnt_start_with_zero_byte, starts_with_zero_byte);
if (subdomain_bits > 8) { ;; more than "." requested
- category = "dns_next_resolver"H;
+ category = cat::dns_next_resolver;
}
if (category == 0) { ;; all categories are requested
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | |