diff --git a/Mainnet/STOContract/README.md b/Mainnet/STOContract/README.md new file mode 100644 index 00000000..4fee3375 --- /dev/null +++ b/Mainnet/STOContract/README.md @@ -0,0 +1,15 @@ +# STO Smart Contract + +**Compiler Version** +``` +v2.0.0 +``` +**Contract Hash** +``` +6260cfd4c58e56ec35a8e7151dbe7737bda1c26b9c0f35a370aa45377eeb1ee6 +``` + +**Contract Byte Code** +``` +4D5A90000300000004000000FFFF0000B800000000000000400000000000000000000000000000000000000000000000000000000000000000000000800000000E1FBA0E00B409CD21B8014CCD21546869732070726F6772616D2063616E6E6F742062652072756E20696E20444F53206D6F64652E0D0D0A2400000000000000504500004C0102001EA8B8B40000000000000000E00022200B013000004E00000002000000000000BE6D000000200000008000000000001000200000000200000400000000000000040000000000000000A0000000020000000000000300408500001000001000000000100000100000000000001000000000000000000000006C6D00004F000000000000000000000000000000000000000000000000000000008000000C000000506D00001C0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000080000000000000000000000082000004800000000000000000000002E74657874000000C44D000000200000004E000000020000000000000000000000000000200000602E72656C6F6300000C000000008000000002000000500000000000000000000000000000400000420000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000A06D0000000000004800000002000500343C00001C31000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004602280D00000A72010000706F0E00000A2A4A02280D00000A7201000070036F0F00000A2A7202280D00000A7215000070038C0E000001281000000A6F0100002B2A7602280D00000A7215000070038C0E000001281000000A046F0200002B2AE20203281300000A0204281700000602052815000006020E042813000006020E0528190000060202281400000A6F1500000A04281B0000062A1E0228070000062A660202280100000602281400000A6F1600000AD728020000062A8E0202281400000A6F1500000A280A000006260203280A00000626020304281C0000062A6A0203280A000006260204280A0000062602030405281D0000062A0000133003004E00000001000011020328030000060A020306280C0000060B0716281700000A281800000A2C2D12007C0100000425711000000107281900000A811000000112000228010000067D030000040203062804000006062A52047B01000004020304280C000006281900000A2A00133002002100000002000011022801000006047B03000004DB0A0203281A00000606281A00000A281B00000A2A4A0202281400000A6F1500000A280E0000062A133003002100000003000011020328030000060A020306280B000006022816000006281C00000A281D00000A2A4A0202281400000A6F1500000A28100000062A133003002800000003000011020328030000060A020306280B000006022816000006281C00000A281D00000A067B02000004D72A1330030099000000040000110202281400000A6F1500000A280A0000060A067B01000004022816000006281C00000A281D00000A0B0207166AFE03722D000070281E00000A12007C02000004254C07D75512007C01000004257110000001022816000006281F00000A81100000010202281400000A6F1500000A0628040000060202281400000A6F1500000A07282000000A0C02086F2100000A7269000070281E00000A2A4602280D00000A728B0000706F2200000A2A4A02280D00000A728B000070036F2300000A2A4602280D00000A72990000706F2200000A2A4A02280D00000A7299000070036F2300000A2A4602280D00000A72A30000706F2400000A2A4A02280D00000A72A3000070036F2500000A2A4E02280D00000A72BB0000706F2600000A16912A6E02280D00000A72BB000070178D1B0000012516039C6F2700000A2A7202280D00000A72CD000070038C0E000001281000000A6F2400000A2A7602280D00000A72CD000070038C0E000001281000000A046F2500000A2A0013300400C2000000050000110416281700000A282800000A2C38021201FE1507000002120102281400000A6F1500000A7D040000041201037D05000004120116281700000A7D0600000407280300002B172A0202281400000A6F1500000A281A0000060A0604282A00000A2C02162A0202281400000A6F1500000A0604282B00000A281B00000602030203281A00000604281900000A281B000006021201FE1507000002120102281400000A6F1500000A7D040000041201037D050000041201047D0600000407280300002B172A000013300500CF000000060000110516281700000A282800000A2C2E021202FE15070000021202037D040000041202047D05000004120216281700000A7D0600000408280300002B172A020302281400000A6F1500000A28200000060A0203281A0000060B0605282A00000A2D090705282A00000A2C02162A020302281400000A6F1500000A0605282B00000A281F00000602030705282B00000A281B00000602040204281A00000605281900000A281B000006021202FE15070000021202037D040000041202047D050000041202057D0600000408280300002B172A00133004006A000000070000110202281400000A6F1500000A03282000000604282C00000A2C02162A0202281400000A6F1500000A0305281F000006021200FE1508000002120002281400000A6F1500000A7D070000041200037D080000041200057D0A0000041200047D0900000406280400002B172A8E02280D00000A72E5000070038C0E000001048C0E000001282D00000A056F2500000A2A8A02280D00000A72E5000070038C0E000001048C0E000001282D00000A6F2400000A2A7202280D00000A7209010070038C1C000001281000000A6F2E00000A2A7602280D00000A7209010070038C1C000001281000000A046F2F00000A2A467237010070038C1D000001281000000A2A4E02280D00000A020328230000066F3000000A2A5202280D00000A02032823000006046F3100000A2A467253010070038C1D000001281000000A2A4E02280D00000A020328260000066F3000000A2A5202280D00000A02032826000006046F3100000A2A7202280D00000A7275010070038C0E000001281000000A6F0E00000A2A7602280D00000A7275010070038C0E000001281000000A046F0F00000A2A8A02280D00000A72A5010070038C0E000001048C0E000001282D00000A6F2E00000A2A8E02280D00000A72A5010070038C0E000001048C0E000001282D00000A056F2F00000A2A4602280D00000A72D50100706F3000000A2A4A02280D00000A72D5010070036F3100000A2A4602280D00000A72990000706F2200000A2A4A02280D00000A7299000070036F2300000A2A4602280D00000A728B0000706F2200000A2A4A02280D00000A728B000070036F2300000A2A4602280D00000A72E10100706F0E00000A2A4A02280D00000A72E1010070036F0F00000A2A4672F9010070038C1D000001281000000A2A4E02280D00000A020328350000066F0E00000A2A5202280D00000A02032835000006046F0F00000A2A4E02280D00000A020328350000066F3200000A2A46721B020070038C1D000001281000000A2A4E02280D00000A020328390000066F0E00000A2A5202280D00000A02032839000006046F0F00000A2A4E02280D00000A020328390000066F3200000A2A5E723D020070038C0E000001048C1D000001282D00000A2A5202280D00000A020304283D0000066F0E00000A2A5602280D00000A020304283D000006056F0F00000A2A5202280D00000A020304283D0000066F3200000A2A5E7275020070038C0E000001048C1D000001282D00000A2A5202280D00000A02030428410000066F0E00000A2A5602280D00000A0203042841000006056F0F00000A2A5202280D00000A02030428410000066F3200000A2A4602280D00000A72A30000706F0E00000A2A4A02280D00000A72A3000070036F0F00000A2A001330030057000000000000000203281300000A021717282200000602181728220000060219162822000006021A172822000006021B17282200000602042830000006020528320000060202281400000A6F1500000A282E00000602176A28340000062A720203022845000006FE0572AD020070281E00000A020328360000062A7A020402032829000006FE0572AD020070281E00000A020304283E0000062A32020304050E0428560000062A4202030405168D1B00000128560000062A0000001330030038000000080000110205285C000006020528240000060A0206286200000602042862000006020603283300000A72D9020070281E00000A02040528530000062A133004003A000000080000110204285B0000060204285D000006020428240000060A020306283400000A72D9020070281E00000A02040328280000060206030428590000062A9E0202281400000A6F1500000A0304282C0000060202281400000A6F1500000A0304285A0000062A3E02032862000006020328290000062A0000133002001100000008000011020328240000060A02062862000006062A3E0203285D000006020328270000062A26020304282B0000062A00133004002900000008000011020428240000060A02042857000006020604285400000602030428550000060206030428580000062A000000133004007B00000009000011020204282400000603283300000A72D9020070281E00000A020328290000060A020306176ADB282A00000602280D00000A020428230000066F3200000A02030428420000060B06176ADB0C07082E1B020308283E0000060D02030907284300000602030709283F000006020308284000000602030428440000062A00133004004C0000000A00001102020428240000067E3500000A283300000A72D9020070281E00000A0204032825000006020328290000060A020306176AD7282A000006060B02030407284300000602030704283F0000062A133008009D0000000B0000110205285C0000060205285D000006020528240000060A020603283300000A72D9020070281E00000A02042862000006020405285300000602280D00000A046F3600000A2C570204166A72F70200701A8D16000001251602281400000A6F1500000A8C0E000001A22517038C0E000001A22518058C1D000001A225190E04A2166A283700000A0B02076F3800000AA51E00000172D9020070281E00000A2A9A020328270000067E3500000A283400000A2C1202280D00000A020328260000066F3200000A2A13300300280000000C000011021200FE15090000021200037D0B0000041200047D0C0000041200057D0D00000406280500002B2A13300300280000000D000011021200FE150A0000021200037D0E0000041200047D0F0000041200057D1000000406280600002B2A13300300280000000E000011021200FE150B0000021200037D110000041200047D120000041200057D1300000406280700002B2A133004003C00000008000011020328240000060A020602281400000A6F1500000A283300000A2D14020602281400000A6F1500000A282B0000062B011772D9020070281E00000A2A133004005500000008000011020328240000060A020602281400000A6F1500000A283300000A2D2D0203282700000602281400000A6F1500000A283300000A2D14020602281400000A6F1500000A282B0000062B011772D9020070281E00000A2A000000133002001000000008000011020328240000060A020628620000062A13300300390000000F00001102285F00000602032862000006021200FE150C000002120002282D0000067D140000041200037D1500000406280800002B0203282E0000062A8A0202281400000A6F1500000A02282D000006283300000A722D030070281E00000A2A133004007A0000001000001102285F000006020328620000060204166AFE037289030070281E00000A0228450000060A0604D70B0228330000060C2B2F02030828550000060206082837000006020806283B000006027E3500000A0308285800000606176AD70A08176AD70C060737CD0202284500000604D72846000006020828340000062A0000133004008500000011000011020328240000060A020602281400000A6F1500000A283300000A72D5030070281E00000A0203285700000602060328540000060203283A0000060B022845000006176ADB1304021104284600000611040C020828360000060D0207092837000006020907283B000006020828380000060203283C00000602067E3500000A0328580000062A5E02037E3500000A283400000A721F040070281E00000A2AE20203281300000A0204286B00000602052867000006020E042865000006020E0528690000060202281400000A6F1500000A04286D0000062A4602280D00000A728B0000706F2200000A2A4A02280D00000A728B000070036F2300000A2A4602280D00000A72990000706F2200000A2A4A02280D00000A7299000070036F2300000A2A4E02280D00000A72BB0000706F2600000A16912A6E02280D00000A72BB000070178D1B0000012516039C6F2700000A2A4602280D00000A72A30000706F2400000A2A4A02280D00000A72A3000070036F2500000A2A7202280D00000A72CD000070038C0E000001281000000A6F2400000A2A7602280D00000A72CD000070038C0E000001281000000A046F2500000A2A13300400C2000000120000110416281700000A282800000A2C38021201FE150D000002120102281400000A6F1500000A7D160000041201037D17000004120116281700000A7D1800000407280900002B172A0202281400000A6F1500000A286C0000060A0604282A00000A2C02162A0202281400000A6F1500000A0604282B00000A286D00000602030203286C00000604281900000A286D000006021201FE150D000002120102281400000A6F1500000A7D160000041201037D170000041201047D1800000407280900002B172A000013300500CF000000130000110516281700000A282800000A2C2E021202FE150D0000021202037D160000041202047D17000004120216281700000A7D1800000408280900002B172A020302281400000A6F1500000A28720000060A0203286C0000060B0605282A00000A2D090705282A00000A2C02162A020302281400000A6F1500000A0605282B00000A287100000602030705282B00000A286D00000602040204286C00000605281900000A286D000006021202FE150D0000021202037D160000041202047D170000041202057D1800000408280900002B172A00133004006A000000140000110202281400000A6F1500000A03287200000604282C00000A2C02162A0202281400000A6F1500000A03052871000006021200FE150E000002120002281400000A6F1500000A7D190000041200037D1A0000041200057D1C0000041200047D1B00000406280A00002B172A8E02280D00000A72E5000070038C0E000001048C0E000001282D00000A056F2500000A2A8A02280D00000A72E5000070038C0E000001048C0E000001282D00000A6F2400000A2A4602280D00000A72590400706F0E00000A2A4A02280D00000A7259040070036F0F00000A2A4602280D00000A726B0400706F3000000A2A4A02280D00000A726B040070036F3100000A2A4602280D00000A72850400706F3000000A2A4A02280D00000A7285040070036F3100000A2A4602280D00000A729B0400706F3000000A2A4A02280D00000A729B040070036F3100000A2A4602280D00000A72B70400706F2400000A2A4A02280D00000A72B7040070036F2500000A2A4602280D00000A72D10400706F2E00000A2A4A02280D00000A72D1040070036F2F00000A2A9E02287300000602283900000A6F3A00000A371202287B00000616281700000A281800000A2A162A4602280D00000A72D50100706F3000000A2A4A02280D00000A72D5010070036F3100000A2A4602280D00000A72F70400706F0B00002B2A4A02280D00000A72F7040070036F3C00000A2A0000001330060006010000150000110203281300000A020519FE05720F0500707263050070281000000A281E00000A0202280D00000A0E086F3600000A727705007072BB050070281000000A281E00000A0202280D00000A0E096F3600000A727705007072D1050070281000000A281E00000A02283D00000A0E0A6F0C00002B0A0206288E000006050B02070E040E050E060E0728850000060C02086F3F00000A72ED050070281E00000A021203FE15120000021203086F4000000A7D2400000409280D00002B020E082878000006020E09287A00000602086F4000000A2876000006020718FE01287E0000060202287D0000062D040E042B07156A281A00000A287C000006020428810000060206288C0000062A0000133006007D00000000000000032C0603172E2F2B5A02166A1A8D160000012516048C10000001A2251705A225180E04A225190E058C1C000001A2166A280E00002B2A02166A1A8D160000012516048C10000001A2251705A225180E04A225190E058C1C000001A2166A280F00002B2A02166A188D16000001251605A225170E04A2166A281000002B2A22022887000006262A00001330080075010000160000110202287F000006722D060070281E00000A0202281400000A6F1600000A166AFE037259060070281E00000A02288800000602288F0000060A02287D0000062D3E02022875000006166A72A5060070188D16000001251602281400000A6F1500000A8C0E000001A22517067B230000048C10000001A2166A283700000A2B4102022875000006166A72BB060070188D16000001251602281400000A6F1500000A8C0E000001A22517067B23000004281D00000A8C1D000001A2166A283700000A0B02076F2100000A2C0D076F3800000AA51E0000012B011672CB060070281E00000A021202FE1513000002120202281400000A6F1500000A7D250000041202067B210000047D260000041202067B230000047D270000041202067B220000047D2800000408281100002B0202287B000006067B23000004282B00000A287C000006067B22000004166A36290202281400000A6F1500000A067B22000004282000000A0B02076F2100000A72F9060070281E00000A172A00000013300800DD0000001700001102022879000006166A7217070070178D16000001251602281400000A6F1500000A8C0E000001A2166A283700000A0A066F2100000A3996000000066F3800000A0C08750E00000114FE03252D0B1203FE150E000001092B0608A50E0000010B2C6F077E3500000A283400000A2C6202022877000006166A723F070070188D160000012516078C0E000001A22517198C1C000001A2166A283700000A0A02066F2100000A2C1F066F3800000A750100001B2513042C0F11042D03162B0911048E16FE032B01167251070070281E00000A2A02167285070070281E00000A2A000000133003004D000000000000000202281400000A6F1500000A022880000006283300000A72BD070070281E00000A0202287F00000616FE01720D080070281E00000A0202288000000602284200000A282000000A6F2100000A2A00000013300800D4000000180000110202287D00000616FE01723908007072A9080070281000000A281E00000A0202281400000A6F1500000A022880000006283300000A72C7080070281E00000A0202287F00000616FE01720D080070281E00000A02287B00000616281700000A282800000A2C02172A02022875000006166A72A5060070188D16000001251602281400000A6F1500000A8C0E000001A2251702287B0000068C10000001A2166A283700000A0A0216281700000A287C00000602066F2100000A2C0D066F3800000AA51E0000012B011672CB060070281E00000A172A133002003E000000190000111200FE15100000020228820000060B160C2B230708A3100000020D097B1F00000402283900000A6F3A00000A3704090A2B0A0817580C08078E6932D7062A000013300400260000001A0000110203288D0000060A020628830000060206068E6917DA8F100000027B1F00000428740000062A0000133004005C0000001B000011038E698D100000020A02283900000A6F3A00000A0B160C2B3B0308A30F0000020D07097B1D000004D70B06081204FE15100000021204077D1F0000041204097B1E0000047D200000041104A4100000020817D60C08038E6932BF062A133003003E0000001C00001102038E16FE037219090070281E00000A030A160B2B210607A30F0000020C02087B1D000004166AFE037265090070281E00000A0717580B07068E6932D92A0000133002009A0000001D00001102288B0000060A02281400000A6F1600000A067B200000045C0B02287B0000060C07281A00000A08281800000A2C4108281D00000A067B20000004D90D02281400000A6F1600000A09DB13041205FE15110000021205097D21000004120511047D220000041205087D2300000411052A1205FE1511000002120502281400000A6F1600000A7D21000004120507281A00000A7D2300000411052A000042534A4201000100000000000C00000076342E302E33303331390000000005006C000000D8140000237E000044150000980D000023537472696E677300000000DC220000B409000023555300902C0000100000002347554944000000A02C00007C04000023426C6F620000000000000002000001571FA209090A000000FA0133001600000100000021000000140000002C0000008F000000C50000000200000042000000030000001E0000001D00000004000000170000002D0000000100000001000000030000000F000000110000000000FB040100000000000600C302370906003403370906001D02F2070F005709000006004802990306000B03B7060600A402B70606006102B70606007E02B7060600E302B70606003102B7060A00F30A870A0E001C00FF080A00770A870A0A00DF01870A0A003C00870A0A00420B870A0600A5013A050A00FC02870A0A002403870A0A00340B870A0600010B3A0506006A053A050A000602870A0600C0033A050A007901870A060052033A05060001003A05060015003A0506006F053A050A001E04870A06009E0C3A050A00AB07870A000000004400000000000100010001001000B805000031000100010001001000F105000031000100210001001000C605000031000100630001001000C80A00003100010073000A0110009C0B00004900010090000A011000080400004900040090000A011000DE0300004900070090000A0110000804000049000B0090000A011000DE03000049000E0090000A011000EA0300004900110090000A011000C70300004900140090000A011000080400004900160090000A011000DE0300004900190090000A011000F20B000049001D0090000A011000ED00000049001F0090000A011000F90600004900210090000A011000FC0300004900240090000A0110001404000049002500900002010000AF0100005D0029009000060027015E020600720862020600210862020600600523010600F30623010600D60B5E0206009F0723010600380723010600AC0B5E020600D60B5E020600600523010600F30623010600A100620206009F0723010600D80023010600A100620206009F0723010600E30723010600D800650206008E07230106009C0723010600600523010600F30623010600D60B5E0206009F0723010600380723010600AC0B5E020600D60B5E020600780962020600020662020600400462020600020662020600CC0062020600B60B62020600C30B5E020600030A23010600310723010600CC0062020600C30B5E020600B10062020606590068025680C6056B025680B8056B025680F1056B02502000000000860805084F000100622000000000810813086F02010075200000000081008E0B740202009220000000008100990B7B020300B020000000008618EC0783020500E92000000000C600770306000A00F1200000000086004C0806000A000B2100000000E601DA068E020A002F2100000000E601450596020C004C21000000008100800B74020F00A6210000000081003308A0021000BC210000000081009208A0021200E92100000000860085084F001400FC210000000086008508A9021400292200000000860060084F0015003C220000000086006008A90215007022000000008600020C0600160015230000000086080D05AF02160027230000000081081805100016003A230000000086088E01AF0217004C230000000081089701100017005F2300000000E6093A0DB302180071230000000081084A0DB8021800842300000000E6098709BE02190098230000000081089409C2021900B42300000000E6015901C7021A00D1230000000081006401CE021B00F023000000008100E5068E021D00C024000000008100520596021F009C2500000000E6017F03D60222001226000000008100AD04E0022500362600000000E6016F01EA02280059260000000086000901F3022A0076260000000081002009F8022B009426000000008100E10CFE022D00A626000000008100740703032E00BA26000000008100810709032F00CF26000000008100A40CFE023100E1260000000081007F0403033200F5260000000081008F04090333000A27000000008100520BA90235002727000000008100690B100336004527000000008100C607170338006827000000008100D9071F033A008C2700000000860860074A003D009E270000000081086A0728033D00B1270000000086088E01AF023E00C327000000008108970110003E00D6270000000086080D05AF023F00E827000000008108180510003F00FB2700000000810889004F0040000D2800000000810899006F0240002028000000008100F10CFE02410032280000000081001D0C2E03420046280000000081002D0C330343005B280000000081000B0C6F0245006F28000000008100CE0CFE024600812800000000810069062E0347009528000000008100790633034800AA2800000000810057066F024A00BE28000000008100040D39034B00D628000000008100560C40034D00EB280000000081006D0C47034F0001290000000081003D0C100352001629000000008100B70C390354002E2900000000810029064003560043290000000081004006470358005929000000008100100610035B006E290000000086083A0D4F005D0080290000000081084A0D6F025D009429000000008618EC074F035E00F729000000008600300C2E036100142A000000008600700C40036200332A000000008600410557036400402A000000008600410562036800542A000000008600450562036B00982A0000000086007F0310036E00DE2A000000008600DC046B037000062B0000000086008703A9027200182B000000008600910303037300352B000000008600D50003037400452B000000008600B90417037500502B000000008100620410037700882B000000008100AA0510037900102C000000008100920510037B00682C000000008100490457037D00112D0000000081009F046F028100382D0000000081004807620382006C2D000000008100730462038500A02D000000008100CA041F038800D42D000000008100CA016F028B001C2E00000000810054076F028C00802E0000000081009D056F028D009C2E0000000086000A0728038E00E12E0000000081001E0D06008F00042F000000008600EE0410038F008C2F000000008600D5066F0291001D30000000008600800D280392003530000000008618EC07830293006E300000000086080D05AF029800803000000000810818051000980093300000000086088E01AF029900A530000000008108970110009900B83000000000E6098709BE029A00CC300000000081089409C2029A00E83000000000E6093A0DB3029B00FA300000000081084A0DB8029B000D3100000000E6015901C7029C002A310000000081006401CE029D00483100000000E601DA068E029F00183200000000E60145059602A100F43200000000E6017F03D602A4006A33000000008100AD04E002A7008E3300000000E6016F01EA02AA00B1330000000086082F044F00AC00C3330000000081083C046F02AC00D633000000008608EE094A00AD00E833000000008108FF092803AD00FB33000000008608C5094A00AE000D34000000008108D4092803AE002034000000008608100A4A00AF003234000000008108220A2803AF0045340000000086083701B302B00057340000000081084801B802B0006A34000000008608D4059C00B1007C34000000008108EB057203B1008F340000000086088F069C00B200B73400000000860860074A00B200C9340000000081086A072803B200DC34000000008608B0087703B300EE34000000008108C0087D03B3000435000000008618EC078403B4001836000000008100D40A9803BE00A13600000000C60077030600C300AC36000000008600EB0B9C00C3003038000000008100BA000600C3001C39000000008600A2089C00C3007839000000008600AA099C00C300583A000000008100F800A403C300A43A000000008100EC08A903C300D83A0000000081009E0AB003C400403B000000008100DC08A903C5008C3B000000008100F606B903C600000001007103000001007F0A000001007F0A00000200A40B000001001702000002005A0D00000300A00100000400230500000500A10900000100070700000200DD0B00000100650500000200070700000300DD0B000001007F0A000001007F0A00000200A40B000001007F0A00000200A40B000001007F0A000001007F0A000001007103000001007103000001007103000001007103000001007F0A000001007F0A00000200710300000100070700000200DD0B00000100650500000200070700000300DD0B00000100400700000200CF0B00000300DD0B00000100A50700000200400700000300710300000100A507000002004007000001004D00000001007D0000000200710300000100EA0000000100EA0000000100EA0000000200710300000100EA0000000100EA0000000100EA00000002007103000001007F0A000001007F0A00000200710300000100A50700000200420A00000100A50700000200420A00000300710300000100710300000100710300000100710300000100710300000100840C00000100840C00000100840C00000200890600000100840C00000100890600000100890600000100890600000200840C000001008906000001007F0A00000200840C000001007F0A00000200840C00000100A50700000200840C00000300A90000000100A50700000200840C00000100A50700000200A90000000100A50700000200A90000000100A50700000200A90000000300840C00000100A50700000200A90000000100710300000100170200000200A00100000300230500000100840C00000100A50700000200840C00000100650500000200070700000300A90000000400610000000100650500000200070700000300A90000000100650500000200070700000300A90000000100E10000000200A90000000100420A00000200E10000000100A50700000100A90000000100A90000000100A50700000200420A00000100070700000200A90000000100650500000200A90000000100070700000200A90000000100650500000200070700000300A90000000400610000000100A90000000100650500000200070700000300A90000000100A50700000200E10000000300A90000000100A50700000200420A00000300E10000000100A90000000100A90000000100A90000000100A507000001007F0A00000200DD0B00000100A900000001007F0A00000100F301000002005A0D00000300A00100000400230500000500A109000001007103000001007103000001007103000001007103000001007F0A000001007F0A00000200710300000100070700000200DD0B00000100650500000200070700000300DD0B00000100400700000200CF0B00000300DD0B00000100A50700000200400700000300710300000100A50700000200400700000100710300000100710300000100710300000100710300000100710300000100710300000100710300000100710300000100F30100000200A50700000300B901000004005A0D00000500A00100000600230500000700A10900000800E30900000900340A00000A00D00800000100B901000002005A0D00000300A00100000400230500000500A10900000100F70800000100F70800000100F70802003500040035000900EC0701001100EC0706001900EC070A002900EC0710003100EC0710003900EC0710004100EC0710004900EC0710005100EC0710005900EC0710009900EC070600A100EC0706006100D5011500C10008001A00C10012001F00C900C10A2500C100080B2B00C100120B37006100EC073F00610082014500D1002D074A00D10057034F0081001C0B5A008100770560008100C906680081001C0B750081002E0D680081009C0668008100280B80006100E40B8E008100B60A68006100570794008900B9099C00C100B303A000C100BD03A500C1002E00AB00C1003900B100C1006609B800C1006F09BE008100660D600061001A04CC008100860560008100A80668008100720D6000C900C10AEB00C1002A05F200C1003205F700C100690AFD00C100740A0301C1001C0710007100660D0F017100720D0F01710002072301C100E80A2E016100F6043401890061034001610025049A01F90022074F00C100920C9F01C1009B0CAC016100B707C00109018A0CC601A900B9099C00A900520A4A006100C301D90161001B014F000900A8004F020900AC0054020900B00059022E000B00DE032E001300E7032E001B0006042E0023000F042E002B0045042E00330056042E003B0061042E0043006E042E004B0045042E005300450481005B005402A1005B005402A30063005402E1005B00540201015B00540261015B00540281015B005402A1015B005402C1015B005402E1015B00540201025B00540221025B00540241025B00540281025B005402A1025B005402C1025B005402E1025B00540221035B00540241035B005402A1045B005402530071007B008600C500D800E1000A0117011E01270144014E01580162016C0172017B0187019001B401F20100020F0214021F02250231023A02020001000300060004000B0005000F0000009808BE0300001C05C20300009B01C20300004E0DC60300009809CB0300009F07CF0300009B01C20300001C05C20300009D00BE0300004E0DBE0300001C05C20300009B01C20300009809CB0300004E0DC60300004004BE030000030ACF030000D809CF030000260ACF0300004C01C6030000EF05D40300009306D40300009F07CF030000C408D803020001000300010002000300020012000500010013000500020014000700010015000700020016000900010017000900020018000B00010019000B0002002D000D0001002E000D0002002F000F00010030000F00020031001100010032001100020033001300010034001300020045001500010046001500020064001700010065001700020066001900010067001900020068001B00010069001B0002006A001D0001006B001D00020073001F00010074001F0002007500210001007600210002007700230001007800230002007900250001007A00250002007B00270001007C00270002007D00290001007E00290002007F002B00020080002D00010081002D00020082002F00010083002F000C02048000000100000000000000000000000000F30A000004000000000000000000000046026600000000000200000000000000000000000000870A000000000200000000000000000000000000FF0800000000060002000700020008000200090003000A0003000B0003000C0003000D0004000E0004000F000500100005001100050012000500130005001400050023003200250032005300D3005300E600530049015300530153005D015300670153008201530095017700A7017D00CF015300D4018300E3018300E8018300ED015300FB01000000000055496E7433320047657455496E7436340053657455496E74363400495374616E64617264546F6B656E3235360047657455496E743235360053657455496E74323536003C4D6F64756C653E00696E7465726661636549440076616C75655F5F00646174610053797374656D2E507269766174652E436F72654C696200696E746572666163654964006765745F4E657874546F6B656E4964007365745F4E657874546F6B656E496400746F6B656E496400526566756E64656400456E737572654B7963566572696669656400496E76657374656400476574417070726F76656400617070726F7665640069640053616C65506572696F640047657443757272656E74506572696F6400537570706F727473496E74657266616365006765745F42616C616E6365004469766964656E6442616C616E6365006765745F546F6B656E42616C616E6365007365745F546F6B656E42616C616E63650047657442616C616E63650053657442616C616E636500416C6C6F77616E636500494D657373616765006765745F4D657373616765006765745F4E616D65007365745F4E616D65006E616D650056616C75655479706500546F6B656E5479706500746F6B656E54797065004372656174650043616E4F706572617465006765745F53746174650049536D617274436F6E7472616374537461746500736D617274436F6E74726163745374617465004950657273697374656E7453746174650073746174650044656275676761626C6541747472696275746500417373656D626C795469746C65417474726962757465005461726765744672616D65776F726B41747472696275746500417373656D626C7946696C6556657273696F6E41747472696275746500417373656D626C79496E666F726D6174696F6E616C56657273696F6E41747472696275746500417373656D626C79436F6E66696775726174696F6E41747472696275746500436F6D70696C6174696F6E52656C61786174696F6E7341747472696275746500417373656D626C7950726F6475637441747472696275746500496E64657841747472696275746500417373656D626C79436F6D70616E79417474726962757465004465706C6F794174747269627574650052756E74696D65436F6D7061746962696C6974794174747269627574650042797465006765745F56616C7565006765745F52657475726E56616C75650076616C7565005265636569766500417070726F76650042616C616E63654F66004F776E65724F660053797374656D2E52756E74696D652E56657273696F6E696E6700476574537472696E6700536574537472696E67004F776E6572736869705472616E7366657265644C6F6700417070726F76616C4C6F6700417070726F76616C466F72416C6C4C6F670053544F53657475704C6F67005472616E736665724C6F6700496E766573744C6F670049426C6F636B006765745F426C6F636B006765745F456E64426C6F636B007365745F456E64426C6F636B00536166655472616E7366657246726F6D496E7465726E616C005472616E73666572496E7465726E616C004C6F67417070726F76616C004765744964546F417070726F76616C005365744964546F417070726F76616C00436C656172417070726F76616C00536574417070726F76616C004973417070726F766564466F72416C6C004C6F67417070726F76616C466F72416C6C00536574417070726F76616C466F72416C6C004D696E74416C6C0043616C6C00536D617274436F6E74726163742E646C6C006765745F53796D626F6C007365745F53796D626F6C0073796D626F6C00476574426F6F6C00536574426F6F6C0053797374656D00536166655472616E7366657246726F6D005472616E73666572546F6B656E7346726F6D0066726F6D00456E756D00426F6F6C65616E006F705F477265617465725468616E006F705F4C6573735468616E004164644E46546F6B656E0056616C69644E46546F6B656E0052656D6F76654E46546F6B656E004469766964656E64546F6B656E005374616E64617264546F6B656E006765745F49734E6F6E46756E6769626C65546F6B656E007365745F49734E6F6E46756E6769626C65546F6B656E005072696365506572546F6B656E00436C656172496E6465784F664F776E65724279546F6B656E00476574496E6465784F664F776E65724279546F6B656E00536574496E6465784F664F776E65724279546F6B656E00436C656172496E6465784279546F6B656E00476574496E6465784279546F6B656E00536574496E6465784279546F6B656E00746F6B656E006765745F53616C654F70656E006F705F4469766973696F6E006F705F5375627472616374696F6E0053797374656D2E5265666C656374696F6E006F705F4164646974696F6E004275726E005472616E73666572546F005472616E73666572546F6B656E73546F0047657453616C65496E666F005A65726F00746F005472616E736665724F776E65727368697000436C656172006765745F4E756D626572006765745F53656E646572005370656E646572007370656E646572004C6F675472616E736665720043616E5472616E73666572006765745F4F776E6572007365745F4F776E6572004765744964546F4F776E6572005365744964546F4F776E65720050726576696F75734F776E6572004E65774F776E6572006F776E6572004953657269616C697A6572006765745F53657269616C697A6572004765744F776E6572546F4F70657261746F72005365744F776E6572546F4F70657261746F72002E63746F720053797374656D2E446961676E6F7374696373006765745F4469766964656E6473007365745F4469766964656E64730043726564697465644469766964656E647300476574576974686472617761626C654469766964656E647300446973747269627574654469766964656E647300476574546F74616C4469766964656E64730057697468647261776E4469766964656E6473004765744469766964656E6473004765744E65774469766964656E647300576974686472617746756E6473006765745F53616C65506572696F6473007365745F53616C65506572696F64730073616C65506572696F64730056616C6964617465506572696F647300536574506572696F647300706572696F647300537472617469732E536D617274436F6E7472616374732E5374616E646172647300536574537570706F72746564496E74657266616365730053797374656D2E52756E74696D652E436F6D70696C6572536572766963657300446562756767696E674D6F646573004765744279746573005365744279746573004475726174696F6E426C6F636B73006765745F446563696D616C73007365745F446563696D616C7300646563696D616C73005769746864726177546F6B656E73006765745F53756363657373006765745F4B594341646472657373007365745F4B594341646472657373006B796341646472657373006765745F546F6B656E41646472657373007365745F546F6B656E41646472657373006765745F4D617070657241646472657373007365745F4D617070657241646472657373006D617070657241646472657373006F70657261746F7241646472657373006765745F4E6577436F6E74726163744164647265737300476574416464726573730053657441646472657373006164647265737300537472617469732E536D617274436F6E74726163747300436F6E7665727453616C65506572696F64496E70757473006F705F4D6F64756C757300466F726D61740053544F436F6E747261637400437265617465546F6B656E436F6E7472616374004973436F6E747261637400536D617274436F6E7472616374004F626A6563740047657453747275637400536574537472756374006F705F496D706C69636974006F705F4578706C696369740049437265617465526573756C7400495472616E73666572526573756C74004765744F776E6572546F4E46546F6B656E436F756E74005365744F776E6572546F4E46546F6B656E436F756E74005570646174654163636F756E74004765744163636F756E74005365744163636F756E74006163636F756E74004F6C64416D6F756E7400526566756E64416D6F756E7400546F6B656E416D6F756E740063757272656E74416D6F756E7400616D6F756E740041737365727400496E766573740053616C65506572696F64496E70757400576974686472617700436C656172546F6B656E4279496E64657800476574546F6B656E4279496E64657800536574546F6B656E4279496E64657800436C656172546F6B656E4F664F776E65724279496E64657800476574546F6B656E4F664F776E65724279496E64657800536574546F6B656E4F664F776E65724279496E64657800696E64657800546F4172726179004765744172726179005365744172726179004765744964546F417070726F76616C4B657900496E6465784F664F776E65724279546F6B656E4B657900476574496E6465784279546F6B656E4B6579004765744964546F4F776E65724B657900476574546F6B656E4279496E6465784B657900476574546F6B656E4F664F776E65724279496E6465784B657900456E737572654F776E65724F6E6C79006F705F4D756C7469706C79006765745F546F74616C537570706C79007365745F546F74616C537570706C7900746F74616C537570706C79006F705F457175616C697479006F705F496E657175616C69747900456E737572654164647265737349734E6F74456D7074790000134400690076006900640065006E006400730000174100630063006F0075006E0074003A007B0030007D00003B54006800650020006100630063006F0075006E007400200068006100730020006E006F0020006400690076006900640065006E00640073002E0000215400720061006E00730066006500720020006600610069006C00650064002E00000D530079006D0062006F006C0000094E0061006D006500001754006F00740061006C0053007500700070006C007900001144006500630069006D0061006C0073000017420061006C0061006E00630065003A007B0030007D00002341006C006C006F00770061006E00630065003A007B0030007D003A007B0031007D00002D53007500700070006F00720074006500640049006E0074006500720066006100630065003A007B0030007D00001B4900640054006F004F0077006E00650072003A007B0030007D0000214900640054006F0041007000700072006F00760061006C003A007B0030007D00002F4F0077006E006500720054006F004E00460054006F006B0065006E0043006F0075006E0074003A007B0030007D00002F4F0077006E006500720054006F004F00700065007200610074006F0072003A007B0030007D003A007B0031007D00000B4F0077006E006500720000174E0065007800740054006F006B0065006E0049006400002154006F006B0065006E004200790049006E006400650078003A007B0030007D00002149006E006400650078004200790054006F006B0065006E003A007B0030007D00003754006F006B0065006E004F0066004F0077006E00650072004200790049006E006400650078003A007B0030007D003A007B0031007D00003749006E006400650078004F0066004F0077006E00650072004200790054006F006B0065006E003A007B0030007D003A007B0031007D00002B540068006500200069006E00640065007800200069007300200069006E00760061006C00690064002E00001D41007300730065007200740020006600610069006C00650064002E0000354F006E004E006F006E00460075006E006700690062006C00650054006F006B0065006E0052006500630065006900760065006400005B4F006E006C00790020006F0077006E006500720020006F0066002000740068006500200063006F006E00740072006100630074002000630061006E00200073006500740020006E006500770020006F0077006E00650072002E00004B740068006500200061006D006F0075006E0074002000730068006F0075006C006400200062006500200068006900670068006500720020007400680061006E0020007A00650072006F0000494F006E006C007900200074006F006B0065006E0020006F0077006E00650072002000630061006E0020006200750072006E002000740068006500200074006F006B0065006E002E000039540068006500200061006400640072006500730073002000630061006E0020006E006F00740020006200650020007A00650072006F002E00001145006E00640042006C006F0063006B00001954006F006B0065006E00410064006400720065007300730000154B00590043004100640064007200650073007300001B4D00610070007000650072004100640064007200650073007300001954006F006B0065006E00420061006C0061006E00630065000025490073004E006F006E00460075006E006700690062006C00650054006F006B0065006E000017530061006C00650050006500720069006F0064007300005354006800650020007B0030007D00200070006100720061006D0065007400650072002000630061006E0020006200650020006200650074007700650065006E0020003000200061006E006400200032002E00001374006F006B0065006E005400790070006500004354006800650020007B0030007D0020006900730020006E006F00740020006100200063006F006E007400720061006300740020006100640072006500730073002E0000156B00790063004100640064007200650073007300001B6D00610070007000650072004100640064007200650073007300003F4300720065006100740069006E006700200074006F006B0065006E00200063006F006E007400720061006300740020006600610069006C00650064002E00002B5400680065002000530054004F00200069007300200063006F006D0070006C0065007400650064002E00004B540068006500200061006D006F0075006E0074002000730068006F0075006C006400200062006500200068006900670068006500720020007400680061006E0020007A00650072006F0000155400720061006E00730066006500720054006F00000F4D0069006E00740041006C006C00002D54006F006B0065006E0020007400720061006E00730066006500720020006600610069006C00650064002E00001D52006500660075006E00640020006600610069006C00650064002E0000274700650074005300650063006F006E0064006100720079004100640064007200650073007300001147006500740043006C00610069006D00003359006F007500720020004B005900430020006900730020006E006F0074002000760065007200690066006900650064002E00003754006800650020006100640064007200650073007300200068006100730020006E006F0020006D0061007000700069006E0067002E00004F4F006E006C007900200063006F006E007400720061006300740020006F0077006E00650072002000630061006E0020007400720061006E0073006600650072002000660075006E00640073002E00002B530054004F0020006900730020006E006F007400200065006E0064006500640020007900650074002E00006F54006800650020007B0030007D0020006D006500740068006F00640020006900730020006E006F007400200073007500700070006F007200740065006400200066006F00720020004E006F006E002D00460075006E006700690062006C006500200054006F006B0065006E002E00011D5700690074006800640072006100770054006F006B0065006E00730000514F006E006C007900200063006F006E007400720061006300740020006F0077006E00650072002000630061006E0020007400720061006E007300660065007200200074006F006B0065006E0073002E00004B50006C0065006100730065002000700072006F00760069006400650020006100740020006C006500610073007400200031002000730061006C006500200070006500720069006F006400004D4400750072006100740069006F006E0042006C006F0063006B0073002000730068006F0075006C006400200068006900670068006500720020007400680061006E0020007A00650072006F000000F4631C212C70084788C53B6008B0C31200042001010803200001052001011111042001010E04200012610420010B0E052002010E0B0500020E0E1C063001011E000E040A01111807300102010E1E0005200101123D042000126904200011390320000B0607021118114105000111410807000202114111410800021141114111410307010B05000111410B04070111180500010B114107070311180B124505200201020E072002124511390B032000020420010E0E052002010E0E05200111410E062002010E11410520011D050E062002010E1D050607021141111C06300101011E00040A01111C08070311411141111C0407011120040A0111200600030E0E1C1C042001020E052002010E0205200111390E062002010E1139040701113907000202113911390607040B0B0B0B0407020B0B03061139060702113912450520010211390B2005124511390B0E1D1C0B0320001C0407011124040A0111240407011128040A011128040701112C040A01112C0407011130040A0111300507030B0B0B08070511390B0B0B0B06070211411134040A0111340807031141114111340407011138040A011138042000127D073001011D1E000E040A011140072002010E1280810B07041D113C115012551148052000128085083001011D1E001D05040A01113C040A0111480930010312550B1D1C0B040A011210040A011208040A01120C08070311441245114C040A01114C0B0705124511391C11391D05021D0504070112450A070411401D11400811400507011D11400B07051D11400B08113C11400807031D113C08113C0B070611400B11410B0B1144087CEC85D7BEA7798E0400000000040100000004020000000306114102060B02060202060903061150042001010B0620011118113907200201113911180A200501123D11410E0E050720020211391141092003021139113911410820021141113911180520010B11390320000E0420001141052001011141032000050420010105062001114111390720020111391141092003021139114111410920030111391139114108200211411139113904200102090520020109020420010E0B05200111390B062002010B11390620020111390B07200202113911390820030111391139020520010111390420010B0B052002010B0B0620020E11390B0620020B11390B0720030111390B0B07200301123D0E0E0A200401113911390B1D0508200301113911390B0620020111390204200101020520001D1140062001011D114013200A01123D11390911410E0E09113911391D050B20051255115011410E0E090420001140062001011D113C0820011D11401D113C04200011440328000B0328000E0428001141032800050428001139032800020528001D11400801000800000000001E01000100540216577261704E6F6E457863657074696F6E5468726F777301080100020000000000350100182E4E4554436F72654170702C56657273696F6E3D76322E310100540E144672616D65776F726B446973706C61794E616D65001001000B49434F436F6E747261637400000A010005446562756700000C010007312E302E302E3000000A010005312E302E30000000000000000000000000000000000010000000000000000000000000000000946D00000000000000000000AE6D0000002000000000000000000000000000000000000000000000A06D0000000000000000000000005F436F72446C6C4D61696E006D73636F7265652E646C6C0000000000FF2500200010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006000000C000000C03D00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +``` diff --git a/Mainnet/STOContract/STOContract.Tests/AddressExtensions.cs b/Mainnet/STOContract/STOContract.Tests/AddressExtensions.cs new file mode 100644 index 00000000..4d21961e --- /dev/null +++ b/Mainnet/STOContract/STOContract.Tests/AddressExtensions.cs @@ -0,0 +1,42 @@ +using Stratis.SmartContracts; +using System; +using System.Collections.Generic; +using System.Text; + +namespace STOContractTests +{ + public static class AddressExtensions + { + private static byte[] HexStringToBytes(string val) + { + if (val.StartsWith("0x", StringComparison.OrdinalIgnoreCase)) + val = val.Substring(2); + + byte[] ret = new byte[val.Length / 2]; + for (int i = 0; i < val.Length; i = i + 2) + { + string hexChars = val.Substring(i, 2); + ret[i / 2] = byte.Parse(hexChars, System.Globalization.NumberStyles.HexNumber); + } + return ret; + } + + public static Address HexToAddress(this string hexString) + { + // uint160 only parses a big-endian hex string + var result = HexStringToBytes(hexString); + return CreateAddress(result); + } + + private static Address CreateAddress(byte[] bytes) + { + uint pn0 = BitConverter.ToUInt32(bytes, 0); + uint pn1 = BitConverter.ToUInt32(bytes, 4); + uint pn2 = BitConverter.ToUInt32(bytes, 8); + uint pn3 = BitConverter.ToUInt32(bytes, 12); + uint pn4 = BitConverter.ToUInt32(bytes, 16); + + return new Address(pn0, pn1, pn2, pn3, pn4); + } + } +} diff --git a/Mainnet/STOContract/STOContract.Tests/CreateResult.cs b/Mainnet/STOContract/STOContract.Tests/CreateResult.cs new file mode 100644 index 00000000..850e2fb8 --- /dev/null +++ b/Mainnet/STOContract/STOContract.Tests/CreateResult.cs @@ -0,0 +1,16 @@ +using Stratis.SmartContracts; + +namespace STOContractTests +{ + public class CreateResult : ICreateResult + { + public Address NewContractAddress { get; private set; } + + public bool Success { get; private set; } + + public static ICreateResult Failed() => new CreateResult { Success = false }; + + + public static ICreateResult Succeed(Address address) => new CreateResult { Success = true, NewContractAddress = address }; + } +} diff --git a/Mainnet/STOContract/STOContract.Tests/InMemoryState.cs b/Mainnet/STOContract/STOContract.Tests/InMemoryState.cs new file mode 100644 index 00000000..2e230334 --- /dev/null +++ b/Mainnet/STOContract/STOContract.Tests/InMemoryState.cs @@ -0,0 +1,83 @@ +using Stratis.SmartContracts; +using System; +using System.Collections.Generic; + +namespace STOContractTests +{ + public class InMemoryState : IPersistentState + { + private readonly Dictionary storage = new Dictionary(); + public bool IsContractResult { get; set; } + public void Clear(string key) => storage.Remove(key); + + public T GetValue(string key) => (T)storage.GetValueOrDefault(key, default(T)); + + public void AddOrReplace(string key, object value) + { + if (!storage.TryAdd(key,value)) + storage[key] = value; + } + public Address GetAddress(string key) => GetValue
(key); + + public T[] GetArray(string key) => GetValue(key); + + public bool GetBool(string key) => GetValue(key); + + public byte[] GetBytes(byte[] key) => throw new NotImplementedException(); + + public byte[] GetBytes(string key) => GetValue(key); + + public char GetChar(string key) => GetValue(key); + + public int GetInt32(string key) => GetValue(key); + + public long GetInt64(string key) => GetValue(key); + + public string GetString(string key) => GetValue(key); + + public T GetStruct(string key) + where T : struct => GetValue(key); + + public uint GetUInt32(string key) => GetValue(key); + + public ulong GetUInt64(string key) => GetValue(key); + + public UInt128 GetUInt128(string key) => GetValue(key); + + public UInt256 GetUInt256(string key) => GetValue(key); + + public bool IsContract(Address address) => IsContractResult; + + public void SetAddress(string key, Address value) => AddOrReplace(key, value); + + public void SetArray(string key, Array a) => AddOrReplace(key, a); + + public void SetBool(string key, bool value) => AddOrReplace(key, value); + + public void SetBytes(byte[] key, byte[] value) + { + throw new NotImplementedException(); + } + + public void SetBytes(string key, byte[] value) => AddOrReplace(key, value); + + public void SetChar(string key, char value) => AddOrReplace(key, value); + + public void SetInt32(string key, int value) => AddOrReplace(key, value); + + public void SetInt64(string key, long value) => AddOrReplace(key, value); + + public void SetString(string key, string value) => AddOrReplace(key, value); + + public void SetStruct(string key, T value) + where T : struct => AddOrReplace(key, value); + + public void SetUInt32(string key, uint value) => AddOrReplace(key, value); + + public void SetUInt64(string key, ulong value) => AddOrReplace(key, value); + + public void SetUInt128(string key, UInt128 value) => AddOrReplace(key, value); + + public void SetUInt256(string key, UInt256 value) => AddOrReplace(key, value); + } +} \ No newline at end of file diff --git a/Mainnet/STOContract/STOContract.Tests/STOContract.Tests.csproj b/Mainnet/STOContract/STOContract.Tests/STOContract.Tests.csproj new file mode 100644 index 00000000..5d87b345 --- /dev/null +++ b/Mainnet/STOContract/STOContract.Tests/STOContract.Tests.csproj @@ -0,0 +1,17 @@ + + + + Exe + netcoreapp3.1 + STOContractTests + + + + + + + + + + + diff --git a/Mainnet/STOContract/STOContract.Tests/STOContractTests.cs b/Mainnet/STOContract/STOContract.Tests/STOContractTests.cs new file mode 100644 index 00000000..4bb16d8c --- /dev/null +++ b/Mainnet/STOContract/STOContract.Tests/STOContractTests.cs @@ -0,0 +1,377 @@ +namespace STOContractTests +{ + using Moq; + using Stratis.SmartContracts; + using Xunit; + using SalePeriod = STOContract.SalePeriod; + using SalePeriodInput = STOContract.SalePeriodInput; + using TokenType = STOContract.TokenType; + + public class STOContractTests + { + private const ulong Satoshis = 100_000_000; + + private readonly Mock mContractState; + private readonly Mock mContractLogger; + private readonly Mock mTransactionExecutor; + private readonly Mock mSerializer; + private readonly Mock mBlock; + + private readonly Address sender; + private readonly Address owner; + private readonly Address investor; + private readonly Address identity; + private readonly Address currentContract; + private readonly Address tokenContract; + private readonly Address kycContract; + private readonly Address mapperContract; + + private readonly InMemoryState state; + private UInt256 totalSupply; + private readonly string name; + private readonly string symbol; + private readonly uint decimals; + + public STOContractTests() + { + mContractLogger = new Mock(); + mContractState = new Mock(); + mTransactionExecutor = new Mock(); + state = new InMemoryState(); + mBlock = new Mock(); + mContractState.Setup(s => s.Block).Returns(mBlock.Object); + mContractState.Setup(s => s.PersistentState).Returns(state); + mContractState.Setup(s => s.ContractLogger).Returns(mContractLogger.Object); + mContractState.Setup(s => s.InternalTransactionExecutor).Returns(mTransactionExecutor.Object); + mSerializer = new Mock(); + mContractState.Setup(s => s.Serializer).Returns(mSerializer.Object); + sender = "0x0000000000000000000000000000000000000001".HexToAddress(); + owner = "0x0000000000000000000000000000000000000002".HexToAddress(); + investor = "0x0000000000000000000000000000000000000003".HexToAddress(); + identity = "0x0000000000000000000000000000000000000004".HexToAddress(); + currentContract = "0x0000000000000000000000000000000000000005".HexToAddress(); + tokenContract = "0x0000000000000000000000000000000000000006".HexToAddress(); + kycContract = "0x0000000000000000000000000000000000000007".HexToAddress(); + mapperContract = "0x0000000000000000000000000000000000000008".HexToAddress(); + + name = "Test Token"; + symbol = "TST"; + totalSupply = 100 * Satoshis; + decimals = 0; + state.IsContractResult = true; + } + + [Fact] + public void Constructor_IsContract_ReturnsFalse_ThrowsAssertException() + { + state.IsContractResult = false; + + Assert.Throws(() => Create(TokenType.StandardToken)); + } + + [Fact] + public void Constructor_TokenType_HigherThan2_ThrowsAssertException() + { + var tokenType = (TokenType)3; + + Assert.Throws(() => Create(tokenType)); + } + + [Fact] + public void Constructor_CreateReturnsFailedResult_ThrowsAssertException() + { + mTransactionExecutor.Setup(m => m.Create(mContractState.Object, 0, new object[] { totalSupply, name, symbol, decimals }, It.IsAny())).Returns(CreateResult.Failed()); + Assert.Throws(() => Create(TokenType.StandardToken)); + + mTransactionExecutor.Verify(m => m.Create(mContractState.Object, 0, new object[] { totalSupply, name, symbol, decimals }, It.IsAny()), Times.Once); + } + + [Fact] + public void Constructor_TokenTypeIsStandardToken_Success() + { + mTransactionExecutor.Setup(m => m.Create(mContractState.Object, 0, new object[] { totalSupply, name, symbol, decimals }, It.IsAny())).Returns(CreateResult.Succeed(tokenContract)); + var (contract, periods) = Create(TokenType.StandardToken); + + Assert.Equal(totalSupply, contract.TokenBalance); + Assert.Equal(owner, contract.Owner); + Assert.Equal(tokenContract, contract.TokenAddress); + Assert.Equal(4ul, contract.EndBlock); + Assert.Equal(periods, contract.SalePeriods); + mTransactionExecutor.Verify(m => m.Create(mContractState.Object, 0, new object[] { totalSupply, name, symbol, decimals }, 0), Times.Once); + } + + [Fact] + public void Constructor_TokenTypeIsDividendToken_Success() + { + mTransactionExecutor.Setup(m => m.Create(mContractState.Object, 0, new object[] { totalSupply, name, symbol, decimals }, It.IsAny())).Returns(CreateResult.Succeed(tokenContract)); + var (contract, periods) = Create(TokenType.DividendToken); + + Assert.Equal(totalSupply, contract.TokenBalance); + Assert.Equal(owner, contract.Owner); + Assert.Equal(tokenContract, contract.TokenAddress); + Assert.Equal(4ul, contract.EndBlock); + Assert.Equal(periods, contract.SalePeriods); + mTransactionExecutor.Verify(m => m.Create(mContractState.Object, 0, new object[] { totalSupply, name, symbol, decimals }, 0), Times.Once); + } + + [Fact] + public void Constructor_TokenTypeIsNonFungibleToken_Success() + { + mTransactionExecutor.Setup(m => m.Create(mContractState.Object, 0, new object[] { name, symbol }, It.IsAny())).Returns(CreateResult.Succeed(tokenContract)); + var (contract, periods) = Create(TokenType.NonFungibleToken); + + Assert.Equal((UInt256)ulong.MaxValue, contract.TokenBalance); + Assert.Equal(owner, contract.Owner); + Assert.Equal(tokenContract, contract.TokenAddress); + Assert.Equal(4ul, contract.EndBlock); + Assert.Equal(periods, contract.SalePeriods); + mTransactionExecutor.Verify(m => m.Create(mContractState.Object, 0, new object[] { name, symbol }, 0), Times.Once); + } + + public (STOContract contract, SalePeriod[] periods) Create(TokenType tokenType) + { + var periodInputs = new[] + { + new SalePeriodInput { PricePerToken = 3 * Satoshis, DurationBlocks = 1 }, + new SalePeriodInput { PricePerToken = 5 * Satoshis, DurationBlocks = 2 } + }; + var periods = new[] + { + new SalePeriod { PricePerToken = 3 * Satoshis, EndBlock = 2 }, + new SalePeriod { PricePerToken = 5 * Satoshis, EndBlock = 4 } + }; + + mContractState.Setup(m => m.Message).Returns(new Message(currentContract, owner, 0)); + mBlock.Setup(s => s.Number).Returns(1); + mSerializer.Setup(m => m.ToArray(new byte[0])).Returns(periodInputs); + var contract = new STOContract(mContractState.Object, owner, (uint)tokenType, totalSupply, name, symbol, decimals, kycContract, mapperContract, new byte[0]); + return (contract, periods); + } + + [Fact] + public void Invest_CalledForStandardToken_Success() + { + var amount = 15 * Satoshis; + mTransactionExecutor.Setup(m => m.Create(mContractState.Object, 0, new object[] { totalSupply, name, symbol, decimals }, It.IsAny())).Returns(CreateResult.Succeed(tokenContract)); + + var (contract, _) = Create(TokenType.StandardToken); + + mContractState.Setup(m => m.Message).Returns(new Message(currentContract, investor, amount)); + + mTransactionExecutor.Setup(m => m.Call(mContractState.Object, tokenContract, 0, nameof(StandardToken.TransferTo), new object[] { investor, (UInt256)5 }, It.IsAny())).Returns(TransferResult.Transferred(true)); + mTransactionExecutor.Setup(m => m.Call(mContractState.Object, mapperContract, 0, "GetSecondaryAddress", new object[] { investor }, It.IsAny())).Returns(TransferResult.Transferred(identity)); + mTransactionExecutor.Setup(m => m.Call(mContractState.Object, kycContract, 0, "GetClaim", new object[] { identity, 3U /*shufti kyc*/ }, It.IsAny())).Returns(TransferResult.Transferred(new byte[] { 1 })); + + Assert.True(contract.Invest()); + + Assert.Equal(totalSupply - 5ul, contract.TokenBalance); + mTransactionExecutor.Verify(s => s.Transfer(It.IsAny(), It.IsAny
(), It.IsAny()), Times.Never); + + mBlock.Setup(s => s.Number).Returns(4); + mTransactionExecutor.Setup(m => m.Call(mContractState.Object, tokenContract, 0, nameof(StandardToken.TransferTo), new object[] { investor, (UInt256)3 }, It.IsAny())).Returns(TransferResult.Transferred(true)); + + Assert.True(contract.Invest()); + + Assert.Equal(totalSupply - 8ul, contract.TokenBalance); + mTransactionExecutor.Verify(s => s.Transfer(It.IsAny(), It.IsAny
(), It.IsAny()), Times.Never); + } + + [Fact] + public void Invest_CalledForNonFungibleToken_Success() + { + var amount = 15 * Satoshis; + var totalSupply = (UInt256)ulong.MaxValue; + + mTransactionExecutor.Setup(m => m.Create(mContractState.Object, 0, new object[] { name, symbol }, It.IsAny())).Returns(CreateResult.Succeed(tokenContract)); + + var (contract, _) = Create(TokenType.NonFungibleToken); + + mContractState.Setup(m => m.Message).Returns(new Message(currentContract, investor, amount)); + + mTransactionExecutor.Setup(m => m.Call(mContractState.Object, tokenContract, 0, nameof(NonFungibleToken.MintAll), new object[] { investor, 5ul }, It.IsAny())).Returns(TransferResult.Transferred(true)); + mTransactionExecutor.Setup(m => m.Call(mContractState.Object, mapperContract, 0, "GetSecondaryAddress", new object[] { investor }, It.IsAny())).Returns(TransferResult.Transferred(identity)); + mTransactionExecutor.Setup(m => m.Call(mContractState.Object, kycContract, 0, "GetClaim", new object[] { identity, 3U /*shufti kyc*/ }, It.IsAny())).Returns(TransferResult.Transferred(new byte[] { 1 })); + + Assert.True(contract.Invest()); + + Assert.Equal(totalSupply - 5, contract.TokenBalance); + mTransactionExecutor.Verify(s => s.Transfer(It.IsAny(), It.IsAny
(), It.IsAny()), Times.Never); + + mBlock.Setup(s => s.Number).Returns(4); + mTransactionExecutor.Setup(m => m.Call(mContractState.Object, tokenContract, 0, nameof(NonFungibleToken.MintAll), new object[] { investor, 3ul }, It.IsAny())).Returns(TransferResult.Transferred(true)); + + Assert.True(contract.Invest()); + + Assert.Equal(totalSupply - 8ul, contract.TokenBalance); + mTransactionExecutor.Verify(s => s.Transfer(It.IsAny(), It.IsAny
(), It.IsAny()), Times.Never); + } + + [Fact] + public void Invest_Refunds_Oversold_Tokens() + { + totalSupply = 60; + var amount = 190 * Satoshis; + mTransactionExecutor.Setup(m => m.Create(mContractState.Object, 0, new object[] { totalSupply, name, symbol, decimals }, It.IsAny())).Returns(CreateResult.Succeed(tokenContract)); + + var (contract, _) = Create(TokenType.StandardToken); + + mContractState.Setup(m => m.Message).Returns(new Message(currentContract, investor, amount)); + mTransactionExecutor.Setup(m => m.Call(mContractState.Object, tokenContract, 0, nameof(StandardToken.TransferTo), new object[] { investor, totalSupply }, It.IsAny())).Returns(TransferResult.Transferred(true)); + mTransactionExecutor.Setup(m => m.Call(mContractState.Object, mapperContract, 0, "GetSecondaryAddress", new object[] { investor }, It.IsAny())).Returns(TransferResult.Transferred(identity)); + mTransactionExecutor.Setup(m => m.Call(mContractState.Object, kycContract, 0, "GetClaim", new object[] { identity, 3U /*shufti kyc*/ }, It.IsAny())).Returns(TransferResult.Transferred(new byte[] { 1 })); + mTransactionExecutor.Setup(m => m.Transfer(mContractState.Object, investor, 10 * Satoshis)).Returns(TransferResult.Transferred(null)); + Assert.True(contract.Invest()); + + Assert.Equal((UInt256)0, contract.TokenBalance); // All tokens are sold + mTransactionExecutor.Verify(s => s.Transfer(mContractState.Object, investor, 10 * Satoshis), Times.Once); + } + + [Fact] + public void Invest_Fails_If_TokenBalance_Is_Zero() + { + var amount = 1 * Satoshis; + mTransactionExecutor.Setup(m => m.Create(mContractState.Object, 0, new object[] { totalSupply, name, symbol, decimals }, It.IsAny())).Returns(CreateResult.Succeed(tokenContract)); + mTransactionExecutor.Setup(m => m.Call(mContractState.Object, kycContract, 0, "GetClaim", new object[] { investor, 3U /*shufti kyc*/ }, It.IsAny())).Returns(TransferResult.Transferred(new byte[] { 1 })); + + var (contract, _) = Create(TokenType.StandardToken); + mContractState.Setup(m => m.Message).Returns(new Message(currentContract, investor, amount)); + state.SetUInt256(nameof(STOContract.TokenBalance), 0); + + Assert.Throws(() => contract.Invest()); + } + + [Fact] + public void Invest_Fails_If_EndBlock_Reached() + { + var amount = 1 * Satoshis; + mTransactionExecutor.Setup(m => m.Create(mContractState.Object, 0, new object[] { totalSupply, name, symbol, decimals }, It.IsAny())).Returns(CreateResult.Succeed(tokenContract)); + + var (contract, _) = Create(TokenType.StandardToken); + + mContractState.Setup(m => m.Message).Returns(new Message(currentContract, investor, amount)); + mBlock.Setup(s => s.Number).Returns(5); + + Assert.Throws(() => contract.Invest()); + } + + [Fact] + public void Invest_Fails_If_Investment_Amount_Is_Zero() + { + var amount = 0ul; + mTransactionExecutor.Setup(m => m.Create(mContractState.Object, 0, new object[] { totalSupply, name, symbol, decimals }, It.IsAny())).Returns(CreateResult.Succeed(tokenContract)); + + var (contract, _) = Create(TokenType.StandardToken); + mContractState.Setup(m => m.Message).Returns(new Message(currentContract, investor, amount)); + + Assert.Throws(() => contract.Invest()); + } + + [Fact] + public void Invest_Fails_If_GetSecondaryAddress_Call_Fails() + { + var amount = 10ul; + mTransactionExecutor.Setup(m => m.Create(mContractState.Object, 0, new object[] { totalSupply, name, symbol, decimals }, It.IsAny())).Returns(CreateResult.Succeed(tokenContract)); + mTransactionExecutor.Setup(m => m.Call(mContractState.Object, mapperContract, 0, "GetSecondaryAddress", new object[] { investor }, It.IsAny())).Returns(TransferResult.Failed()); + var (contract, _) = Create(TokenType.StandardToken); + mContractState.Setup(m => m.Message).Returns(new Message(currentContract, investor, amount)); + + Assert.Throws(() => contract.Invest()); + } + + [Fact] + public void Invest_Fails_If_GetSecondaryAddress_Call_Returns_Zero_Address() + { + var amount = 10ul; + mTransactionExecutor.Setup(m => m.Create(mContractState.Object, 0, new object[] { totalSupply, name, symbol, decimals }, It.IsAny())).Returns(CreateResult.Succeed(tokenContract)); + mTransactionExecutor.Setup(m => m.Call(mContractState.Object, mapperContract, 0, "GetSecondaryAddress", new object[] { investor }, It.IsAny())).Returns(TransferResult.Transferred(Address.Zero)); + var (contract, _) = Create(TokenType.StandardToken); + mContractState.Setup(m => m.Message).Returns(new Message(currentContract, investor, amount)); + + Assert.Throws(() => contract.Invest()); + } + + [Fact] + public void Invest_Fails_If_GetClaim_Call_Fails() + { + var amount = 10ul; + mTransactionExecutor.Setup(m => m.Create(mContractState.Object, 0, new object[] { totalSupply, name, symbol, decimals }, It.IsAny())).Returns(CreateResult.Succeed(tokenContract)); + mTransactionExecutor.Setup(m => m.Call(mContractState.Object, mapperContract, 0, "GetSecondaryAddress", new object[] { investor }, It.IsAny())).Returns(TransferResult.Transferred(identity)); + mTransactionExecutor.Setup(m => m.Call(mContractState.Object, kycContract, 0, "GetClaim", new object[] { identity, 3U /*shufti kyc*/ }, It.IsAny())).Returns(TransferResult.Failed()); + var (contract, _) = Create(TokenType.StandardToken); + mContractState.Setup(m => m.Message).Returns(new Message(currentContract, investor, amount)); + + Assert.Throws(() => contract.Invest()); + } + + [Fact] + public void Invest_Fails_If_GetClaim_Call_Returns_Null() + { + var amount = 10ul; + mTransactionExecutor.Setup(m => m.Create(mContractState.Object, 0, new object[] { totalSupply, name, symbol, decimals }, It.IsAny())).Returns(CreateResult.Succeed(tokenContract)); + mTransactionExecutor.Setup(m => m.Call(mContractState.Object, mapperContract, 0, "GetSecondaryAddress", new object[] { investor }, It.IsAny())).Returns(TransferResult.Transferred(identity)); + mTransactionExecutor.Setup(m => m.Call(mContractState.Object, kycContract, 0, "GetClaim", new object[] { identity, 3U /*shufti kyc*/ }, It.IsAny())).Returns(TransferResult.Transferred(null)); + var (contract, _) = Create(TokenType.StandardToken); + mContractState.Setup(m => m.Message).Returns(new Message(currentContract, investor, amount)); + + Assert.Throws(() => contract.Invest()); + } + + [Fact] + public void WithdrawFunds_Fails_If_Caller_Is_Not_Owner() + { + var amount = 0ul; + mTransactionExecutor.Setup(m => m.Create(mContractState.Object, 0, new object[] { totalSupply, name, symbol, decimals }, It.IsAny())).Returns(CreateResult.Succeed(tokenContract)); + + var (contract, _) = Create(TokenType.StandardToken); + mContractState.Setup(m => m.Message).Returns(new Message(currentContract, investor, amount)); + mBlock.Setup(m => m.Number).Returns(5); + + Assert.Throws(() => contract.WithdrawFunds()); + } + + [Fact] + public void WithdrawFunds_Fails_If_Sale_Is_Open() + { + var amount = 0ul; + mTransactionExecutor.Setup(m => m.Create(mContractState.Object, 0, new object[] { totalSupply, name, symbol, decimals }, It.IsAny())).Returns(CreateResult.Succeed(tokenContract)); + + var (contract, _) = Create(TokenType.StandardToken); + mContractState.Setup(m => m.Message).Returns(new Message(currentContract, owner, amount)); + mBlock.Setup(m => m.Number).Returns(4); + + Assert.Throws(() => contract.WithdrawFunds()); + } + + [Fact] + public void WithdrawFunds_Called_By_Owner_Success() + { + var amount = 0ul; + mTransactionExecutor.Setup(m => m.Create(mContractState.Object, 0, new object[] { totalSupply, name, symbol, decimals }, It.IsAny())).Returns(CreateResult.Succeed(tokenContract)); + + var (contract, _) = Create(TokenType.StandardToken); + mContractState.Setup(m => m.Message).Returns(new Message(currentContract, owner, amount)); + mBlock.Setup(m => m.Number).Returns(4); + + Assert.Throws(() => contract.WithdrawFunds()); + } + + [Fact] + public void WithdrawTokens_Called_By_Owner_After_Sale_Is_Closed_Success() + { + var amount = 0ul; + mTransactionExecutor.Setup(m => m.Create(mContractState.Object, 0, new object[] { totalSupply, name, symbol, decimals }, It.IsAny())).Returns(CreateResult.Succeed(tokenContract)); + + var (contract, _) = Create(TokenType.StandardToken); + mContractState.Setup(m => m.Message).Returns(new Message(currentContract, owner, amount)); + mBlock.Setup(m => m.Number).Returns(5); + state.SetUInt256(nameof(contract.TokenBalance), 100); + mTransactionExecutor.Setup(m => m.Call(mContractState.Object, tokenContract, 0, nameof(StandardToken.TransferTo), new object[] { owner, (UInt256)100 }, It.IsAny())).Returns(TransferResult.Transferred(true)); + + var success = contract.WithdrawTokens(); + + Assert.True(success); + Assert.Equal((UInt256)0, state.GetUInt256(nameof(contract.TokenBalance))); + mTransactionExecutor.Verify(m => m.Call(mContractState.Object, tokenContract, 0, nameof(StandardToken.TransferTo), new object[] { owner, (UInt256)100 }, 0)); + } + } +} diff --git a/Mainnet/STOContract/STOContract.Tests/TransferResult.cs b/Mainnet/STOContract/STOContract.Tests/TransferResult.cs new file mode 100644 index 00000000..1817264b --- /dev/null +++ b/Mainnet/STOContract/STOContract.Tests/TransferResult.cs @@ -0,0 +1,16 @@ +using Stratis.SmartContracts; + +namespace STOContractTests +{ + public class TransferResult : ITransferResult + { + public object ReturnValue { get; private set; } + + public bool Success { get; private set; } + + public static ITransferResult Failed() => new TransferResult { Success = false }; + + + public static ITransferResult Transferred(object returnValue) => new TransferResult { Success = true, ReturnValue = returnValue }; + } +} diff --git a/Mainnet/STOContract/STOContract.sln b/Mainnet/STOContract/STOContract.sln new file mode 100644 index 00000000..c41d8b23 --- /dev/null +++ b/Mainnet/STOContract/STOContract.sln @@ -0,0 +1,31 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.29920.165 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "STOContract", "STOContract\STOContract.csproj", "{5A9239B7-6AB3-40AC-BC6A-42B6F6E7FE76}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "STOContract.Tests", "STOContract.Tests\STOContract.Tests.csproj", "{658848FE-FF0B-43CE-8B58-96CCAD85CD8E}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {5A9239B7-6AB3-40AC-BC6A-42B6F6E7FE76}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5A9239B7-6AB3-40AC-BC6A-42B6F6E7FE76}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5A9239B7-6AB3-40AC-BC6A-42B6F6E7FE76}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5A9239B7-6AB3-40AC-BC6A-42B6F6E7FE76}.Release|Any CPU.Build.0 = Release|Any CPU + {658848FE-FF0B-43CE-8B58-96CCAD85CD8E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {658848FE-FF0B-43CE-8B58-96CCAD85CD8E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {658848FE-FF0B-43CE-8B58-96CCAD85CD8E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {658848FE-FF0B-43CE-8B58-96CCAD85CD8E}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {91890BE7-62E8-4268-ADB3-2AD475A31C1E} + EndGlobalSection +EndGlobal diff --git a/Mainnet/STOContract/STOContract/DividendToken.cs b/Mainnet/STOContract/STOContract/DividendToken.cs new file mode 100644 index 00000000..3b032045 --- /dev/null +++ b/Mainnet/STOContract/STOContract/DividendToken.cs @@ -0,0 +1,304 @@ +using Stratis.SmartContracts; +using Stratis.SmartContracts.Standards; + +public class DividendToken : SmartContract, IStandardToken256 +{ + public ulong Dividends + { + get => State.GetUInt64(nameof(this.Dividends)); + private set => State.SetUInt64(nameof(this.Dividends), value); + } + + private Account GetAccount(Address address) => State.GetStruct($"Account:{address}"); + + private void SetAccount(Address address, Account account) => State.SetStruct($"Account:{address}", account); + + + public DividendToken(ISmartContractState state, UInt256 totalSupply, string name, string symbol, byte decimals) + : base(state) + { + this.TotalSupply = totalSupply; + this.Name = name; + this.Symbol = symbol; + this.Decimals = decimals; + this.SetBalance(Message.Sender, totalSupply); + } + + + public override void Receive() + { + DistributeDividends(); + } + + /// + /// It is advised that deposit amount should to be evenly divided by total supply, + /// otherwise small amount of satoshi may lost(burn) + /// + public void DistributeDividends() + { + Dividends += Message.Value; + } + + public bool TransferTo(Address to, UInt256 amount) + { + UpdateAccount(Message.Sender); + UpdateAccount(to); + + return TransferTokensTo(to, amount); + } + + public bool TransferFrom(Address from, Address to, UInt256 amount) + { + UpdateAccount(from); + UpdateAccount(to); + + return TransferTokensFrom(from, to, amount); + } + + private Account UpdateAccount(Address address) + { + var account = GetAccount(address); + var newDividends = GetNewDividends(address, account); + + if (newDividends > 0) + { + account.DividendBalance += newDividends; + account.CreditedDividends = Dividends; + SetAccount(address, account); + } + + return account; + } + + private UInt256 GetWithdrawableDividends(Address address, Account account) + { + return account.DividendBalance + GetNewDividends(address, account); //Delay divide by TotalSupply to final stage for avoid decimal value loss. + } + + private UInt256 GetNewDividends(Address address, Account account) + { + var notCreditedDividends = checked(Dividends - account.CreditedDividends); + return GetBalance(address) * notCreditedDividends; + } + + /// + /// Get Withdrawable dividends + /// + /// + public ulong GetDividends() => GetDividends(Message.Sender); + + /// + /// Get Withdrawable dividends + /// + /// + /// + public ulong GetDividends(Address address) + { + var account = GetAccount(address); + + var withdrawable = GetWithdrawableDividends(address, account) / TotalSupply; + + return (ulong)withdrawable; + } + + /// + /// Get the all divididends since beginning (Withdrawable Dividends + Withdrawn Dividends) + /// + /// + public ulong GetTotalDividends() => GetTotalDividends(Message.Sender); + + /// + /// Get the all divididends since beginning (Withdrawable Dividends + Withdrawn Dividends) + /// + /// + /// + public ulong GetTotalDividends(Address address) + { + var account = GetAccount(address); + var withdrawable = GetWithdrawableDividends(address, account) / TotalSupply; + return (ulong)withdrawable + account.WithdrawnDividends; + } + + /// + /// Withdraws all dividends + /// + public void Withdraw() + { + var account = UpdateAccount(Message.Sender); + var balance = (ulong)(account.DividendBalance / TotalSupply); + + Assert(balance > 0, "The account has no dividends."); + + account.WithdrawnDividends += balance; + + account.DividendBalance %= TotalSupply; + + SetAccount(Message.Sender, account); + + var transfer = Transfer(Message.Sender, balance); + + Assert(transfer.Success, "Transfer failed."); + } + + public struct Account + { + /// + /// Withdrawable Dividend Balance. Exact value should to divided by + /// + public UInt256 DividendBalance; + + public ulong WithdrawnDividends; + + /// + /// Dividends computed and added to + /// + + public ulong CreditedDividends; + } + + #region StandardToken code is inlined + + + public string Symbol + { + get => State.GetString(nameof(this.Symbol)); + private set => State.SetString(nameof(this.Symbol), value); + } + + public string Name + { + get => State.GetString(nameof(this.Name)); + private set => State.SetString(nameof(this.Name), value); + } + + /// + public UInt256 TotalSupply + { + get => State.GetUInt256(nameof(this.TotalSupply)); + private set => State.SetUInt256(nameof(this.TotalSupply), value); + } + + public byte Decimals + { + get => State.GetBytes(nameof(Decimals))[0]; + private set => State.SetBytes(nameof(Decimals), new[] { value }); + } + + + /// + public UInt256 GetBalance(Address address) + { + return State.GetUInt256($"Balance:{address}"); + } + + private void SetBalance(Address address, UInt256 value) + { + State.SetUInt256($"Balance:{address}", value); + } + + /// + private bool TransferTokensTo(Address to, UInt256 amount) + { + if (amount == 0) + { + Log(new TransferLog { From = Message.Sender, To = to, Amount = 0 }); + + return true; + } + + UInt256 senderBalance = GetBalance(Message.Sender); + + if (senderBalance < amount) + { + return false; + } + + SetBalance(Message.Sender, senderBalance - amount); + + SetBalance(to, checked(GetBalance(to) + amount)); + + Log(new TransferLog { From = Message.Sender, To = to, Amount = amount }); + + return true; + } + + /// + private bool TransferTokensFrom(Address from, Address to, UInt256 amount) + { + if (amount == 0) + { + Log(new TransferLog { From = from, To = to, Amount = 0 }); + + return true; + } + + UInt256 senderAllowance = Allowance(from, Message.Sender); + UInt256 fromBalance = GetBalance(from); + + if (senderAllowance < amount || fromBalance < amount) + { + return false; + } + + SetApproval(from, Message.Sender, senderAllowance - amount); + + SetBalance(from, fromBalance - amount); + + SetBalance(to, checked(GetBalance(to) + amount)); + + Log(new TransferLog { From = from, To = to, Amount = amount }); + + return true; + } + + /// + public bool Approve(Address spender, UInt256 currentAmount, UInt256 amount) + { + if (Allowance(Message.Sender, spender) != currentAmount) + { + return false; + } + + SetApproval(Message.Sender, spender, amount); + + Log(new ApprovalLog { Owner = Message.Sender, Spender = spender, Amount = amount, OldAmount = currentAmount }); + + return true; + } + + private void SetApproval(Address owner, Address spender, UInt256 value) + { + State.SetUInt256($"Allowance:{owner}:{spender}", value); + } + + /// + public UInt256 Allowance(Address owner, Address spender) + { + return State.GetUInt256($"Allowance:{owner}:{spender}"); + } + + public struct TransferLog + { + [Index] + public Address From; + + [Index] + public Address To; + + public UInt256 Amount; + } + + public struct ApprovalLog + { + [Index] + public Address Owner; + + [Index] + public Address Spender; + + public UInt256 OldAmount; + + public UInt256 Amount; + } + #endregion +} diff --git a/Mainnet/STOContract/STOContract/NonFungibleToken.cs b/Mainnet/STOContract/STOContract/NonFungibleToken.cs new file mode 100644 index 00000000..c1e9fe43 --- /dev/null +++ b/Mainnet/STOContract/STOContract/NonFungibleToken.cs @@ -0,0 +1,629 @@ +using Stratis.SmartContracts; +/// +/// A non fungible token contract. +/// +public class NonFungibleToken : SmartContract +{ + public struct TransferLog + { + [Index] + public Address From; + [Index] + public Address To; + [Index] + public ulong TokenId; + } + + public struct ApprovalLog + { + [Index] + public Address Owner; + [Index] + public Address Approved; + [Index] + public ulong TokenId; + } + + public struct ApprovalForAllLog + { + [Index] + public Address Owner; + [Index] + public Address Operator; + + public bool Approved; + } + + public struct OwnershipTransferedLog + { + [Index] + public Address PreviousOwner; + + [Index] + public Address NewOwner; + } + + /// + /// Function to check which interfaces are supported by this contract. + /// + /// Id of the interface. + /// True if is supported, false otherwise. + public bool SupportsInterface(uint interfaceID) + { + return State.GetBool($"SupportedInterface:{interfaceID}"); + } + + /// + /// Sets the supported interface value. + /// + /// The interface id. + /// A value indicating if the interface id is supported. + private void SetSupportedInterfaces(uint interfaceId, bool value) => State.SetBool($"SupportedInterface:{interfaceId}", value); + + /// + /// Gets the key to the persistent state for the owner by NFT ID. + /// + /// The NFT ID. + /// The persistent storage key to get or set the NFT owner. + private string GetIdToOwnerKey(ulong id) => $"IdToOwner:{id}"; + + /// + /// Gets the address of the owner of the NFT ID. + /// + /// The ID of the NFT + ///The owner address. + private Address GetIdToOwner(ulong id) => State.GetAddress(GetIdToOwnerKey(id)); + + /// + /// Sets the owner to the NFT ID. + /// + /// The ID of the NFT + /// The address of the owner. + private void SetIdToOwner(ulong id, Address value) => State.SetAddress(GetIdToOwnerKey(id), value); + + /// + /// Gets the key to the persistent state for the approval address by NFT ID. + /// + /// The NFT ID. + /// The persistent storage key to get or set the NFT approval. + private string GetIdToApprovalKey(ulong id) => $"IdToApproval:{id}"; + + /// + /// Getting from NFT ID the approval address. + /// + /// The ID of the NFT + /// Address of the approval. + private Address GetIdToApproval(ulong id) => State.GetAddress(GetIdToApprovalKey(id)); + + /// + /// Setting to NFT ID to approval address. + /// + /// The ID of the NFT + /// The address of the approval. + private void SetIdToApproval(ulong id, Address value) => State.SetAddress(GetIdToApprovalKey(id), value); + + /// + /// Gets the amount of non fungible tokens the owner has. + /// + /// The address of the owner. + /// The amount of non fungible tokens. + private ulong GetOwnerToNFTokenCount(Address address) => State.GetUInt64($"OwnerToNFTokenCount:{address}"); + + /// + /// Sets the owner count of this non fungible tokens. + /// + /// The address of the owner. + /// The amount of tokens. + private void SetOwnerToNFTokenCount(Address address, ulong value) => State.SetUInt64($"OwnerToNFTokenCount:{address}", value); + + /// + /// Gets the permission value of the operator authorization to perform actions on behalf of the owner. + /// + /// The owner address of the NFT. + /// >Address of the authorized operators + /// A value indicating if the operator has permissions to act on behalf of the owner. + private bool GetOwnerToOperator(Address owner, Address operatorAddress) => State.GetBool($"OwnerToOperator:{owner}:{operatorAddress}"); + + /// + /// Sets the owner to operator permission. + /// + /// The owner address of the NFT. + /// >Address to add to the set of authorized operators. + /// The permission value. + private void SetOwnerToOperator(Address owner, Address operatorAddress, bool value) => State.SetBool($"OwnerToOperator:{owner}:{operatorAddress}", value); + + /// + /// Owner of the contract is responsible to for minting/burning + /// + public Address Owner + { + get => State.GetAddress(nameof(Owner)); + private set => State.SetAddress(nameof(Owner), value); + } + + /// + /// Name for non-fungible token contract + /// + public string Name + { + get => State.GetString(nameof(Name)); + private set => State.SetString(nameof(Name), value); + } + + /// + /// Symbol for non-fungible token contract + /// + public string Symbol + { + get => State.GetString(nameof(Symbol)); + private set => State.SetString(nameof(Symbol), value); + } + + /// + /// The next token index which is going to be minted + /// + private ulong NextTokenId + { + get => State.GetUInt64(nameof(NextTokenId)); + set => State.SetUInt64(nameof(NextTokenId), value); + } + + private string GetTokenByIndexKey(ulong index) => $"TokenByIndex:{index}"; + + private ulong GetTokenByIndex(ulong index) => State.GetUInt64(GetTokenByIndexKey(index)); + + private void SetTokenByIndex(ulong index, ulong token) => State.SetUInt64(GetTokenByIndexKey(index), token); + + private void ClearTokenByIndex(ulong index) => State.Clear(GetTokenByIndexKey(index)); + + private string GetIndexByTokenKey(ulong token) => $"IndexByToken:{token}"; + + private ulong GetIndexByToken(ulong token) => State.GetUInt64(GetIndexByTokenKey(token)); + + private void SetIndexByToken(ulong token, ulong index) => State.SetUInt64(GetIndexByTokenKey(token), index); + + private void ClearIndexByToken(ulong token) => State.Clear(GetIndexByTokenKey(token)); + + private string GetTokenOfOwnerByIndexKey(Address address, ulong index) => $"TokenOfOwnerByIndex:{address}:{index}"; + + private ulong GetTokenOfOwnerByIndex(Address address, ulong index) => State.GetUInt64(GetTokenOfOwnerByIndexKey(address, index)); + + private void SetTokenOfOwnerByIndex(Address owner, ulong index, ulong tokenId) => State.SetUInt64(GetTokenOfOwnerByIndexKey(owner, index), tokenId); + + private void ClearTokenOfOwnerByIndex(Address owner, ulong index) => State.Clear(GetTokenOfOwnerByIndexKey(owner, index)); + + private string IndexOfOwnerByTokenKey(Address owner, ulong tokenId) => $"IndexOfOwnerByToken:{owner}:{tokenId}"; + private ulong GetIndexOfOwnerByToken(Address owner, ulong tokenId) => State.GetUInt64(IndexOfOwnerByTokenKey(owner, tokenId)); + private void SetIndexOfOwnerByToken(Address owner, ulong tokenId, ulong index) => State.SetUInt64(IndexOfOwnerByTokenKey(owner, tokenId), index); + private void ClearIndexOfOwnerByToken(Address owner, ulong tokenId) => State.Clear(IndexOfOwnerByTokenKey(owner, tokenId)); + public ulong TotalSupply + { + get => State.GetUInt64(nameof(TotalSupply)); + private set => State.SetUInt64(nameof(TotalSupply), value); + } + + /// + /// Constructor. Initializes the supported interfaces. + /// + /// The smart contract state. + public NonFungibleToken(ISmartContractState state, string name, string symbol) : base(state) + { + // todo: discuss callback handling and supported interface numbering with community. + this.SetSupportedInterfaces((uint)0x00000001, true); // (ERC165) - ISupportsInterface + this.SetSupportedInterfaces((uint)0x00000002, true); // (ERC721) - INonFungibleToken, + this.SetSupportedInterfaces((uint)0x00000003, false); // (ERC721) - INonFungibleTokenReceiver + this.SetSupportedInterfaces((uint)0x00000004, true); // (ERC721) - INonFungibleTokenMetadata + this.SetSupportedInterfaces((uint)0x00000005, true); // (ERC721) - IERC721Enumerable + + this.Name = name; + this.Symbol = symbol; + this.Owner = Message.Sender; + this.NextTokenId = 1; + } + + public ulong TokenByIndex(ulong index) + { + Assert(index < TotalSupply, "The index is invalid."); + + return GetTokenByIndex(index); + } + + public ulong TokenOfOwnerByIndex(Address owner, ulong index) + { + Assert(index < GetOwnerToNFTokenCount(owner), "The index is invalid."); + + return GetTokenOfOwnerByIndex(owner, index); + } + + + + /// + /// Transfers the ownership of an NFT from one address to another address. This function can + /// be changed to payable. + /// + /// Throws unless is the current owner, an authorized operator, or the + /// approved address for this NFT.Throws if 'from' is not the current owner.Throws if 'to' is + /// the zero address.Throws if 'tokenId' is not a valid NFT. When transfer is complete, this + /// function checks if 'to' is a smart contract. If so, it calls + /// 'OnNonFungibleTokenReceived' on 'to' and throws if the return value true. + /// The current owner of the NFT. + /// The new owner. + /// The NFT to transfer. + /// Additional data with no specified format, sent in call to 'to'. + public void SafeTransferFrom(Address from, Address to, ulong tokenId, byte[] data) + { + SafeTransferFromInternal(from, to, tokenId, data); + } + + /// + /// Transfers the ownership of an NFT from one address to another address. This function can + /// be changed to payable. + /// + /// This works identically to the other function with an extra data parameter, except this + /// function just sets data to an empty byte array. + /// The current owner of the NFT. + /// The new owner. + /// The NFT to transfer. + public void SafeTransferFrom(Address from, Address to, ulong tokenId) + { + SafeTransferFromInternal(from, to, tokenId, new byte[0]); + } + + /// + /// Throws unless is the current owner, an authorized operator, or the approved + /// address for this NFT.Throws if is not the current owner.Throws if is the zero + /// address.Throws if is not a valid NFT. This function can be changed to payable. + /// + /// The caller is responsible to confirm that is capable of receiving NFTs or else + /// they maybe be permanently lost. + /// The current owner of the NFT. + /// The new owner. + /// The NFT to transfer. + public void TransferFrom(Address from, Address to, ulong tokenId) + { + CanTransfer(tokenId); + + Address tokenOwner = GetIdToOwner(tokenId); + EnsureAddressIsNotEmpty(tokenOwner); + EnsureAddressIsNotEmpty(to); + Assert(tokenOwner == from); + + TransferInternal(to, tokenId); + } + + /// + /// Set or reaffirm the approved address for an NFT. This function can be changed to payable. + /// + /// + /// The zero address indicates there is no approved address. Throws unless is + /// the current NFT owner, or an authorized operator of the current owner. + /// + /// Address to be approved for the given NFT ID. + /// ID of the token to be approved. + public void Approve(Address approved, ulong tokenId) + { + CanOperate(tokenId); + ValidNFToken(tokenId); + + Address tokenOwner = GetIdToOwner(tokenId); + Assert(approved != tokenOwner); + + SetIdToApproval(tokenId, approved); + LogApproval(tokenOwner, approved, tokenId); + } + + /// + /// Enables or disables approval for a third party ("operator") to manage all of + /// 's assets. It also Logs the ApprovalForAll event. + /// + /// This works even if sender doesn't own any tokens at the time. + /// Address to add to the set of authorized operators. + /// True if the operators is approved, false to revoke approval. + public void SetApprovalForAll(Address operatorAddress, bool approved) + { + SetOwnerToOperator(Message.Sender, operatorAddress, approved); + LogApprovalForAll(Message.Sender, operatorAddress, approved); + } + + /// + /// Returns the number of NFTs owned by 'owner'. NFTs assigned to the zero address are + /// considered invalid, and this function throws for queries about the zero address. + /// + /// Address for whom to query the balance. + /// Balance of owner. + public ulong BalanceOf(Address owner) + { + EnsureAddressIsNotEmpty(owner); + return GetOwnerToNFTokenCount(owner); + } + + /// + /// Returns the address of the owner of the NFT. NFTs assigned to zero address are considered invalid, and queries about them do throw. + /// + /// The identifier for an NFT. + /// Address of tokenId owner. + public Address OwnerOf(ulong tokenId) + { + Address owner = GetIdToOwner(tokenId); + EnsureAddressIsNotEmpty(owner); + return owner; + } + + /// + /// Get the approved address for a single NFT. + /// + /// Throws if 'tokenId' is not a valid NFT. + /// ID of the NFT to query the approval of. + /// Address that tokenId is approved for. + public Address GetApproved(ulong tokenId) + { + ValidNFToken(tokenId); + + return GetIdToApproval(tokenId); + } + + /// + /// Checks if 'operator' is an approved operator for 'owner'. + /// + /// The address that owns the NFTs. + /// The address that acts on behalf of the owner. + /// True if approved for all, false otherwise. + public bool IsApprovedForAll(Address owner, Address operatorAddress) + { + return GetOwnerToOperator(owner, operatorAddress); + } + + /// + /// Actually preforms the transfer. + /// + /// Does NO checks. + /// Address of a new owner. + /// The NFT that is being transferred. + private void TransferInternal(Address to, ulong tokenId) + { + Address from = GetIdToOwner(tokenId); + ClearApproval(tokenId); + + RemoveNFToken(from, tokenId); + AddNFToken(to, tokenId); + + LogTransfer(from, to, tokenId); + } + + /// + /// Removes a NFT from owner. + /// + /// Use and override this function with caution. Wrong usage can have serious consequences. + /// Address from wich we want to remove the NFT. + /// Which NFT we want to remove. + private void RemoveNFToken(Address from, ulong tokenId) + { + Assert(GetIdToOwner(tokenId) == from); + var tokenCount = GetOwnerToNFTokenCount(from); + SetOwnerToNFTokenCount(from, checked(tokenCount - 1)); + State.Clear(GetIdToOwnerKey(tokenId)); + + ulong index = GetIndexOfOwnerByToken(from, tokenId); + ulong lastIndex = tokenCount - 1; + + if (index != lastIndex) + { + ulong lastToken = GetTokenOfOwnerByIndex(from, lastIndex); + SetIndexOfOwnerByToken(from, lastToken, index); + SetTokenOfOwnerByIndex(from, index, lastToken); + } + + ClearTokenOfOwnerByIndex(from, lastIndex); + ClearIndexOfOwnerByToken(from, tokenId); + } + + /// + /// Assignes a new NFT to owner. + /// + /// Use and override this function with caution. Wrong usage can have serious consequences. + /// Address to which we want to add the NFT. + /// Which NFT we want to add. + private void AddNFToken(Address to, ulong tokenId) + { + Assert(GetIdToOwner(tokenId) == Address.Zero); + + SetIdToOwner(tokenId, to); + ulong currentTokenAmount = GetOwnerToNFTokenCount(to); + SetOwnerToNFTokenCount(to, checked(currentTokenAmount + 1)); + + var index = currentTokenAmount; + SetIndexOfOwnerByToken(to, tokenId, index); + SetTokenOfOwnerByIndex(to, index, tokenId); + } + + /// + /// Actually perform the safeTransferFrom. + /// + /// The current owner of the NFT. + /// The new owner. + /// The NFT to transfer. + /// Additional data with no specified format, sent in call to 'to' if it is a contract. + private void SafeTransferFromInternal(Address from, Address to, ulong tokenId, byte[] data) + { + CanTransfer(tokenId); + ValidNFToken(tokenId); + + Address tokenOwner = GetIdToOwner(tokenId); + Assert(tokenOwner == from); + EnsureAddressIsNotEmpty(to); + + TransferInternal(to, tokenId); + + if (State.IsContract(to)) + { + ITransferResult result = Call(to, 0, "OnNonFungibleTokenReceived", new object[] { Message.Sender, from, tokenId, data }, 0); + Assert((bool)result.ReturnValue); + } + } + + /// + /// Clears the current approval of a given NFT ID. + /// + /// ID of the NFT to be transferred + private void ClearApproval(ulong tokenId) + { + if (GetIdToApproval(tokenId) != Address.Zero) + { + State.Clear(GetIdToApprovalKey(tokenId)); + } + } + + /// + /// This logs when ownership of any NFT changes by any mechanism. This event logs when NFTs are + /// created('from' == 0) and destroyed('to' == 0). Exception: during contract creation, any + /// number of NFTs may be created and assigned without logging Transfer.At the time of any + /// transfer, the approved Address for that NFT (if any) is reset to none. + /// + /// The from address. + /// The to address. + /// The NFT ID. + private void LogTransfer(Address from, Address to, ulong tokenId) + { + Log(new TransferLog() { From = from, To = to, TokenId = tokenId }); + } + + /// + /// This logs when the approved Address for an NFT is changed or reaffirmed. The zero + /// Address indicates there is no approved Address. When a Transfer logs, this also + /// indicates that the approved Address for that NFT (if any) is reset to none. + /// + /// The owner address. + /// The approved address. + /// The NFT ID. + private void LogApproval(Address owner, Address approved, ulong tokenId) + { + Log(new ApprovalLog() { Owner = owner, Approved = approved, TokenId = tokenId }); + } + + /// + /// This logs when an operator is enabled or disabled for an owner. The operator can manage all NFTs of the owner. + /// + /// The owner address + /// The operator address. + /// A boolean indicating if it has been approved. + private void LogApprovalForAll(Address owner, Address operatorAddress, bool approved) + { + Log(new ApprovalForAllLog() { Owner = owner, Operator = operatorAddress, Approved = approved }); + } + + + /// + /// Guarantees that the is an owner or operator of the given NFT. + /// + /// ID of the NFT to validate. + private void CanOperate(ulong tokenId) + { + Address tokenOwner = GetIdToOwner(tokenId); + Assert(tokenOwner == Message.Sender || GetOwnerToOperator(tokenOwner, Message.Sender)); + } + + /// + /// Guarantees that the msg.sender is allowed to transfer NFT. + /// + /// ID of the NFT to transfer. + private void CanTransfer(ulong tokenId) + { + Address tokenOwner = GetIdToOwner(tokenId); + Assert( + tokenOwner == Message.Sender + || GetIdToApproval(tokenId) == Message.Sender + || GetOwnerToOperator(tokenOwner, Message.Sender) + ); + } + + /// + /// Guarantees that tokenId is a valid Token. + /// + /// ID of the NFT to validate. + private void ValidNFToken(ulong tokenId) + { + Address tokenOwner = GetIdToOwner(tokenId); + EnsureAddressIsNotEmpty(tokenOwner); + } + + /// + /// Sets the contract owner who can mint/bur + /// + /// + public void TransferOwnership(Address owner) + { + EnsureOwnerOnly(); + EnsureAddressIsNotEmpty(owner); + + Log(new OwnershipTransferedLog { PreviousOwner = Owner, NewOwner = owner }); + + Owner = owner; + } + + private void EnsureOwnerOnly() + { + Assert(Message.Sender == Owner, "Only owner of the contract can set new owner."); + } + + /// + /// Mints new tokens + /// + /// The address that will own the minted NFT + /// Number of tokens will be created + public void MintAll(Address address, ulong amount) + { + EnsureOwnerOnly(); + EnsureAddressIsNotEmpty(address); + Assert(amount > 0, "the amount should be higher than zero"); + + var index = TotalSupply; + var lastIndex = checked(index + amount); + var tokenId = NextTokenId; + + while (index < lastIndex) + { + AddNFToken(address, tokenId); + SetTokenByIndex(index, tokenId); + SetIndexByToken(tokenId, index); + + LogTransfer(Address.Zero, address, tokenId); + + checked + { + index++; + tokenId++; + } + } + + TotalSupply = checked(TotalSupply + amount); + NextTokenId = tokenId; + } + + public void Burn(ulong tokenId) + { + Address tokenOwner = GetIdToOwner(tokenId); + + Assert(tokenOwner == Message.Sender, "Only token owner can burn the token."); + + ClearApproval(tokenId); + RemoveNFToken(tokenOwner, tokenId); + + //move last token to removed token and delete last token info + var index = GetIndexByToken(tokenId); + var lastTokenIndex = checked(--TotalSupply); + var lastToken = GetTokenByIndex(lastTokenIndex); + + SetTokenByIndex(index, lastToken); + SetIndexByToken(lastToken, index); + + ClearTokenByIndex(lastTokenIndex); + ClearIndexByToken(tokenId); + + LogTransfer(tokenOwner, Address.Zero, tokenId); + } + + public void EnsureAddressIsNotEmpty(Address address) + { + Assert(address != Address.Zero, "The address can not be zero."); + } +} \ No newline at end of file diff --git a/Mainnet/STOContract/STOContract/STOContract.cs b/Mainnet/STOContract/STOContract/STOContract.cs new file mode 100644 index 00000000..7347324d --- /dev/null +++ b/Mainnet/STOContract/STOContract/STOContract.cs @@ -0,0 +1,284 @@ +using Stratis.SmartContracts; +using Stratis.SmartContracts.Standards; + +[Deploy] +public class STOContract : SmartContract +{ + public ulong EndBlock + { + get => State.GetUInt64(nameof(EndBlock)); + private set => State.SetUInt64(nameof(EndBlock), value); + } + + public Address TokenAddress + { + get => State.GetAddress(nameof(TokenAddress)); + private set => State.SetAddress(nameof(TokenAddress), value); + } + + public Address KYCAddress + { + get => State.GetAddress(nameof(KYCAddress)); + private set => State.SetAddress(nameof(KYCAddress), value); + } + + public Address MapperAddress + { + get => State.GetAddress(nameof(MapperAddress)); + private set => State.SetAddress(nameof(MapperAddress), value); + } + + public UInt256 TokenBalance + { + get => State.GetUInt256(nameof(TokenBalance)); + private set => State.SetUInt256(nameof(TokenBalance), value); + } + + public bool IsNonFungibleToken + { + get => State.GetBool(nameof(IsNonFungibleToken)); + private set => State.SetBool(nameof(IsNonFungibleToken), value); + } + + public bool SaleOpen => EndBlock >= Block.Number && TokenBalance > 0; + + public Address Owner + { + get => State.GetAddress(nameof(Owner)); + private set => State.SetAddress(nameof(Owner), value); + } + + public SalePeriod[] SalePeriods + { + get => State.GetArray(nameof(SalePeriods)); + private set => State.SetArray(nameof(SalePeriods), value); + } + + public STOContract(ISmartContractState smartContractState, + Address owner, + uint tokenType, + UInt256 totalSupply, + string name, + string symbol, + uint decimals, + Address kycAddress, + Address mapperAddress, + byte[] salePeriods) : base(smartContractState) + { + Assert(tokenType < 3, $"The {nameof(tokenType)} parameter can be between 0 and 2."); + + Assert(State.IsContract(kycAddress), $"The {nameof(kycAddress)} is not a contract adress."); + Assert(State.IsContract(mapperAddress), $"The {nameof(mapperAddress)} is not a contract adress."); + + var periods = Serializer.ToArray(salePeriods); + + ValidatePeriods(periods); + var tokenTypeEnum = (TokenType)tokenType; + var result = CreateTokenContract(tokenTypeEnum, totalSupply, name, symbol, decimals); + + Assert(result.Success, "Creating token contract failed."); + + Log(new STOSetupLog { TokenAddress = result.NewContractAddress }); + + KYCAddress = kycAddress; + MapperAddress = mapperAddress; + TokenAddress = result.NewContractAddress; + IsNonFungibleToken = tokenTypeEnum == TokenType.NonFungibleToken; + TokenBalance = IsNonFungibleToken ? (UInt256)ulong.MaxValue : totalSupply; + Owner = owner; + SetPeriods(periods); + } + + private ICreateResult CreateTokenContract(TokenType tokenType, UInt256 totalSupply, string name, string symbol, uint decimals) + { + switch (tokenType) + { + case TokenType.StandardToken: return Create(parameters: new object[] { totalSupply, name, symbol, decimals }); + case TokenType.DividendToken: return Create(parameters: new object[] { totalSupply, name, symbol, decimals }); + default: return Create(parameters: new object[] { name, symbol }); + } + } + public override void Receive() => Invest(); + + public bool Invest() + { + Assert(SaleOpen, "The STO is completed."); + Assert(Message.Value > 0, "The amount should be higher than zero"); + + EnsureKycVerified(); + + var saleInfo = GetSaleInfo(); + + var result = IsNonFungibleToken ? + Call(TokenAddress, 0, nameof(NonFungibleToken.MintAll), new object[] { Message.Sender, (ulong)saleInfo.TokenAmount }) : + Call(TokenAddress, 0, nameof(IStandardToken.TransferTo), new object[] { Message.Sender, saleInfo.TokenAmount }); + + Assert(result.Success && (bool)result.ReturnValue, "Token transfer failed."); + + Log(new InvestLog { Sender = Message.Sender, Invested = saleInfo.Invested, TokenAmount = saleInfo.TokenAmount, Refunded = saleInfo.RefundAmount }); + + TokenBalance -= saleInfo.TokenAmount; + + if (saleInfo.RefundAmount > 0) // refund over sold amount + { + result = Transfer(Message.Sender, saleInfo.RefundAmount); + Assert(result.Success, "Refund failed."); + } + + return true; + } + + private void EnsureKycVerified() + { + var result = Call(MapperAddress, 0, "GetSecondaryAddress", new object[] { Message.Sender }); + + if (result.Success && result.ReturnValue is Address identityAddress && identityAddress != Address.Zero) + { + result = Call(KYCAddress, 0, "GetClaim", new object[] { identityAddress, (uint)3 /*shufti kyc*/ }); + + Assert(result.Success && result.ReturnValue is byte[] b && b?.Length > 0, "Your KYC is not verified."); + + return; + } + + Assert(false, "The address has no mapping."); + } + + public bool WithdrawFunds() + { + Assert(Message.Sender == Owner, "Only contract owner can transfer funds."); + Assert(!SaleOpen, "STO is not ended yet."); + + var result = Transfer(Owner, Balance); + + return result.Success; + } + + + public bool WithdrawTokens() + { + Assert(!IsNonFungibleToken, $"The {nameof(WithdrawTokens)} method is not supported for Non-Fungible Token."); + Assert(Message.Sender == Owner, "Only contract owner can transfer tokens."); + Assert(!SaleOpen, "STO is not ended yet."); + + if (TokenBalance == 0) + return true; + + var result = Call(TokenAddress, 0, nameof(StandardToken.TransferTo), new object[] { Message.Sender, TokenBalance }); + + TokenBalance = 0; + + Assert(result.Success && (bool)result.ReturnValue, "Token transfer failed."); + + return true; + } + + private SalePeriod GetCurrentPeriod() + { + var result = default(SalePeriod); + + foreach (var period in SalePeriods) + { + if (period.EndBlock >= Block.Number) + { + result = period; + break; + } + } + + return result; + } + private void SetPeriods(SalePeriodInput[] periods) + { + var salePeriods = ConvertSalePeriodInputs(periods); + + SalePeriods = salePeriods; + EndBlock = salePeriods[salePeriods.Length - 1].EndBlock; + } + + private SalePeriod[] ConvertSalePeriodInputs(SalePeriodInput[] periods) + { + var result = new SalePeriod[periods.Length]; + var blockNumber = Block.Number; + for (int i = 0; i < periods.Length; i++) + { + var input = periods[i]; + blockNumber = checked(blockNumber + input.DurationBlocks); + result[i] = new SalePeriod + { + EndBlock = blockNumber, + PricePerToken = input.PricePerToken + }; + } + + return result; + } + + private void ValidatePeriods(SalePeriodInput[] periods) + { + Assert(periods.Length > 0, "Please provide at least 1 sale period"); + + foreach (var period in periods) + { + Assert(period.DurationBlocks > 0, "DurationBlocks should higher than zero"); + } + } + + private SaleInfo GetSaleInfo() + { + var period = GetCurrentPeriod(); + + var tokenAmount = Message.Value / period.PricePerToken; + + var tokenBalance = TokenBalance; + if (tokenAmount > tokenBalance) // refund over sold amount + { + var spend = (ulong)tokenBalance * period.PricePerToken; + var refund = Message.Value - spend; + + return new SaleInfo { Invested = spend, RefundAmount = refund, TokenAmount = tokenBalance }; + } + + return new SaleInfo { Invested = Message.Value, TokenAmount = tokenAmount }; + } + + public struct SalePeriodInput + { + public ulong DurationBlocks; + public ulong PricePerToken; + } + + public struct SalePeriod + { + public ulong EndBlock; + public ulong PricePerToken; + } + + public struct SaleInfo + { + public ulong Invested; + public ulong RefundAmount; + public UInt256 TokenAmount; + + } + + public struct STOSetupLog + { + public Address TokenAddress; + } + + public struct InvestLog + { + [Index] + public Address Sender; + public ulong Invested; + public UInt256 TokenAmount; + public ulong Refunded; + } + public enum TokenType : uint + { + StandardToken, + DividendToken, + NonFungibleToken + } +} diff --git a/Mainnet/STOContract/STOContract/STOContract.csproj b/Mainnet/STOContract/STOContract/STOContract.csproj new file mode 100644 index 00000000..235402b0 --- /dev/null +++ b/Mainnet/STOContract/STOContract/STOContract.csproj @@ -0,0 +1,17 @@ + + + + netcoreapp2.1 + + 8.0 + + + + + + + + Never + + + diff --git a/Mainnet/STOContract/STOContract/StandardToken.cs b/Mainnet/STOContract/STOContract/StandardToken.cs new file mode 100644 index 00000000..d72fafd7 --- /dev/null +++ b/Mainnet/STOContract/STOContract/StandardToken.cs @@ -0,0 +1,166 @@ +using Stratis.SmartContracts; +using Stratis.SmartContracts.Standards; +/// +/// Implementation of a standard token contract for the Stratis Platform. +/// +public class StandardToken : SmartContract, IStandardToken256 +{ + /// + /// Constructor used to create a new instance of the token. Assigns the total token supply to the creator of the contract. + /// + /// The execution state for the contract. + /// The total token supply. + /// The name of the token. + /// The symbol used to identify the token. + public StandardToken(ISmartContractState smartContractState, UInt256 totalSupply, string name, string symbol, byte decimals) + : base(smartContractState) + { + this.TotalSupply = totalSupply; + this.Name = name; + this.Symbol = symbol; + this.Decimals = decimals; + this.SetBalance(Message.Sender, totalSupply); + } + + public string Symbol + { + get => State.GetString(nameof(this.Symbol)); + private set => State.SetString(nameof(this.Symbol), value); + } + + public string Name + { + get => State.GetString(nameof(this.Name)); + private set => State.SetString(nameof(this.Name), value); + } + + /// + public byte Decimals + { + get => State.GetBytes(nameof(this.Decimals))[0]; + private set => State.SetBytes(nameof(this.Decimals), new[] { value }); + } + + /// + public UInt256 TotalSupply + { + get => State.GetUInt256(nameof(this.TotalSupply)); + private set => State.SetUInt256(nameof(this.TotalSupply), value); + } + + /// + public UInt256 GetBalance(Address address) + { + return State.GetUInt256($"Balance:{address}"); + } + + private void SetBalance(Address address, UInt256 value) + { + State.SetUInt256($"Balance:{address}", value); + } + + /// + public bool TransferTo(Address to, UInt256 amount) + { + if (amount == 0) + { + Log(new TransferLog { From = Message.Sender, To = to, Amount = 0 }); + + return true; + } + + UInt256 senderBalance = GetBalance(Message.Sender); + + if (senderBalance < amount) + { + return false; + } + + SetBalance(Message.Sender, senderBalance - amount); + + SetBalance(to, checked(GetBalance(to) + amount)); + + Log(new TransferLog { From = Message.Sender, To = to, Amount = amount }); + + return true; + } + + /// + public bool TransferFrom(Address from, Address to, UInt256 amount) + { + if (amount == 0) + { + Log(new TransferLog { From = from, To = to, Amount = 0 }); + + return true; + } + + UInt256 senderAllowance = Allowance(from, Message.Sender); + UInt256 fromBalance = GetBalance(from); + + if (senderAllowance < amount || fromBalance < amount) + { + return false; + } + + SetApproval(from, Message.Sender, senderAllowance - amount); + + SetBalance(from, fromBalance - amount); + + SetBalance(to, checked(GetBalance(to) + amount)); + + Log(new TransferLog { From = from, To = to, Amount = amount }); + + return true; + } + + /// + public bool Approve(Address spender, UInt256 currentAmount, UInt256 amount) + { + if (Allowance(Message.Sender, spender) != currentAmount) + { + return false; + } + + SetApproval(Message.Sender, spender, amount); + + Log(new ApprovalLog { Owner = Message.Sender, Spender = spender, Amount = amount, OldAmount = currentAmount }); + + return true; + } + + private void SetApproval(Address owner, Address spender, UInt256 value) + { + State.SetUInt256($"Allowance:{owner}:{spender}", value); + } + + /// + public UInt256 Allowance(Address owner, Address spender) + { + return State.GetUInt256($"Allowance:{owner}:{spender}"); + } + + public struct TransferLog + { + [Index] + public Address From; + + [Index] + public Address To; + + public UInt256 Amount; + } + + public struct ApprovalLog + { + [Index] + public Address Owner; + + [Index] + public Address Spender; + + public UInt256 OldAmount; + + public UInt256 Amount; + } +}