From e300fb50c415d1f5b423961973d4080f3cffa645 Mon Sep 17 00:00:00 2001 From: Gabriel Martinez Rodriguez Date: Fri, 13 Dec 2024 16:21:50 +0100 Subject: [PATCH 01/19] feat(contracts): drand contract --- bun.lockb | Bin 888256 -> 888680 bytes contracts/Makefile | 2 + contracts/deployments/anvil/random/abis.json | 284 ++++++++- contracts/deployments/anvil/random/abis.ts | 288 ++++++++- .../deployments/anvil/random/deployment.json | 2 +- contracts/foundry.toml | 2 +- contracts/package.json | 1 + contracts/remappings.txt | 3 +- contracts/src/Random.sol | 75 --- contracts/src/Randomness/Drand.sol | 74 +++ contracts/src/Randomness/Random.sol | 28 + contracts/src/Randomness/RandomCommitment.sol | 74 +++ contracts/src/deploy/DeployRandom.s.sol | 18 +- .../deployments/unknown/random/abiMap.json | 3 + .../deployments/unknown/random/abis.json | 521 ++++++++++++++++ .../deployments/unknown/random/abis.ts | 554 ++++++++++++++++++ .../unknown/random/deployment.json | 3 + packages/contracts/src/Randomness/Drand.sol | 74 +++ packages/contracts/src/Randomness/Random.sol | 28 + .../src/Randomness/RandomCommitment.sol | 74 +++ 20 files changed, 2013 insertions(+), 95 deletions(-) delete mode 100644 contracts/src/Random.sol create mode 100644 contracts/src/Randomness/Drand.sol create mode 100644 contracts/src/Randomness/Random.sol create mode 100644 contracts/src/Randomness/RandomCommitment.sol create mode 100644 packages/contracts/deployments/unknown/random/abiMap.json create mode 100644 packages/contracts/deployments/unknown/random/abis.json create mode 100644 packages/contracts/deployments/unknown/random/abis.ts create mode 100644 packages/contracts/deployments/unknown/random/deployment.json create mode 100644 packages/contracts/src/Randomness/Drand.sol create mode 100644 packages/contracts/src/Randomness/Random.sol create mode 100644 packages/contracts/src/Randomness/RandomCommitment.sol diff --git a/bun.lockb b/bun.lockb index 064d89247c0441cc3d61236df1c80cdc771d9f29..60932c607b99664ffd9152c35f963cbf9c601587 100755 GIT binary patch delta 107048 zcmeFadz_8c|M$Q5Hk*Ap4>-0xMJIs!28wB+0 zy<&9R&_^j7=8kDq@_Z;5@B)GPn`#9EO~9{%fk0ibD0|%09MVn*2Yr4ISRegd@G$V* zB%6Lo?O>oD{6%cKzNoNZW)bD3z?H6RUBBK|^9u{6 zj#s0eKxjn7ePBcI)>z&V(d1qAZSnmY*y;<5vWsRF<`njbZ93xUE-yxG;YS`84A8at z%|SILzhHcBHf8p2WXp=hyw+{oMPce%2e28KQy%cU-V&}{OJajtx9xLHQ_J$EO`MuH zDYq!erJj&CaiZG!D|M)S&FF{>SFgEE|7Wbcb&JNo(juvorWTA->e;cat@~wvh^jn= z7)i#PId!@s1E06FEt-^U`#uI#{44zef#zUIE33~0)xSJYr9XUHz%TD2$4AHVQrb3N zOD!6rccISt7U#g#@QXl9TzCEgCrpWzw`oywF~zhe zdd<d;B~;B;JclIf#booU@BM)b_d6j|44B1vB5xFurIs~*vge#8`MbO zks1ti055lVAxOO?^G_sj6cLAm9l?EFZ9(sW?cm?n2?h=a%Z{@HcmpUq9gD8n(&~5{ zzo$pgAFy>`OZ0gTZv@qVAnB69cbxtl*fdZQn15+6n{g6?DoQ@V7JLD^da@a=9_IG8 z`Y`Yact7Dl*4s3k?i8fJHU)$Rqu?GNND5eWq zA-dp;%3ol+bi6;O#uWzwod;Mao(#$Ch?Qj? zVD4#7^Xdx%%McR}hcng6USAm3$-(GWFv=@8qED?s%#FMrll6*MtFZ?3QyUG|#p@Ru`e zy8OKSoYV>90$;*4*ES8c<@xrTl2>3QR`rv`SQs|#`bhV*KhZNkjD z*`*_FfhWiMbZG0n9JE8yF_zt-MatTdw$QHUT34?mR+DuD*gUqdTh9`7)^|Pz<6y6{h;z<1Q93dCg)5T5H;qQ*9&f zn;P4BRHu@aP%Vke3xa{8!90hjfSuqe4);&9Yxvs^9{{zMF9qdCQ$VfdMTM!;3MS+P zMi*II9shN}Uz1OPYY=;a8jKDWkC@-o3AG#^D741@t}wQ`(@CM8b7MO@onG?bJiAgK zeW@)b?K0c@1((>d?+jP5d2o$w9#dDpGtf0;*OO1{((Yv|Iwc9l){ zt!u!lMK;}4pcaeOi)9X+b85k~N$Sy}CAOz?98PjL5>!J@0p*OxJN$$KG%Z`fwPMzB z>3%_11y_TzTrm~OazE2xb+r33+psNaSP8<;c*M0f;!)S)=g_t5re0?&xE);uzYEv2 z?7ZAoa4jegm=8){M?N)Z;R-u6*ON~T&q0?*rh{tuQ`ZLrCCWIP1j=~NT$^$H)Ixs= zzX7fcQwt`sE9Oj{f1@3{9EU4b+8zyutHAil?G$4zmVqwIox94$PcAH&mN&6DkXM*J zb7ppN;G|W-65n|WOYJ=FPC_lb7vEre)PVwIvhf8|3uZDzFRZpx?65Vq!SQusF}l`) zcs|eN`<8MvH9r8=kf%X8?d?u~p{&GCvgtE(ii+}bX66)5)k+=cTy7_2T~OzkJ-1rC z7F0poK^5HgcI#9d;To!PP_yAOP-8#!4m;;2PR%YV%9#+DnU_B^n{)Uy7eA?lgLXEj zh@3*2z=dVYowg?zl0cR?8`RL825KSe<1l{e=zh0N-wjdCPSA(@Q zszaCEXB!+^?<@hA^WFuQ1DQ=NcsJb*5*Z}pIE$Zc> z?Dl|~HlKkq)eKMt)CJX(pGdEsf9UXEp!5epIqe!ymRkzSGVB{Ob0+1?y#(%0NAPgX z|J$Ch!a-2e@jN0lMtzk4>#~J6qrds6^sVu)%`%3@(N6+38rp+AX%c)u3k4V01O~p)zanYi}sArkL`t_$D&7AbUa}Pzjf5PX)Dk z&$z+*K#SLG`rlr)>0gJ-5*>(_rIu{7CO-kLo{j<4p?(f$v7)L&d2iT!BT5LUMW=#l z;RSEnjJ+KnwcVO>FsKH-!PQw~+V3qpWCoNoolLx(w9nf%eOFNF$K_4$*@q$PLcHo} z2&%!qP>&iG{3zDHdyA4ITzmGBRzCjT9Gm?D+N*4XKn>K0_ic5jgBpl5P|fZD%9F0% zVf)`WE#T*CLq6$^K{aRRhZeVjYWBZ&`dBi5W~CLpk8DL%6}F%pbY;ATgsQOZ$Cm&5 z%7Cv=VfSXS2s}Z7vPBWJQ5j2U$XX_##2=s)@q(A;- zE%P#{*)g?X)^zsjKnLPgL32HrFOlcHT}Y>Q2)Gwzk5& zNj$782o&W_E1q6ZFjbf{H)s52(y2AIm^&)H&2QFjhk+{RtAE?>XZ>#N9X}+E%P!36 z(K`?rh)(NE=Ktmr?gZ7HUk}=5^N?}s6uR-Ui;o}s*2A?n%*>fEYcBD(z}0~3UHC`~TpAx@*DnIsVNUGi;ML^uAM|`GkFqJcW(!^ zb}pY2ySjH9p4Mvo*))!{)*A$>uJ}W~E8yzfcc41^HmLczuf1*S)i+tI{#W^*cj=dd zGHO3iZU4QaUtX*Ex4HzgKy`jj&bXebJV48e^^dkF+2CTcK&@Bt?D1Uj)OjvGo_==0 z_(Fy*aLci_*B7T+{HSZlpEiZv>=+D$YwRx|UYG&Od0K#sSqV>TL;mUIWl&~&1e6_O zpc1a=5%M1|w}Q)#7lWFY<3Wv0D(K&i9KVtVsiIGNT3p>L}SoAOQ{;eT!T!sX^|UENQfZsYF)HEpg0^~_`vC`%kT&06@GLDsUp(R-lxgRAYSaCywv zL#!q9K$ZUnNS{mQ-%3CQKk5>cfEt<+poX9iD5H!!(^l9YRQyqnHw2Y#-!M00K%Iz_ zsaPG`O9N%3I<7?*i}%3Q@~xoe&ikMm`0d$tRNeyB^PLo= zo}6>8U8&MQjbc$jKC?U!c+tf_04jfBZr&6Y$iSS@wnKSfLoJ=h5Kx6@fpWIz#@Ln2 zzsitd0J?_zicC9sFP@WEG@ju+Wvn&%UQiZ#7xYaIs$t2XEYOq&$mt(G-#Ym^P>Vzz z;!70q69p?_C8&(!#}~}f6@9g9`9g;?K&89z8L@l71xGzPm0#*eoqj>6Tz=mb0eCuYx@O1lCR3ufl?1~JeMT?NM9 zz#Zk%$0x+JiPrhz6C*w$;uGZPi6wSs9hqwrj^l8k&AkC!>%qQBwt{a#dGQCJ^fy2` zRZ}XEMM6}po)^t2D4d#InEUZ$%ijcLi65B3iq9>WRzUj$+ue-rRWj9j>vK+sD0pzT=~8ty`1?qP(EFqDI z4ahFk!bb?6$h{pecQ`p>iF-vY{KR{Qyw|7Trh=jS2>jcoy_Y4rCBFF;ohhk!Eq z$)GG#jHP6;d}pz2hlN^W`BhwQJMucHhUDa=PRyHGSaiL362g*LcE5gSFM+DPgI)GI z3vG43EU@kV22^$NW5r3NQ*raJvLoNqarZ_gdtB^?e%(t(F0!#pn%HAm!4g}_1W>tK zxm*jbwvCLxI=UFH+Oo%w&zW9S7+7?T<>QJu*wd-m**wVN*NFT|cg0dWk*0w1yAPN7 zJMe({PZH2_eKV+Kxfs;@)J*V5um`B~Uu{>=@11Rd^;g(Y_$gL(a&&O3m>uMCQ&lNv zD7dCU>##kZ7Sy(9t^m56CjP?y*XwO*EpN2xzl~-0Z;{z&rS1Hz@!j(C#?LHhW-QO2 zFl(kNs0&x8rp}r+Q?+#>y*wkna>o~Je+5UcXlp&_p4j&OEqXi$lmFc4)GI*c{;ziN z3m#4$(6&Ql$hBuZ_UOBHZi*c@{kVtIa`xAK_@e1^zX*PBD$WfKY}~0<&^OoYp51z= zNvE40qk`QVPeayjJiAA?K8jdjzSfd4z$YUrcOL^|aTE61_igG$iQmp*(Xt1wI9z!uV)D8wt zLJY>ruZ{*kF{OyXjiy4-%_L`%WTqDkWRN5j%U%%m9)KMW3&gS)M}t+SLP<`j8w{K% z(Ucz-j0Ou#=2-Io%aj6M(|W!A(k+?_XhDYaIIMO z(r6_0uwY<7Jl-@a2zn6|wJ&7KZwjU*!FrmM=E3yHSsdxqltks;Ehw@QOosNtQOaPZ<{La=qIX5lZZsr>|auVz>UK6=Au3X;MO{OPMT$j&7isxTxF7{(!aM5H9Clt)horeDCn;O z-Ylq^WFsGh_3@cjk=+T_ZcVgS`!>PY2iIgo&TnJu@Xc(7r-r-;iVUCRH}+o%CSMM< zwN|I;k5f5pxLb7WP3(OzB_<(wWBkiEq@(1Q5n5HVu@=(;957S!8 z*kb0VV4CKvLby)-cJX|gmqTDm>6{T3zz`BT$7xelwC9}I~i6TcL~hau9T0!>_S}iXw>@}rj-!C!_aLy1q0{#tZIK+)F+oDvZWpq49rNdn_;sOtm(1Az-X9fs&2)1 zVduhF2v_^$91+v*1){pzW+iFa2pd>GlT{q-8%YfYvTSh=M!k;RYOwI-u=7mH#iSyX zok*3@Js7w!!ES=7Qu;^*cPXftaYE7E94lM)+VD#Y?faT z^i1jH%%WG&HQzW2+!753PsDR$SvRM9Q9^bxP=LDl(j|{gs#IkRW2B(b~=?F z{}lBaof_{yJ8bx5*zqPSBh)uSNP8)%?vLttyOhp1Ij!dT@~qQq_;loEn1A?7RR0!A zVw#4V4}_Vl5>^gEcKOwmG195XPOhD>>dD+;P`t}ZIRR$lbh=#+>q@>lru+(ApaR+} zmU4Bv*I;lwnKF%n4NMeY2CJ^@6Ifz(2(~lHEAT>NG6C+XbU9P7k5HhAB_ zG)VT$)AlS|v}dyWh0>B>{bMPs(!C9Y?2%Vzl$OJ-UvWxY84Ztx^@>%lPWP@Qq#c&+ zV?#9jE$n1}T8=aOWY$iYo2d<%1^2kfpvgXqD}-YSC-&Npu)bj@wwDc62R;Ar zd>2fI4tu_P^F1$uAZu{5;@sZ(95>rd`6WT7>uRjN+GGOW2Sn+th?6nrJB+kzHmBL= zquyMYRseR$#nJF1ur&2AxW|;P;RZVNTpuCS#MIY)!R^+j^hpIvoT|w zLz--DNiYpjBvwU-UWdtz7^0=oaGOjGMb?6JZyupDZ3(;5k{~Df<2!t8{0u5<+y?V& zmA{0(9cxzK96TwUdY+n=HJ{eLGg3M*PdF>T*y6bafbIBF&bPBlW#=5-WMg#qO`LJrgr*P^PYlP zr*F4D8vYeFAXa%zx;HG_*2Z*VAy@{psnpU}T%2vm^6$LJI2-L>azg#bnVomg*&9){ z#9I&h6E@BthwSmrVRh2=?ttl5#n#29{weI#SS4FprwO(J%vml@g^pFf?7W-i{fvA*vb{MEpJ>N|Idy$Bbn8U3 zdM)nz-KalidIH{m5#gv(^RRk6iu;R!4M2}AcVgM(7=nJ_$!<5l;6QL#)-RU z=(MS()%}?6m8oVd7>?vqLadA(>P$i!X|7TmI77gC!l(xqeFi3n_59WQJDBScy*+xG zb#v~&&qlq+3xa`(em=33={4DvunCE{ul~Z$n1TN#;@1C#9X8XhYPN;~*aeA{+hAkk zEVQA}jD3jVpI2ljGC3ATL+=!sol@J+vV&gBw0jo6gK5KW7Sow&gJVtn^T+p&HS({H z-DlgB4gJf}G?;%hGF5lrulLM0s~@58FOlt>X2A*{J_q-RRld|WILC~AH28=2{9J2K z9BLrBewk}lKZf}S7c<*U*<+lNR}hp{If`wGdRt-kOi=z+G*tg$v-)vHZQ{jdCm6ov zVy-`?@^P~LPEZ3y!CIcXUt-<3Zmiu^(eUf@uuiP<+VoKFWv108az1bwqh(T_WEV=0 zmA#Yhbt{Qaa4v6ZGaTR6XLTW7_3oD%cnp$1awHuJi5EjBETB4bK%bS(}3C z;TH+|wR!26yA1v*GZeer?0k~S-$x#$=6Hjyur~(=2y5L08|0VaeNJ$MUzMJCjJ`5H z@>Cpp{7N%+Gghd(z^n$n84KdE6cXCLz_farN-`EQ%DOp(mMt`^QM_+aM%m^siiS?T z%CvfhQi`uKV?pnWtDIN(zJ0&;328QyyYs;qVyJ7uxmQ39r2FKm4WRGz>o<`#~O zOX52=M``aBnC~V2-d^kKnyg=(nRaOy7v`^&rrkWQ*D%e(ps6~%2J=hlbB&$ttmAlW z3C!-0+Am*)sT9uq*sa0R`1U}`@G#h*SlN>F(2YyY&KKzWhf7VX7wP+;Ww!4DdmC5< zJ(WTj*|MnjAx!sjB5$R)gRXUH^`JVm>RL1QB}%Ats(*6yPP@*oS1h8~AO`b$rc-73 zGuUaSvI9Pww%qP^VMQs(MoUO;Gigrac|?cENN; zrfKXMkuKNU-8^yh4HaH*#%`gbZ(eWs7V@M}iTfMwipy>F> zw#GH&;T!FgWH6qj4c|az5bF#lt#sz`FP!(n&Q^=GE{A`I4UA=N>|4iJcTSuNytOEH zVr!tjft^aW!~91_lUF5}e+1K)-xA+)c#swP@)px-J53!{9t_MRksXj{V0!3bS+m=$EtCAlT@KSm z%DA#!zX7uzSN?X?OTXPV++LTL!gQZw0XT_@--YTCiG9S;@eb>_wf&Pz60C2ml9T5{ zgfvt<-R9xLE|}f_tFU0}JMCNy`TNZIFs^UT}hQsG^9 za7$ThX9Dc{Xm|{)udaLE4GK|@f6ffOxz^e}yw0}MKPyH0!PKRMkB8@=;H&G?z4e4- zcXkdQWc&upgVp!FeZoC~)8B2M(Z_>SFHkD7ZPApc?&t3I;S{;a%;%e7! zcAp)M#&)Q#hN>Nh#o9f^`sT2MH^@s4T}%(hc^wmSF6FYDzz zgB%X)?Vq}@A|z+vImm5MuL`EYV;=Hsy7>dPzJwLLb5T^LJ;B}%(*glI81=#%?8CcS zCc6P%HVUeN511-W2i|h1<|X&hvzfCn?Y{Umm-8+US+DSWVqZt0*kzXMT6hDDBl^np zP~c&+^J8Yov5&_6Nba}-rVW-6)j8!cd512}!xgeR$G@v#x`mJm_7+U5B3D2jIVC+F z&ohdUzghWP=M6BsA(gWseFD>P`Sp0cH^!Nc#7ki|rCPKDcD|pI{&~GNCE9mwG<+58 zboD0u8X+D*a8ONp!nH=*VE9bf$XMA|>E6RmXC6~(<0tJLXR}-st(E;GH?LbyN+QiJhyp5y1rpMM*Sg}EMuPl)PB)2|3Qg27RH~Z znBTWy=4Qo*_4*8f>|$BJ3fNxJ^EoL`%Q-Jb-NSVl#~C$o`Gq$dH#yL;EnhQwYx4Y z0+BNj^+{UfO}ifW=_0u>?E}>p{5uly>=?nxrs7wgXg$8&cH4T^0hpS|&7Nn?k+a^i zmaASN*P{5Z!~8?Qk1(x3)$zmLw!_G_x+WSagK3?so-iMyXr1(r9O1_Aa4ynX7CE1g zf1HRvtBE{-qM4D%8~H=2rNq;CgWt7%wBEW9X7^&PCeOfBB3~`AYBqU~buU(VTY6~t zd#2TITsoGaoT2yZ!5yabH!fH6OaT#VsUMnNbn9WfiX|N?>XZkEZxsgKtL;2K$BES8|p#S>aIMBtKMnkg54{b+GM6 zywNefI+b8QKN!0ABeU}$ZT=FekCnQ9>`ReJA3GB#o^z~2k%|5H65$VEwv=$jC-m5s zy_jHz9}K>0GJ~OhUau-UWjIc7$Ss8F!xPq&Tpo%)2pw$eJy2zvGVoJe+ZJ{g!Lx1b zzE5rJ@t?(Gy<&noJ##Evzy~T%nTlGWe&PM7TrJf5S)a$#hM%eq*7_omU=YDRrmQYD zBV^kV{)nLMLFkAtZKb2Wm+o1^1;t{gD!47{6R)A<>8iZwxWZ@k^% zO9|Q*dLI$gYYa}+ydym7+qgf4rxSDo@@RGN-_^l>-z7YADM6QZJ3-gNmft5jmPyc= zZzVyO_5*@8ZK(YZW@n>NzoZ}HO86dv&Wbz<1ZR1@%eSeB4 z^_CFSDUS)jo3MF5+Y_l}4?Bi8UcosHrYY*jg^v5h>}(P`$-D5E8md7`oq*7mTy=IOv^Wn*1ZI?r&--9S|6ZSvC0MMkx_*HTINy|Igve0z33RX zd-kc{mCrA;3TyWMH^T|z8n0(py?%?o0pZ!GcQ;JV#r)t%un*>6*-X3FNt*Y&wT}O6 z-dhK=`v7OI$OkZuSYoaB4m%isy}@IN@DSK=J@O1cLWt+-x21cv|42+&-WQ(tD?~Y6`S=HJp@TVP9W&wAc2{7dioAQg6GB{W?Ako@Yv1vs$hU zg#2}%kLSLMdbNWgckokjXTnr~Jx<;Q)B7FHW4K7|P{<95=I$Dpbtc6n@kfi?Uh{rh z1Y*yUI>s%BjqvX?KNE5u%hQf-NtW4h%Y|t(3i*#Yy{DlXejJI_DkE%K9f@<_z#mZ3 z7~z)G`2kF6Z121-wL`v_){B+D6Ae#+ac*0n-7d2oy*_}TLmuPH1#DEEc;zZ-IZQ*y z#L_1rFg2gOkA3roNJy(v9rn%lO+|Z3y2uOpn>+QgzXTsJnH^9MqUv)7u9q(}=ygMZ znX$5U>EZVX@h)v)did#jp}+)R8&N+LxWErRNQesuZ#aG>B;)XQj>&LbgOI!7!@?6` z7x`s;N@%Q~vu{InP&Hgi2)Ck)R|r|hEZ>%v6i?vwIgBPybUpu^J_Baw83P`99Hv89 zVvg3T)hHDEU|~k6e>q`EsAukW6cfF1MHR3jy9MXTf;C%%1 zS4UGmloha9d=Pa%9tG0?1WnaJ9(lKIZgw6UIw?5UBzI+}eBWdOq1uO=)m=j;h0i%0 z-!WxfL+Rmif+1~mPUdCJe^G#-N=G1ybZC@r_^d8wn1x!B4A$-TNfeORIxh37%oo zHajt-OMyzrst(>&9sI63n8w38m-h1N;J>PaP5UMioKJ9|E%)~7;MabzaxkaZj70og zf~VP}oBd$Q>BOIu5C>KVmnMSVc7i(GaCdv2H%a~MV6aQ`a%NaRvvUXw%EzeI0W|-H z@TV2EljGG_r9~iPApTQb@7Vr!O8ZMh_zu_@y{C`-PDq|t{d(Y}0d_Lk=i75&dY)0w zlz+wKhiM!2KN<7(!jzZk&MWYtr&vF&zQ^B>;_pfRCFWBYpNH@TeEU<~p~BzCMmc8P z^Jae9rZ9BcHkS7)G|8Hr6j zG6Q9riim%k6ZsfLb2y=RqX&foxhU9`3Ar8SKk75tH)E2)p}^ICg*u$y4fCrv<;{5s z1k<8bZIYxj>`LPwegs9kOQP;blc9fa@dOmDUDauxMbSEAmE|{hb%xnR zDUrmRk201dw&Kgq{96|zAE8tip+E03=&Zk|S&O2DFHvgbkN-^>Iox?cqDzmV_+yxO zzEk_`_;Ou70RzGkN7wLGu%V{19iJGEuPirD*bB*hZL)ZvE*; znBB^>>Yg|e%-_e*Bkz>BJp;S1-(M%sus32NVA z&hxVD@Ju`Tm~KDwxfZ>&sbU0GDc^)}TRV4bV$yM(TmqA?l4o@^Qu{pX1l1k72*pkp z8Dt|&{%9Y8)<544oZTAEhv^X6z?3&-*NEP{-W!bX?kN@}pYdFk5ILFNvxe4X3 z(|8YxtBE$Bd_la;nnOh}4H>RV*-tr!8*nH;;=;d|bREj6eh-zm1%Jj?%y)C|JebZn zmKm7rWZCzy>PvO+Y&)XuwazHst*kB!ekzQC60`m5b~c$C+3v*6pGf^kjdW8?11%xaciff_BpO8?t?9B z47;YkOx&*euIWTru-dArRq>{NY+#B^igXvtsL0~2IOtNb^Pk1>{ zE`q5ycAf8%n=l7!@_1MpasI8|G6Z5tmzoc@SZ?YKe@DS z=5hBPs5)b(>GA?TQJ?C%uJt_lut}c61t>Y+jGe-XBQrn5BdL@rq4daVf;u}Vx)R(pt+Ad#nMkSD zHCxVbE0Fa7m>j&8{ba>4e1*3g2Vgo+`TiNHRrq&5%R|wh8NXXHcX?FXyq)|)A$tkZbq3ve!K6soo%OD!V+E{ifXg(-|mCCs{!SI0ZVLX zz7-;K;_Iv?^)s;CMA=8r4F#scs_h;_kxdhdcX+Y2yS3Rwm}VTSHaCffVAVTe{fn*t zId5KtH={WJd^kYylf;(=GB=cP3a;r|(n z2#SVfnRe6IreOZ`in~-# z;k#O>&gG_6F=x|3ms^)&$iIn(%V0fB*;-zEenC)k9ux2>>rq$GVgFs-ctToP*tvPM zv&Pp`cJ+-AQ0eyc)%41Q{cw%JFimytlKN8=mpIDWxrv#-9@>@I>Yi7i=y*wAKTb

^8)g1g2@N{htb}5Fm>LhJbDRF2w?a)H;Q7I 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 z zIliF|S2tX-`7eh?&oaV(SJh14tRUExqqKdYOpjg1lrtNd+(FN-E;UG z!+(3B10gzfD_E{k-itrxcIrqBJ5De2J))8rxLdcGoX zSWgi;BX`Q6gJnoTvpOe^@mB_p3?ZdT_g~>2yU3rbzm;>WG|Pz_RC=Y^`;e2Z&#=@0 zY&kMn$?^NwH1n6DkS@kLuQd4|G0zXcLUmj-ZdFi%v4;Xr^!+zGC$2OH9+B4EmFC(b zT5<)Jp;&%VuJ4i^zLE+3x%w@CSZNCX!GJum(lq(Q`&rNjtIYa8s8O2LCf%Pe-B2_0 zPbSJgR-1BhLXrn-xRqUF?*2&@wbvNWV~X3AH(4bniys#bO>`HZfT5qqsk{Dc@u)|%0An4;c$!W5NZt!X4N_gd5Q3B6Nlty!w=Pu7|m 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 nnb6GVE_(fewG^xaR)=UeO%$ diff --git a/contracts/Makefile b/contracts/Makefile index 6bc2e25129..8d899b8615 100755 --- a/contracts/Makefile +++ b/contracts/Makefile @@ -47,9 +47,11 @@ setup: ln $(LN_FLAGS) ../$(NODE_MODULES)/solady lib/solady ln $(LN_FLAGS) ../$(NODE_MODULES)/kernel lib/kernel ln $(LN_FLAGS) ../$(NODE_MODULES)/forge-std lib/forge-std + ln $(LN_FLAGS) ../$(NODE_MODULES)/@kevincharm/bls-bn254 lib/bls-bn254 ln $(LN_FLAGS) ../$(NODE_MODULES)/account-abstraction lib/account-abstraction ln $(LN_FLAGS) ../../$(NODE_MODULES)/@openzeppelin/contracts lib/openzeppelin/src ln $(LN_FLAGS) ../../$(NODE_MODULES)/@openzeppelin/contracts-upgradeable lib/oz-upgradeable/src + .PHONY: setup #################################################################################################### diff --git a/contracts/deployments/anvil/random/abis.json b/contracts/deployments/anvil/random/abis.json index 0420b5fbbf..98b6473587 100644 --- a/contracts/deployments/anvil/random/abis.json +++ b/contracts/deployments/anvil/random/abis.json @@ -2,12 +2,28 @@ "Random": [ { "type": "constructor", - "inputs": [], + "inputs": [ + { + "name": "_publicKey", + "type": "uint256[4]", + "internalType": "uint256[4]" + }, + { + "name": "_genesisTimestamp", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "_period", + "type": "uint256", + "internalType": "uint256" + } + ], "stateMutability": "nonpayable" }, { "type": "function", - "name": "BLOCK_TIME", + "name": "DRAND_DELAY", "inputs": [], "outputs": [ { @@ -18,6 +34,19 @@ ], "stateMutability": "view" }, + { + "type": "function", + "name": "DST", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "bytes", + "internalType": "bytes" + } + ], + "stateMutability": "view" + }, { "type": "function", "name": "PRECOMMIT_DELAY", @@ -31,9 +60,41 @@ ], "stateMutability": "view" }, + { + "type": "function", + "name": "genesisTimestamp", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, { "type": "function", "name": "getRevealedValue", + "inputs": [ + { + "name": "blockNumber", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "nextValidTimestamp", "inputs": [ { "name": "timestamp", @@ -63,12 +124,25 @@ ], "stateMutability": "view" }, + { + "type": "function", + "name": "period", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, { "type": "function", "name": "postCommitment", "inputs": [ { - "name": "timestamp", + "name": "blockNumber", "type": "uint256", "internalType": "uint256" }, @@ -81,6 +155,94 @@ "outputs": [], "stateMutability": "nonpayable" }, + { + "type": "function", + "name": "postDrand", + "inputs": [ + { + "name": "round", + "type": "uint64", + "internalType": "uint64" + }, + { + "name": "signature", + "type": "uint256[2]", + "internalType": "uint256[2]" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "publicKey", + "inputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "random", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "bytes32", + "internalType": "bytes32" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "randomForTimestamp", + "inputs": [ + { + "name": "timestamp", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "", + "type": "bytes32", + "internalType": "bytes32" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "randomness", + "inputs": [ + { + "name": "round", + "type": "uint64", + "internalType": "uint64" + } + ], + "outputs": [ + { + "name": "randomness", + "type": "bytes32", + "internalType": "bytes32" + } + ], + "stateMutability": "view" + }, { "type": "function", "name": "renounceOwnership", @@ -93,7 +255,7 @@ "name": "revealValue", "inputs": [ { - "name": "timestamp", + "name": "blockNumber", "type": "uint256", "internalType": "uint256" }, @@ -124,7 +286,7 @@ "name": "unsafeGetRevealedValue", "inputs": [ { - "name": "timestamp", + "name": "blockNumber", "type": "uint256", "internalType": "uint256" } @@ -143,7 +305,7 @@ "name": "CommitmentPosted", "inputs": [ { - "name": "timestamp", + "name": "blockNumber", "type": "uint256", "indexed": true, "internalType": "uint256" @@ -181,7 +343,7 @@ "name": "ValueRevealed", "inputs": [ { - "name": "timestamp", + "name": "blockNumber", "type": "uint256", "indexed": true, "internalType": "uint256" @@ -195,6 +357,17 @@ ], "anonymous": false }, + { + "type": "error", + "name": "BNAddFailed", + "inputs": [ + { + "name": "input", + "type": "uint256[4]", + "internalType": "uint256[4]" + } + ] + }, { "type": "error", "name": "CommitmentAlreadyExists", @@ -205,11 +378,108 @@ "name": "CommitmentTooLate", "inputs": [] }, + { + "type": "error", + "name": "DrandNotAvailable", + "inputs": [ + { + "name": "round", + "type": "uint64", + "internalType": "uint64" + } + ] + }, + { + "type": "error", + "name": "InvalidDSTLength", + "inputs": [ + { + "name": "dst", + "type": "bytes", + "internalType": "bytes" + } + ] + }, + { + "type": "error", + "name": "InvalidFieldElement", + "inputs": [ + { + "name": "x", + "type": "uint256", + "internalType": "uint256" + } + ] + }, + { + "type": "error", + "name": "InvalidPublicKey", + "inputs": [ + { + "name": "publicKey", + "type": "uint256[4]", + "internalType": "uint256[4]" + } + ] + }, { "type": "error", "name": "InvalidReveal", "inputs": [] }, + { + "type": "error", + "name": "InvalidSignature", + "inputs": [ + { + "name": "publicKey", + "type": "uint256[4]", + "internalType": "uint256[4]" + }, + { + "name": "message", + "type": "uint256[2]", + "internalType": "uint256[2]" + }, + { + "name": "signature", + "type": "uint256[2]", + "internalType": "uint256[2]" + } + ] + }, + { + "type": "error", + "name": "MapToPointFailed", + "inputs": [ + { + "name": "noSqrt", + "type": "uint256", + "internalType": "uint256" + } + ] + }, + { + "type": "error", + "name": "ModExpFailed", + "inputs": [ + { + "name": "base", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "exponent", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "modulus", + "type": "uint256", + "internalType": "uint256" + } + ] + }, { "type": "error", "name": "NoCommitmentFound", diff --git a/contracts/deployments/anvil/random/abis.ts b/contracts/deployments/anvil/random/abis.ts index 43869df739..48835e8f78 100644 --- a/contracts/deployments/anvil/random/abis.ts +++ b/contracts/deployments/anvil/random/abis.ts @@ -2,16 +2,33 @@ import type { MapTuple, ObjectFromTuples, UnionToTuple } from "@happy.tech/common" import type { Address } from "viem" + const contractToAbi = ({ "Random": [ { "type": "constructor", - "inputs": [], + "inputs": [ + { + "name": "_publicKey", + "type": "uint256[4]", + "internalType": "uint256[4]" + }, + { + "name": "_genesisTimestamp", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "_period", + "type": "uint256", + "internalType": "uint256" + } + ], "stateMutability": "nonpayable" }, { "type": "function", - "name": "BLOCK_TIME", + "name": "DRAND_DELAY", "inputs": [], "outputs": [ { @@ -22,6 +39,19 @@ const contractToAbi = ({ ], "stateMutability": "view" }, + { + "type": "function", + "name": "DST", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "bytes", + "internalType": "bytes" + } + ], + "stateMutability": "view" + }, { "type": "function", "name": "PRECOMMIT_DELAY", @@ -35,9 +65,41 @@ const contractToAbi = ({ ], "stateMutability": "view" }, + { + "type": "function", + "name": "genesisTimestamp", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, { "type": "function", "name": "getRevealedValue", + "inputs": [ + { + "name": "blockNumber", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "nextValidTimestamp", "inputs": [ { "name": "timestamp", @@ -67,12 +129,25 @@ const contractToAbi = ({ ], "stateMutability": "view" }, + { + "type": "function", + "name": "period", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, { "type": "function", "name": "postCommitment", "inputs": [ { - "name": "timestamp", + "name": "blockNumber", "type": "uint256", "internalType": "uint256" }, @@ -85,6 +160,94 @@ const contractToAbi = ({ "outputs": [], "stateMutability": "nonpayable" }, + { + "type": "function", + "name": "postDrand", + "inputs": [ + { + "name": "round", + "type": "uint64", + "internalType": "uint64" + }, + { + "name": "signature", + "type": "uint256[2]", + "internalType": "uint256[2]" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "publicKey", + "inputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "random", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "bytes32", + "internalType": "bytes32" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "randomForTimestamp", + "inputs": [ + { + "name": "timestamp", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "", + "type": "bytes32", + "internalType": "bytes32" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "randomness", + "inputs": [ + { + "name": "round", + "type": "uint64", + "internalType": "uint64" + } + ], + "outputs": [ + { + "name": "randomness", + "type": "bytes32", + "internalType": "bytes32" + } + ], + "stateMutability": "view" + }, { "type": "function", "name": "renounceOwnership", @@ -97,7 +260,7 @@ const contractToAbi = ({ "name": "revealValue", "inputs": [ { - "name": "timestamp", + "name": "blockNumber", "type": "uint256", "internalType": "uint256" }, @@ -128,7 +291,7 @@ const contractToAbi = ({ "name": "unsafeGetRevealedValue", "inputs": [ { - "name": "timestamp", + "name": "blockNumber", "type": "uint256", "internalType": "uint256" } @@ -147,7 +310,7 @@ const contractToAbi = ({ "name": "CommitmentPosted", "inputs": [ { - "name": "timestamp", + "name": "blockNumber", "type": "uint256", "indexed": true, "internalType": "uint256" @@ -185,7 +348,7 @@ const contractToAbi = ({ "name": "ValueRevealed", "inputs": [ { - "name": "timestamp", + "name": "blockNumber", "type": "uint256", "indexed": true, "internalType": "uint256" @@ -199,6 +362,17 @@ const contractToAbi = ({ ], "anonymous": false }, + { + "type": "error", + "name": "BNAddFailed", + "inputs": [ + { + "name": "input", + "type": "uint256[4]", + "internalType": "uint256[4]" + } + ] + }, { "type": "error", "name": "CommitmentAlreadyExists", @@ -209,11 +383,108 @@ const contractToAbi = ({ "name": "CommitmentTooLate", "inputs": [] }, + { + "type": "error", + "name": "DrandNotAvailable", + "inputs": [ + { + "name": "round", + "type": "uint64", + "internalType": "uint64" + } + ] + }, + { + "type": "error", + "name": "InvalidDSTLength", + "inputs": [ + { + "name": "dst", + "type": "bytes", + "internalType": "bytes" + } + ] + }, + { + "type": "error", + "name": "InvalidFieldElement", + "inputs": [ + { + "name": "x", + "type": "uint256", + "internalType": "uint256" + } + ] + }, + { + "type": "error", + "name": "InvalidPublicKey", + "inputs": [ + { + "name": "publicKey", + "type": "uint256[4]", + "internalType": "uint256[4]" + } + ] + }, { "type": "error", "name": "InvalidReveal", "inputs": [] }, + { + "type": "error", + "name": "InvalidSignature", + "inputs": [ + { + "name": "publicKey", + "type": "uint256[4]", + "internalType": "uint256[4]" + }, + { + "name": "message", + "type": "uint256[2]", + "internalType": "uint256[2]" + }, + { + "name": "signature", + "type": "uint256[2]", + "internalType": "uint256[2]" + } + ] + }, + { + "type": "error", + "name": "MapToPointFailed", + "inputs": [ + { + "name": "noSqrt", + "type": "uint256", + "internalType": "uint256" + } + ] + }, + { + "type": "error", + "name": "ModExpFailed", + "inputs": [ + { + "name": "base", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "exponent", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "modulus", + "type": "uint256", + "internalType": "uint256" + } + ] + }, { "type": "error", "name": "NoCommitmentFound", @@ -260,7 +531,7 @@ const aliasToContract = ({ }) as const export const deployment = ({ - "Random": "0x51745e910FaD45A6cA620bCc7a7fAB7683B142e5" + "Random": "0x9ACE2eE177EB91eEed9591ea50Cb903d551DD0f9" }) as const export type ContractToAbi = typeof contractToAbi @@ -281,3 +552,4 @@ for (const [alias, contractName] of Object.entries(aliasToContract)) { abis[alias as ContractAlias] = contractToAbi[contractName as ContractName] as any } + diff --git a/contracts/deployments/anvil/random/deployment.json b/contracts/deployments/anvil/random/deployment.json index 260d25ee58..6c2374d088 100644 --- a/contracts/deployments/anvil/random/deployment.json +++ b/contracts/deployments/anvil/random/deployment.json @@ -1,3 +1,3 @@ { - "Random": "0x51745e910FaD45A6cA620bCc7a7fAB7683B142e5" + "Random": "0x9ACE2eE177EB91eEed9591ea50Cb903d551DD0f9" } \ No newline at end of file diff --git a/contracts/foundry.toml b/contracts/foundry.toml index 36490827f0..febade1e5e 100755 --- a/contracts/foundry.toml +++ b/contracts/foundry.toml @@ -6,7 +6,7 @@ solc_version = "0.8.26" optimizer = true optimizer_runs = 20000 -via_ir = false +via_ir = true ignored_warnings_from = ["node_modules", "lib"] diff --git a/contracts/package.json b/contracts/package.json index 1db262e707..2d8f45c406 100755 --- a/contracts/package.json +++ b/contracts/package.json @@ -12,6 +12,7 @@ "./random/anvil": "./deployments/anvil/random/abis.ts" }, "dependencies": { + "@kevincharm/bls-bn254": "^2.0.0", "@openzeppelin/contracts": "^5.0.2", "@openzeppelin/contracts-upgradeable": "^5.0.2", "ExcessivelySafeCall": "git@github.com:nomad-xyz/ExcessivelySafeCall#release/0.0.1", diff --git a/contracts/remappings.txt b/contracts/remappings.txt index bdeea23e87..4fae0a7315 100755 --- a/contracts/remappings.txt +++ b/contracts/remappings.txt @@ -6,4 +6,5 @@ account-abstraction/=lib/account-abstraction/ kernel/=lib/kernel/src/ solady/=lib/solady/src/ ExcessivelySafeCall/=lib/ExcessivelySafeCall/src/ -@openzeppelin/contracts=lib/openzeppelin/src \ No newline at end of file +@openzeppelin/contracts=lib/openzeppelin/src +bls-bn254/=lib/bls-bn254/contracts/ \ No newline at end of file diff --git a/contracts/src/Random.sol b/contracts/src/Random.sol deleted file mode 100644 index 0ce8bc6389..0000000000 --- a/contracts/src/Random.sol +++ /dev/null @@ -1,75 +0,0 @@ -// SPDX-License-Identifier: BSD-3-Clause-Clear -pragma solidity ^0.8.20; - -import {Ownable} from "openzeppelin/access/Ownable.sol"; - -contract Random is Ownable { - uint256 public constant PRECOMMIT_DELAY = 10; - uint256 public constant BLOCK_TIME = 2; - - uint256 private currentRevealedValue; - uint256 private currentRevealTimestamp; - mapping(uint256 timestamp => bytes32) private commitments; - - event CommitmentPosted(uint256 indexed timestamp, bytes32 commitment); - event ValueRevealed(uint256 indexed timestamp, uint256 revealedValue); - - error CommitmentTooLate(); - error CommitmentAlreadyExists(); - error NoCommitmentFound(); - error RevealMustBeOnExactBlock(); - error InvalidReveal(); - error RevealedValueNotAvailable(); - - constructor() Ownable(msg.sender) {} - - function postCommitment(uint256 timestamp, bytes32 commitmentHash) external onlyOwner { - if (block.timestamp > timestamp - PRECOMMIT_DELAY) { - revert CommitmentTooLate(); - } - - if (commitments[timestamp] != 0) { - revert CommitmentAlreadyExists(); - } - - commitments[timestamp] = commitmentHash; - emit CommitmentPosted(timestamp, commitmentHash); - } - - function revealValue(uint256 timestamp, uint256 revealedValue) external onlyOwner { - bytes32 storedCommitment = commitments[timestamp]; - - if (storedCommitment == 0) { - revert NoCommitmentFound(); - } - - if (block.timestamp != timestamp) { - revert RevealMustBeOnExactBlock(); - } - - if (storedCommitment != keccak256(abi.encodePacked(revealedValue))) { - revert InvalidReveal(); - } - - currentRevealedValue = revealedValue; - currentRevealTimestamp = timestamp; - delete commitments[timestamp]; - emit ValueRevealed(timestamp, revealedValue); - } - - function unsafeGetRevealedValue(uint256 timestamp) external view returns (uint256) { - if (currentRevealTimestamp != timestamp) { - return 0; - } - - return currentRevealedValue; - } - - function getRevealedValue(uint256 timestamp) external view returns (uint256) { - if (currentRevealTimestamp != timestamp) { - revert RevealedValueNotAvailable(); - } - - return currentRevealedValue; - } -} diff --git a/contracts/src/Randomness/Drand.sol b/contracts/src/Randomness/Drand.sol new file mode 100644 index 0000000000..6f6969c5ab --- /dev/null +++ b/contracts/src/Randomness/Drand.sol @@ -0,0 +1,74 @@ +// SPDX-License-Identifier: BSD-3-Clause-Clear +pragma solidity ^0.8.20; + +import {BLS} from "bls-bn254/BLS.sol"; + +contract Drand { + bytes public constant DST = bytes("BLS_SIG_BN254G1_XMD:KECCAK-256_SVDW_RO_NUL_"); + + uint256[4] public publicKey; + uint256 public genesisTimestamp; + uint256 public period; + mapping(uint64 round => bytes32 randomness) public randomness; + + error InvalidPublicKey(uint256[4] publicKey); + error InvalidSignature(uint256[4] publicKey, uint256[2] message, uint256[2] signature); + error DrandNotAvailable(uint64 round); + + constructor(uint256[4] memory _publicKey, uint256 _genesisTimestamp, uint256 _period) { + if (!BLS.isValidPublicKey(_publicKey)) { + revert InvalidPublicKey(_publicKey); + } + publicKey = _publicKey; + genesisTimestamp = _genesisTimestamp; + period = _period; + } + + function postDrand(uint64 round, uint256[2] memory signature) external { + // Encode round for hash-to-point + bytes memory hashedRoundBytes = new bytes(32); + assembly { + mstore(0x00, round) + let hashedRound := keccak256(0x18, 0x08) // hash the last 8 bytes (uint64) of `round` + mstore(add(0x20, hashedRoundBytes), hashedRound) + } + uint256[2] memory message = BLS.hashToPoint(DST, hashedRoundBytes); + + // NB: Always check that the signature is a valid signature (a valid G1 point on the curve)! + bool isValidSignature = BLS.isValidSignature(signature); + if (!isValidSignature) { + revert InvalidSignature(publicKey, message, signature); + } + + // Verify the signature over the message using the public key + (bool pairingSuccess, bool callSuccess) = BLS.verifySingle(signature, publicKey, message); + if (!pairingSuccess) { + revert InvalidSignature(publicKey, message, signature); + } + + bytes32 roundRandomness = keccak256(abi.encode(signature)); + + randomness[round] = roundRandomness; + } + + function _unsafeGetDrand(uint64 round) internal view returns (bytes32) { + return randomness[round]; + } + + function _getDrand(uint64 round) internal view returns (bytes32) { + if (randomness[round] == 0) { + revert DrandNotAvailable(round); + } + + return randomness[round]; + } + + function _getDrandAtTimestamp(uint256 timestamp) internal view returns (bytes32) { + uint64 round = uint64((timestamp - genesisTimestamp) / period); + return _getDrand(round); + } + + function _nextValidTimestamp(uint256 timestamp) internal view returns (uint256) { + return timestamp + (period - (timestamp % period)); + } +} diff --git a/contracts/src/Randomness/Random.sol b/contracts/src/Randomness/Random.sol new file mode 100644 index 0000000000..7ab23d5a13 --- /dev/null +++ b/contracts/src/Randomness/Random.sol @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: BSD-3-Clause-Clear +pragma solidity ^0.8.20; + +import {RandomCommitment} from "./RandomCommitment.sol"; +import {Drand} from "./Drand.sol"; + +contract Random is RandomCommitment, Drand { + uint256 public constant DRAND_DELAY = 6; + + constructor(uint256[4] memory _publicKey, uint256 _genesisTimestamp, uint256 _period) + RandomCommitment() + Drand(_publicKey, _genesisTimestamp, _period) + {} + + function random() external view returns (bytes32) { + bytes32 drand = _getDrandAtTimestamp(block.timestamp - DRAND_DELAY); + uint256 revealedValue = getRevealedValue(block.number); + return keccak256(abi.encodePacked(drand, revealedValue)); + } + + function randomForTimestamp(uint256 timestamp) external view returns (bytes32) { + return _getDrandAtTimestamp(timestamp); + } + + function nextValidTimestamp(uint256 timestamp) public view returns (uint256) { + return _nextValidTimestamp(timestamp); + } +} diff --git a/contracts/src/Randomness/RandomCommitment.sol b/contracts/src/Randomness/RandomCommitment.sol new file mode 100644 index 0000000000..9efeb90d2a --- /dev/null +++ b/contracts/src/Randomness/RandomCommitment.sol @@ -0,0 +1,74 @@ +// SPDX-License-Identifier: BSD-3-Clause-Clear +pragma solidity ^0.8.20; + +import {Ownable} from "openzeppelin/access/Ownable.sol"; + +contract RandomCommitment is Ownable { + uint256 public constant PRECOMMIT_DELAY = 10; + + uint256 private currentRevealedValue; + uint256 private currentRevealBlockNumber; + mapping(uint256 blockNumber => bytes32) private commitments; + + event CommitmentPosted(uint256 indexed blockNumber, bytes32 commitment); + event ValueRevealed(uint256 indexed blockNumber, uint256 revealedValue); + + error CommitmentTooLate(); + error CommitmentAlreadyExists(); + error NoCommitmentFound(); + error RevealMustBeOnExactBlock(); + error InvalidReveal(); + error RevealedValueNotAvailable(); + + constructor() Ownable(msg.sender) {} + + function postCommitment(uint256 blockNumber, bytes32 commitmentHash) external onlyOwner { + if (block.number > blockNumber - PRECOMMIT_DELAY) { + revert CommitmentTooLate(); + } + + if (commitments[blockNumber] != 0) { + revert CommitmentAlreadyExists(); + } + + commitments[blockNumber] = commitmentHash; + emit CommitmentPosted(blockNumber, commitmentHash); + } + + function revealValue(uint256 blockNumber, uint256 revealedValue) external onlyOwner { + bytes32 storedCommitment = commitments[blockNumber]; + + if (storedCommitment == 0) { + revert NoCommitmentFound(); + } + + if (block.number != blockNumber) { + revert RevealMustBeOnExactBlock(); + } + + if (storedCommitment != keccak256(abi.encodePacked(revealedValue))) { + revert InvalidReveal(); + } + + currentRevealedValue = revealedValue; + currentRevealBlockNumber = blockNumber; + delete commitments[blockNumber]; + emit ValueRevealed(blockNumber, revealedValue); + } + + function unsafeGetRevealedValue(uint256 blockNumber) public view returns (uint256) { + if (currentRevealBlockNumber != blockNumber) { + return 0; + } + + return currentRevealedValue; + } + + function getRevealedValue(uint256 blockNumber) public view returns (uint256) { + if (currentRevealBlockNumber != blockNumber) { + revert RevealedValueNotAvailable(); + } + + return currentRevealedValue; + } +} diff --git a/contracts/src/deploy/DeployRandom.s.sol b/contracts/src/deploy/DeployRandom.s.sol index b63047a7e2..6d3caf5fae 100644 --- a/contracts/src/deploy/DeployRandom.s.sol +++ b/contracts/src/deploy/DeployRandom.s.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.20; import {BaseDeployScript} from "./BaseDeployScript.sol"; -import {Random} from "../Random.sol"; +import {Random} from "../Randomness/Random.sol"; /** * @dev Deploys the Randomness contract. @@ -12,8 +12,22 @@ contract DeployL1 is BaseDeployScript { Random public random; + uint256[4] public DRAND_PUBLIC_KEY; + uint256 public constant DRAND_GENESIS_TIMESTAMP = 1727521075; + uint256 public constant DRAND_PERIOD = 3; + + constructor() { + DRAND_PUBLIC_KEY = [ + 2416910118189096557713698606232949750075245832257361418817199221841198809231, + 3565178688866727608783247307855519961161197286613423629330948765523825963906, + 18766085122067595057703228467555884757373773082319035490740181099798629248523, + 263980444642394177375858669180402387903005329333277938776544051059273779190 + ]; + } + function deploy() internal override { - (address _random,) = deployDeterministic("Random", type(Random).creationCode, abi.encode(), DEPLOYMENT_SALT); + (address _random,) = deployDeterministic("Random", type(Random).creationCode, abi.encode(DRAND_PUBLIC_KEY, DRAND_GENESIS_TIMESTAMP, DRAND_PERIOD), DEPLOYMENT_SALT); random = Random(_random); + deployed("Random", address(random)); } } diff --git a/packages/contracts/deployments/unknown/random/abiMap.json b/packages/contracts/deployments/unknown/random/abiMap.json new file mode 100644 index 0000000000..3c0b222951 --- /dev/null +++ b/packages/contracts/deployments/unknown/random/abiMap.json @@ -0,0 +1,3 @@ +{ + "Random": "Random" +} \ No newline at end of file diff --git a/packages/contracts/deployments/unknown/random/abis.json b/packages/contracts/deployments/unknown/random/abis.json new file mode 100644 index 0000000000..98b6473587 --- /dev/null +++ b/packages/contracts/deployments/unknown/random/abis.json @@ -0,0 +1,521 @@ +{ + "Random": [ + { + "type": "constructor", + "inputs": [ + { + "name": "_publicKey", + "type": "uint256[4]", + "internalType": "uint256[4]" + }, + { + "name": "_genesisTimestamp", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "_period", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "DRAND_DELAY", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "DST", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "bytes", + "internalType": "bytes" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "PRECOMMIT_DELAY", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "genesisTimestamp", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "getRevealedValue", + "inputs": [ + { + "name": "blockNumber", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "nextValidTimestamp", + "inputs": [ + { + "name": "timestamp", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "owner", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "period", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "postCommitment", + "inputs": [ + { + "name": "blockNumber", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "commitmentHash", + "type": "bytes32", + "internalType": "bytes32" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "postDrand", + "inputs": [ + { + "name": "round", + "type": "uint64", + "internalType": "uint64" + }, + { + "name": "signature", + "type": "uint256[2]", + "internalType": "uint256[2]" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "publicKey", + "inputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "random", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "bytes32", + "internalType": "bytes32" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "randomForTimestamp", + "inputs": [ + { + "name": "timestamp", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "", + "type": "bytes32", + "internalType": "bytes32" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "randomness", + "inputs": [ + { + "name": "round", + "type": "uint64", + "internalType": "uint64" + } + ], + "outputs": [ + { + "name": "randomness", + "type": "bytes32", + "internalType": "bytes32" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "renounceOwnership", + "inputs": [], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "revealValue", + "inputs": [ + { + "name": "blockNumber", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "revealedValue", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "transferOwnership", + "inputs": [ + { + "name": "newOwner", + "type": "address", + "internalType": "address" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "unsafeGetRevealedValue", + "inputs": [ + { + "name": "blockNumber", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "event", + "name": "CommitmentPosted", + "inputs": [ + { + "name": "blockNumber", + "type": "uint256", + "indexed": true, + "internalType": "uint256" + }, + { + "name": "commitment", + "type": "bytes32", + "indexed": false, + "internalType": "bytes32" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "OwnershipTransferred", + "inputs": [ + { + "name": "previousOwner", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "newOwner", + "type": "address", + "indexed": true, + "internalType": "address" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "ValueRevealed", + "inputs": [ + { + "name": "blockNumber", + "type": "uint256", + "indexed": true, + "internalType": "uint256" + }, + { + "name": "revealedValue", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + } + ], + "anonymous": false + }, + { + "type": "error", + "name": "BNAddFailed", + "inputs": [ + { + "name": "input", + "type": "uint256[4]", + "internalType": "uint256[4]" + } + ] + }, + { + "type": "error", + "name": "CommitmentAlreadyExists", + "inputs": [] + }, + { + "type": "error", + "name": "CommitmentTooLate", + "inputs": [] + }, + { + "type": "error", + "name": "DrandNotAvailable", + "inputs": [ + { + "name": "round", + "type": "uint64", + "internalType": "uint64" + } + ] + }, + { + "type": "error", + "name": "InvalidDSTLength", + "inputs": [ + { + "name": "dst", + "type": "bytes", + "internalType": "bytes" + } + ] + }, + { + "type": "error", + "name": "InvalidFieldElement", + "inputs": [ + { + "name": "x", + "type": "uint256", + "internalType": "uint256" + } + ] + }, + { + "type": "error", + "name": "InvalidPublicKey", + "inputs": [ + { + "name": "publicKey", + "type": "uint256[4]", + "internalType": "uint256[4]" + } + ] + }, + { + "type": "error", + "name": "InvalidReveal", + "inputs": [] + }, + { + "type": "error", + "name": "InvalidSignature", + "inputs": [ + { + "name": "publicKey", + "type": "uint256[4]", + "internalType": "uint256[4]" + }, + { + "name": "message", + "type": "uint256[2]", + "internalType": "uint256[2]" + }, + { + "name": "signature", + "type": "uint256[2]", + "internalType": "uint256[2]" + } + ] + }, + { + "type": "error", + "name": "MapToPointFailed", + "inputs": [ + { + "name": "noSqrt", + "type": "uint256", + "internalType": "uint256" + } + ] + }, + { + "type": "error", + "name": "ModExpFailed", + "inputs": [ + { + "name": "base", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "exponent", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "modulus", + "type": "uint256", + "internalType": "uint256" + } + ] + }, + { + "type": "error", + "name": "NoCommitmentFound", + "inputs": [] + }, + { + "type": "error", + "name": "OwnableInvalidOwner", + "inputs": [ + { + "name": "owner", + "type": "address", + "internalType": "address" + } + ] + }, + { + "type": "error", + "name": "OwnableUnauthorizedAccount", + "inputs": [ + { + "name": "account", + "type": "address", + "internalType": "address" + } + ] + }, + { + "type": "error", + "name": "RevealMustBeOnExactBlock", + "inputs": [] + }, + { + "type": "error", + "name": "RevealedValueNotAvailable", + "inputs": [] + } + ] +} diff --git a/packages/contracts/deployments/unknown/random/abis.ts b/packages/contracts/deployments/unknown/random/abis.ts new file mode 100644 index 0000000000..498792bcef --- /dev/null +++ b/packages/contracts/deployments/unknown/random/abis.ts @@ -0,0 +1,554 @@ +// This file is auto-generated by `make deploy` in `packages/contracts/Makefile` +import type { MapTuple, ObjectFromTuples, UnionToTuple } from "@happychain/common" +import type { Address } from "viem" + +const contractToAbi = ({ + "Random": [ + { + "type": "constructor", + "inputs": [ + { + "name": "_publicKey", + "type": "uint256[4]", + "internalType": "uint256[4]" + }, + { + "name": "_genesisTimestamp", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "_period", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "DRAND_DELAY", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "DST", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "bytes", + "internalType": "bytes" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "PRECOMMIT_DELAY", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "genesisTimestamp", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "getRevealedValue", + "inputs": [ + { + "name": "blockNumber", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "nextValidTimestamp", + "inputs": [ + { + "name": "timestamp", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "owner", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "period", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "postCommitment", + "inputs": [ + { + "name": "blockNumber", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "commitmentHash", + "type": "bytes32", + "internalType": "bytes32" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "postDrand", + "inputs": [ + { + "name": "round", + "type": "uint64", + "internalType": "uint64" + }, + { + "name": "signature", + "type": "uint256[2]", + "internalType": "uint256[2]" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "publicKey", + "inputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "random", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "bytes32", + "internalType": "bytes32" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "randomForTimestamp", + "inputs": [ + { + "name": "timestamp", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "", + "type": "bytes32", + "internalType": "bytes32" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "randomness", + "inputs": [ + { + "name": "round", + "type": "uint64", + "internalType": "uint64" + } + ], + "outputs": [ + { + "name": "randomness", + "type": "bytes32", + "internalType": "bytes32" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "renounceOwnership", + "inputs": [], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "revealValue", + "inputs": [ + { + "name": "blockNumber", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "revealedValue", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "transferOwnership", + "inputs": [ + { + "name": "newOwner", + "type": "address", + "internalType": "address" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "unsafeGetRevealedValue", + "inputs": [ + { + "name": "blockNumber", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "event", + "name": "CommitmentPosted", + "inputs": [ + { + "name": "blockNumber", + "type": "uint256", + "indexed": true, + "internalType": "uint256" + }, + { + "name": "commitment", + "type": "bytes32", + "indexed": false, + "internalType": "bytes32" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "OwnershipTransferred", + "inputs": [ + { + "name": "previousOwner", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "newOwner", + "type": "address", + "indexed": true, + "internalType": "address" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "ValueRevealed", + "inputs": [ + { + "name": "blockNumber", + "type": "uint256", + "indexed": true, + "internalType": "uint256" + }, + { + "name": "revealedValue", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + } + ], + "anonymous": false + }, + { + "type": "error", + "name": "BNAddFailed", + "inputs": [ + { + "name": "input", + "type": "uint256[4]", + "internalType": "uint256[4]" + } + ] + }, + { + "type": "error", + "name": "CommitmentAlreadyExists", + "inputs": [] + }, + { + "type": "error", + "name": "CommitmentTooLate", + "inputs": [] + }, + { + "type": "error", + "name": "DrandNotAvailable", + "inputs": [ + { + "name": "round", + "type": "uint64", + "internalType": "uint64" + } + ] + }, + { + "type": "error", + "name": "InvalidDSTLength", + "inputs": [ + { + "name": "dst", + "type": "bytes", + "internalType": "bytes" + } + ] + }, + { + "type": "error", + "name": "InvalidFieldElement", + "inputs": [ + { + "name": "x", + "type": "uint256", + "internalType": "uint256" + } + ] + }, + { + "type": "error", + "name": "InvalidPublicKey", + "inputs": [ + { + "name": "publicKey", + "type": "uint256[4]", + "internalType": "uint256[4]" + } + ] + }, + { + "type": "error", + "name": "InvalidReveal", + "inputs": [] + }, + { + "type": "error", + "name": "InvalidSignature", + "inputs": [ + { + "name": "publicKey", + "type": "uint256[4]", + "internalType": "uint256[4]" + }, + { + "name": "message", + "type": "uint256[2]", + "internalType": "uint256[2]" + }, + { + "name": "signature", + "type": "uint256[2]", + "internalType": "uint256[2]" + } + ] + }, + { + "type": "error", + "name": "MapToPointFailed", + "inputs": [ + { + "name": "noSqrt", + "type": "uint256", + "internalType": "uint256" + } + ] + }, + { + "type": "error", + "name": "ModExpFailed", + "inputs": [ + { + "name": "base", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "exponent", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "modulus", + "type": "uint256", + "internalType": "uint256" + } + ] + }, + { + "type": "error", + "name": "NoCommitmentFound", + "inputs": [] + }, + { + "type": "error", + "name": "OwnableInvalidOwner", + "inputs": [ + { + "name": "owner", + "type": "address", + "internalType": "address" + } + ] + }, + { + "type": "error", + "name": "OwnableUnauthorizedAccount", + "inputs": [ + { + "name": "account", + "type": "address", + "internalType": "address" + } + ] + }, + { + "type": "error", + "name": "RevealMustBeOnExactBlock", + "inputs": [] + }, + { + "type": "error", + "name": "RevealedValueNotAvailable", + "inputs": [] + } + ] +} +) as const + +const aliasToContract = ({ + "Random": "Random" +}) as const + +export const deployment = ({ + "Random": "0x9ACE2eE177EB91eEed9591ea50Cb903d551DD0f9" +}) as const + +export type ContractToAbi = typeof contractToAbi +export type AliasToContract = typeof aliasToContract +export type ContractName = keyof ContractToAbi +export type ContractAlias = keyof AliasToContract +export type Deployment = Record + +type AliasTuple = UnionToTuple +type AbiValuesTuple = MapTuple, ContractToAbi> + +export type StaticAbis = ObjectFromTuples + +export const abis = {} as StaticAbis + +for (const [alias, contractName] of Object.entries(aliasToContract)) { + // biome-ignore lint/suspicious/noExplicitAny: safe generated code + abis[alias as ContractAlias] = contractToAbi[contractName as ContractName] as any +} + + diff --git a/packages/contracts/deployments/unknown/random/deployment.json b/packages/contracts/deployments/unknown/random/deployment.json new file mode 100644 index 0000000000..6c2374d088 --- /dev/null +++ b/packages/contracts/deployments/unknown/random/deployment.json @@ -0,0 +1,3 @@ +{ + "Random": "0x9ACE2eE177EB91eEed9591ea50Cb903d551DD0f9" +} \ No newline at end of file diff --git a/packages/contracts/src/Randomness/Drand.sol b/packages/contracts/src/Randomness/Drand.sol new file mode 100644 index 0000000000..6f6969c5ab --- /dev/null +++ b/packages/contracts/src/Randomness/Drand.sol @@ -0,0 +1,74 @@ +// SPDX-License-Identifier: BSD-3-Clause-Clear +pragma solidity ^0.8.20; + +import {BLS} from "bls-bn254/BLS.sol"; + +contract Drand { + bytes public constant DST = bytes("BLS_SIG_BN254G1_XMD:KECCAK-256_SVDW_RO_NUL_"); + + uint256[4] public publicKey; + uint256 public genesisTimestamp; + uint256 public period; + mapping(uint64 round => bytes32 randomness) public randomness; + + error InvalidPublicKey(uint256[4] publicKey); + error InvalidSignature(uint256[4] publicKey, uint256[2] message, uint256[2] signature); + error DrandNotAvailable(uint64 round); + + constructor(uint256[4] memory _publicKey, uint256 _genesisTimestamp, uint256 _period) { + if (!BLS.isValidPublicKey(_publicKey)) { + revert InvalidPublicKey(_publicKey); + } + publicKey = _publicKey; + genesisTimestamp = _genesisTimestamp; + period = _period; + } + + function postDrand(uint64 round, uint256[2] memory signature) external { + // Encode round for hash-to-point + bytes memory hashedRoundBytes = new bytes(32); + assembly { + mstore(0x00, round) + let hashedRound := keccak256(0x18, 0x08) // hash the last 8 bytes (uint64) of `round` + mstore(add(0x20, hashedRoundBytes), hashedRound) + } + uint256[2] memory message = BLS.hashToPoint(DST, hashedRoundBytes); + + // NB: Always check that the signature is a valid signature (a valid G1 point on the curve)! + bool isValidSignature = BLS.isValidSignature(signature); + if (!isValidSignature) { + revert InvalidSignature(publicKey, message, signature); + } + + // Verify the signature over the message using the public key + (bool pairingSuccess, bool callSuccess) = BLS.verifySingle(signature, publicKey, message); + if (!pairingSuccess) { + revert InvalidSignature(publicKey, message, signature); + } + + bytes32 roundRandomness = keccak256(abi.encode(signature)); + + randomness[round] = roundRandomness; + } + + function _unsafeGetDrand(uint64 round) internal view returns (bytes32) { + return randomness[round]; + } + + function _getDrand(uint64 round) internal view returns (bytes32) { + if (randomness[round] == 0) { + revert DrandNotAvailable(round); + } + + return randomness[round]; + } + + function _getDrandAtTimestamp(uint256 timestamp) internal view returns (bytes32) { + uint64 round = uint64((timestamp - genesisTimestamp) / period); + return _getDrand(round); + } + + function _nextValidTimestamp(uint256 timestamp) internal view returns (uint256) { + return timestamp + (period - (timestamp % period)); + } +} diff --git a/packages/contracts/src/Randomness/Random.sol b/packages/contracts/src/Randomness/Random.sol new file mode 100644 index 0000000000..7ab23d5a13 --- /dev/null +++ b/packages/contracts/src/Randomness/Random.sol @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: BSD-3-Clause-Clear +pragma solidity ^0.8.20; + +import {RandomCommitment} from "./RandomCommitment.sol"; +import {Drand} from "./Drand.sol"; + +contract Random is RandomCommitment, Drand { + uint256 public constant DRAND_DELAY = 6; + + constructor(uint256[4] memory _publicKey, uint256 _genesisTimestamp, uint256 _period) + RandomCommitment() + Drand(_publicKey, _genesisTimestamp, _period) + {} + + function random() external view returns (bytes32) { + bytes32 drand = _getDrandAtTimestamp(block.timestamp - DRAND_DELAY); + uint256 revealedValue = getRevealedValue(block.number); + return keccak256(abi.encodePacked(drand, revealedValue)); + } + + function randomForTimestamp(uint256 timestamp) external view returns (bytes32) { + return _getDrandAtTimestamp(timestamp); + } + + function nextValidTimestamp(uint256 timestamp) public view returns (uint256) { + return _nextValidTimestamp(timestamp); + } +} diff --git a/packages/contracts/src/Randomness/RandomCommitment.sol b/packages/contracts/src/Randomness/RandomCommitment.sol new file mode 100644 index 0000000000..9efeb90d2a --- /dev/null +++ b/packages/contracts/src/Randomness/RandomCommitment.sol @@ -0,0 +1,74 @@ +// SPDX-License-Identifier: BSD-3-Clause-Clear +pragma solidity ^0.8.20; + +import {Ownable} from "openzeppelin/access/Ownable.sol"; + +contract RandomCommitment is Ownable { + uint256 public constant PRECOMMIT_DELAY = 10; + + uint256 private currentRevealedValue; + uint256 private currentRevealBlockNumber; + mapping(uint256 blockNumber => bytes32) private commitments; + + event CommitmentPosted(uint256 indexed blockNumber, bytes32 commitment); + event ValueRevealed(uint256 indexed blockNumber, uint256 revealedValue); + + error CommitmentTooLate(); + error CommitmentAlreadyExists(); + error NoCommitmentFound(); + error RevealMustBeOnExactBlock(); + error InvalidReveal(); + error RevealedValueNotAvailable(); + + constructor() Ownable(msg.sender) {} + + function postCommitment(uint256 blockNumber, bytes32 commitmentHash) external onlyOwner { + if (block.number > blockNumber - PRECOMMIT_DELAY) { + revert CommitmentTooLate(); + } + + if (commitments[blockNumber] != 0) { + revert CommitmentAlreadyExists(); + } + + commitments[blockNumber] = commitmentHash; + emit CommitmentPosted(blockNumber, commitmentHash); + } + + function revealValue(uint256 blockNumber, uint256 revealedValue) external onlyOwner { + bytes32 storedCommitment = commitments[blockNumber]; + + if (storedCommitment == 0) { + revert NoCommitmentFound(); + } + + if (block.number != blockNumber) { + revert RevealMustBeOnExactBlock(); + } + + if (storedCommitment != keccak256(abi.encodePacked(revealedValue))) { + revert InvalidReveal(); + } + + currentRevealedValue = revealedValue; + currentRevealBlockNumber = blockNumber; + delete commitments[blockNumber]; + emit ValueRevealed(blockNumber, revealedValue); + } + + function unsafeGetRevealedValue(uint256 blockNumber) public view returns (uint256) { + if (currentRevealBlockNumber != blockNumber) { + return 0; + } + + return currentRevealedValue; + } + + function getRevealedValue(uint256 blockNumber) public view returns (uint256) { + if (currentRevealBlockNumber != blockNumber) { + revert RevealedValueNotAvailable(); + } + + return currentRevealedValue; + } +} From 892464a5885a48c9660d79872aa7c36a39b62dc4 Mon Sep 17 00:00:00 2001 From: Gabriel Martinez Rodriguez Date: Mon, 16 Dec 2024 12:24:44 +0100 Subject: [PATCH 02/19] feat(contract): last adjustments to the random contract --- contracts/.env.example | 9 +++- contracts/src/deploy/DeployRandom.s.sol | 4 +- packages/contracts/src/Randomness/Drand.sol | 47 +++++++++++--------- packages/contracts/src/Randomness/Random.sol | 38 +++++++++++++--- 4 files changed, 67 insertions(+), 31 deletions(-) diff --git a/contracts/.env.example b/contracts/.env.example index d30267df7a..5a30c0ecc4 100755 --- a/contracts/.env.example +++ b/contracts/.env.example @@ -94,4 +94,11 @@ export ALLOWED_BUNDLERS_LOCAL=0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc,0x976EA export ALLOWED_BUNDLERS_TEST=0x14dC79964da2C08b23698B3D3cc7Ca32193d9955,0x23618e81E3f5cdF7f54C3d65f7FBc0aBf5B21E8f # Used in scripts/account_abstraction_demo.ts -export BUNDLER_LOCAL=http://127.0.0.1:3000 \ No newline at end of file +export BUNDLER_LOCAL=http://127.0.0.1:3000 + +#################################################################################################### +# Random Contract Configuration +#################################################################################################### + +export HAPPY_GENESIS_TIMESTAMP= +export HAPPY_TIME_BLOCK=2 diff --git a/contracts/src/deploy/DeployRandom.s.sol b/contracts/src/deploy/DeployRandom.s.sol index 6d3caf5fae..3eee01d653 100644 --- a/contracts/src/deploy/DeployRandom.s.sol +++ b/contracts/src/deploy/DeployRandom.s.sol @@ -26,7 +26,9 @@ contract DeployL1 is BaseDeployScript { } function deploy() internal override { - (address _random,) = deployDeterministic("Random", type(Random).creationCode, abi.encode(DRAND_PUBLIC_KEY, DRAND_GENESIS_TIMESTAMP, DRAND_PERIOD), DEPLOYMENT_SALT); + uint256 happyGenesisTimestamp = vm.envUint("HAPPY_GENESIS_TIMESTAMP"); + uint256 happyTimeBlock = vm.envUint("HAPPY_TIME_BLOCK"); + (address _random,) = deployDeterministic("Random", type(Random).creationCode, abi.encode(DRAND_PUBLIC_KEY, DRAND_GENESIS_TIMESTAMP, DRAND_PERIOD, happyGenesisTimestamp, happyTimeBlock), DEPLOYMENT_SALT); random = Random(_random); deployed("Random", address(random)); } diff --git a/packages/contracts/src/Randomness/Drand.sol b/packages/contracts/src/Randomness/Drand.sol index 6f6969c5ab..37c911f9db 100644 --- a/packages/contracts/src/Randomness/Drand.sol +++ b/packages/contracts/src/Randomness/Drand.sol @@ -6,22 +6,24 @@ import {BLS} from "bls-bn254/BLS.sol"; contract Drand { bytes public constant DST = bytes("BLS_SIG_BN254G1_XMD:KECCAK-256_SVDW_RO_NUL_"); - uint256[4] public publicKey; - uint256 public genesisTimestamp; - uint256 public period; - mapping(uint64 round => bytes32 randomness) public randomness; + uint256[4] public drandPublicKey; + uint256 public drandGenesisTimestamp; + uint256 public drandPeriod; + mapping(uint64 round => bytes32 randomness) public drandRandomness; + + event DrandRandomnessPosted(uint64 indexed round, bytes32 randomness); error InvalidPublicKey(uint256[4] publicKey); error InvalidSignature(uint256[4] publicKey, uint256[2] message, uint256[2] signature); error DrandNotAvailable(uint64 round); - constructor(uint256[4] memory _publicKey, uint256 _genesisTimestamp, uint256 _period) { - if (!BLS.isValidPublicKey(_publicKey)) { - revert InvalidPublicKey(_publicKey); + constructor(uint256[4] memory _drandPublicKey, uint256 _drandGenesisTimestamp, uint256 _drandPeriod) { + if (!BLS.isValidPublicKey(_drandPublicKey)) { + revert InvalidPublicKey(_drandPublicKey); } - publicKey = _publicKey; - genesisTimestamp = _genesisTimestamp; - period = _period; + drandPublicKey = _drandPublicKey; + drandGenesisTimestamp = _drandGenesisTimestamp; + drandPeriod = _drandPeriod; } function postDrand(uint64 round, uint256[2] memory signature) external { @@ -37,38 +39,39 @@ contract Drand { // NB: Always check that the signature is a valid signature (a valid G1 point on the curve)! bool isValidSignature = BLS.isValidSignature(signature); if (!isValidSignature) { - revert InvalidSignature(publicKey, message, signature); + revert InvalidSignature(drandPublicKey, message, signature); } // Verify the signature over the message using the public key - (bool pairingSuccess, bool callSuccess) = BLS.verifySingle(signature, publicKey, message); + (bool pairingSuccess, bool callSuccess) = BLS.verifySingle(signature, drandPublicKey, message); if (!pairingSuccess) { - revert InvalidSignature(publicKey, message, signature); + revert InvalidSignature(drandPublicKey, message, signature); } bytes32 roundRandomness = keccak256(abi.encode(signature)); - randomness[round] = roundRandomness; + drandRandomness[round] = roundRandomness; + emit DrandRandomnessPosted(round, roundRandomness); } - function _unsafeGetDrand(uint64 round) internal view returns (bytes32) { - return randomness[round]; + function unsafeGetDrand(uint64 round) public view returns (bytes32) { + return drandRandomness[round]; } - function _getDrand(uint64 round) internal view returns (bytes32) { - if (randomness[round] == 0) { + function getDrand(uint64 round) public view returns (bytes32) { + if (drandRandomness[round] == 0) { revert DrandNotAvailable(round); } - return randomness[round]; + return drandRandomness[round]; } function _getDrandAtTimestamp(uint256 timestamp) internal view returns (bytes32) { - uint64 round = uint64((timestamp - genesisTimestamp) / period); - return _getDrand(round); + uint64 round = uint64((timestamp - drandGenesisTimestamp) / drandPeriod); + return getDrand(round); } function _nextValidTimestamp(uint256 timestamp) internal view returns (uint256) { - return timestamp + (period - (timestamp % period)); + return timestamp + (drandPeriod - (timestamp % drandPeriod)); } } diff --git a/packages/contracts/src/Randomness/Random.sol b/packages/contracts/src/Randomness/Random.sol index 7ab23d5a13..edb830c3f4 100644 --- a/packages/contracts/src/Randomness/Random.sol +++ b/packages/contracts/src/Randomness/Random.sol @@ -5,12 +5,20 @@ import {RandomCommitment} from "./RandomCommitment.sol"; import {Drand} from "./Drand.sol"; contract Random is RandomCommitment, Drand { - uint256 public constant DRAND_DELAY = 6; + uint256 public constant DRAND_DELAY = 2; + uint256 public happyGenesisTimestamp; + uint256 public happyTimeBlock; - constructor(uint256[4] memory _publicKey, uint256 _genesisTimestamp, uint256 _period) - RandomCommitment() - Drand(_publicKey, _genesisTimestamp, _period) - {} + constructor( + uint256[4] memory _publicKey, + uint256 _genesisTimestamp, + uint256 _period, + uint256 _happyGenesisTimestamp, + uint256 _happyTimeBlock + ) RandomCommitment() Drand(_publicKey, _genesisTimestamp, _period) { + happyGenesisTimestamp = _happyGenesisTimestamp; + happyTimeBlock = _happyTimeBlock; + } function random() external view returns (bytes32) { bytes32 drand = _getDrandAtTimestamp(block.timestamp - DRAND_DELAY); @@ -19,10 +27,26 @@ contract Random is RandomCommitment, Drand { } function randomForTimestamp(uint256 timestamp) external view returns (bytes32) { - return _getDrandAtTimestamp(timestamp); + return _getDrandAtTimestamp(timestamp - DRAND_DELAY); + } + + function randomForBlock(uint256 blockNumber) external view returns (bytes32) { + return _getDrandAtTimestamp(blockNumberToTimestamp(blockNumber)); } function nextValidTimestamp(uint256 timestamp) public view returns (uint256) { - return _nextValidTimestamp(timestamp); + return _nextValidTimestamp(timestamp - DRAND_DELAY) + DRAND_DELAY + 2 * drandPeriod; + } + + function nextValidBlock(uint256 blockNumber) public view returns (uint256) { + return timestampToBlockNumber(nextValidTimestamp(blockNumberToTimestamp(blockNumber))); + } + + function blockNumberToTimestamp(uint256 blockNumber) internal view returns (uint256) { + return happyGenesisTimestamp + (blockNumber - 1) * happyTimeBlock; + } + + function timestampToBlockNumber(uint256 timestamp) internal view returns (uint256) { + return (timestamp - happyGenesisTimestamp) / happyTimeBlock + 1; } } From 0b276cbfe6171589baae36d9818fdb1199ff89a9 Mon Sep 17 00:00:00 2001 From: Gabriel Martinez Rodriguez Date: Mon, 16 Dec 2024 12:26:06 +0100 Subject: [PATCH 03/19] chore(contract): modified deployment files for random contract deployed on anvil --- .../happy-sepolia/random/abis.json | 415 ++++++++++++++++- .../deployments/happy-sepolia/random/abis.ts | 417 +++++++++++++++++- .../happy-sepolia/random/transactions.json | 66 +-- 3 files changed, 851 insertions(+), 47 deletions(-) diff --git a/contracts/deployments/happy-sepolia/random/abis.json b/contracts/deployments/happy-sepolia/random/abis.json index 0420b5fbbf..5bf845ec23 100644 --- a/contracts/deployments/happy-sepolia/random/abis.json +++ b/contracts/deployments/happy-sepolia/random/abis.json @@ -2,12 +2,38 @@ "Random": [ { "type": "constructor", - "inputs": [], + "inputs": [ + { + "name": "_publicKey", + "type": "uint256[4]", + "internalType": "uint256[4]" + }, + { + "name": "_genesisTimestamp", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "_period", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "_happyGenesisTimestamp", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "_happyTimeBlock", + "type": "uint256", + "internalType": "uint256" + } + ], "stateMutability": "nonpayable" }, { "type": "function", - "name": "BLOCK_TIME", + "name": "DRAND_DELAY", "inputs": [], "outputs": [ { @@ -18,6 +44,19 @@ ], "stateMutability": "view" }, + { + "type": "function", + "name": "DST", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "bytes", + "internalType": "bytes" + } + ], + "stateMutability": "view" + }, { "type": "function", "name": "PRECOMMIT_DELAY", @@ -31,9 +70,156 @@ ], "stateMutability": "view" }, + { + "type": "function", + "name": "drandGenesisTimestamp", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "drandPeriod", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "drandPublicKey", + "inputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "drandRandomness", + "inputs": [ + { + "name": "round", + "type": "uint64", + "internalType": "uint64" + } + ], + "outputs": [ + { + "name": "randomness", + "type": "bytes32", + "internalType": "bytes32" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "getDrand", + "inputs": [ + { + "name": "round", + "type": "uint64", + "internalType": "uint64" + } + ], + "outputs": [ + { + "name": "", + "type": "bytes32", + "internalType": "bytes32" + } + ], + "stateMutability": "view" + }, { "type": "function", "name": "getRevealedValue", + "inputs": [ + { + "name": "blockNumber", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "happyGenesisTimestamp", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "happyTimeBlock", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "nextValidBlock", + "inputs": [ + { + "name": "blockNumber", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "nextValidTimestamp", "inputs": [ { "name": "timestamp", @@ -68,7 +254,7 @@ "name": "postCommitment", "inputs": [ { - "name": "timestamp", + "name": "blockNumber", "type": "uint256", "internalType": "uint256" }, @@ -81,6 +267,75 @@ "outputs": [], "stateMutability": "nonpayable" }, + { + "type": "function", + "name": "postDrand", + "inputs": [ + { + "name": "round", + "type": "uint64", + "internalType": "uint64" + }, + { + "name": "signature", + "type": "uint256[2]", + "internalType": "uint256[2]" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "random", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "bytes32", + "internalType": "bytes32" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "randomForBlock", + "inputs": [ + { + "name": "blockNumber", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "", + "type": "bytes32", + "internalType": "bytes32" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "randomForTimestamp", + "inputs": [ + { + "name": "timestamp", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "", + "type": "bytes32", + "internalType": "bytes32" + } + ], + "stateMutability": "view" + }, { "type": "function", "name": "renounceOwnership", @@ -93,7 +348,7 @@ "name": "revealValue", "inputs": [ { - "name": "timestamp", + "name": "blockNumber", "type": "uint256", "internalType": "uint256" }, @@ -119,12 +374,31 @@ "outputs": [], "stateMutability": "nonpayable" }, + { + "type": "function", + "name": "unsafeGetDrand", + "inputs": [ + { + "name": "round", + "type": "uint64", + "internalType": "uint64" + } + ], + "outputs": [ + { + "name": "", + "type": "bytes32", + "internalType": "bytes32" + } + ], + "stateMutability": "view" + }, { "type": "function", "name": "unsafeGetRevealedValue", "inputs": [ { - "name": "timestamp", + "name": "blockNumber", "type": "uint256", "internalType": "uint256" } @@ -143,7 +417,7 @@ "name": "CommitmentPosted", "inputs": [ { - "name": "timestamp", + "name": "blockNumber", "type": "uint256", "indexed": true, "internalType": "uint256" @@ -157,6 +431,25 @@ ], "anonymous": false }, + { + "type": "event", + "name": "DrandRandomnessPosted", + "inputs": [ + { + "name": "round", + "type": "uint64", + "indexed": true, + "internalType": "uint64" + }, + { + "name": "randomness", + "type": "bytes32", + "indexed": false, + "internalType": "bytes32" + } + ], + "anonymous": false + }, { "type": "event", "name": "OwnershipTransferred", @@ -181,7 +474,7 @@ "name": "ValueRevealed", "inputs": [ { - "name": "timestamp", + "name": "blockNumber", "type": "uint256", "indexed": true, "internalType": "uint256" @@ -195,6 +488,17 @@ ], "anonymous": false }, + { + "type": "error", + "name": "BNAddFailed", + "inputs": [ + { + "name": "input", + "type": "uint256[4]", + "internalType": "uint256[4]" + } + ] + }, { "type": "error", "name": "CommitmentAlreadyExists", @@ -205,11 +509,108 @@ "name": "CommitmentTooLate", "inputs": [] }, + { + "type": "error", + "name": "DrandNotAvailable", + "inputs": [ + { + "name": "round", + "type": "uint64", + "internalType": "uint64" + } + ] + }, + { + "type": "error", + "name": "InvalidDSTLength", + "inputs": [ + { + "name": "dst", + "type": "bytes", + "internalType": "bytes" + } + ] + }, + { + "type": "error", + "name": "InvalidFieldElement", + "inputs": [ + { + "name": "x", + "type": "uint256", + "internalType": "uint256" + } + ] + }, + { + "type": "error", + "name": "InvalidPublicKey", + "inputs": [ + { + "name": "publicKey", + "type": "uint256[4]", + "internalType": "uint256[4]" + } + ] + }, { "type": "error", "name": "InvalidReveal", "inputs": [] }, + { + "type": "error", + "name": "InvalidSignature", + "inputs": [ + { + "name": "publicKey", + "type": "uint256[4]", + "internalType": "uint256[4]" + }, + { + "name": "message", + "type": "uint256[2]", + "internalType": "uint256[2]" + }, + { + "name": "signature", + "type": "uint256[2]", + "internalType": "uint256[2]" + } + ] + }, + { + "type": "error", + "name": "MapToPointFailed", + "inputs": [ + { + "name": "noSqrt", + "type": "uint256", + "internalType": "uint256" + } + ] + }, + { + "type": "error", + "name": "ModExpFailed", + "inputs": [ + { + "name": "base", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "exponent", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "modulus", + "type": "uint256", + "internalType": "uint256" + } + ] + }, { "type": "error", "name": "NoCommitmentFound", diff --git a/contracts/deployments/happy-sepolia/random/abis.ts b/contracts/deployments/happy-sepolia/random/abis.ts index 43869df739..630c6e95ce 100644 --- a/contracts/deployments/happy-sepolia/random/abis.ts +++ b/contracts/deployments/happy-sepolia/random/abis.ts @@ -6,12 +6,38 @@ const contractToAbi = ({ "Random": [ { "type": "constructor", - "inputs": [], + "inputs": [ + { + "name": "_publicKey", + "type": "uint256[4]", + "internalType": "uint256[4]" + }, + { + "name": "_genesisTimestamp", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "_period", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "_happyGenesisTimestamp", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "_happyTimeBlock", + "type": "uint256", + "internalType": "uint256" + } + ], "stateMutability": "nonpayable" }, { "type": "function", - "name": "BLOCK_TIME", + "name": "DRAND_DELAY", "inputs": [], "outputs": [ { @@ -22,6 +48,19 @@ const contractToAbi = ({ ], "stateMutability": "view" }, + { + "type": "function", + "name": "DST", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "bytes", + "internalType": "bytes" + } + ], + "stateMutability": "view" + }, { "type": "function", "name": "PRECOMMIT_DELAY", @@ -35,9 +74,156 @@ const contractToAbi = ({ ], "stateMutability": "view" }, + { + "type": "function", + "name": "drandGenesisTimestamp", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "drandPeriod", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "drandPublicKey", + "inputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "drandRandomness", + "inputs": [ + { + "name": "round", + "type": "uint64", + "internalType": "uint64" + } + ], + "outputs": [ + { + "name": "randomness", + "type": "bytes32", + "internalType": "bytes32" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "getDrand", + "inputs": [ + { + "name": "round", + "type": "uint64", + "internalType": "uint64" + } + ], + "outputs": [ + { + "name": "", + "type": "bytes32", + "internalType": "bytes32" + } + ], + "stateMutability": "view" + }, { "type": "function", "name": "getRevealedValue", + "inputs": [ + { + "name": "blockNumber", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "happyGenesisTimestamp", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "happyTimeBlock", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "nextValidBlock", + "inputs": [ + { + "name": "blockNumber", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "nextValidTimestamp", "inputs": [ { "name": "timestamp", @@ -72,7 +258,7 @@ const contractToAbi = ({ "name": "postCommitment", "inputs": [ { - "name": "timestamp", + "name": "blockNumber", "type": "uint256", "internalType": "uint256" }, @@ -85,6 +271,75 @@ const contractToAbi = ({ "outputs": [], "stateMutability": "nonpayable" }, + { + "type": "function", + "name": "postDrand", + "inputs": [ + { + "name": "round", + "type": "uint64", + "internalType": "uint64" + }, + { + "name": "signature", + "type": "uint256[2]", + "internalType": "uint256[2]" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "random", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "bytes32", + "internalType": "bytes32" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "randomForBlock", + "inputs": [ + { + "name": "blockNumber", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "", + "type": "bytes32", + "internalType": "bytes32" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "randomForTimestamp", + "inputs": [ + { + "name": "timestamp", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "", + "type": "bytes32", + "internalType": "bytes32" + } + ], + "stateMutability": "view" + }, { "type": "function", "name": "renounceOwnership", @@ -97,7 +352,7 @@ const contractToAbi = ({ "name": "revealValue", "inputs": [ { - "name": "timestamp", + "name": "blockNumber", "type": "uint256", "internalType": "uint256" }, @@ -123,12 +378,31 @@ const contractToAbi = ({ "outputs": [], "stateMutability": "nonpayable" }, + { + "type": "function", + "name": "unsafeGetDrand", + "inputs": [ + { + "name": "round", + "type": "uint64", + "internalType": "uint64" + } + ], + "outputs": [ + { + "name": "", + "type": "bytes32", + "internalType": "bytes32" + } + ], + "stateMutability": "view" + }, { "type": "function", "name": "unsafeGetRevealedValue", "inputs": [ { - "name": "timestamp", + "name": "blockNumber", "type": "uint256", "internalType": "uint256" } @@ -147,7 +421,7 @@ const contractToAbi = ({ "name": "CommitmentPosted", "inputs": [ { - "name": "timestamp", + "name": "blockNumber", "type": "uint256", "indexed": true, "internalType": "uint256" @@ -161,6 +435,25 @@ const contractToAbi = ({ ], "anonymous": false }, + { + "type": "event", + "name": "DrandRandomnessPosted", + "inputs": [ + { + "name": "round", + "type": "uint64", + "indexed": true, + "internalType": "uint64" + }, + { + "name": "randomness", + "type": "bytes32", + "indexed": false, + "internalType": "bytes32" + } + ], + "anonymous": false + }, { "type": "event", "name": "OwnershipTransferred", @@ -185,7 +478,7 @@ const contractToAbi = ({ "name": "ValueRevealed", "inputs": [ { - "name": "timestamp", + "name": "blockNumber", "type": "uint256", "indexed": true, "internalType": "uint256" @@ -199,6 +492,17 @@ const contractToAbi = ({ ], "anonymous": false }, + { + "type": "error", + "name": "BNAddFailed", + "inputs": [ + { + "name": "input", + "type": "uint256[4]", + "internalType": "uint256[4]" + } + ] + }, { "type": "error", "name": "CommitmentAlreadyExists", @@ -209,11 +513,108 @@ const contractToAbi = ({ "name": "CommitmentTooLate", "inputs": [] }, + { + "type": "error", + "name": "DrandNotAvailable", + "inputs": [ + { + "name": "round", + "type": "uint64", + "internalType": "uint64" + } + ] + }, + { + "type": "error", + "name": "InvalidDSTLength", + "inputs": [ + { + "name": "dst", + "type": "bytes", + "internalType": "bytes" + } + ] + }, + { + "type": "error", + "name": "InvalidFieldElement", + "inputs": [ + { + "name": "x", + "type": "uint256", + "internalType": "uint256" + } + ] + }, + { + "type": "error", + "name": "InvalidPublicKey", + "inputs": [ + { + "name": "publicKey", + "type": "uint256[4]", + "internalType": "uint256[4]" + } + ] + }, { "type": "error", "name": "InvalidReveal", "inputs": [] }, + { + "type": "error", + "name": "InvalidSignature", + "inputs": [ + { + "name": "publicKey", + "type": "uint256[4]", + "internalType": "uint256[4]" + }, + { + "name": "message", + "type": "uint256[2]", + "internalType": "uint256[2]" + }, + { + "name": "signature", + "type": "uint256[2]", + "internalType": "uint256[2]" + } + ] + }, + { + "type": "error", + "name": "MapToPointFailed", + "inputs": [ + { + "name": "noSqrt", + "type": "uint256", + "internalType": "uint256" + } + ] + }, + { + "type": "error", + "name": "ModExpFailed", + "inputs": [ + { + "name": "base", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "exponent", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "modulus", + "type": "uint256", + "internalType": "uint256" + } + ] + }, { "type": "error", "name": "NoCommitmentFound", @@ -260,7 +661,7 @@ const aliasToContract = ({ }) as const export const deployment = ({ - "Random": "0x51745e910FaD45A6cA620bCc7a7fAB7683B142e5" + "Random": "0x5FbDB2315678afecb367f032d93F642f64180aa3" }) as const export type ContractToAbi = typeof contractToAbi diff --git a/contracts/deployments/happy-sepolia/random/transactions.json b/contracts/deployments/happy-sepolia/random/transactions.json index 52ae922b75..906dd62b2d 100644 --- a/contracts/deployments/happy-sepolia/random/transactions.json +++ b/contracts/deployments/happy-sepolia/random/transactions.json @@ -1,20 +1,25 @@ { "transactions": [ { - "hash": "0xbd88a005ee7688787396e35537769b0f4360c64d0fc1268d1e581a1a264a568c", - "transactionType": "CREATE2", + "hash": "0xf14e183e774da1688029fd42690e1db562182e2d9594c1482e8f2e6a164d33f7", + "transactionType": "CREATE", "contractName": "Random", "contractAddress": "0x51745e910fad45a6ca620bcc7a7fab7683b142e5", "function": null, - "arguments": null, + "arguments": [ + "[2416910118189096557713698606232949750075245832257361418817199221841198809231, 3565178688866727608783247307855519961161197286613423629330948765523825963906, 18766085122067595057703228467555884757373773082319035490740181099798629248523, 263980444642394177375858669180402387903005329333277938776544051059273779190]", + "1727521075", + "3", + "1734347812", + "1" + ], "transaction": { - "from": "0xee3ae13ed56e877874a6c5fbe7cda7fc8573a7be", - "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", - "gas": "0x88ac4", + "from": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", + "gas": "0x3678db", "value": "0x0", - "input": "0x00000000000000000000000000000000000000000000000000000000000000006080604052348015600e575f80fd5b503380603357604051631e4fbdf760e01b81525f600482015260240160405180910390fd5b603a81603f565b50608e565b5f80546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b6105f08061009b5f395ff3fe608060405234801561000f575f80fd5b506004361061009f575f3560e01c80638da5cb5b11610072578063f5fa417a11610058578063f5fa417a14610133578063f930191014610146578063fd0a5aaf1461014e575f80fd5b80638da5cb5b146100f9578063f2fde38b14610120575f80fd5b80631d539681146100a35780635f6f07dc146100b8578063715018a6146100cb5780637b308b6d146100d3575b5f80fd5b6100b66100b136600461050b565b610156565b005b6100b66100c636600461050b565b610237565b6100b6610375565b6100e66100e136600461052b565b610388565b6040519081526020015b60405180910390f35b5f5460405173ffffffffffffffffffffffffffffffffffffffff90911681526020016100f0565b6100b661012e366004610542565b6103a1565b6100e661014136600461052b565b610409565b6100e6600281565b6100e6600a81565b61015e610445565b610169600a8361057c565b4211156101a2576040517f4c8e490f00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f82815260036020526040902054156101e7576040517f145718a700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f82815260036020526040908190208290555182907f7a27537ccaa1ad9d9b038704d7cf6ec9b4e73e786ec619181e3eb1451de868d89061022b9084815260200190565b60405180910390a25050565b61023f610445565b5f8281526003602052604081205490819003610287576040517fc6c155d800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8242146102c0576040517f7fc88be700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60408051602081018490520160405160208183030381529060405280519060200120811461031a576040517f9ea6d12700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600182905560028390555f83815260036020526040808220919091555183907f42629d9632d9a72fcd50885db3d64e9e40e4c13f06af15d9241ef817bdf265bc906103689085815260200190565b60405180910390a2505050565b61037d610445565b6103865f610497565b565b5f816002541461039957505f919050565b505060015490565b6103a9610445565b73ffffffffffffffffffffffffffffffffffffffff81166103fd576040517f1e4fbdf70000000000000000000000000000000000000000000000000000000081525f60048201526024015b60405180910390fd5b61040681610497565b50565b5f8160025414610399576040517f481b130600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f5473ffffffffffffffffffffffffffffffffffffffff163314610386576040517f118cdaa70000000000000000000000000000000000000000000000000000000081523360048201526024016103f4565b5f805473ffffffffffffffffffffffffffffffffffffffff8381167fffffffffffffffffffffffff0000000000000000000000000000000000000000831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b5f806040838503121561051c575f80fd5b50508035926020909101359150565b5f6020828403121561053b575f80fd5b5035919050565b5f60208284031215610552575f80fd5b813573ffffffffffffffffffffffffffffffffffffffff81168114610575575f80fd5b9392505050565b818103818111156105b4577f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b9291505056fea2646970667358221220b9d206ef5d57f69370f92d0b959fff0ce2b1f5951c7c981e6fb314500210fa6e64736f6c634300081a0033", - "nonce": "0x14", - "chainId": "0xd8" + "input": "0x608060405234801561000f575f80fd5b5060405161310638038061310683398101604081905261002e91610342565b848484338061005757604051631e4fbdf760e01b81525f60048201526024015b60405180910390fd5b610060816100b2565b5061006a83610101565b610089578260405163c7728ee960e01b815260040161004e91906103da565b610095600484816102dc565b5060089190915560095550600b91909155600c555061040a915050565b5f80546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b80515f905f805160206130e6833981519152111580610131575060208201515f805160206130e683398151915211155b80610168575060408201515f805160206130e6833981519152111580610168575060608201515f805160206130e683398151915211155b1561017457505f919050565b61017d82610183565b92915050565b5f815160208301515f805160206130e68339815191528283095f805160206130e683398151915282830981828301015f805160206130e683398151915282838401085f805160206130e683398151915286825f805160206130e6833981519152038601099350505f805160206130e683398151915284835f805160206130e68339815191520383010991505f805160206130e68339815191527f2b149d40ceb8aaae81be18991be06ac3b5b4c5e559dbefa33267e6dc24a138e5840894505f805160206130e68339815191527e9713b03af0fed4cd2cafadeed8fdf4a74fa084e52d1852e4a2bd0685c315d28308935060408701519250606087015191505f805160206130e683398151915280835f805160206130e68339815191520385085f805160206130e68339815191528486080990505f805160206130e6833981519152828460011b0994149290931491909116949350505050565b826004810192821561030a579160200282015b8281111561030a5782518255916020019190600101906102ef565b5061031692915061031a565b5090565b5b80821115610316575f815560010161031b565b634e487b7160e01b5f52604160045260245ffd5b5f805f805f6101008688031215610357575f80fd5b86601f870112610365575f80fd5b604051608081016001600160401b03811182821017156103875761038761032e565b60405280608088018981111561039b575f80fd5b885b818110156103b557805183526020928301920161039d565b505160a089015160c08a015160e0909a0151939b919a50989750919550909350505050565b6080810181835f5b60048110156104015781518352602092830192909101906001016103e2565b50505092915050565b612ccf806104175f395ff3fe608060405234801561000f575f80fd5b506004361061018f575f3560e01c80637b308b6d116100dd578063f14ff4ef11610088578063f5fa417a11610063578063f5fa417a14610326578063f61ff62314610339578063fd0a5aaf1461034c575f80fd5b8063f14ff4ef146102ed578063f2fde38b14610300578063f398a6db14610313575f80fd5b80638da5cb5b116100b85780638da5cb5b146102aa5780639c8c9b4a146102d1578063dc0998aa146102da575f80fd5b80637b308b6d146102865780638285d8891461029957806386377e8b146102a1575f80fd5b80635f7c75221161013d5780636fe318c1116101185780636fe318c114610256578063715018a6146102755780637425518f1461027d575f80fd5b80635f7c75221461021b5780636037d218146102305780636d1ef32814610243575f80fd5b80634a8ae6311161016d5780634a8ae631146101ed5780635ec01e4d146102005780635f6f07dc14610208575f80fd5b8063110a85bf146101935780631d539681146101af5780633d55a583146101c4575b5f80fd5b61019c600b5481565b6040519081526020015b60405180910390f35b6101c26101bd3660046127c7565b610354565b005b61019c6101d23660046127fe565b67ffffffffffffffff165f908152600a602052604090205490565b61019c6101fb366004612817565b610435565b61019c61044f565b6101c26102163660046127c7565b61049f565b6102236105dd565b6040516101a6919061282e565b61019c61023e3660046127fe565b6105f9565b6101c26102513660046128ae565b610677565b61019c6102643660046127fe565b600a6020525f908152604090205481565b6101c2610832565b61019c60095481565b61019c610294366004612817565b610845565b61019c600281565b61019c600c5481565b5f5460405173ffffffffffffffffffffffffffffffffffffffff90911681526020016101a6565b61019c60085481565b61019c6102e8366004612817565b61085e565b61019c6102fb366004612817565b610874565b6101c261030e36600461293a565b610881565b61019c610321366004612817565b6108e4565b61019c610334366004612817565b6108f9565b61019c610347366004612817565b610935565b61019c600a81565b61035c61096d565b610367600a8361299a565b4311156103a0576040517f4c8e490f00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f82815260036020526040902054156103e5576040517f145718a700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f82815260036020526040908190208290555182907f7a27537ccaa1ad9d9b038704d7cf6ec9b4e73e786ec619181e3eb1451de868d8906104299084815260200190565b60405180910390a25050565b5f61044961044460028461299a565b6109bf565b92915050565b5f8061045f61044460024261299a565b90505f61046b436108f9565b6040805160208101859052908101829052909150606001604051602081830303815290604052805190602001209250505090565b6104a761096d565b5f82815260036020526040812054908190036104ef576040517fc6c155d800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b824314610528576040517f7fc88be700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b604080516020810184905201604051602081830303815290604052805190602001208114610582576040517f9ea6d12700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600182905560028390555f83815260036020526040808220919091555183907f42629d9632d9a72fcd50885db3d64e9e40e4c13f06af15d9241ef817bdf265bc906105d09085815260200190565b60405180910390a2505050565b6040518060600160405280602b8152602001612c6f602b913981565b67ffffffffffffffff81165f908152600a6020526040812054810361065b576040517fc3f4d68a00000000000000000000000000000000000000000000000000000000815267ffffffffffffffff831660048201526024015b60405180910390fd5b5067ffffffffffffffff165f908152600a602052604090205490565b6040805160208082528183019092525f91602082018180368337019050509050825f526008601820808260200152505f6106c96040518060600160405280602b8152602001612c6f602b9139836109ee565b90505f6106d584610aa3565b90508061071557600482856040517fb8cf7d30000000000000000000000000000000000000000000000000000000008152600401610652939291906129d5565b6040805160808101918290525f91829161075291889190600490819081845b81548152602001906001019080831161073457505050505086610b13565b915091508161079457600484876040517fb8cf7d30000000000000000000000000000000000000000000000000000000008152600401610652939291906129d5565b5f866040516020016107a69190612a22565b604080518083037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0018152828252805160209182012067ffffffffffffffff8c165f818152600a845293909320819055808452935090917fb262efe8f3fb8d5cb448e8771d8d93fe981dfa54d2e292afe1b4970dc9237134910160405180910390a25050505050505050565b61083a61096d565b6108435f610cc5565b565b5f816002541461085657505f919050565b505060015490565b6004816004811061086d575f80fd5b0154905081565b5f61044961044483610d39565b61088961096d565b73ffffffffffffffffffffffffffffffffffffffff81166108d8576040517f1e4fbdf70000000000000000000000000000000000000000000000000000000081525f6004820152602401610652565b6108e181610cc5565b50565b5f6104496108f461034784610d39565b610d60565b5f8160025414610856576040517f481b130600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f60095460026109459190612a30565b6002610959610954828661299a565b610d87565b6109639190612a47565b6104499190612a47565b5f5473ffffffffffffffffffffffffffffffffffffffff163314610843576040517f118cdaa7000000000000000000000000000000000000000000000000000000008152336004820152602401610652565b5f80600954600854846109d2919061299a565b6109dc9190612a87565b90506109e7816105f9565b9392505050565b6109f661276d565b5f610a018484610dad565b90505f610a1482825b6020020151610ef8565b90505f610a22836001610a0a565b9050610a2c61278b565b825181526020808401518282015282516040808401919091529083015160608301525f908460808460066107d05a03fa905080610a9757816040517f128e3f080000000000000000000000000000000000000000000000000000000081526004016106529190612ac7565b50919695505050505050565b80515f907f30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47111580610af9575060208201517f30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd4711155b15610b0557505f919050565b610449826113a0565b919050565b5f805f604051806101800160405280875f60028110610b3457610b34612a9a565b6020020151815260200187600160028110610b5157610b51612a9a565b602002015181526020017f198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c281526020017f1800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed81526020017f275dc4a288d1afb3cbb1ac09187524c7db36395df7be3b99e673b13a075a65ec81526020017f1d9befcd05a5323e6da4d435f3b617cdb3af83285c2df711ef39c01571827f9d8152602001855f60028110610c0557610c05612a9a565b6020020151815260200185600160028110610c2257610c22612a9a565b6020020151815260200186600160048110610c3f57610c3f612a9a565b60200201518152602001865f60048110610c5b57610c5b612a9a565b6020020151815260200186600360048110610c7857610c78612a9a565b6020020151815260200186600260048110610c9557610c95612a9a565b602002015190529050610ca66127a9565b6020816101808460086107d05a03fa9051151597909650945050505050565b5f805473ffffffffffffffffffffffffffffffffffffffff8381167fffffffffffffffffffffffff0000000000000000000000000000000000000000831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b600c545f90610d4960018461299a565b610d539190612a30565b600b546104499190612a47565b5f600c54600b5483610d72919061299a565b610d7c9190612a87565b610449906001612a47565b5f60095482610d969190612af7565b600954610da3919061299a565b6104499083612a47565b610db561276d565b5f610dc08484611446565b90505f805f806018850177ffffffffffffffffffffffffffffffffffffffffffffffff815116935060308601905077ffffffffffffffffffffffffffffffffffffffffffffffff81511694507f30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47857f30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd4778010000000000000000000000000000000000000000000000008709086048870151606088015177ffffffffffffffffffffffffffffffffffffffffffffffff908116975016945092507f30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47905084817801000000000000000000000000000000000000000000000000860908604080518082019091529283526020830152509695505050505050565b610f0061276d565b7f30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd478210610f5c576040517fd53e941500000000000000000000000000000000000000000000000000000000815260048101839052602401610652565b5f7f30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd4760047f30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd478586090990505f7f30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd478260010890507f30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd4761101a837f30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd4761299a565b60010891505f61104c7f30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47838509611630565b90505f7f30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd4778016789af3a83522eb353c98fc6b36d713d5d8d1cc5dffffffa7f30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47847f30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47888b09090990505f7f30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47611120837f30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd4761299a565b7f183227397098d014dc2822db40c0ac2ecbc0b548b438e5469e10460b6c3e7ea30890505f7f30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47837f183227397098d014dc2822db40c0ac2ecbc0b548b438e5469e10460b6c3e7ea30890505f7f30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd4786870990505f7f30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd4786830990505f7f30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47807f30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd478485097f10216f7ba065e00de81ac1e7808072c9dd2b2385cd7b438469602eb24829a9bd0960010890505f8061125a6112558861163a565b6116ac565b5f0b6001036112c357868c5261126f8761163a565b905061127a8161175c565b60208e01919091529150816112be576040517f396ec77100000000000000000000000000000000000000000000000000000000815260048101829052602401610652565b61133f565b6112cf6112558761163a565b5f0b6001036112e457858c5261126f8661163a565b828c526112f08361163a565b90506112fb8161175c565b60208e019190915291508161133f576040517f396ec77100000000000000000000000000000000000000000000000000000000815260048101829052602401610652565b60208c015161134d90611796565b6113568e611796565b146113905760208c015161138a907f30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd4761299a565b60208d01525b5050505050505050505050919050565b5f815160208301517f30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd478283097f30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd4783820990507f30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd476003820890507f30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd4782830914949350505050565b815160609060ff81111561148857836040517f26e4f9ba000000000000000000000000000000000000000000000000000000008152600401610652919061282e565b60408051608880825260c082019092525f916020820181803683370190505090505f81855f60605f8a886040516020016114c89796959493929190612b21565b60405160208183030381529060405290505f818051906020012090505f81600189876040516020016114fd9493929190612bfc565b604080518083037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe001815282825280516020820120606080855260808501909352909350915f91602082018180368337019050509050600360015b818110156115f45785841861156e826001612a47565b8d8b6040516020016115839493929190612bfc565b604080518083037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe001815291905260207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff830181028501810195909552805194810194909420939450600101611558565b507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff016020908102820101919091529550505050505092915050565b5f610449826117a2565b5f7f30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd4760037f30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47847f30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47868709090892915050565b5f806116b783611efa565b90506116e460017f30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd4761299a565b810361171257507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff92915050565b8015801590611722575080600114155b15610449576040517f396ec77100000000000000000000000000000000000000000000000000000000815260048101849052602401610652565b5f8061176783612027565b9150827f30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47838409149050915091565b5f610449600283612af7565b5f7f30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd478083840991508083830981838209828283098385830984848309858484098684850997508684840987858409945087898a09985087898a09985087898a09985087898a09985087898a09985087878a09985087898a09985087898a09985087898a099850878a8a09985087898a09985087898a09985087898a09985087898a099850878a8a09985087898a09985087898a09985087898a09985087898a09985087898a09985087848a09985087898a09985087898a09985087898a09985087898a09985087898a09985087848a09985087898a09985087898a09985087898a099850878a8a09985087898a09985087898a09985087898a09985087898a09985087848a09985087898a09985087898a09985087898a09985087898a09985087898a099850878a8a09985087898a09985087898a09985087898a09985087898a09985087878a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087818a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087868a09985087898a09985087898a09985087898a09985087898a09985087878a09985087898a09985087898a09985087898a09985087898a09985087848a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087868a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a099850878a8a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087828a09985087898a09985087898a09985087898a09985087898a09985087898a09985087818a09985087898a09985087898a09985087898a09985087868a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087878a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087868a09985087898a09985087898a09985087898a09985087878a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087828a09985087898a09985087898a09985087898a09985087898a09985087828a09985087898a09985087898a09985087898a09985087898a09985087898a09985087868a09985087898a09985087898a09985087898a09985087848a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087828a09985087898a09985087898a09985087898a09985087898a09985087868a09985087898a09985087898a09985087898a09985087898a09985087898a09985087838a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087828a09985087898a09985087898a099850878a8a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087848a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087848a09985087898a09985087898a09985087898a09985087898a09985087898a09985087868a09985087898a09985087898a099850878a8a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087818a09985050868889099750868889099750868889099750868889099750868889099750868889099750868489099750868889099750868889099750868889099750868889099750868889099750868989099750868889099750868889099750868889099750868889099750868889099750868889099750868989099750868889099750868889099750868889099750868889099750868889099750868689099750868889099750868889099750868889099750868889099750868889099750868889099750868889099750868889099750868889099750868189099750508587880996508587880996508587880996508585880996508587880996508587880996508587880996508585880996508587880996508587880996508587880996508587880996508587880996508587880996508587880996508587880996508583880996508587880996508587880996508587880996508587880996508581880996505050838586099450838586099450838586099450838586099450838186099450508284850993508284850993508284850993508281850993508284850993508284850993508285850993508284850993508284850993508284850993508284850993508284850993508284850993508281850995945050505050565b6040805160c080825260e082019092525f918291906020820181803683370190505060208082018181526040830182905260608301829052608083018690527f183227397098d014dc2822db40c0ac2ecbc0b548b438e5469e10460b6c3e7ea360a08401527f30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd4760c0808501919091529293505f92839160055afa90505f51925080612020576040517fc6daf7ab000000000000000000000000000000000000000000000000000000008152600481018590527f183227397098d014dc2822db40c0ac2ecbc0b548b438e5469e10460b6c3e7ea360248201527f30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd476044820152606401610652565b5050919050565b5f7f30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd478083840991508083830981838209828283098385830984848309858484098684850997508684840987858409945087898a09985087898a09985087898a09985087898a09985087898a09985087878a09985087898a09985087898a09985087898a099850878a8a09985087898a09985087898a09985087898a09985087898a099850878a8a09985087898a09985087898a09985087898a09985087898a09985087898a09985087848a09985087898a09985087898a09985087898a09985087898a09985087898a09985087848a09985087898a09985087898a09985087898a099850878a8a09985087898a09985087898a09985087898a09985087898a09985087848a09985087898a09985087898a09985087898a09985087898a09985087898a099850878a8a09985087898a09985087898a09985087898a09985087898a09985087878a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087818a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087868a09985087898a09985087898a09985087898a09985087898a09985087878a09985087898a09985087898a09985087898a09985087898a09985087848a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087868a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a099850878a8a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087828a09985087898a09985087898a09985087898a09985087898a09985087898a09985087818a09985087898a09985087898a09985087898a09985087868a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087878a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087868a09985087898a09985087898a09985087898a09985087878a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087828a09985087898a09985087898a09985087898a09985087898a09985087828a09985087898a09985087898a09985087898a09985087898a09985087898a09985087868a09985087898a09985087898a09985087898a09985087848a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087828a09985087898a09985087898a09985087898a09985087898a09985087868a09985087898a09985087898a09985087898a09985087898a09985087898a09985087838a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087828a09985087898a09985087898a099850878a8a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087848a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087848a09985087898a09985087898a09985087898a09985087898a09985087898a09985087868a09985087898a09985087898a099850878a8a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087898a09985087818a09985050868889099750868889099750868889099750868889099750868889099750868889099750868489099750868889099750868889099750868889099750868889099750868889099750868989099750868889099750868889099750868889099750868889099750868889099750868889099750868989099750868889099750868889099750868889099750868889099750868889099750868689099750868889099750868889099750868889099750868889099750868889099750868889099750868889099750868889099750868889099750868189099750508587880996508587880996508587880996508585880996508587880996508587880996508587880996508585880996508587880996508587880996508587880996508587880996508587880996508587880996508587880996508587880996508583880996508587880996508587880996508587880996508587880996508581880996508587880996508587880996508587880996508587880996508583880996508587880996508587880996508587880996508584880996508587880996508587880996508587880996508587880996508587880996508581880996505050505050808283099392505050565b60405180604001604052806002906020820280368337509192915050565b60405180608001604052806004906020820280368337509192915050565b60405180602001604052806001906020820280368337509192915050565b5f80604083850312156127d8575f80fd5b50508035926020909101359150565b803567ffffffffffffffff81168114610b0e575f80fd5b5f6020828403121561280e575f80fd5b6109e7826127e7565b5f60208284031215612827575f80fd5b5035919050565b602081525f82518060208401528060208501604085015e5f6040828501015260407fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f83011684010191505092915050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b5f80606083850312156128bf575f80fd5b6128c8836127e7565b915083603f8401126128d8575f80fd5b6040516040810181811067ffffffffffffffff821117156128fb576128fb612881565b60405280606085018681111561290f575f80fd5b602086015b8181101561292c578035835260209283019201612914565b505050809150509250929050565b5f6020828403121561294a575f80fd5b813573ffffffffffffffffffffffffffffffffffffffff811681146109e7575f80fd5b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b818103818111156104495761044961296d565b805f5b60028110156129cf5781518452602093840193909101906001016129b0565b50505050565b610100810181855f5b60048110156129fd5781548352602090920191600191820191016129de565b505050612a0d60808301856129ad565b612a1a60c08301846129ad565b949350505050565b6040810161044982846129ad565b80820281158282048414176104495761044961296d565b808201808211156104495761044961296d565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601260045260245ffd5b5f82612a9557612a95612a5a565b500490565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52603260045260245ffd5b6080810181835f5b6004811015612aee578151835260209283019290910190600101612acf565b50505092915050565b5f82612b0557612b05612a5a565b500690565b5f81518060208401855e5f93019283525090919050565b5f612b35612b2f838b612b0a565b89612b0a565b7fff000000000000000000000000000000000000000000000000000000000000008860f81b1681527fff000000000000000000000000000000000000000000000000000000000000008760f81b1660018201527fff000000000000000000000000000000000000000000000000000000000000008660f81b166002820152612bc06003820186612b0a565b60f89490941b7fff0000000000000000000000000000000000000000000000000000000000000016845250506001909101979650505050505050565b8481527fff000000000000000000000000000000000000000000000000000000000000008460f81b1660208201525f612c386021830185612b0a565b60f89390931b7fff000000000000000000000000000000000000000000000000000000000000001683525050600101939250505056fe424c535f5349475f424e32353447315f584d443a4b454343414b2d3235365f535644575f524f5f4e554c5fa26469706673582212202fd9bb390e4afffd96a5eac36eeaa90e7558beccfde421e09e7c70674504abca64736f6c634300081a003330644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd470557ec32c2ad488e4d4f6008f89a346f18492092ccc0d594610de2732c8b808f07e1d1d335df83fa98462005690372c643340060d205306a9aa8106b6bd0b382297d3a4f9749b33eb2d904c9d9ebf17224150ddd7abd7567a9bec6c74480ee0b0095685ae3a85ba243747b1b2f426049010f6b73a0cf1d389351d5aaaa1047f60000000000000000000000000000000000000000000000000000000066f7e13300000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000067600c240000000000000000000000000000000000000000000000000000000000000001", + "nonce": "0x0", + "chainId": "0x7a69" }, "additionalContracts": [], "isFixedGasLimit": false @@ -23,7 +28,7 @@ "receipts": [ { "status": "0x1", - "cumulativeGasUsed": "0x6da71", + "cumulativeGasUsed": "0x29e6d0", "logs": [ { "address": "0x51745e910fad45a6ca620bcc7a7fab7683b142e5", @@ -33,37 +38,34 @@ "0x0000000000000000000000004e59b44847b379578588920ca78fbf26c0b4956c" ], "data": "0x", - "blockHash": "0x294394e9a85f230aa7742f76d0fe35298805d71df8b1995043f2f3db571e32e4", - "blockNumber": "0x712d8e", - "transactionHash": "0xbd88a005ee7688787396e35537769b0f4360c64d0fc1268d1e581a1a264a568c", - "transactionIndex": "0x1", + "blockHash": "0xb71a399092177ae884f9f133b0610abab56396c27f5fb93a10cae78cdcb4912c", + "blockNumber": "0x6", + "blockTimestamp": "0x67600d77", + "transactionHash": "0xf14e183e774da1688029fd42690e1db562182e2d9594c1482e8f2e6a164d33f7", + "transactionIndex": "0x0", "logIndex": "0x0", "removed": false } ], "logsBloom": "0x00000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004001000000000000000000000000000000000000020000000000000000000800000000000000000000000000000000400000000000000000200000000000000000000000001000000000000000000000000000000001000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000080000000000000000000000000000000", "type": "0x2", - "transactionHash": "0xbd88a005ee7688787396e35537769b0f4360c64d0fc1268d1e581a1a264a568c", - "transactionIndex": "0x1", - "blockHash": "0x294394e9a85f230aa7742f76d0fe35298805d71df8b1995043f2f3db571e32e4", - "blockNumber": "0x712d8e", - "gasUsed": "0x62f2f", - "effectiveGasPrice": "0xfd", - "from": "0xee3ae13ed56e877874a6c5fbe7cda7fc8573a7be", - "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", - "contractAddress": "0x51745e910fad45a6ca620bcc7a7fab7683b142e5", - "l1BaseFeeScalar": "0xa31c2", - "l1BlobBaseFee": "0x1", - "l1BlobBaseFeeScalar": "0x0", - "l1Fee": "0x19cb2abcef8ba", - "l1GasPrice": "0xa961baff7", - "l1GasUsed": "0x3a59" + "transactionHash": "0xf14e183e774da1688029fd42690e1db562182e2d9594c1482e8f2e6a164d33f7", + "transactionIndex": "0x0", + "blockHash": "0xb71a399092177ae884f9f133b0610abab56396c27f5fb93a10cae78cdcb4912c", + "blockNumber": "0x6", + "gasUsed": "0x29e6d0", + "effectiveGasPrice": "0x1e925e89", + "blobGasPrice": "0x1", + "from": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", + "to": null, + "contractAddress": "0x5fbdb2315678afecb367f032d93f642f64180aa3", + "root": "0xffcf090a4e06a43746a3cfc827f38a459922276c52e6bf8b924dda5221b7a345" } ], "libraries": [], "pending": [], "returns": {}, - "timestamp": 1737999999, - "chain": 216, - "commit": "91cb8c79" + "timestamp": 1734348151, + "chain": 31337, + "commit": "79f7e6eb" } \ No newline at end of file From 5d1c92ae15a11c88f35d32e0cfa00cc37c24738f Mon Sep 17 00:00:00 2001 From: Gabriel Martinez Rodriguez Date: Mon, 16 Dec 2024 12:29:51 +0100 Subject: [PATCH 04/19] chore(contract): format random ABI --- .../deployments/happy-sepolia/random/abis.ts | 1317 ++++++++--------- 1 file changed, 658 insertions(+), 659 deletions(-) diff --git a/contracts/deployments/happy-sepolia/random/abis.ts b/contracts/deployments/happy-sepolia/random/abis.ts index 630c6e95ce..36b404276d 100644 --- a/contracts/deployments/happy-sepolia/random/abis.ts +++ b/contracts/deployments/happy-sepolia/random/abis.ts @@ -2,667 +2,666 @@ import type { MapTuple, ObjectFromTuples, UnionToTuple } from "@happy.tech/common" import type { Address } from "viem" -const contractToAbi = ({ - "Random": [ - { - "type": "constructor", - "inputs": [ - { - "name": "_publicKey", - "type": "uint256[4]", - "internalType": "uint256[4]" - }, - { - "name": "_genesisTimestamp", - "type": "uint256", - "internalType": "uint256" - }, - { - "name": "_period", - "type": "uint256", - "internalType": "uint256" - }, - { - "name": "_happyGenesisTimestamp", - "type": "uint256", - "internalType": "uint256" - }, - { - "name": "_happyTimeBlock", - "type": "uint256", - "internalType": "uint256" - } - ], - "stateMutability": "nonpayable" - }, - { - "type": "function", - "name": "DRAND_DELAY", - "inputs": [], - "outputs": [ - { - "name": "", - "type": "uint256", - "internalType": "uint256" - } - ], - "stateMutability": "view" - }, - { - "type": "function", - "name": "DST", - "inputs": [], - "outputs": [ - { - "name": "", - "type": "bytes", - "internalType": "bytes" - } - ], - "stateMutability": "view" - }, - { - "type": "function", - "name": "PRECOMMIT_DELAY", - "inputs": [], - "outputs": [ - { - "name": "", - "type": "uint256", - "internalType": "uint256" - } - ], - "stateMutability": "view" - }, - { - "type": "function", - "name": "drandGenesisTimestamp", - "inputs": [], - "outputs": [ - { - "name": "", - "type": "uint256", - "internalType": "uint256" - } - ], - "stateMutability": "view" - }, - { - "type": "function", - "name": "drandPeriod", - "inputs": [], - "outputs": [ - { - "name": "", - "type": "uint256", - "internalType": "uint256" - } - ], - "stateMutability": "view" - }, - { - "type": "function", - "name": "drandPublicKey", - "inputs": [ - { - "name": "", - "type": "uint256", - "internalType": "uint256" - } - ], - "outputs": [ - { - "name": "", - "type": "uint256", - "internalType": "uint256" - } - ], - "stateMutability": "view" - }, - { - "type": "function", - "name": "drandRandomness", - "inputs": [ - { - "name": "round", - "type": "uint64", - "internalType": "uint64" - } - ], - "outputs": [ - { - "name": "randomness", - "type": "bytes32", - "internalType": "bytes32" - } - ], - "stateMutability": "view" - }, - { - "type": "function", - "name": "getDrand", - "inputs": [ - { - "name": "round", - "type": "uint64", - "internalType": "uint64" - } - ], - "outputs": [ - { - "name": "", - "type": "bytes32", - "internalType": "bytes32" - } - ], - "stateMutability": "view" - }, - { - "type": "function", - "name": "getRevealedValue", - "inputs": [ - { - "name": "blockNumber", - "type": "uint256", - "internalType": "uint256" - } - ], - "outputs": [ - { - "name": "", - "type": "uint256", - "internalType": "uint256" - } - ], - "stateMutability": "view" - }, - { - "type": "function", - "name": "happyGenesisTimestamp", - "inputs": [], - "outputs": [ - { - "name": "", - "type": "uint256", - "internalType": "uint256" - } - ], - "stateMutability": "view" - }, - { - "type": "function", - "name": "happyTimeBlock", - "inputs": [], - "outputs": [ - { - "name": "", - "type": "uint256", - "internalType": "uint256" - } - ], - "stateMutability": "view" - }, - { - "type": "function", - "name": "nextValidBlock", - "inputs": [ - { - "name": "blockNumber", - "type": "uint256", - "internalType": "uint256" - } - ], - "outputs": [ - { - "name": "", - "type": "uint256", - "internalType": "uint256" - } - ], - "stateMutability": "view" - }, - { - "type": "function", - "name": "nextValidTimestamp", - "inputs": [ - { - "name": "timestamp", - "type": "uint256", - "internalType": "uint256" - } - ], - "outputs": [ - { - "name": "", - "type": "uint256", - "internalType": "uint256" - } - ], - "stateMutability": "view" - }, - { - "type": "function", - "name": "owner", - "inputs": [], - "outputs": [ - { - "name": "", - "type": "address", - "internalType": "address" - } - ], - "stateMutability": "view" - }, - { - "type": "function", - "name": "postCommitment", - "inputs": [ - { - "name": "blockNumber", - "type": "uint256", - "internalType": "uint256" - }, - { - "name": "commitmentHash", - "type": "bytes32", - "internalType": "bytes32" - } - ], - "outputs": [], - "stateMutability": "nonpayable" - }, - { - "type": "function", - "name": "postDrand", - "inputs": [ - { - "name": "round", - "type": "uint64", - "internalType": "uint64" - }, - { - "name": "signature", - "type": "uint256[2]", - "internalType": "uint256[2]" - } - ], - "outputs": [], - "stateMutability": "nonpayable" - }, - { - "type": "function", - "name": "random", - "inputs": [], - "outputs": [ - { - "name": "", - "type": "bytes32", - "internalType": "bytes32" - } - ], - "stateMutability": "view" - }, - { - "type": "function", - "name": "randomForBlock", - "inputs": [ - { - "name": "blockNumber", - "type": "uint256", - "internalType": "uint256" - } - ], - "outputs": [ - { - "name": "", - "type": "bytes32", - "internalType": "bytes32" - } - ], - "stateMutability": "view" - }, - { - "type": "function", - "name": "randomForTimestamp", - "inputs": [ - { - "name": "timestamp", - "type": "uint256", - "internalType": "uint256" - } - ], - "outputs": [ - { - "name": "", - "type": "bytes32", - "internalType": "bytes32" - } - ], - "stateMutability": "view" - }, - { - "type": "function", - "name": "renounceOwnership", - "inputs": [], - "outputs": [], - "stateMutability": "nonpayable" - }, - { - "type": "function", - "name": "revealValue", - "inputs": [ - { - "name": "blockNumber", - "type": "uint256", - "internalType": "uint256" - }, - { - "name": "revealedValue", - "type": "uint256", - "internalType": "uint256" - } - ], - "outputs": [], - "stateMutability": "nonpayable" - }, - { - "type": "function", - "name": "transferOwnership", - "inputs": [ - { - "name": "newOwner", - "type": "address", - "internalType": "address" - } - ], - "outputs": [], - "stateMutability": "nonpayable" - }, - { - "type": "function", - "name": "unsafeGetDrand", - "inputs": [ - { - "name": "round", - "type": "uint64", - "internalType": "uint64" - } - ], - "outputs": [ - { - "name": "", - "type": "bytes32", - "internalType": "bytes32" - } - ], - "stateMutability": "view" - }, - { - "type": "function", - "name": "unsafeGetRevealedValue", - "inputs": [ - { - "name": "blockNumber", - "type": "uint256", - "internalType": "uint256" - } - ], - "outputs": [ - { - "name": "", - "type": "uint256", - "internalType": "uint256" - } - ], - "stateMutability": "view" - }, - { - "type": "event", - "name": "CommitmentPosted", - "inputs": [ - { - "name": "blockNumber", - "type": "uint256", - "indexed": true, - "internalType": "uint256" - }, - { - "name": "commitment", - "type": "bytes32", - "indexed": false, - "internalType": "bytes32" - } - ], - "anonymous": false - }, - { - "type": "event", - "name": "DrandRandomnessPosted", - "inputs": [ - { - "name": "round", - "type": "uint64", - "indexed": true, - "internalType": "uint64" - }, - { - "name": "randomness", - "type": "bytes32", - "indexed": false, - "internalType": "bytes32" - } - ], - "anonymous": false - }, - { - "type": "event", - "name": "OwnershipTransferred", - "inputs": [ - { - "name": "previousOwner", - "type": "address", - "indexed": true, - "internalType": "address" - }, - { - "name": "newOwner", - "type": "address", - "indexed": true, - "internalType": "address" - } - ], - "anonymous": false - }, - { - "type": "event", - "name": "ValueRevealed", - "inputs": [ - { - "name": "blockNumber", - "type": "uint256", - "indexed": true, - "internalType": "uint256" - }, - { - "name": "revealedValue", - "type": "uint256", - "indexed": false, - "internalType": "uint256" - } - ], - "anonymous": false - }, - { - "type": "error", - "name": "BNAddFailed", - "inputs": [ - { - "name": "input", - "type": "uint256[4]", - "internalType": "uint256[4]" - } - ] - }, - { - "type": "error", - "name": "CommitmentAlreadyExists", - "inputs": [] - }, - { - "type": "error", - "name": "CommitmentTooLate", - "inputs": [] - }, - { - "type": "error", - "name": "DrandNotAvailable", - "inputs": [ - { - "name": "round", - "type": "uint64", - "internalType": "uint64" - } - ] - }, - { - "type": "error", - "name": "InvalidDSTLength", - "inputs": [ - { - "name": "dst", - "type": "bytes", - "internalType": "bytes" - } - ] - }, - { - "type": "error", - "name": "InvalidFieldElement", - "inputs": [ - { - "name": "x", - "type": "uint256", - "internalType": "uint256" - } - ] - }, - { - "type": "error", - "name": "InvalidPublicKey", - "inputs": [ - { - "name": "publicKey", - "type": "uint256[4]", - "internalType": "uint256[4]" - } - ] - }, - { - "type": "error", - "name": "InvalidReveal", - "inputs": [] - }, - { - "type": "error", - "name": "InvalidSignature", - "inputs": [ - { - "name": "publicKey", - "type": "uint256[4]", - "internalType": "uint256[4]" - }, - { - "name": "message", - "type": "uint256[2]", - "internalType": "uint256[2]" - }, - { - "name": "signature", - "type": "uint256[2]", - "internalType": "uint256[2]" - } - ] - }, - { - "type": "error", - "name": "MapToPointFailed", - "inputs": [ - { - "name": "noSqrt", - "type": "uint256", - "internalType": "uint256" - } - ] - }, - { - "type": "error", - "name": "ModExpFailed", - "inputs": [ - { - "name": "base", - "type": "uint256", - "internalType": "uint256" - }, - { - "name": "exponent", - "type": "uint256", - "internalType": "uint256" - }, - { - "name": "modulus", - "type": "uint256", - "internalType": "uint256" - } - ] - }, - { - "type": "error", - "name": "NoCommitmentFound", - "inputs": [] - }, - { - "type": "error", - "name": "OwnableInvalidOwner", - "inputs": [ - { - "name": "owner", - "type": "address", - "internalType": "address" - } - ] - }, - { - "type": "error", - "name": "OwnableUnauthorizedAccount", - "inputs": [ - { - "name": "account", - "type": "address", - "internalType": "address" - } - ] - }, - { - "type": "error", - "name": "RevealMustBeOnExactBlock", - "inputs": [] - }, - { - "type": "error", - "name": "RevealedValueNotAvailable", - "inputs": [] - } - ] -} -) as const +const contractToAbi = { + Random: [ + { + type: "constructor", + inputs: [ + { + name: "_publicKey", + type: "uint256[4]", + internalType: "uint256[4]", + }, + { + name: "_genesisTimestamp", + type: "uint256", + internalType: "uint256", + }, + { + name: "_period", + type: "uint256", + internalType: "uint256", + }, + { + name: "_happyGenesisTimestamp", + type: "uint256", + internalType: "uint256", + }, + { + name: "_happyTimeBlock", + type: "uint256", + internalType: "uint256", + }, + ], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "DRAND_DELAY", + inputs: [], + outputs: [ + { + name: "", + type: "uint256", + internalType: "uint256", + }, + ], + stateMutability: "view", + }, + { + type: "function", + name: "DST", + inputs: [], + outputs: [ + { + name: "", + type: "bytes", + internalType: "bytes", + }, + ], + stateMutability: "view", + }, + { + type: "function", + name: "PRECOMMIT_DELAY", + inputs: [], + outputs: [ + { + name: "", + type: "uint256", + internalType: "uint256", + }, + ], + stateMutability: "view", + }, + { + type: "function", + name: "drandGenesisTimestamp", + inputs: [], + outputs: [ + { + name: "", + type: "uint256", + internalType: "uint256", + }, + ], + stateMutability: "view", + }, + { + type: "function", + name: "drandPeriod", + inputs: [], + outputs: [ + { + name: "", + type: "uint256", + internalType: "uint256", + }, + ], + stateMutability: "view", + }, + { + type: "function", + name: "drandPublicKey", + inputs: [ + { + name: "", + type: "uint256", + internalType: "uint256", + }, + ], + outputs: [ + { + name: "", + type: "uint256", + internalType: "uint256", + }, + ], + stateMutability: "view", + }, + { + type: "function", + name: "drandRandomness", + inputs: [ + { + name: "round", + type: "uint64", + internalType: "uint64", + }, + ], + outputs: [ + { + name: "randomness", + type: "bytes32", + internalType: "bytes32", + }, + ], + stateMutability: "view", + }, + { + type: "function", + name: "getDrand", + inputs: [ + { + name: "round", + type: "uint64", + internalType: "uint64", + }, + ], + outputs: [ + { + name: "", + type: "bytes32", + internalType: "bytes32", + }, + ], + stateMutability: "view", + }, + { + type: "function", + name: "getRevealedValue", + inputs: [ + { + name: "blockNumber", + type: "uint256", + internalType: "uint256", + }, + ], + outputs: [ + { + name: "", + type: "uint256", + internalType: "uint256", + }, + ], + stateMutability: "view", + }, + { + type: "function", + name: "happyGenesisTimestamp", + inputs: [], + outputs: [ + { + name: "", + type: "uint256", + internalType: "uint256", + }, + ], + stateMutability: "view", + }, + { + type: "function", + name: "happyTimeBlock", + inputs: [], + outputs: [ + { + name: "", + type: "uint256", + internalType: "uint256", + }, + ], + stateMutability: "view", + }, + { + type: "function", + name: "nextValidBlock", + inputs: [ + { + name: "blockNumber", + type: "uint256", + internalType: "uint256", + }, + ], + outputs: [ + { + name: "", + type: "uint256", + internalType: "uint256", + }, + ], + stateMutability: "view", + }, + { + type: "function", + name: "nextValidTimestamp", + inputs: [ + { + name: "timestamp", + type: "uint256", + internalType: "uint256", + }, + ], + outputs: [ + { + name: "", + type: "uint256", + internalType: "uint256", + }, + ], + stateMutability: "view", + }, + { + type: "function", + name: "owner", + inputs: [], + outputs: [ + { + name: "", + type: "address", + internalType: "address", + }, + ], + stateMutability: "view", + }, + { + type: "function", + name: "postCommitment", + inputs: [ + { + name: "blockNumber", + type: "uint256", + internalType: "uint256", + }, + { + name: "commitmentHash", + type: "bytes32", + internalType: "bytes32", + }, + ], + outputs: [], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "postDrand", + inputs: [ + { + name: "round", + type: "uint64", + internalType: "uint64", + }, + { + name: "signature", + type: "uint256[2]", + internalType: "uint256[2]", + }, + ], + outputs: [], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "random", + inputs: [], + outputs: [ + { + name: "", + type: "bytes32", + internalType: "bytes32", + }, + ], + stateMutability: "view", + }, + { + type: "function", + name: "randomForBlock", + inputs: [ + { + name: "blockNumber", + type: "uint256", + internalType: "uint256", + }, + ], + outputs: [ + { + name: "", + type: "bytes32", + internalType: "bytes32", + }, + ], + stateMutability: "view", + }, + { + type: "function", + name: "randomForTimestamp", + inputs: [ + { + name: "timestamp", + type: "uint256", + internalType: "uint256", + }, + ], + outputs: [ + { + name: "", + type: "bytes32", + internalType: "bytes32", + }, + ], + stateMutability: "view", + }, + { + type: "function", + name: "renounceOwnership", + inputs: [], + outputs: [], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "revealValue", + inputs: [ + { + name: "blockNumber", + type: "uint256", + internalType: "uint256", + }, + { + name: "revealedValue", + type: "uint256", + internalType: "uint256", + }, + ], + outputs: [], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "transferOwnership", + inputs: [ + { + name: "newOwner", + type: "address", + internalType: "address", + }, + ], + outputs: [], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "unsafeGetDrand", + inputs: [ + { + name: "round", + type: "uint64", + internalType: "uint64", + }, + ], + outputs: [ + { + name: "", + type: "bytes32", + internalType: "bytes32", + }, + ], + stateMutability: "view", + }, + { + type: "function", + name: "unsafeGetRevealedValue", + inputs: [ + { + name: "blockNumber", + type: "uint256", + internalType: "uint256", + }, + ], + outputs: [ + { + name: "", + type: "uint256", + internalType: "uint256", + }, + ], + stateMutability: "view", + }, + { + type: "event", + name: "CommitmentPosted", + inputs: [ + { + name: "blockNumber", + type: "uint256", + indexed: true, + internalType: "uint256", + }, + { + name: "commitment", + type: "bytes32", + indexed: false, + internalType: "bytes32", + }, + ], + anonymous: false, + }, + { + type: "event", + name: "DrandRandomnessPosted", + inputs: [ + { + name: "round", + type: "uint64", + indexed: true, + internalType: "uint64", + }, + { + name: "randomness", + type: "bytes32", + indexed: false, + internalType: "bytes32", + }, + ], + anonymous: false, + }, + { + type: "event", + name: "OwnershipTransferred", + inputs: [ + { + name: "previousOwner", + type: "address", + indexed: true, + internalType: "address", + }, + { + name: "newOwner", + type: "address", + indexed: true, + internalType: "address", + }, + ], + anonymous: false, + }, + { + type: "event", + name: "ValueRevealed", + inputs: [ + { + name: "blockNumber", + type: "uint256", + indexed: true, + internalType: "uint256", + }, + { + name: "revealedValue", + type: "uint256", + indexed: false, + internalType: "uint256", + }, + ], + anonymous: false, + }, + { + type: "error", + name: "BNAddFailed", + inputs: [ + { + name: "input", + type: "uint256[4]", + internalType: "uint256[4]", + }, + ], + }, + { + type: "error", + name: "CommitmentAlreadyExists", + inputs: [], + }, + { + type: "error", + name: "CommitmentTooLate", + inputs: [], + }, + { + type: "error", + name: "DrandNotAvailable", + inputs: [ + { + name: "round", + type: "uint64", + internalType: "uint64", + }, + ], + }, + { + type: "error", + name: "InvalidDSTLength", + inputs: [ + { + name: "dst", + type: "bytes", + internalType: "bytes", + }, + ], + }, + { + type: "error", + name: "InvalidFieldElement", + inputs: [ + { + name: "x", + type: "uint256", + internalType: "uint256", + }, + ], + }, + { + type: "error", + name: "InvalidPublicKey", + inputs: [ + { + name: "publicKey", + type: "uint256[4]", + internalType: "uint256[4]", + }, + ], + }, + { + type: "error", + name: "InvalidReveal", + inputs: [], + }, + { + type: "error", + name: "InvalidSignature", + inputs: [ + { + name: "publicKey", + type: "uint256[4]", + internalType: "uint256[4]", + }, + { + name: "message", + type: "uint256[2]", + internalType: "uint256[2]", + }, + { + name: "signature", + type: "uint256[2]", + internalType: "uint256[2]", + }, + ], + }, + { + type: "error", + name: "MapToPointFailed", + inputs: [ + { + name: "noSqrt", + type: "uint256", + internalType: "uint256", + }, + ], + }, + { + type: "error", + name: "ModExpFailed", + inputs: [ + { + name: "base", + type: "uint256", + internalType: "uint256", + }, + { + name: "exponent", + type: "uint256", + internalType: "uint256", + }, + { + name: "modulus", + type: "uint256", + internalType: "uint256", + }, + ], + }, + { + type: "error", + name: "NoCommitmentFound", + inputs: [], + }, + { + type: "error", + name: "OwnableInvalidOwner", + inputs: [ + { + name: "owner", + type: "address", + internalType: "address", + }, + ], + }, + { + type: "error", + name: "OwnableUnauthorizedAccount", + inputs: [ + { + name: "account", + type: "address", + internalType: "address", + }, + ], + }, + { + type: "error", + name: "RevealMustBeOnExactBlock", + inputs: [], + }, + { + type: "error", + name: "RevealedValueNotAvailable", + inputs: [], + }, + ], +} as const -const aliasToContract = ({ - "Random": "Random" -}) as const +const aliasToContract = { + Random: "Random", +} as const -export const deployment = ({ - "Random": "0x5FbDB2315678afecb367f032d93F642f64180aa3" -}) as const +export const deployment = { + Random: "0x5FbDB2315678afecb367f032d93F642f64180aa3", +} as const export type ContractToAbi = typeof contractToAbi export type AliasToContract = typeof aliasToContract From 94072c36716cdedf22aca9421a1d911712a1770f Mon Sep 17 00:00:00 2001 From: Gabriel Martinez Rodriguez Date: Tue, 17 Dec 2024 15:46:20 +0100 Subject: [PATCH 05/19] chore(contract): pr review --- contracts/src/deploy/DeployRandom.s.sol | 8 +-- packages/contracts/src/Randomness/Drand.sol | 19 ++++++ packages/contracts/src/Randomness/Random.sol | 64 +++++++++++++++---- .../src/Randomness/RandomCommitment.sol | 36 ++++++++++- 4 files changed, 109 insertions(+), 18 deletions(-) diff --git a/contracts/src/deploy/DeployRandom.s.sol b/contracts/src/deploy/DeployRandom.s.sol index 3eee01d653..7a44fb95b8 100644 --- a/contracts/src/deploy/DeployRandom.s.sol +++ b/contracts/src/deploy/DeployRandom.s.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.20; import {BaseDeployScript} from "./BaseDeployScript.sol"; -import {Random} from "../Randomness/Random.sol"; +import {Random} from "../randomness/Random.sol"; /** * @dev Deploys the Randomness contract. @@ -12,12 +12,12 @@ contract DeployL1 is BaseDeployScript { Random public random; - uint256[4] public DRAND_PUBLIC_KEY; + uint256[4] public drandPublicKey; uint256 public constant DRAND_GENESIS_TIMESTAMP = 1727521075; uint256 public constant DRAND_PERIOD = 3; constructor() { - DRAND_PUBLIC_KEY = [ + drandPublicKey = [ 2416910118189096557713698606232949750075245832257361418817199221841198809231, 3565178688866727608783247307855519961161197286613423629330948765523825963906, 18766085122067595057703228467555884757373773082319035490740181099798629248523, @@ -28,7 +28,7 @@ contract DeployL1 is BaseDeployScript { function deploy() internal override { uint256 happyGenesisTimestamp = vm.envUint("HAPPY_GENESIS_TIMESTAMP"); uint256 happyTimeBlock = vm.envUint("HAPPY_TIME_BLOCK"); - (address _random,) = deployDeterministic("Random", type(Random).creationCode, abi.encode(DRAND_PUBLIC_KEY, DRAND_GENESIS_TIMESTAMP, DRAND_PERIOD, happyGenesisTimestamp, happyTimeBlock), DEPLOYMENT_SALT); + (address _random,) = deployDeterministic("Random", type(Random).creationCode, abi.encode(drandPublicKey, DRAND_GENESIS_TIMESTAMP, DRAND_PERIOD, happyGenesisTimestamp, happyTimeBlock), DEPLOYMENT_SALT); random = Random(_random); deployed("Random", address(random)); } diff --git a/packages/contracts/src/Randomness/Drand.sol b/packages/contracts/src/Randomness/Drand.sol index 37c911f9db..8acf885b87 100644 --- a/packages/contracts/src/Randomness/Drand.sol +++ b/packages/contracts/src/Randomness/Drand.sol @@ -26,6 +26,12 @@ contract Drand { drandPeriod = _drandPeriod; } + /** + * @notice Posts a new Drand signature for a given drand round. + * @dev This function is used to submit a new signature for a specific drand round. + * @param round The drand round number + * @param signature The signature of the drand round + */ function postDrand(uint64 round, uint256[2] memory signature) external { // Encode round for hash-to-point bytes memory hashedRoundBytes = new bytes(32); @@ -54,10 +60,23 @@ contract Drand { emit DrandRandomnessPosted(round, roundRandomness); } + /** + * @notice Retrieves the randomness value for a specific drand round. + * @dev This function does not revert if the randomness value is not available. Instead, it returns 0. + * @param round The drand round number + * @return randomness The randomness value for the specified drand round, or 0 + * if the randomness value is not available. + */ function unsafeGetDrand(uint64 round) public view returns (bytes32) { return drandRandomness[round]; } + /** + * @notice Retrieves the randomness value for a specific drand round. + * @dev This function reverts if the randomness value is not available. + * @param round The drand round number + * @return randomness The randomness value for the specified drand round. + */ function getDrand(uint64 round) public view returns (bytes32) { if (drandRandomness[round] == 0) { revert DrandNotAvailable(round); diff --git a/packages/contracts/src/Randomness/Random.sol b/packages/contracts/src/Randomness/Random.sol index edb830c3f4..6c8cd84822 100644 --- a/packages/contracts/src/Randomness/Random.sol +++ b/packages/contracts/src/Randomness/Random.sol @@ -6,47 +6,85 @@ import {Drand} from "./Drand.sol"; contract Random is RandomCommitment, Drand { uint256 public constant DRAND_DELAY = 2; - uint256 public happyGenesisTimestamp; - uint256 public happyTimeBlock; + uint256 public immutable HAPPY_GENESIS_BLOCK; + uint256 public immutable HAPPY_TIME_BLOCK; constructor( uint256[4] memory _publicKey, uint256 _genesisTimestamp, uint256 _period, - uint256 _happyGenesisTimestamp, + uint256 _happyGenesisBlock, uint256 _happyTimeBlock ) RandomCommitment() Drand(_publicKey, _genesisTimestamp, _period) { - happyGenesisTimestamp = _happyGenesisTimestamp; - happyTimeBlock = _happyTimeBlock; + HAPPY_GENESIS_BLOCK = _happyGenesisBlock; + HAPPY_TIME_BLOCK = _happyTimeBlock; } - function random() external view returns (bytes32) { + /** + * @notice Returns a safe random bytes32 value that changes every block + * @dev The random value is generated from the drand randomness at the timestamp when the block was generated + * and the revealed value at the current block number. + * @return randomValue A random bytes32 value + */ + function random() external view returns (bytes32 randomValue) { bytes32 drand = _getDrandAtTimestamp(block.timestamp - DRAND_DELAY); uint256 revealedValue = getRevealedValue(block.number); - return keccak256(abi.encodePacked(drand, revealedValue)); + randomValue = keccak256(abi.encodePacked(drand, revealedValue)); } + /** + * @notice Returns the latest drand randomness at the given timestamp. + * @dev The drand randomness associated with the timestamp when the block was generated will differ + * from the one provided directly by the drand network. + * This discrepancy exists because we introduce a security margin (DRAND_DELAY seconds) to ensure + * the drand value can be safely included in a block. + * @param timestamp The timestamp at which to fetch the drand randomness + * @return drandRandomness The last drand randomness at the given timestamp + */ function randomForTimestamp(uint256 timestamp) external view returns (bytes32) { return _getDrandAtTimestamp(timestamp - DRAND_DELAY); } + /** + * @notice Returns the latest drand value at the specified block number. + * @dev The drand randomness associated with the timestamp when the block was generated will differ + * from the one provided directly by the drand network. + * This discrepancy exists because we introduce a security margin (DRAND_DELAY seconds) to ensure + * the drand value can be safely included in a block. + * @param blockNumber The block number for which to retrieve the drand randomness. + * @return drandRandomness The latest drand randomness at the specified block number. + */ function randomForBlock(uint256 blockNumber) external view returns (bytes32) { return _getDrandAtTimestamp(blockNumberToTimestamp(blockNumber)); } - function nextValidTimestamp(uint256 timestamp) public view returns (uint256) { - return _nextValidTimestamp(timestamp - DRAND_DELAY) + DRAND_DELAY + 2 * drandPeriod; + /** + * @notice Returns the next block number where the drand randomness is guaranteed to be unknown. + * This ensures that at the returned block, no one can have prior knowledge of the drand randomness, + * maintaining its unpredictability. + * @param blockNumber The block number from which to calculate the next valid block. + * @return nextValidBlockNumber The next block number where the drand randomness remains unrevealed. + */ + function nextValidBlock(uint256 blockNumber) external view returns (uint256) { + return timestampToBlockNumber(nextValidTimestamp(blockNumberToTimestamp(blockNumber))); } - function nextValidBlock(uint256 blockNumber) public view returns (uint256) { - return timestampToBlockNumber(nextValidTimestamp(blockNumberToTimestamp(blockNumber))); + /** + * @notice Returns the next timestamp where the drand randomness is guaranteed to be unknown. + * This ensures that at the returned timestamp, no one can have prior knowledge of the drand randomness, + * maintaining its unpredictability. + * @param timestamp The timestamp from which to calculate the next valid timestamp. + * @return nextValidTimestamp The next timestamp where the drand randomness remains unrevealed. + */ + function nextValidTimestamp(uint256 timestamp) public view returns (uint256) { + return _nextValidTimestamp(timestamp - DRAND_DELAY) + DRAND_DELAY + 2 * drandPeriod; } function blockNumberToTimestamp(uint256 blockNumber) internal view returns (uint256) { - return happyGenesisTimestamp + (blockNumber - 1) * happyTimeBlock; + return HAPPY_GENESIS_BLOCK + (blockNumber - 1) * HAPPY_TIME_BLOCK; } function timestampToBlockNumber(uint256 timestamp) internal view returns (uint256) { - return (timestamp - happyGenesisTimestamp) / happyTimeBlock + 1; + return (timestamp - HAPPY_GENESIS_BLOCK) / HAPPY_TIME_BLOCK + 1; } } diff --git a/packages/contracts/src/Randomness/RandomCommitment.sol b/packages/contracts/src/Randomness/RandomCommitment.sol index 9efeb90d2a..2ca36ddd7d 100644 --- a/packages/contracts/src/Randomness/RandomCommitment.sol +++ b/packages/contracts/src/Randomness/RandomCommitment.sol @@ -4,7 +4,14 @@ pragma solidity ^0.8.20; import {Ownable} from "openzeppelin/access/Ownable.sol"; contract RandomCommitment is Ownable { - uint256 public constant PRECOMMIT_DELAY = 10; + /** + * @dev This delay ensures that the commitment is not submitted too late, + * maintaining the unpredictability of the randomness. It should be set to + * at least 12 hours to accommodate the sequencing window size. + * For more details, please refer to: + * https://specs.optimism.io/protocol/configurability.html#sequencing-window-size + */ + uint256 public constant PRECOMMIT_DELAY = 43200; uint256 private currentRevealedValue; uint256 private currentRevealBlockNumber; @@ -22,6 +29,13 @@ contract RandomCommitment is Ownable { constructor() Ownable(msg.sender) {} + /** + * @notice Posts a commitment for a specific block number. + * @dev This function allows the owner to set a commitment for a future block number with + * at least PRECOMMIT_DELAY blocks of delay. + * @param blockNumber The block number for which to set the commitment. + * @param commitmentHash The hash of the commitment to be stored. + */ function postCommitment(uint256 blockNumber, bytes32 commitmentHash) external onlyOwner { if (block.number > blockNumber - PRECOMMIT_DELAY) { revert CommitmentTooLate(); @@ -35,6 +49,13 @@ contract RandomCommitment is Ownable { emit CommitmentPosted(blockNumber, commitmentHash); } + /** + * @notice Reveals the value for a specific block number. + * @dev This function allows the owner to reveal the value for a block number that has a commitment. + * The reveal must be on the exact block number that the commitment was posted for. + * @param blockNumber The block number for which to reveal the value. + * @param revealedValue The value to be revealed. + */ function revealValue(uint256 blockNumber, uint256 revealedValue) external onlyOwner { bytes32 storedCommitment = commitments[blockNumber]; @@ -56,6 +77,12 @@ contract RandomCommitment is Ownable { emit ValueRevealed(blockNumber, revealedValue); } + /** + * @notice Retrieves the revealed value for a specific block number. + * @dev This function does not revert if the reveal is unavailable. Instead, it returns 0. + * @param blockNumber The block number for which to retrieve the revealed value. + * @return revealedValue The revealed value for the specified block number, or 0 if the reveal is not available. + */ function unsafeGetRevealedValue(uint256 blockNumber) public view returns (uint256) { if (currentRevealBlockNumber != blockNumber) { return 0; @@ -64,6 +91,13 @@ contract RandomCommitment is Ownable { return currentRevealedValue; } + /** + * @notice Retrieves the revealed value for a specific block number. + * @dev This function verifies that a reveal exists for the given block number before returning the value. + * It reverts with `RevealedValueNotAvailable` if no valid reveal is found. + * @param blockNumber The block number for which to retrieve the revealed value. + * @return revealedValue The revealed value associated with the specified block number. + */ function getRevealedValue(uint256 blockNumber) public view returns (uint256) { if (currentRevealBlockNumber != blockNumber) { revert RevealedValueNotAvailable(); From bc337f02852ff554b58c1bf4831f2d6c418b6444 Mon Sep 17 00:00:00 2001 From: Gabriel Martinez Rodriguez Date: Tue, 17 Dec 2024 15:59:48 +0100 Subject: [PATCH 06/19] chore(contract): pr review --- packages/contracts/src/Randomness/Drand.sol | 17 ++++++++--------- packages/contracts/src/Randomness/Random.sol | 2 +- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/packages/contracts/src/Randomness/Drand.sol b/packages/contracts/src/Randomness/Drand.sol index 8acf885b87..9ee00ff481 100644 --- a/packages/contracts/src/Randomness/Drand.sol +++ b/packages/contracts/src/Randomness/Drand.sol @@ -7,8 +7,8 @@ contract Drand { bytes public constant DST = bytes("BLS_SIG_BN254G1_XMD:KECCAK-256_SVDW_RO_NUL_"); uint256[4] public drandPublicKey; - uint256 public drandGenesisTimestamp; - uint256 public drandPeriod; + uint256 public immutable DRAND_GENESIS_TIMESTAMP; + uint256 public immutable DRAND_PERIOD; mapping(uint64 round => bytes32 randomness) public drandRandomness; event DrandRandomnessPosted(uint64 indexed round, bytes32 randomness); @@ -22,8 +22,8 @@ contract Drand { revert InvalidPublicKey(_drandPublicKey); } drandPublicKey = _drandPublicKey; - drandGenesisTimestamp = _drandGenesisTimestamp; - drandPeriod = _drandPeriod; + DRAND_GENESIS_TIMESTAMP = _drandGenesisTimestamp; + DRAND_PERIOD = _drandPeriod; } /** @@ -43,13 +43,12 @@ contract Drand { uint256[2] memory message = BLS.hashToPoint(DST, hashedRoundBytes); // NB: Always check that the signature is a valid signature (a valid G1 point on the curve)! - bool isValidSignature = BLS.isValidSignature(signature); - if (!isValidSignature) { + if (!BLS.isValidSignature(signature)) { revert InvalidSignature(drandPublicKey, message, signature); } // Verify the signature over the message using the public key - (bool pairingSuccess, bool callSuccess) = BLS.verifySingle(signature, drandPublicKey, message); + (bool pairingSuccess, ) = BLS.verifySingle(signature, drandPublicKey, message); if (!pairingSuccess) { revert InvalidSignature(drandPublicKey, message, signature); } @@ -86,11 +85,11 @@ contract Drand { } function _getDrandAtTimestamp(uint256 timestamp) internal view returns (bytes32) { - uint64 round = uint64((timestamp - drandGenesisTimestamp) / drandPeriod); + uint64 round = uint64((timestamp - DRAND_GENESIS_TIMESTAMP) / DRAND_PERIOD); return getDrand(round); } function _nextValidTimestamp(uint256 timestamp) internal view returns (uint256) { - return timestamp + (drandPeriod - (timestamp % drandPeriod)); + return timestamp + (DRAND_PERIOD - (timestamp % DRAND_PERIOD)); } } diff --git a/packages/contracts/src/Randomness/Random.sol b/packages/contracts/src/Randomness/Random.sol index 6c8cd84822..8b9bbab34d 100644 --- a/packages/contracts/src/Randomness/Random.sol +++ b/packages/contracts/src/Randomness/Random.sol @@ -77,7 +77,7 @@ contract Random is RandomCommitment, Drand { * @return nextValidTimestamp The next timestamp where the drand randomness remains unrevealed. */ function nextValidTimestamp(uint256 timestamp) public view returns (uint256) { - return _nextValidTimestamp(timestamp - DRAND_DELAY) + DRAND_DELAY + 2 * drandPeriod; + return _nextValidTimestamp(timestamp - DRAND_DELAY) + DRAND_DELAY + 2 * DRAND_PERIOD; } function blockNumberToTimestamp(uint256 blockNumber) internal view returns (uint256) { From 8f32e288d172e885f8b814d14009fefaad64fbd4 Mon Sep 17 00:00:00 2001 From: Gabriel Martinez Rodriguez Date: Tue, 17 Dec 2024 16:02:14 +0100 Subject: [PATCH 07/19] chore(contract): pr review --- packages/contracts/src/Randomness/Drand.sol | 2 +- packages/contracts/src/Randomness/Random.sol | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/contracts/src/Randomness/Drand.sol b/packages/contracts/src/Randomness/Drand.sol index 9ee00ff481..853197203b 100644 --- a/packages/contracts/src/Randomness/Drand.sol +++ b/packages/contracts/src/Randomness/Drand.sol @@ -48,7 +48,7 @@ contract Drand { } // Verify the signature over the message using the public key - (bool pairingSuccess, ) = BLS.verifySingle(signature, drandPublicKey, message); + (bool pairingSuccess,) = BLS.verifySingle(signature, drandPublicKey, message); if (!pairingSuccess) { revert InvalidSignature(drandPublicKey, message, signature); } diff --git a/packages/contracts/src/Randomness/Random.sol b/packages/contracts/src/Randomness/Random.sol index 8b9bbab34d..963ee22c98 100644 --- a/packages/contracts/src/Randomness/Random.sol +++ b/packages/contracts/src/Randomness/Random.sol @@ -55,7 +55,7 @@ contract Random is RandomCommitment, Drand { * @return drandRandomness The latest drand randomness at the specified block number. */ function randomForBlock(uint256 blockNumber) external view returns (bytes32) { - return _getDrandAtTimestamp(blockNumberToTimestamp(blockNumber)); + return _getDrandAtTimestamp(_blockNumberToTimestamp(blockNumber)); } /** @@ -66,7 +66,7 @@ contract Random is RandomCommitment, Drand { * @return nextValidBlockNumber The next block number where the drand randomness remains unrevealed. */ function nextValidBlock(uint256 blockNumber) external view returns (uint256) { - return timestampToBlockNumber(nextValidTimestamp(blockNumberToTimestamp(blockNumber))); + return _timestampToBlockNumber(nextValidTimestamp(_blockNumberToTimestamp(blockNumber))); } /** @@ -80,11 +80,11 @@ contract Random is RandomCommitment, Drand { return _nextValidTimestamp(timestamp - DRAND_DELAY) + DRAND_DELAY + 2 * DRAND_PERIOD; } - function blockNumberToTimestamp(uint256 blockNumber) internal view returns (uint256) { + function _blockNumberToTimestamp(uint256 blockNumber) internal view returns (uint256) { return HAPPY_GENESIS_BLOCK + (blockNumber - 1) * HAPPY_TIME_BLOCK; } - function timestampToBlockNumber(uint256 timestamp) internal view returns (uint256) { + function _timestampToBlockNumber(uint256 timestamp) internal view returns (uint256) { return (timestamp - HAPPY_GENESIS_BLOCK) / HAPPY_TIME_BLOCK + 1; } } From 6d0c8d3360fc3d04e2f36c8f3c4d123c35068335 Mon Sep 17 00:00:00 2001 From: Gabriel Martinez Rodriguez Date: Tue, 17 Dec 2024 16:06:34 +0100 Subject: [PATCH 08/19] chore(contract): added comment to deploy random script --- contracts/src/deploy/DeployRandom.s.sol | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/contracts/src/deploy/DeployRandom.s.sol b/contracts/src/deploy/DeployRandom.s.sol index 7a44fb95b8..807af438c0 100644 --- a/contracts/src/deploy/DeployRandom.s.sol +++ b/contracts/src/deploy/DeployRandom.s.sol @@ -13,6 +13,11 @@ contract DeployL1 is BaseDeployScript { Random public random; uint256[4] public drandPublicKey; + + /* + * To understand this values. Please refer to the following link: + * https://docs.anyrand.com/diy/quickstart + */ uint256 public constant DRAND_GENESIS_TIMESTAMP = 1727521075; uint256 public constant DRAND_PERIOD = 3; From 73bfbd420c6c0d9480ea50a1c594ffe2e8ae6f15 Mon Sep 17 00:00:00 2001 From: Gabriel Martinez Rodriguez Date: Tue, 17 Dec 2024 16:18:58 +0100 Subject: [PATCH 09/19] chore(contracts): added comments to .env.example --- contracts/.env.example | 3 +++ 1 file changed, 3 insertions(+) diff --git a/contracts/.env.example b/contracts/.env.example index 5a30c0ecc4..86d25ad100 100755 --- a/contracts/.env.example +++ b/contracts/.env.example @@ -100,5 +100,8 @@ export BUNDLER_LOCAL=http://127.0.0.1:3000 # Random Contract Configuration #################################################################################################### +# The timestamp of the first block of the Happy chain. It should be in seconds. export HAPPY_GENESIS_TIMESTAMP= + +# The time between blocks in the Happy chain. It should be in seconds. export HAPPY_TIME_BLOCK=2 From d9c0f01233a97251edcaa1c1d30d6293a3b98b58 Mon Sep 17 00:00:00 2001 From: Gabriel Martinez Rodriguez Date: Tue, 17 Dec 2024 17:11:48 +0100 Subject: [PATCH 10/19] chore(contract): added comments and fixed typo --- contracts/src/deploy/DeployRandom.s.sol | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/contracts/src/deploy/DeployRandom.s.sol b/contracts/src/deploy/DeployRandom.s.sol index 807af438c0..c12823326d 100644 --- a/contracts/src/deploy/DeployRandom.s.sol +++ b/contracts/src/deploy/DeployRandom.s.sol @@ -7,18 +7,18 @@ import {Random} from "../randomness/Random.sol"; /** * @dev Deploys the Randomness contract. */ -contract DeployL1 is BaseDeployScript { - bytes32 public constant DEPLOYMENT_SALT = bytes32(uint256(0)); - +contract DeployRandom is BaseDeployScript { Random public random; - uint256[4] public drandPublicKey; - /* - * To understand this values. Please refer to the following link: + * To understand these values. Please refer to the following link: * https://docs.anyrand.com/diy/quickstart */ + // Drand evmnet public key + uint256[4] public drandPublicKey; + // Drand evmnet genesis time (2024-01-29 00:11:15 UTC) uint256 public constant DRAND_GENESIS_TIMESTAMP = 1727521075; + // Drand evmnet period (3 seconds) uint256 public constant DRAND_PERIOD = 3; constructor() { From 5102e6036fe4ca7a016782b7e1264468bc901ed4 Mon Sep 17 00:00:00 2001 From: Gabriel Martinez Rodriguez Date: Tue, 17 Dec 2024 17:13:54 +0100 Subject: [PATCH 11/19] chore(contracts): rename HAPPY_TIME_BLOCK env --- contracts/.env.example | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/.env.example b/contracts/.env.example index 86d25ad100..9278a95144 100755 --- a/contracts/.env.example +++ b/contracts/.env.example @@ -104,4 +104,4 @@ export BUNDLER_LOCAL=http://127.0.0.1:3000 export HAPPY_GENESIS_TIMESTAMP= # The time between blocks in the Happy chain. It should be in seconds. -export HAPPY_TIME_BLOCK=2 +export HAPPY_TIME_BLOCK_SECONDS=2 From 402b48c49dc8a2296967907c962923966f595ea8 Mon Sep 17 00:00:00 2001 From: Gabriel Martinez Rodriguez Date: Tue, 17 Dec 2024 17:20:47 +0100 Subject: [PATCH 12/19] chore(contracts): use uint128 instead of uint256 for CurrentReveal --- packages/contracts/src/Randomness/Random.sol | 4 +-- .../src/Randomness/RandomCommitment.sol | 34 +++++++++++-------- 2 files changed, 21 insertions(+), 17 deletions(-) diff --git a/packages/contracts/src/Randomness/Random.sol b/packages/contracts/src/Randomness/Random.sol index 963ee22c98..abba6877f8 100644 --- a/packages/contracts/src/Randomness/Random.sol +++ b/packages/contracts/src/Randomness/Random.sol @@ -27,8 +27,8 @@ contract Random is RandomCommitment, Drand { * @return randomValue A random bytes32 value */ function random() external view returns (bytes32 randomValue) { - bytes32 drand = _getDrandAtTimestamp(block.timestamp - DRAND_DELAY); - uint256 revealedValue = getRevealedValue(block.number); + bytes32 drand = _getDrandAtTimestamp(uint128(block.timestamp - DRAND_DELAY)); + uint128 revealedValue = getRevealedValue(uint128(block.number)); randomValue = keccak256(abi.encodePacked(drand, revealedValue)); } diff --git a/packages/contracts/src/Randomness/RandomCommitment.sol b/packages/contracts/src/Randomness/RandomCommitment.sol index 2ca36ddd7d..5065479899 100644 --- a/packages/contracts/src/Randomness/RandomCommitment.sol +++ b/packages/contracts/src/Randomness/RandomCommitment.sol @@ -4,6 +4,11 @@ pragma solidity ^0.8.20; import {Ownable} from "openzeppelin/access/Ownable.sol"; contract RandomCommitment is Ownable { + struct CurrentReveal { + uint128 value; + uint128 blockNumber; + } + /** * @dev This delay ensures that the commitment is not submitted too late, * maintaining the unpredictability of the randomness. It should be set to @@ -13,12 +18,11 @@ contract RandomCommitment is Ownable { */ uint256 public constant PRECOMMIT_DELAY = 43200; - uint256 private currentRevealedValue; - uint256 private currentRevealBlockNumber; - mapping(uint256 blockNumber => bytes32) private commitments; + CurrentReveal private currentReveal; + mapping(uint128 blockNumber => bytes32) private commitments; - event CommitmentPosted(uint256 indexed blockNumber, bytes32 commitment); - event ValueRevealed(uint256 indexed blockNumber, uint256 revealedValue); + event CommitmentPosted(uint128 indexed blockNumber, bytes32 commitment); + event ValueRevealed(uint128 indexed blockNumber, uint128 revealedValue); error CommitmentTooLate(); error CommitmentAlreadyExists(); @@ -36,7 +40,7 @@ contract RandomCommitment is Ownable { * @param blockNumber The block number for which to set the commitment. * @param commitmentHash The hash of the commitment to be stored. */ - function postCommitment(uint256 blockNumber, bytes32 commitmentHash) external onlyOwner { + function postCommitment(uint128 blockNumber, bytes32 commitmentHash) external onlyOwner { if (block.number > blockNumber - PRECOMMIT_DELAY) { revert CommitmentTooLate(); } @@ -56,7 +60,7 @@ contract RandomCommitment is Ownable { * @param blockNumber The block number for which to reveal the value. * @param revealedValue The value to be revealed. */ - function revealValue(uint256 blockNumber, uint256 revealedValue) external onlyOwner { + function revealValue(uint128 blockNumber, uint128 revealedValue) external onlyOwner { bytes32 storedCommitment = commitments[blockNumber]; if (storedCommitment == 0) { @@ -71,8 +75,8 @@ contract RandomCommitment is Ownable { revert InvalidReveal(); } - currentRevealedValue = revealedValue; - currentRevealBlockNumber = blockNumber; + currentReveal.value = revealedValue; + currentReveal.blockNumber = blockNumber; delete commitments[blockNumber]; emit ValueRevealed(blockNumber, revealedValue); } @@ -83,12 +87,12 @@ contract RandomCommitment is Ownable { * @param blockNumber The block number for which to retrieve the revealed value. * @return revealedValue The revealed value for the specified block number, or 0 if the reveal is not available. */ - function unsafeGetRevealedValue(uint256 blockNumber) public view returns (uint256) { - if (currentRevealBlockNumber != blockNumber) { + function unsafeGetRevealedValue(uint128 blockNumber) public view returns (uint128) { + if (currentReveal.blockNumber != blockNumber) { return 0; } - return currentRevealedValue; + return currentReveal.value; } /** @@ -98,11 +102,11 @@ contract RandomCommitment is Ownable { * @param blockNumber The block number for which to retrieve the revealed value. * @return revealedValue The revealed value associated with the specified block number. */ - function getRevealedValue(uint256 blockNumber) public view returns (uint256) { - if (currentRevealBlockNumber != blockNumber) { + function getRevealedValue(uint128 blockNumber) public view returns (uint128) { + if (currentReveal.blockNumber != blockNumber) { revert RevealedValueNotAvailable(); } - return currentRevealedValue; + return currentReveal.value; } } From afe73d782c806b95d94bebf96d63942860fd4c53 Mon Sep 17 00:00:00 2001 From: Gabriel Martinez Rodriguez Date: Wed, 8 Jan 2025 16:35:49 +0100 Subject: [PATCH 13/19] chore(randomness): pr review --- contracts/.env.example | 3 +- packages/contracts/src/Randomness/Random.sol | 90 ------------------- .../src/{Randomness => randomness}/Drand.sol | 21 ++--- packages/contracts/src/randomness/Random.sol | 62 +++++++++++++ .../RandomCommitment.sol | 18 +--- 5 files changed, 76 insertions(+), 118 deletions(-) delete mode 100644 packages/contracts/src/Randomness/Random.sol rename packages/contracts/src/{Randomness => randomness}/Drand.sol (80%) create mode 100644 packages/contracts/src/randomness/Random.sol rename packages/contracts/src/{Randomness => randomness}/RandomCommitment.sol (70%) diff --git a/contracts/.env.example b/contracts/.env.example index 9278a95144..0a182d3b25 100755 --- a/contracts/.env.example +++ b/contracts/.env.example @@ -101,7 +101,8 @@ export BUNDLER_LOCAL=http://127.0.0.1:3000 #################################################################################################### # The timestamp of the first block of the Happy chain. It should be in seconds. -export HAPPY_GENESIS_TIMESTAMP= +# This example value is the timestamp of the first block of the Happy chain testnet. +export HAPPY_GENESIS_TIMESTAMP=1723165536 # The time between blocks in the Happy chain. It should be in seconds. export HAPPY_TIME_BLOCK_SECONDS=2 diff --git a/packages/contracts/src/Randomness/Random.sol b/packages/contracts/src/Randomness/Random.sol deleted file mode 100644 index abba6877f8..0000000000 --- a/packages/contracts/src/Randomness/Random.sol +++ /dev/null @@ -1,90 +0,0 @@ -// SPDX-License-Identifier: BSD-3-Clause-Clear -pragma solidity ^0.8.20; - -import {RandomCommitment} from "./RandomCommitment.sol"; -import {Drand} from "./Drand.sol"; - -contract Random is RandomCommitment, Drand { - uint256 public constant DRAND_DELAY = 2; - uint256 public immutable HAPPY_GENESIS_BLOCK; - uint256 public immutable HAPPY_TIME_BLOCK; - - constructor( - uint256[4] memory _publicKey, - uint256 _genesisTimestamp, - uint256 _period, - uint256 _happyGenesisBlock, - uint256 _happyTimeBlock - ) RandomCommitment() Drand(_publicKey, _genesisTimestamp, _period) { - HAPPY_GENESIS_BLOCK = _happyGenesisBlock; - HAPPY_TIME_BLOCK = _happyTimeBlock; - } - - /** - * @notice Returns a safe random bytes32 value that changes every block - * @dev The random value is generated from the drand randomness at the timestamp when the block was generated - * and the revealed value at the current block number. - * @return randomValue A random bytes32 value - */ - function random() external view returns (bytes32 randomValue) { - bytes32 drand = _getDrandAtTimestamp(uint128(block.timestamp - DRAND_DELAY)); - uint128 revealedValue = getRevealedValue(uint128(block.number)); - randomValue = keccak256(abi.encodePacked(drand, revealedValue)); - } - - /** - * @notice Returns the latest drand randomness at the given timestamp. - * @dev The drand randomness associated with the timestamp when the block was generated will differ - * from the one provided directly by the drand network. - * This discrepancy exists because we introduce a security margin (DRAND_DELAY seconds) to ensure - * the drand value can be safely included in a block. - * @param timestamp The timestamp at which to fetch the drand randomness - * @return drandRandomness The last drand randomness at the given timestamp - */ - function randomForTimestamp(uint256 timestamp) external view returns (bytes32) { - return _getDrandAtTimestamp(timestamp - DRAND_DELAY); - } - - /** - * @notice Returns the latest drand value at the specified block number. - * @dev The drand randomness associated with the timestamp when the block was generated will differ - * from the one provided directly by the drand network. - * This discrepancy exists because we introduce a security margin (DRAND_DELAY seconds) to ensure - * the drand value can be safely included in a block. - * @param blockNumber The block number for which to retrieve the drand randomness. - * @return drandRandomness The latest drand randomness at the specified block number. - */ - function randomForBlock(uint256 blockNumber) external view returns (bytes32) { - return _getDrandAtTimestamp(_blockNumberToTimestamp(blockNumber)); - } - - /** - * @notice Returns the next block number where the drand randomness is guaranteed to be unknown. - * This ensures that at the returned block, no one can have prior knowledge of the drand randomness, - * maintaining its unpredictability. - * @param blockNumber The block number from which to calculate the next valid block. - * @return nextValidBlockNumber The next block number where the drand randomness remains unrevealed. - */ - function nextValidBlock(uint256 blockNumber) external view returns (uint256) { - return _timestampToBlockNumber(nextValidTimestamp(_blockNumberToTimestamp(blockNumber))); - } - - /** - * @notice Returns the next timestamp where the drand randomness is guaranteed to be unknown. - * This ensures that at the returned timestamp, no one can have prior knowledge of the drand randomness, - * maintaining its unpredictability. - * @param timestamp The timestamp from which to calculate the next valid timestamp. - * @return nextValidTimestamp The next timestamp where the drand randomness remains unrevealed. - */ - function nextValidTimestamp(uint256 timestamp) public view returns (uint256) { - return _nextValidTimestamp(timestamp - DRAND_DELAY) + DRAND_DELAY + 2 * DRAND_PERIOD; - } - - function _blockNumberToTimestamp(uint256 blockNumber) internal view returns (uint256) { - return HAPPY_GENESIS_BLOCK + (blockNumber - 1) * HAPPY_TIME_BLOCK; - } - - function _timestampToBlockNumber(uint256 timestamp) internal view returns (uint256) { - return (timestamp - HAPPY_GENESIS_BLOCK) / HAPPY_TIME_BLOCK + 1; - } -} diff --git a/packages/contracts/src/Randomness/Drand.sol b/packages/contracts/src/randomness/Drand.sol similarity index 80% rename from packages/contracts/src/Randomness/Drand.sol rename to packages/contracts/src/randomness/Drand.sol index 853197203b..68946098f0 100644 --- a/packages/contracts/src/Randomness/Drand.sol +++ b/packages/contracts/src/randomness/Drand.sol @@ -28,9 +28,6 @@ contract Drand { /** * @notice Posts a new Drand signature for a given drand round. - * @dev This function is used to submit a new signature for a specific drand round. - * @param round The drand round number - * @param signature The signature of the drand round */ function postDrand(uint64 round, uint256[2] memory signature) external { // Encode round for hash-to-point @@ -41,7 +38,6 @@ contract Drand { mstore(add(0x20, hashedRoundBytes), hashedRound) } uint256[2] memory message = BLS.hashToPoint(DST, hashedRoundBytes); - // NB: Always check that the signature is a valid signature (a valid G1 point on the curve)! if (!BLS.isValidSignature(signature)) { revert InvalidSignature(drandPublicKey, message, signature); @@ -61,10 +57,7 @@ contract Drand { /** * @notice Retrieves the randomness value for a specific drand round. - * @dev This function does not revert if the randomness value is not available. Instead, it returns 0. - * @param round The drand round number - * @return randomness The randomness value for the specified drand round, or 0 - * if the randomness value is not available. + * This function does not revert if the randomness value is not available. Instead, it returns 0. */ function unsafeGetDrand(uint64 round) public view returns (bytes32) { return drandRandomness[round]; @@ -72,9 +65,7 @@ contract Drand { /** * @notice Retrieves the randomness value for a specific drand round. - * @dev This function reverts if the randomness value is not available. - * @param round The drand round number - * @return randomness The randomness value for the specified drand round. + * This function reverts if the randomness value is not available. */ function getDrand(uint64 round) public view returns (bytes32) { if (drandRandomness[round] == 0) { @@ -84,12 +75,18 @@ contract Drand { return drandRandomness[round]; } + /** + * @notice Returns the latest drand value at the given timestamp. + */ function _getDrandAtTimestamp(uint256 timestamp) internal view returns (bytes32) { uint64 round = uint64((timestamp - DRAND_GENESIS_TIMESTAMP) / DRAND_PERIOD); return getDrand(round); } - function _nextValidTimestamp(uint256 timestamp) internal view returns (uint256) { + /** + * @notice Returns the timestamp in which a new drand value will be available after the given timestamp. + */ + function _nextValidDrandTimestamp(uint256 timestamp) internal view returns (uint256) { return timestamp + (DRAND_PERIOD - (timestamp % DRAND_PERIOD)); } } diff --git a/packages/contracts/src/randomness/Random.sol b/packages/contracts/src/randomness/Random.sol new file mode 100644 index 0000000000..71e991135d --- /dev/null +++ b/packages/contracts/src/randomness/Random.sol @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: BSD-3-Clause-Clear +pragma solidity ^0.8.20; + +import {RandomCommitment} from "./RandomCommitment.sol"; +import {Drand} from "./Drand.sol"; + +contract Random is RandomCommitment, Drand { + /* + * This is the amount of time by which we delay reading the values from the Drand network. + * This approach is implemented to avoid issues when posting Drand values to the network + * in blocks where the Drand network period coincides with the Happy Chain period. + * For example, if there is a block at timestamp 6 and a new Drand value is also generated at timestamp 6, + * we would read the last Drand value generated before timestamp 6 - DRAND_DELAY. + */ + uint256 public constant DRAND_DELAY = 2; + uint256 public immutable HAPPY_GENESIS_TIMESTAMP; + uint256 public immutable HAPPY_BLOCK_TIME; + + constructor( + uint256[4] memory _drandPublicKey, + uint256 _drandGenesisTimestamp, + uint256 _drandPeriod, + uint256 _happyGenesisTimestamp, + uint256 _happyBlockTime + ) RandomCommitment() Drand(_drandPublicKey, _drandGenesisTimestamp, _drandPeriod) { + HAPPY_GENESIS_TIMESTAMP = _happyGenesisTimestamp; + HAPPY_BLOCK_TIME = _happyBlockTime; + } + + /** + * @notice Returns a safe random bytes32 value that changes every block + * @dev The random value is generated from the drand randomness at the timestamp when the block was generated + * and the revealed value at the current block number. + */ + function random() external view returns (bytes32 randomValue) { + bytes32 drand = _getDrandAtTimestamp(uint128(block.timestamp - DRAND_DELAY)); + uint128 revealedValue = getRevealedValue(uint128(block.number)); + randomValue = keccak256(abi.encodePacked(drand, revealedValue)); + } + + /** + * @notice Returns the latest drand randomness at the given timestamp. + * @dev The drand randomness associated with the timestamp when the block was generated will differ + * from the one provided directly by the drand network. + * This discrepancy exists because we introduce a security margin (DRAND_DELAY seconds) to ensure + * the drand value can be safely included in a block. + */ + function randomForTimestamp(uint256 timestamp) external view returns (bytes32) { + return _getDrandAtTimestamp(timestamp - DRAND_DELAY); + } + + /** + * @notice Returns the next timestamp where the drand randomness is guaranteed to be unknown. + * This ensures that at the returned timestamp, no one can have prior knowledge of the drand randomness, + * maintaining its unpredictability. + * @param timestamp The timestamp from which to calculate the next valid timestamp. + * @return nextValidTimestamp The next timestamp where the drand randomness remains unrevealed. + */ + function nextValidTimestamp(uint256 timestamp) public view returns (uint256) { + return _nextValidDrandTimestamp(timestamp - DRAND_DELAY) + DRAND_DELAY + 2 * DRAND_PERIOD; + } +} diff --git a/packages/contracts/src/Randomness/RandomCommitment.sol b/packages/contracts/src/randomness/RandomCommitment.sol similarity index 70% rename from packages/contracts/src/Randomness/RandomCommitment.sol rename to packages/contracts/src/randomness/RandomCommitment.sol index 5065479899..66d93cc97b 100644 --- a/packages/contracts/src/Randomness/RandomCommitment.sol +++ b/packages/contracts/src/randomness/RandomCommitment.sol @@ -35,10 +35,6 @@ contract RandomCommitment is Ownable { /** * @notice Posts a commitment for a specific block number. - * @dev This function allows the owner to set a commitment for a future block number with - * at least PRECOMMIT_DELAY blocks of delay. - * @param blockNumber The block number for which to set the commitment. - * @param commitmentHash The hash of the commitment to be stored. */ function postCommitment(uint128 blockNumber, bytes32 commitmentHash) external onlyOwner { if (block.number > blockNumber - PRECOMMIT_DELAY) { @@ -55,10 +51,6 @@ contract RandomCommitment is Ownable { /** * @notice Reveals the value for a specific block number. - * @dev This function allows the owner to reveal the value for a block number that has a commitment. - * The reveal must be on the exact block number that the commitment was posted for. - * @param blockNumber The block number for which to reveal the value. - * @param revealedValue The value to be revealed. */ function revealValue(uint128 blockNumber, uint128 revealedValue) external onlyOwner { bytes32 storedCommitment = commitments[blockNumber]; @@ -83,9 +75,7 @@ contract RandomCommitment is Ownable { /** * @notice Retrieves the revealed value for a specific block number. - * @dev This function does not revert if the reveal is unavailable. Instead, it returns 0. - * @param blockNumber The block number for which to retrieve the revealed value. - * @return revealedValue The revealed value for the specified block number, or 0 if the reveal is not available. + * This function does not revert if the reveal is unavailable. Instead, it returns 0. */ function unsafeGetRevealedValue(uint128 blockNumber) public view returns (uint128) { if (currentReveal.blockNumber != blockNumber) { @@ -97,10 +87,8 @@ contract RandomCommitment is Ownable { /** * @notice Retrieves the revealed value for a specific block number. - * @dev This function verifies that a reveal exists for the given block number before returning the value. - * It reverts with `RevealedValueNotAvailable` if no valid reveal is found. - * @param blockNumber The block number for which to retrieve the revealed value. - * @return revealedValue The revealed value associated with the specified block number. + * This function verifies that a reveal exists for the given block number before returning the value. + * It reverts with `RevealedValueNotAvailable` if no valid reveal is found. */ function getRevealedValue(uint128 blockNumber) public view returns (uint128) { if (currentReveal.blockNumber != blockNumber) { From f0ba16caa823046ad1563c4549e85f1ebc9430d1 Mon Sep 17 00:00:00 2001 From: Gabriel Martinez Rodriguez Date: Thu, 9 Jan 2025 11:06:04 +0100 Subject: [PATCH 14/19] feat(randomnness): added MIN_PRECOMMIT_TIME to Randomness contract --- packages/contracts/src/randomness/Random.sol | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/contracts/src/randomness/Random.sol b/packages/contracts/src/randomness/Random.sol index 71e991135d..b0bb3027b8 100644 --- a/packages/contracts/src/randomness/Random.sol +++ b/packages/contracts/src/randomness/Random.sol @@ -13,6 +13,7 @@ contract Random is RandomCommitment, Drand { * we would read the last Drand value generated before timestamp 6 - DRAND_DELAY. */ uint256 public constant DRAND_DELAY = 2; + uint256 public constant MIN_PRECOMMIT_TIME = 3; uint256 public immutable HAPPY_GENESIS_TIMESTAMP; uint256 public immutable HAPPY_BLOCK_TIME; @@ -57,6 +58,6 @@ contract Random is RandomCommitment, Drand { * @return nextValidTimestamp The next timestamp where the drand randomness remains unrevealed. */ function nextValidTimestamp(uint256 timestamp) public view returns (uint256) { - return _nextValidDrandTimestamp(timestamp - DRAND_DELAY) + DRAND_DELAY + 2 * DRAND_PERIOD; + return _nextValidDrandTimestamp(timestamp + MIN_PRECOMMIT_TIME - 1) + DRAND_DELAY; } } From e1ca8cf04d3d3ded417cc7872632853e26c8fce0 Mon Sep 17 00:00:00 2001 From: Gabriel Martinez Rodriguez Date: Tue, 21 Jan 2025 15:27:36 +0100 Subject: [PATCH 15/19] chore(contract): pr review --- contracts/.env.example | 13 +------ contracts/Makefile | 5 +++ contracts/src/deploy/DeployRandom.s.sol | 4 +-- packages/contracts/src/randomness/Drand.sol | 2 ++ packages/contracts/src/randomness/Random.sol | 37 ++++++++++---------- 5 files changed, 27 insertions(+), 34 deletions(-) diff --git a/contracts/.env.example b/contracts/.env.example index 0a182d3b25..d30267df7a 100755 --- a/contracts/.env.example +++ b/contracts/.env.example @@ -94,15 +94,4 @@ export ALLOWED_BUNDLERS_LOCAL=0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc,0x976EA export ALLOWED_BUNDLERS_TEST=0x14dC79964da2C08b23698B3D3cc7Ca32193d9955,0x23618e81E3f5cdF7f54C3d65f7FBc0aBf5B21E8f # Used in scripts/account_abstraction_demo.ts -export BUNDLER_LOCAL=http://127.0.0.1:3000 - -#################################################################################################### -# Random Contract Configuration -#################################################################################################### - -# The timestamp of the first block of the Happy chain. It should be in seconds. -# This example value is the timestamp of the first block of the Happy chain testnet. -export HAPPY_GENESIS_TIMESTAMP=1723165536 - -# The time between blocks in the Happy chain. It should be in seconds. -export HAPPY_TIME_BLOCK_SECONDS=2 +export BUNDLER_LOCAL=http://127.0.0.1:3000 \ No newline at end of file diff --git a/contracts/Makefile b/contracts/Makefile index 8d899b8615..8003ecd6eb 100755 --- a/contracts/Makefile +++ b/contracts/Makefile @@ -355,9 +355,14 @@ deploy-aa: ## Deploys the AA contracts. make deploy DEPLOY_SCRIPT=DeployAA.s.sol .PHONY: deploy-aa +<<<<<<< HEAD deploy-mocks: ## Deploys the mock contracts. $(eval $(call set-deployment-name,mocks)) make deploy DEPLOY_SCRIPT=DeployMocks.s.sol +======= +deploy-mocks: + make deploy DEPLOY_SCRIPT=mocks/DeployMocks.s.sol +>>>>>>> ea3ee0e2 (chore(contract): pr review) .PHONY: deploy-mocks deploy-random: ## Deploys the randomness contracts and saves the deployment. diff --git a/contracts/src/deploy/DeployRandom.s.sol b/contracts/src/deploy/DeployRandom.s.sol index c12823326d..d98ed9a30e 100644 --- a/contracts/src/deploy/DeployRandom.s.sol +++ b/contracts/src/deploy/DeployRandom.s.sol @@ -31,9 +31,7 @@ contract DeployRandom is BaseDeployScript { } function deploy() internal override { - uint256 happyGenesisTimestamp = vm.envUint("HAPPY_GENESIS_TIMESTAMP"); - uint256 happyTimeBlock = vm.envUint("HAPPY_TIME_BLOCK"); - (address _random,) = deployDeterministic("Random", type(Random).creationCode, abi.encode(drandPublicKey, DRAND_GENESIS_TIMESTAMP, DRAND_PERIOD, happyGenesisTimestamp, happyTimeBlock), DEPLOYMENT_SALT); + (address _random,) = deployDeterministic("Random", type(Random).creationCode, abi.encode(drandPublicKey, DRAND_GENESIS_TIMESTAMP, DRAND_PERIOD), DEPLOYMENT_SALT); random = Random(_random); deployed("Random", address(random)); } diff --git a/packages/contracts/src/randomness/Drand.sol b/packages/contracts/src/randomness/Drand.sol index 68946098f0..feaec762e2 100644 --- a/packages/contracts/src/randomness/Drand.sol +++ b/packages/contracts/src/randomness/Drand.sol @@ -32,6 +32,8 @@ contract Drand { function postDrand(uint64 round, uint256[2] memory signature) external { // Encode round for hash-to-point bytes memory hashedRoundBytes = new bytes(32); + + // hashedRoundBytes = keccak256(abi.encodePacked(round)) — not valid solidity syntax assembly { mstore(0x00, round) let hashedRound := keccak256(0x18, 0x08) // hash the last 8 bytes (uint64) of `round` diff --git a/packages/contracts/src/randomness/Random.sol b/packages/contracts/src/randomness/Random.sol index b0bb3027b8..c59ba5a09f 100644 --- a/packages/contracts/src/randomness/Random.sol +++ b/packages/contracts/src/randomness/Random.sol @@ -6,27 +6,28 @@ import {Drand} from "./Drand.sol"; contract Random is RandomCommitment, Drand { /* - * This is the amount of time by which we delay reading the values from the Drand network. - * This approach is implemented to avoid issues when posting Drand values to the network - * in blocks where the Drand network period coincides with the Happy Chain period. - * For example, if there is a block at timestamp 6 and a new Drand value is also generated at timestamp 6, - * we would read the last Drand value generated before timestamp 6 - DRAND_DELAY. + * The amount of time in seconds by which we delay reading the values from the Drand network. + * + * This is necessary, because whenever a Drand value is generated at time T, it is not possible to guarantee it + * will be posted on a block with timestamp T (even if such a block exists) because of network delays. */ uint256 public constant DRAND_DELAY = 2; + /** + * The minimum amount of time (in seconds) that commitments to future Drand randomness must be made + * in advance. This delay is relative to Drand timestamp (so DRAND_DELAY must also be added). + * + * This is used in the computation of nextValidTimestamp — always use that function to get a + * lower bound on the timestamp to commit to. + * + * This is necessary to enable independent detection of sequencer delays which could signify that the + * sequencer is waiting to know the Drand randomness before including a commitment to future Drand randomness. + */ uint256 public constant MIN_PRECOMMIT_TIME = 3; - uint256 public immutable HAPPY_GENESIS_TIMESTAMP; - uint256 public immutable HAPPY_BLOCK_TIME; - constructor( - uint256[4] memory _drandPublicKey, - uint256 _drandGenesisTimestamp, - uint256 _drandPeriod, - uint256 _happyGenesisTimestamp, - uint256 _happyBlockTime - ) RandomCommitment() Drand(_drandPublicKey, _drandGenesisTimestamp, _drandPeriod) { - HAPPY_GENESIS_TIMESTAMP = _happyGenesisTimestamp; - HAPPY_BLOCK_TIME = _happyBlockTime; - } + constructor(uint256[4] memory _drandPublicKey, uint256 _drandGenesisTimestamp, uint256 _drandPeriod) + RandomCommitment() + Drand(_drandPublicKey, _drandGenesisTimestamp, _drandPeriod) + {} /** * @notice Returns a safe random bytes32 value that changes every block @@ -54,8 +55,6 @@ contract Random is RandomCommitment, Drand { * @notice Returns the next timestamp where the drand randomness is guaranteed to be unknown. * This ensures that at the returned timestamp, no one can have prior knowledge of the drand randomness, * maintaining its unpredictability. - * @param timestamp The timestamp from which to calculate the next valid timestamp. - * @return nextValidTimestamp The next timestamp where the drand randomness remains unrevealed. */ function nextValidTimestamp(uint256 timestamp) public view returns (uint256) { return _nextValidDrandTimestamp(timestamp + MIN_PRECOMMIT_TIME - 1) + DRAND_DELAY; From 9e33ae8d24de68f64180e67ebeb2e27927827bfe Mon Sep 17 00:00:00 2001 From: Gabriel Martinez Rodriguez Date: Mon, 3 Feb 2025 13:28:25 +0100 Subject: [PATCH 16/19] chore(contract): solve conflict --- contracts/Makefile | 5 ----- 1 file changed, 5 deletions(-) diff --git a/contracts/Makefile b/contracts/Makefile index 8003ecd6eb..8d899b8615 100755 --- a/contracts/Makefile +++ b/contracts/Makefile @@ -355,14 +355,9 @@ deploy-aa: ## Deploys the AA contracts. make deploy DEPLOY_SCRIPT=DeployAA.s.sol .PHONY: deploy-aa -<<<<<<< HEAD deploy-mocks: ## Deploys the mock contracts. $(eval $(call set-deployment-name,mocks)) make deploy DEPLOY_SCRIPT=DeployMocks.s.sol -======= -deploy-mocks: - make deploy DEPLOY_SCRIPT=mocks/DeployMocks.s.sol ->>>>>>> ea3ee0e2 (chore(contract): pr review) .PHONY: deploy-mocks deploy-random: ## Deploys the randomness contracts and saves the deployment. From b704f36d914fbf3b48e2b208ba7e420886565e59 Mon Sep 17 00:00:00 2001 From: Gabriel Martinez Rodriguez Date: Mon, 3 Feb 2025 13:29:15 +0100 Subject: [PATCH 17/19] chore: format --- contracts/src/deploy/DeployRandom.s.sol | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/contracts/src/deploy/DeployRandom.s.sol b/contracts/src/deploy/DeployRandom.s.sol index d98ed9a30e..34c8a89f8d 100644 --- a/contracts/src/deploy/DeployRandom.s.sol +++ b/contracts/src/deploy/DeployRandom.s.sol @@ -31,7 +31,12 @@ contract DeployRandom is BaseDeployScript { } function deploy() internal override { - (address _random,) = deployDeterministic("Random", type(Random).creationCode, abi.encode(drandPublicKey, DRAND_GENESIS_TIMESTAMP, DRAND_PERIOD), DEPLOYMENT_SALT); + (address _random,) = deployDeterministic( + "Random", + type(Random).creationCode, + abi.encode(drandPublicKey, DRAND_GENESIS_TIMESTAMP, DRAND_PERIOD), + DEPLOYMENT_SALT + ); random = Random(_random); deployed("Random", address(random)); } From e7e47a1379c769f5edf41e280345ed0de2107fdb Mon Sep 17 00:00:00 2001 From: Gabriel Martinez Rodriguez Date: Mon, 3 Feb 2025 13:35:01 +0100 Subject: [PATCH 18/19] chore(contract): add deployment salt --- contracts/src/deploy/DeployRandom.s.sol | 1 + 1 file changed, 1 insertion(+) diff --git a/contracts/src/deploy/DeployRandom.s.sol b/contracts/src/deploy/DeployRandom.s.sol index 34c8a89f8d..004ab84fa0 100644 --- a/contracts/src/deploy/DeployRandom.s.sol +++ b/contracts/src/deploy/DeployRandom.s.sol @@ -8,6 +8,7 @@ import {Random} from "../randomness/Random.sol"; * @dev Deploys the Randomness contract. */ contract DeployRandom is BaseDeployScript { + bytes32 public constant DEPLOYMENT_SALT = bytes32(uint256(0)); Random public random; /* From d6eaecb66752938fbc989984221183e0568eaf60 Mon Sep 17 00:00:00 2001 From: Gabriel Martinez Rodriguez Date: Tue, 4 Feb 2025 12:40:18 +0100 Subject: [PATCH 19/19] chore: package/contracts --- .../deployments/unknown/random/abiMap.json | 3 - .../deployments/unknown/random/abis.json | 521 ---------------- .../deployments/unknown/random/abis.ts | 554 ------------------ .../unknown/random/deployment.json | 3 - packages/contracts/src/randomness/Drand.sol | 94 --- packages/contracts/src/randomness/Random.sol | 62 -- .../src/randomness/RandomCommitment.sol | 100 ---- 7 files changed, 1337 deletions(-) delete mode 100644 packages/contracts/deployments/unknown/random/abiMap.json delete mode 100644 packages/contracts/deployments/unknown/random/abis.json delete mode 100644 packages/contracts/deployments/unknown/random/abis.ts delete mode 100644 packages/contracts/deployments/unknown/random/deployment.json delete mode 100644 packages/contracts/src/randomness/Drand.sol delete mode 100644 packages/contracts/src/randomness/Random.sol delete mode 100644 packages/contracts/src/randomness/RandomCommitment.sol diff --git a/packages/contracts/deployments/unknown/random/abiMap.json b/packages/contracts/deployments/unknown/random/abiMap.json deleted file mode 100644 index 3c0b222951..0000000000 --- a/packages/contracts/deployments/unknown/random/abiMap.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "Random": "Random" -} \ No newline at end of file diff --git a/packages/contracts/deployments/unknown/random/abis.json b/packages/contracts/deployments/unknown/random/abis.json deleted file mode 100644 index 98b6473587..0000000000 --- a/packages/contracts/deployments/unknown/random/abis.json +++ /dev/null @@ -1,521 +0,0 @@ -{ - "Random": [ - { - "type": "constructor", - "inputs": [ - { - "name": "_publicKey", - "type": "uint256[4]", - "internalType": "uint256[4]" - }, - { - "name": "_genesisTimestamp", - "type": "uint256", - "internalType": "uint256" - }, - { - "name": "_period", - "type": "uint256", - "internalType": "uint256" - } - ], - "stateMutability": "nonpayable" - }, - { - "type": "function", - "name": "DRAND_DELAY", - "inputs": [], - "outputs": [ - { - "name": "", - "type": "uint256", - "internalType": "uint256" - } - ], - "stateMutability": "view" - }, - { - "type": "function", - "name": "DST", - "inputs": [], - "outputs": [ - { - "name": "", - "type": "bytes", - "internalType": "bytes" - } - ], - "stateMutability": "view" - }, - { - "type": "function", - "name": "PRECOMMIT_DELAY", - "inputs": [], - "outputs": [ - { - "name": "", - "type": "uint256", - "internalType": "uint256" - } - ], - "stateMutability": "view" - }, - { - "type": "function", - "name": "genesisTimestamp", - "inputs": [], - "outputs": [ - { - "name": "", - "type": "uint256", - "internalType": "uint256" - } - ], - "stateMutability": "view" - }, - { - "type": "function", - "name": "getRevealedValue", - "inputs": [ - { - "name": "blockNumber", - "type": "uint256", - "internalType": "uint256" - } - ], - "outputs": [ - { - "name": "", - "type": "uint256", - "internalType": "uint256" - } - ], - "stateMutability": "view" - }, - { - "type": "function", - "name": "nextValidTimestamp", - "inputs": [ - { - "name": "timestamp", - "type": "uint256", - "internalType": "uint256" - } - ], - "outputs": [ - { - "name": "", - "type": "uint256", - "internalType": "uint256" - } - ], - "stateMutability": "view" - }, - { - "type": "function", - "name": "owner", - "inputs": [], - "outputs": [ - { - "name": "", - "type": "address", - "internalType": "address" - } - ], - "stateMutability": "view" - }, - { - "type": "function", - "name": "period", - "inputs": [], - "outputs": [ - { - "name": "", - "type": "uint256", - "internalType": "uint256" - } - ], - "stateMutability": "view" - }, - { - "type": "function", - "name": "postCommitment", - "inputs": [ - { - "name": "blockNumber", - "type": "uint256", - "internalType": "uint256" - }, - { - "name": "commitmentHash", - "type": "bytes32", - "internalType": "bytes32" - } - ], - "outputs": [], - "stateMutability": "nonpayable" - }, - { - "type": "function", - "name": "postDrand", - "inputs": [ - { - "name": "round", - "type": "uint64", - "internalType": "uint64" - }, - { - "name": "signature", - "type": "uint256[2]", - "internalType": "uint256[2]" - } - ], - "outputs": [], - "stateMutability": "nonpayable" - }, - { - "type": "function", - "name": "publicKey", - "inputs": [ - { - "name": "", - "type": "uint256", - "internalType": "uint256" - } - ], - "outputs": [ - { - "name": "", - "type": "uint256", - "internalType": "uint256" - } - ], - "stateMutability": "view" - }, - { - "type": "function", - "name": "random", - "inputs": [], - "outputs": [ - { - "name": "", - "type": "bytes32", - "internalType": "bytes32" - } - ], - "stateMutability": "view" - }, - { - "type": "function", - "name": "randomForTimestamp", - "inputs": [ - { - "name": "timestamp", - "type": "uint256", - "internalType": "uint256" - } - ], - "outputs": [ - { - "name": "", - "type": "bytes32", - "internalType": "bytes32" - } - ], - "stateMutability": "view" - }, - { - "type": "function", - "name": "randomness", - "inputs": [ - { - "name": "round", - "type": "uint64", - "internalType": "uint64" - } - ], - "outputs": [ - { - "name": "randomness", - "type": "bytes32", - "internalType": "bytes32" - } - ], - "stateMutability": "view" - }, - { - "type": "function", - "name": "renounceOwnership", - "inputs": [], - "outputs": [], - "stateMutability": "nonpayable" - }, - { - "type": "function", - "name": "revealValue", - "inputs": [ - { - "name": "blockNumber", - "type": "uint256", - "internalType": "uint256" - }, - { - "name": "revealedValue", - "type": "uint256", - "internalType": "uint256" - } - ], - "outputs": [], - "stateMutability": "nonpayable" - }, - { - "type": "function", - "name": "transferOwnership", - "inputs": [ - { - "name": "newOwner", - "type": "address", - "internalType": "address" - } - ], - "outputs": [], - "stateMutability": "nonpayable" - }, - { - "type": "function", - "name": "unsafeGetRevealedValue", - "inputs": [ - { - "name": "blockNumber", - "type": "uint256", - "internalType": "uint256" - } - ], - "outputs": [ - { - "name": "", - "type": "uint256", - "internalType": "uint256" - } - ], - "stateMutability": "view" - }, - { - "type": "event", - "name": "CommitmentPosted", - "inputs": [ - { - "name": "blockNumber", - "type": "uint256", - "indexed": true, - "internalType": "uint256" - }, - { - "name": "commitment", - "type": "bytes32", - "indexed": false, - "internalType": "bytes32" - } - ], - "anonymous": false - }, - { - "type": "event", - "name": "OwnershipTransferred", - "inputs": [ - { - "name": "previousOwner", - "type": "address", - "indexed": true, - "internalType": "address" - }, - { - "name": "newOwner", - "type": "address", - "indexed": true, - "internalType": "address" - } - ], - "anonymous": false - }, - { - "type": "event", - "name": "ValueRevealed", - "inputs": [ - { - "name": "blockNumber", - "type": "uint256", - "indexed": true, - "internalType": "uint256" - }, - { - "name": "revealedValue", - "type": "uint256", - "indexed": false, - "internalType": "uint256" - } - ], - "anonymous": false - }, - { - "type": "error", - "name": "BNAddFailed", - "inputs": [ - { - "name": "input", - "type": "uint256[4]", - "internalType": "uint256[4]" - } - ] - }, - { - "type": "error", - "name": "CommitmentAlreadyExists", - "inputs": [] - }, - { - "type": "error", - "name": "CommitmentTooLate", - "inputs": [] - }, - { - "type": "error", - "name": "DrandNotAvailable", - "inputs": [ - { - "name": "round", - "type": "uint64", - "internalType": "uint64" - } - ] - }, - { - "type": "error", - "name": "InvalidDSTLength", - "inputs": [ - { - "name": "dst", - "type": "bytes", - "internalType": "bytes" - } - ] - }, - { - "type": "error", - "name": "InvalidFieldElement", - "inputs": [ - { - "name": "x", - "type": "uint256", - "internalType": "uint256" - } - ] - }, - { - "type": "error", - "name": "InvalidPublicKey", - "inputs": [ - { - "name": "publicKey", - "type": "uint256[4]", - "internalType": "uint256[4]" - } - ] - }, - { - "type": "error", - "name": "InvalidReveal", - "inputs": [] - }, - { - "type": "error", - "name": "InvalidSignature", - "inputs": [ - { - "name": "publicKey", - "type": "uint256[4]", - "internalType": "uint256[4]" - }, - { - "name": "message", - "type": "uint256[2]", - "internalType": "uint256[2]" - }, - { - "name": "signature", - "type": "uint256[2]", - "internalType": "uint256[2]" - } - ] - }, - { - "type": "error", - "name": "MapToPointFailed", - "inputs": [ - { - "name": "noSqrt", - "type": "uint256", - "internalType": "uint256" - } - ] - }, - { - "type": "error", - "name": "ModExpFailed", - "inputs": [ - { - "name": "base", - "type": "uint256", - "internalType": "uint256" - }, - { - "name": "exponent", - "type": "uint256", - "internalType": "uint256" - }, - { - "name": "modulus", - "type": "uint256", - "internalType": "uint256" - } - ] - }, - { - "type": "error", - "name": "NoCommitmentFound", - "inputs": [] - }, - { - "type": "error", - "name": "OwnableInvalidOwner", - "inputs": [ - { - "name": "owner", - "type": "address", - "internalType": "address" - } - ] - }, - { - "type": "error", - "name": "OwnableUnauthorizedAccount", - "inputs": [ - { - "name": "account", - "type": "address", - "internalType": "address" - } - ] - }, - { - "type": "error", - "name": "RevealMustBeOnExactBlock", - "inputs": [] - }, - { - "type": "error", - "name": "RevealedValueNotAvailable", - "inputs": [] - } - ] -} diff --git a/packages/contracts/deployments/unknown/random/abis.ts b/packages/contracts/deployments/unknown/random/abis.ts deleted file mode 100644 index 498792bcef..0000000000 --- a/packages/contracts/deployments/unknown/random/abis.ts +++ /dev/null @@ -1,554 +0,0 @@ -// This file is auto-generated by `make deploy` in `packages/contracts/Makefile` -import type { MapTuple, ObjectFromTuples, UnionToTuple } from "@happychain/common" -import type { Address } from "viem" - -const contractToAbi = ({ - "Random": [ - { - "type": "constructor", - "inputs": [ - { - "name": "_publicKey", - "type": "uint256[4]", - "internalType": "uint256[4]" - }, - { - "name": "_genesisTimestamp", - "type": "uint256", - "internalType": "uint256" - }, - { - "name": "_period", - "type": "uint256", - "internalType": "uint256" - } - ], - "stateMutability": "nonpayable" - }, - { - "type": "function", - "name": "DRAND_DELAY", - "inputs": [], - "outputs": [ - { - "name": "", - "type": "uint256", - "internalType": "uint256" - } - ], - "stateMutability": "view" - }, - { - "type": "function", - "name": "DST", - "inputs": [], - "outputs": [ - { - "name": "", - "type": "bytes", - "internalType": "bytes" - } - ], - "stateMutability": "view" - }, - { - "type": "function", - "name": "PRECOMMIT_DELAY", - "inputs": [], - "outputs": [ - { - "name": "", - "type": "uint256", - "internalType": "uint256" - } - ], - "stateMutability": "view" - }, - { - "type": "function", - "name": "genesisTimestamp", - "inputs": [], - "outputs": [ - { - "name": "", - "type": "uint256", - "internalType": "uint256" - } - ], - "stateMutability": "view" - }, - { - "type": "function", - "name": "getRevealedValue", - "inputs": [ - { - "name": "blockNumber", - "type": "uint256", - "internalType": "uint256" - } - ], - "outputs": [ - { - "name": "", - "type": "uint256", - "internalType": "uint256" - } - ], - "stateMutability": "view" - }, - { - "type": "function", - "name": "nextValidTimestamp", - "inputs": [ - { - "name": "timestamp", - "type": "uint256", - "internalType": "uint256" - } - ], - "outputs": [ - { - "name": "", - "type": "uint256", - "internalType": "uint256" - } - ], - "stateMutability": "view" - }, - { - "type": "function", - "name": "owner", - "inputs": [], - "outputs": [ - { - "name": "", - "type": "address", - "internalType": "address" - } - ], - "stateMutability": "view" - }, - { - "type": "function", - "name": "period", - "inputs": [], - "outputs": [ - { - "name": "", - "type": "uint256", - "internalType": "uint256" - } - ], - "stateMutability": "view" - }, - { - "type": "function", - "name": "postCommitment", - "inputs": [ - { - "name": "blockNumber", - "type": "uint256", - "internalType": "uint256" - }, - { - "name": "commitmentHash", - "type": "bytes32", - "internalType": "bytes32" - } - ], - "outputs": [], - "stateMutability": "nonpayable" - }, - { - "type": "function", - "name": "postDrand", - "inputs": [ - { - "name": "round", - "type": "uint64", - "internalType": "uint64" - }, - { - "name": "signature", - "type": "uint256[2]", - "internalType": "uint256[2]" - } - ], - "outputs": [], - "stateMutability": "nonpayable" - }, - { - "type": "function", - "name": "publicKey", - "inputs": [ - { - "name": "", - "type": "uint256", - "internalType": "uint256" - } - ], - "outputs": [ - { - "name": "", - "type": "uint256", - "internalType": "uint256" - } - ], - "stateMutability": "view" - }, - { - "type": "function", - "name": "random", - "inputs": [], - "outputs": [ - { - "name": "", - "type": "bytes32", - "internalType": "bytes32" - } - ], - "stateMutability": "view" - }, - { - "type": "function", - "name": "randomForTimestamp", - "inputs": [ - { - "name": "timestamp", - "type": "uint256", - "internalType": "uint256" - } - ], - "outputs": [ - { - "name": "", - "type": "bytes32", - "internalType": "bytes32" - } - ], - "stateMutability": "view" - }, - { - "type": "function", - "name": "randomness", - "inputs": [ - { - "name": "round", - "type": "uint64", - "internalType": "uint64" - } - ], - "outputs": [ - { - "name": "randomness", - "type": "bytes32", - "internalType": "bytes32" - } - ], - "stateMutability": "view" - }, - { - "type": "function", - "name": "renounceOwnership", - "inputs": [], - "outputs": [], - "stateMutability": "nonpayable" - }, - { - "type": "function", - "name": "revealValue", - "inputs": [ - { - "name": "blockNumber", - "type": "uint256", - "internalType": "uint256" - }, - { - "name": "revealedValue", - "type": "uint256", - "internalType": "uint256" - } - ], - "outputs": [], - "stateMutability": "nonpayable" - }, - { - "type": "function", - "name": "transferOwnership", - "inputs": [ - { - "name": "newOwner", - "type": "address", - "internalType": "address" - } - ], - "outputs": [], - "stateMutability": "nonpayable" - }, - { - "type": "function", - "name": "unsafeGetRevealedValue", - "inputs": [ - { - "name": "blockNumber", - "type": "uint256", - "internalType": "uint256" - } - ], - "outputs": [ - { - "name": "", - "type": "uint256", - "internalType": "uint256" - } - ], - "stateMutability": "view" - }, - { - "type": "event", - "name": "CommitmentPosted", - "inputs": [ - { - "name": "blockNumber", - "type": "uint256", - "indexed": true, - "internalType": "uint256" - }, - { - "name": "commitment", - "type": "bytes32", - "indexed": false, - "internalType": "bytes32" - } - ], - "anonymous": false - }, - { - "type": "event", - "name": "OwnershipTransferred", - "inputs": [ - { - "name": "previousOwner", - "type": "address", - "indexed": true, - "internalType": "address" - }, - { - "name": "newOwner", - "type": "address", - "indexed": true, - "internalType": "address" - } - ], - "anonymous": false - }, - { - "type": "event", - "name": "ValueRevealed", - "inputs": [ - { - "name": "blockNumber", - "type": "uint256", - "indexed": true, - "internalType": "uint256" - }, - { - "name": "revealedValue", - "type": "uint256", - "indexed": false, - "internalType": "uint256" - } - ], - "anonymous": false - }, - { - "type": "error", - "name": "BNAddFailed", - "inputs": [ - { - "name": "input", - "type": "uint256[4]", - "internalType": "uint256[4]" - } - ] - }, - { - "type": "error", - "name": "CommitmentAlreadyExists", - "inputs": [] - }, - { - "type": "error", - "name": "CommitmentTooLate", - "inputs": [] - }, - { - "type": "error", - "name": "DrandNotAvailable", - "inputs": [ - { - "name": "round", - "type": "uint64", - "internalType": "uint64" - } - ] - }, - { - "type": "error", - "name": "InvalidDSTLength", - "inputs": [ - { - "name": "dst", - "type": "bytes", - "internalType": "bytes" - } - ] - }, - { - "type": "error", - "name": "InvalidFieldElement", - "inputs": [ - { - "name": "x", - "type": "uint256", - "internalType": "uint256" - } - ] - }, - { - "type": "error", - "name": "InvalidPublicKey", - "inputs": [ - { - "name": "publicKey", - "type": "uint256[4]", - "internalType": "uint256[4]" - } - ] - }, - { - "type": "error", - "name": "InvalidReveal", - "inputs": [] - }, - { - "type": "error", - "name": "InvalidSignature", - "inputs": [ - { - "name": "publicKey", - "type": "uint256[4]", - "internalType": "uint256[4]" - }, - { - "name": "message", - "type": "uint256[2]", - "internalType": "uint256[2]" - }, - { - "name": "signature", - "type": "uint256[2]", - "internalType": "uint256[2]" - } - ] - }, - { - "type": "error", - "name": "MapToPointFailed", - "inputs": [ - { - "name": "noSqrt", - "type": "uint256", - "internalType": "uint256" - } - ] - }, - { - "type": "error", - "name": "ModExpFailed", - "inputs": [ - { - "name": "base", - "type": "uint256", - "internalType": "uint256" - }, - { - "name": "exponent", - "type": "uint256", - "internalType": "uint256" - }, - { - "name": "modulus", - "type": "uint256", - "internalType": "uint256" - } - ] - }, - { - "type": "error", - "name": "NoCommitmentFound", - "inputs": [] - }, - { - "type": "error", - "name": "OwnableInvalidOwner", - "inputs": [ - { - "name": "owner", - "type": "address", - "internalType": "address" - } - ] - }, - { - "type": "error", - "name": "OwnableUnauthorizedAccount", - "inputs": [ - { - "name": "account", - "type": "address", - "internalType": "address" - } - ] - }, - { - "type": "error", - "name": "RevealMustBeOnExactBlock", - "inputs": [] - }, - { - "type": "error", - "name": "RevealedValueNotAvailable", - "inputs": [] - } - ] -} -) as const - -const aliasToContract = ({ - "Random": "Random" -}) as const - -export const deployment = ({ - "Random": "0x9ACE2eE177EB91eEed9591ea50Cb903d551DD0f9" -}) as const - -export type ContractToAbi = typeof contractToAbi -export type AliasToContract = typeof aliasToContract -export type ContractName = keyof ContractToAbi -export type ContractAlias = keyof AliasToContract -export type Deployment = Record - -type AliasTuple = UnionToTuple -type AbiValuesTuple = MapTuple, ContractToAbi> - -export type StaticAbis = ObjectFromTuples - -export const abis = {} as StaticAbis - -for (const [alias, contractName] of Object.entries(aliasToContract)) { - // biome-ignore lint/suspicious/noExplicitAny: safe generated code - abis[alias as ContractAlias] = contractToAbi[contractName as ContractName] as any -} - - diff --git a/packages/contracts/deployments/unknown/random/deployment.json b/packages/contracts/deployments/unknown/random/deployment.json deleted file mode 100644 index 6c2374d088..0000000000 --- a/packages/contracts/deployments/unknown/random/deployment.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "Random": "0x9ACE2eE177EB91eEed9591ea50Cb903d551DD0f9" -} \ No newline at end of file diff --git a/packages/contracts/src/randomness/Drand.sol b/packages/contracts/src/randomness/Drand.sol deleted file mode 100644 index feaec762e2..0000000000 --- a/packages/contracts/src/randomness/Drand.sol +++ /dev/null @@ -1,94 +0,0 @@ -// SPDX-License-Identifier: BSD-3-Clause-Clear -pragma solidity ^0.8.20; - -import {BLS} from "bls-bn254/BLS.sol"; - -contract Drand { - bytes public constant DST = bytes("BLS_SIG_BN254G1_XMD:KECCAK-256_SVDW_RO_NUL_"); - - uint256[4] public drandPublicKey; - uint256 public immutable DRAND_GENESIS_TIMESTAMP; - uint256 public immutable DRAND_PERIOD; - mapping(uint64 round => bytes32 randomness) public drandRandomness; - - event DrandRandomnessPosted(uint64 indexed round, bytes32 randomness); - - error InvalidPublicKey(uint256[4] publicKey); - error InvalidSignature(uint256[4] publicKey, uint256[2] message, uint256[2] signature); - error DrandNotAvailable(uint64 round); - - constructor(uint256[4] memory _drandPublicKey, uint256 _drandGenesisTimestamp, uint256 _drandPeriod) { - if (!BLS.isValidPublicKey(_drandPublicKey)) { - revert InvalidPublicKey(_drandPublicKey); - } - drandPublicKey = _drandPublicKey; - DRAND_GENESIS_TIMESTAMP = _drandGenesisTimestamp; - DRAND_PERIOD = _drandPeriod; - } - - /** - * @notice Posts a new Drand signature for a given drand round. - */ - function postDrand(uint64 round, uint256[2] memory signature) external { - // Encode round for hash-to-point - bytes memory hashedRoundBytes = new bytes(32); - - // hashedRoundBytes = keccak256(abi.encodePacked(round)) — not valid solidity syntax - assembly { - mstore(0x00, round) - let hashedRound := keccak256(0x18, 0x08) // hash the last 8 bytes (uint64) of `round` - mstore(add(0x20, hashedRoundBytes), hashedRound) - } - uint256[2] memory message = BLS.hashToPoint(DST, hashedRoundBytes); - // NB: Always check that the signature is a valid signature (a valid G1 point on the curve)! - if (!BLS.isValidSignature(signature)) { - revert InvalidSignature(drandPublicKey, message, signature); - } - - // Verify the signature over the message using the public key - (bool pairingSuccess,) = BLS.verifySingle(signature, drandPublicKey, message); - if (!pairingSuccess) { - revert InvalidSignature(drandPublicKey, message, signature); - } - - bytes32 roundRandomness = keccak256(abi.encode(signature)); - - drandRandomness[round] = roundRandomness; - emit DrandRandomnessPosted(round, roundRandomness); - } - - /** - * @notice Retrieves the randomness value for a specific drand round. - * This function does not revert if the randomness value is not available. Instead, it returns 0. - */ - function unsafeGetDrand(uint64 round) public view returns (bytes32) { - return drandRandomness[round]; - } - - /** - * @notice Retrieves the randomness value for a specific drand round. - * This function reverts if the randomness value is not available. - */ - function getDrand(uint64 round) public view returns (bytes32) { - if (drandRandomness[round] == 0) { - revert DrandNotAvailable(round); - } - - return drandRandomness[round]; - } - - /** - * @notice Returns the latest drand value at the given timestamp. - */ - function _getDrandAtTimestamp(uint256 timestamp) internal view returns (bytes32) { - uint64 round = uint64((timestamp - DRAND_GENESIS_TIMESTAMP) / DRAND_PERIOD); - return getDrand(round); - } - - /** - * @notice Returns the timestamp in which a new drand value will be available after the given timestamp. - */ - function _nextValidDrandTimestamp(uint256 timestamp) internal view returns (uint256) { - return timestamp + (DRAND_PERIOD - (timestamp % DRAND_PERIOD)); - } -} diff --git a/packages/contracts/src/randomness/Random.sol b/packages/contracts/src/randomness/Random.sol deleted file mode 100644 index c59ba5a09f..0000000000 --- a/packages/contracts/src/randomness/Random.sol +++ /dev/null @@ -1,62 +0,0 @@ -// SPDX-License-Identifier: BSD-3-Clause-Clear -pragma solidity ^0.8.20; - -import {RandomCommitment} from "./RandomCommitment.sol"; -import {Drand} from "./Drand.sol"; - -contract Random is RandomCommitment, Drand { - /* - * The amount of time in seconds by which we delay reading the values from the Drand network. - * - * This is necessary, because whenever a Drand value is generated at time T, it is not possible to guarantee it - * will be posted on a block with timestamp T (even if such a block exists) because of network delays. - */ - uint256 public constant DRAND_DELAY = 2; - /** - * The minimum amount of time (in seconds) that commitments to future Drand randomness must be made - * in advance. This delay is relative to Drand timestamp (so DRAND_DELAY must also be added). - * - * This is used in the computation of nextValidTimestamp — always use that function to get a - * lower bound on the timestamp to commit to. - * - * This is necessary to enable independent detection of sequencer delays which could signify that the - * sequencer is waiting to know the Drand randomness before including a commitment to future Drand randomness. - */ - uint256 public constant MIN_PRECOMMIT_TIME = 3; - - constructor(uint256[4] memory _drandPublicKey, uint256 _drandGenesisTimestamp, uint256 _drandPeriod) - RandomCommitment() - Drand(_drandPublicKey, _drandGenesisTimestamp, _drandPeriod) - {} - - /** - * @notice Returns a safe random bytes32 value that changes every block - * @dev The random value is generated from the drand randomness at the timestamp when the block was generated - * and the revealed value at the current block number. - */ - function random() external view returns (bytes32 randomValue) { - bytes32 drand = _getDrandAtTimestamp(uint128(block.timestamp - DRAND_DELAY)); - uint128 revealedValue = getRevealedValue(uint128(block.number)); - randomValue = keccak256(abi.encodePacked(drand, revealedValue)); - } - - /** - * @notice Returns the latest drand randomness at the given timestamp. - * @dev The drand randomness associated with the timestamp when the block was generated will differ - * from the one provided directly by the drand network. - * This discrepancy exists because we introduce a security margin (DRAND_DELAY seconds) to ensure - * the drand value can be safely included in a block. - */ - function randomForTimestamp(uint256 timestamp) external view returns (bytes32) { - return _getDrandAtTimestamp(timestamp - DRAND_DELAY); - } - - /** - * @notice Returns the next timestamp where the drand randomness is guaranteed to be unknown. - * This ensures that at the returned timestamp, no one can have prior knowledge of the drand randomness, - * maintaining its unpredictability. - */ - function nextValidTimestamp(uint256 timestamp) public view returns (uint256) { - return _nextValidDrandTimestamp(timestamp + MIN_PRECOMMIT_TIME - 1) + DRAND_DELAY; - } -} diff --git a/packages/contracts/src/randomness/RandomCommitment.sol b/packages/contracts/src/randomness/RandomCommitment.sol deleted file mode 100644 index 66d93cc97b..0000000000 --- a/packages/contracts/src/randomness/RandomCommitment.sol +++ /dev/null @@ -1,100 +0,0 @@ -// SPDX-License-Identifier: BSD-3-Clause-Clear -pragma solidity ^0.8.20; - -import {Ownable} from "openzeppelin/access/Ownable.sol"; - -contract RandomCommitment is Ownable { - struct CurrentReveal { - uint128 value; - uint128 blockNumber; - } - - /** - * @dev This delay ensures that the commitment is not submitted too late, - * maintaining the unpredictability of the randomness. It should be set to - * at least 12 hours to accommodate the sequencing window size. - * For more details, please refer to: - * https://specs.optimism.io/protocol/configurability.html#sequencing-window-size - */ - uint256 public constant PRECOMMIT_DELAY = 43200; - - CurrentReveal private currentReveal; - mapping(uint128 blockNumber => bytes32) private commitments; - - event CommitmentPosted(uint128 indexed blockNumber, bytes32 commitment); - event ValueRevealed(uint128 indexed blockNumber, uint128 revealedValue); - - error CommitmentTooLate(); - error CommitmentAlreadyExists(); - error NoCommitmentFound(); - error RevealMustBeOnExactBlock(); - error InvalidReveal(); - error RevealedValueNotAvailable(); - - constructor() Ownable(msg.sender) {} - - /** - * @notice Posts a commitment for a specific block number. - */ - function postCommitment(uint128 blockNumber, bytes32 commitmentHash) external onlyOwner { - if (block.number > blockNumber - PRECOMMIT_DELAY) { - revert CommitmentTooLate(); - } - - if (commitments[blockNumber] != 0) { - revert CommitmentAlreadyExists(); - } - - commitments[blockNumber] = commitmentHash; - emit CommitmentPosted(blockNumber, commitmentHash); - } - - /** - * @notice Reveals the value for a specific block number. - */ - function revealValue(uint128 blockNumber, uint128 revealedValue) external onlyOwner { - bytes32 storedCommitment = commitments[blockNumber]; - - if (storedCommitment == 0) { - revert NoCommitmentFound(); - } - - if (block.number != blockNumber) { - revert RevealMustBeOnExactBlock(); - } - - if (storedCommitment != keccak256(abi.encodePacked(revealedValue))) { - revert InvalidReveal(); - } - - currentReveal.value = revealedValue; - currentReveal.blockNumber = blockNumber; - delete commitments[blockNumber]; - emit ValueRevealed(blockNumber, revealedValue); - } - - /** - * @notice Retrieves the revealed value for a specific block number. - * This function does not revert if the reveal is unavailable. Instead, it returns 0. - */ - function unsafeGetRevealedValue(uint128 blockNumber) public view returns (uint128) { - if (currentReveal.blockNumber != blockNumber) { - return 0; - } - - return currentReveal.value; - } - - /** - * @notice Retrieves the revealed value for a specific block number. - * This function verifies that a reveal exists for the given block number before returning the value. - * It reverts with `RevealedValueNotAvailable` if no valid reveal is found. - */ - function getRevealedValue(uint128 blockNumber) public view returns (uint128) { - if (currentReveal.blockNumber != blockNumber) { - revert RevealedValueNotAvailable(); - } - - return currentReveal.value; - } -}