From 664c25256a1e59ab7b8dfc294439c536ef89d37d Mon Sep 17 00:00:00 2001 From: Hash Palak Date: Wed, 19 Mar 2025 17:06:05 +0600 Subject: [PATCH 1/8] Enhance addPremint function to handle large amounts and standardize ERC20 decimals. Added validation to prevent exceeding Solidity literal limits. --- packages/core/solidity/src/erc20.ts | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/packages/core/solidity/src/erc20.ts b/packages/core/solidity/src/erc20.ts index df7246a76..e4462c392 100644 --- a/packages/core/solidity/src/erc20.ts +++ b/packages/core/solidity/src/erc20.ts @@ -179,11 +179,21 @@ function addPremint( const decimalPlace = decimals.length - exponent; const zeroes = new Array(Math.max(0, -decimalPlace)).fill('0').join(''); const units = integer + decimals + zeroes; - const exp = decimalPlace <= 0 ? 'decimals()' : `(decimals() - ${decimalPlace})`; + + // Add 18 decimals to the number (standard ERC20 decimals) + const fullAmount = units + '000000000000000000'; + + // Check if the final number would be too large + if (fullAmount.length > 77) { // Solidity has a limit on number literals + throw new OptionsError({ + premint: 'Amount too large. Please use a smaller number to avoid Solidity literal limits', + }); + } c.addConstructorArgument({ type: 'address', name: 'recipient' }); - const mintLine = `_mint(recipient, ${units} * 10 ** ${exp});`; + // Generate the mint line with the full amount directly + const mintLine = `_mint(recipient, ${fullAmount});`; if (crossChainBridging) { if (premintChainId === '') { From f25c4c932aee1bd9d67014e6ff5d93d4c3b22082 Mon Sep 17 00:00:00 2001 From: Hash Palak Date: Fri, 21 Mar 2025 00:48:58 +0600 Subject: [PATCH 2/8] enhanced ERC20 addPremint function with uint256 validation and overflow checks. --- packages/core/solidity/src/erc20.ts | 50 +++++++++++++++++++++++------ tsconfig.base.json | 2 +- 2 files changed, 42 insertions(+), 10 deletions(-) diff --git a/packages/core/solidity/src/erc20.ts b/packages/core/solidity/src/erc20.ts index e4462c392..0f0b3d6ed 100644 --- a/packages/core/solidity/src/erc20.ts +++ b/packages/core/solidity/src/erc20.ts @@ -163,6 +163,30 @@ export function isValidChainId(str: string): boolean { return chainIdPattern.test(str); } +const UINT256_MAX = BigInt(2) ** BigInt(256) - BigInt(1); + +function toUint256(value: string, field: string): bigint { + const isValidNumber = /^\d+$/.test(value); + if (!isValidNumber) { + throw new OptionsError({ + [field]: 'Not a valid number', + }); + } + try { + const numValue = BigInt(value); + if (numValue > UINT256_MAX) { + throw new OptionsError({ + [field]: 'Value is greater than uint256 max value', + }); + } + return numValue; + } catch (e) { + throw new OptionsError({ + [field]: 'Value is greater than uint256 max value', + }); + } +} + function addPremint( c: ContractBuilder, amount: string, @@ -179,21 +203,29 @@ function addPremint( const decimalPlace = decimals.length - exponent; const zeroes = new Array(Math.max(0, -decimalPlace)).fill('0').join(''); const units = integer + decimals + zeroes; - - // Add 18 decimals to the number (standard ERC20 decimals) - const fullAmount = units + '000000000000000000'; - - // Check if the final number would be too large - if (fullAmount.length > 77) { // Solidity has a limit on number literals + const exp = decimalPlace <= 0 ? 'decimals()' : `(decimals() - ${decimalPlace})`; + + // Validate base units is within uint256 range + toUint256(units, 'premint'); + + // Check for potential overflow assuming decimals() = 18 + const assumedExp = decimalPlace <= 0 ? 18 : (18 - decimalPlace); + try { + const calculatedValue = BigInt(units) * (BigInt(10) ** BigInt(assumedExp)); + if (calculatedValue > UINT256_MAX) { + throw new OptionsError({ + premint: 'Amount would overflow uint256 after applying decimals', + }); + } + } catch { throw new OptionsError({ - premint: 'Amount too large. Please use a smaller number to avoid Solidity literal limits', + premint: 'Amount would overflow uint256 after applying decimals', }); } c.addConstructorArgument({ type: 'address', name: 'recipient' }); - // Generate the mint line with the full amount directly - const mintLine = `_mint(recipient, ${fullAmount});`; + const mintLine = `_mint(recipient, ${units} * 10 ** ${exp});`; if (crossChainBridging) { if (premintChainId === '') { diff --git a/tsconfig.base.json b/tsconfig.base.json index 8a049330d..fd69eb3ba 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -6,7 +6,7 @@ "moduleResolution": "node", "esModuleInterop": true, "resolveJsonModule": true, - "lib": ["es2019", "es2019.array"], + "lib": ["es2019", "es2019.array", "ES2020.BigInt"], "forceConsistentCasingInFileNames": true, "isolatedModules": true }, From 52499902773a6e8f4403584edceaa262925e7276 Mon Sep 17 00:00:00 2001 From: Eric Lau Date: Thu, 20 Mar 2025 22:53:51 -0400 Subject: [PATCH 3/8] Handle negative exponent, refactor --- packages/core/solidity/src/erc20.ts | 33 +++++++++++++---------------- 1 file changed, 15 insertions(+), 18 deletions(-) diff --git a/packages/core/solidity/src/erc20.ts b/packages/core/solidity/src/erc20.ts index 0f0b3d6ed..3d73f2857 100644 --- a/packages/core/solidity/src/erc20.ts +++ b/packages/core/solidity/src/erc20.ts @@ -172,19 +172,21 @@ function toUint256(value: string, field: string): bigint { [field]: 'Not a valid number', }); } - try { - const numValue = BigInt(value); - if (numValue > UINT256_MAX) { - throw new OptionsError({ - [field]: 'Value is greater than uint256 max value', - }); - } - return numValue; - } catch (e) { + const numValue = BigInt(value); + if (numValue > UINT256_MAX) { throw new OptionsError({ [field]: 'Value is greater than uint256 max value', }); } + return numValue; +} + +function scaleByPowerOfTen(base: bigint, exponent: number): bigint { + if (exponent < 0) { + return base / (BigInt(10) ** BigInt(-exponent)); + } else { + return base * (BigInt(10) ** BigInt(exponent)); + } } function addPremint( @@ -206,18 +208,13 @@ function addPremint( const exp = decimalPlace <= 0 ? 'decimals()' : `(decimals() - ${decimalPlace})`; // Validate base units is within uint256 range - toUint256(units, 'premint'); + const validatedBaseUnits = toUint256(units, 'premint'); // Check for potential overflow assuming decimals() = 18 const assumedExp = decimalPlace <= 0 ? 18 : (18 - decimalPlace); - try { - const calculatedValue = BigInt(units) * (BigInt(10) ** BigInt(assumedExp)); - if (calculatedValue > UINT256_MAX) { - throw new OptionsError({ - premint: 'Amount would overflow uint256 after applying decimals', - }); - } - } catch { + const calculatedValue = scaleByPowerOfTen(validatedBaseUnits, assumedExp); + + if (calculatedValue > UINT256_MAX) { throw new OptionsError({ premint: 'Amount would overflow uint256 after applying decimals', }); From 84c2549cada6d7337c0ef086001038e5375bdeee Mon Sep 17 00:00:00 2001 From: Eric Lau Date: Thu, 20 Mar 2025 22:58:07 -0400 Subject: [PATCH 4/8] Revert tsconfig since es2019 includes BigInt --- tsconfig.base.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tsconfig.base.json b/tsconfig.base.json index fd69eb3ba..8a049330d 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -6,7 +6,7 @@ "moduleResolution": "node", "esModuleInterop": true, "resolveJsonModule": true, - "lib": ["es2019", "es2019.array", "ES2020.BigInt"], + "lib": ["es2019", "es2019.array"], "forceConsistentCasingInFileNames": true, "isolatedModules": true }, From 0bb5698bb0b4acd47dd73f34bdb2f4d691a4934e Mon Sep 17 00:00:00 2001 From: Eric Lau Date: Thu, 20 Mar 2025 23:50:33 -0400 Subject: [PATCH 5/8] Add tests --- packages/core/solidity/src/erc20.test.ts | 29 ++++++++ packages/core/solidity/src/erc20.test.ts.md | 63 ++++++++++++++++++ packages/core/solidity/src/erc20.test.ts.snap | Bin 3016 -> 3196 bytes 3 files changed, 92 insertions(+) diff --git a/packages/core/solidity/src/erc20.test.ts b/packages/core/solidity/src/erc20.test.ts index 14a2caa46..167139eda 100644 --- a/packages/core/solidity/src/erc20.test.ts +++ b/packages/core/solidity/src/erc20.test.ts @@ -69,6 +69,35 @@ testERC20('erc20 premint of 0', { premint: '0', }); +function testPremint(scenario: string, premint: string, expectedError?: string) { + test(`erc20 premint - ${scenario} - ${expectedError ? 'invalid' : 'valid'}`, async t => { + if (expectedError) { + const error = t.throws(() => + buildERC20({ + name: 'MyToken', + symbol: 'MTK', + premint, + }), + ); + t.is((error as OptionsError).messages.premint, expectedError); + } else { + const c = buildERC20({ + name: 'MyToken', + symbol: 'MTK', + premint, + }); + t.snapshot(printContract(c)); + } + }); +} + +testPremint('max literal', '115792089237316195423570985008687907853269984665640564039457.584007913129639935'); // 2^256 - 1, shifted by 18 decimals +testPremint('max literal + 1', '115792089237316195423570985008687907853269984665640564039457.584007913129639936', 'Value is greater than uint256 max value'); +testPremint('no arithmetic overflow', '115792089237316195423570985008687907853269984665640564039457'); // 2^256 - 1, truncated by 18 decimals +testPremint('arithmetic overflow', '115792089237316195423570985008687907853269984665640564039458', 'Amount would overflow uint256 after applying decimals'); +testPremint('e notation', '1e59'); +testPremint('e notation arithmetic overflow', '1e60', 'Amount would overflow uint256 after applying decimals'); + testERC20('erc20 mintable', { mintable: true, access: 'ownable', diff --git a/packages/core/solidity/src/erc20.test.ts.md b/packages/core/solidity/src/erc20.test.ts.md index 81a604339..ed14a684d 100644 --- a/packages/core/solidity/src/erc20.test.ts.md +++ b/packages/core/solidity/src/erc20.test.ts.md @@ -235,6 +235,69 @@ Generated by [AVA](https://avajs.dev). }␊ ` +## erc20 premint - max literal - valid + +> Snapshot 1 + + `// SPDX-License-Identifier: MIT␊ + // Compatible with OpenZeppelin Contracts ^5.0.0␊ + pragma solidity ^0.8.22;␊ + ␊ + import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";␊ + import {ERC20Permit} from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol";␊ + ␊ + contract MyToken is ERC20, ERC20Permit {␊ + constructor(address recipient)␊ + ERC20("MyToken", "MTK")␊ + ERC20Permit("MyToken")␊ + {␊ + _mint(recipient, 115792089237316195423570985008687907853269984665640564039457584007913129639935 * 10 ** (decimals() - 18));␊ + }␊ + }␊ + ` + +## erc20 premint - no arithmetic overflow - valid + +> Snapshot 1 + + `// SPDX-License-Identifier: MIT␊ + // Compatible with OpenZeppelin Contracts ^5.0.0␊ + pragma solidity ^0.8.22;␊ + ␊ + import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";␊ + import {ERC20Permit} from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol";␊ + ␊ + contract MyToken is ERC20, ERC20Permit {␊ + constructor(address recipient)␊ + ERC20("MyToken", "MTK")␊ + ERC20Permit("MyToken")␊ + {␊ + _mint(recipient, 115792089237316195423570985008687907853269984665640564039457 * 10 ** decimals());␊ + }␊ + }␊ + ` + +## erc20 premint - e notation - valid + +> Snapshot 1 + + `// SPDX-License-Identifier: MIT␊ + // Compatible with OpenZeppelin Contracts ^5.0.0␊ + pragma solidity ^0.8.22;␊ + ␊ + import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";␊ + import {ERC20Permit} from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol";␊ + ␊ + contract MyToken is ERC20, ERC20Permit {␊ + constructor(address recipient)␊ + ERC20("MyToken", "MTK")␊ + ERC20Permit("MyToken")␊ + {␊ + _mint(recipient, 100000000000000000000000000000000000000000000000000000000000 * 10 ** decimals());␊ + }␊ + }␊ + ` + ## erc20 mintable > Snapshot 1 diff --git a/packages/core/solidity/src/erc20.test.ts.snap b/packages/core/solidity/src/erc20.test.ts.snap index c17d6fdaa5082271f36c353eef3badf5bd4313cc..7619e43320ada8b3b59bbed2d09015bba639ad83 100644 GIT binary patch literal 3196 zcmV-?41@DQRzV8gSs#lC00000000B+UCnPJNfn<@A!W3OJt0^jYCQt%j2$~poWwH(O(w}M)=VZ* zKKN#%N_ROfrMucwU7a{HjKpd$NZh!Z8a@z|MHzWiXZb)2KLgD~INJvPizq{Ji z?Y85O?xZcvM0Qobde!ynSMT>;Rrjau6GfBG^j{TzjTmMMIy-?nmH?tsy@quHyLv}s z`p2KjGGy@kw|{>3))#J>pWF9uy?f^;x2sifu($QS$_p$ZqN7SnMug!uM)Wb*X&ug0 zt6)=8yO7}%1%V69I$*bp$PZArixf=2rbZZr64Sv)jfL7mZLUkv9WwfYR z0$t-o!4lAja%o9(R5m}yPMs&#FJS=&Ng~3l#R;ia!C?o1wx%fB1tzDSR2@KyfOd{3 z#WKqE*gds|x z;{D*ZTNI73b059Ry3s{+!EM}b)NIUd#OO%fZhDzxPDor$Pcxkt~3}G6#QO znH~J?*eWK3Kf6P;%3Yho;D|8j+`l0msttp6G>X|}a@69QD-Mt|ntMf&&M1}`l1GOd zv5XvJ9E`c(340tXd&R-a$t>U{L(Dx}Ff$d_7mhCEBrqp|Nia%t+#rfnvt5DfbNh1+ zoBopRsA6hD!LA`tB;hVbgpHfinB!UWq?f^BtyTl`V6g_~=YfnQtU^UEmikSCawY+^ zHpmkMnMe@y9`Yks0V?bP1v5mU0xBj1V8H~x)8vM7{!TuW*__~ujn!tow$`jKtu8ID zEH)d<^`*vYt-02y)z((lR-3iewZ>9?rP*9tURi0ZEZ59`OU>oRYGZA=R$FZ@E-luZ zD@)DhQp1CaqK_4z0v6WQmP0wF%LhwNNqLQMPvH)5Yq)Eu@YK|Jr^v=w@9Q~6 z7ZG~7Lt_u9FM%RvP@;pP)7`MncDbXvql~RYq+evsdhiHX`<{uRo*q(Y#IDk|<4B? zcQCmU8hS#~!;zgYo@QfDB7fC36p3oOzS)78Jfm1XHD|PB=uA@uPV`SX!+Di$C)(;R zii=;XszETjtoVWx?rHNRr@@?jZA38`Z@<00VqjG@>?rf$?(^-J$Itd#TOV%QAcH8? zC^#a}-k3i$}W`nU~>c4brRpaLa8(K({5?VI&-p@B< zqyS`R$-hS46%w2pg1sSq#qPWw65PalM<&PQuDpSyuj6G-+ld0#`yHr9Dd7l8f?{J0 zsOm=7G3HE*}GSS^#0XheVgOEK+kZeLl z`7UOiEu_H9B6Q1(B^O-1^{L1@STB|BwR5?6tm>y*kilZkF;fNCP&js&&E9m?ciu{dw=uW8?BeeAs$uhE!UHrTd1!M z<+6>wr)7TLo)Giksf^CSra7T?b8fH>a^{wKSIlo+ie%JPv`Zv|1JBk2vws2XTXRSU z8tWi>fpxUtNOv1)?dNvzB?|Exr5;-PWoj z!xWz+kK(2gm9woq#n5g|PIV|~E9$rfUmZULip8{Z-Rb)Shw;94)DG9tqg~|TL zf!m++FxfQ(%h7?3yUpo^J1$l=>nK@{g;U4EJeHRzmY2v6j*jPj#w``UxlZxCe90K| zlHuIu^e!2rNA?DI$w>l#xxYM1f4Rv&-2|T!jUwIdR!asK9b|4gX9AqeAKZ3bA9_+x z`tXuomA4+(YKGa${U6R|KF@>G8D8*av*&42^J1D?np|MF&t=((92u;ygT-3#T+|5D z)t<5-W!t8F2w6%DA+b5GUvMz;NgkKJJ}{CZM|o0EGo`7YyV$dEv6r2LkcOhfY#kyR zsTkc;3S}fty*>Cc2XPM`15dnjC;6^@Ep9?DaibovH zozC9fxR#Q>iz!aVgq}YaPa(qZeMk@4{o`#13lD?k`jQZfcbZwSS_xrV`_q4+C zXPc(<8jEp1nd2HI$@q?C=#eAtv$`!;JK#(MMj_F=kRs7zNFIE`Io53Ej`fkR`2a5@ z$QXqFc0iQKe;To+K977rGp=7VhUTJt=K&gcF46>45Xp{nv?ri~Ky2ba9rkz@;HB(O)7kmeA57 zxF$Tn!R536XVH`x)9YK~WDG?as?{oZY6o8)Yp>lw(JKbgVH;JT>B|IpMIX=L{3GW< zY2Mf%sz>=`*sIeYSM(j7xVO3cjNr*q#DH5m{7 zy$BE9FuH;r+wXjxbKSrAPVTzru6ypf=dSzgU3a&Mqocip5gQ2hA|&P-@`Ex6^)W?+ zAx_58-iP}eTS6&Z(RCyq{&%MrITZ;}5x+vnx~~mc30355zUu#&V7-r6r% z&wY|Qy&sDR00000000B+UCWCkNg1CfLN|NxB)Z7P)}mJRRCU$#_AIkAwoFfV?^?|C zgMRpSi>S<~uAnm`b0Q-9G2?*CUIcGm^t%299=+{F@Fs{}ya`?w1P_V`f*|?M$coI$ zs;)<7clKe1%82;li;ORR@%_Gdl>VX z{Hdx!4!?i%=eKWs;fDQp^X`o=-ulVSW)mFjZGNxu94m-0)M%@SaNNU)J_6hA!-ZxO zY#6!;Iqqr*oMGMvyCxz(K&FW_Ou&XgIE4yl;G+l2t>xB&N#U^$0W&nLVtxTWYArup zURim(uz+>bpd7r~+TU1dy#_sM=%Dh%FcEp_Noy*;v?e!B5NSGD-G7&vp;aD>LiP~V zF&{+~I_HQmY!KFrNbqDY_+`NM#i3mYUp0wc-`5 z)InwY@L8o6Ef=QNz^m5_uNS-?BTip|u0;v#YDm&){}npz@5S!4r(QcR(`~+tJQ#(q z%Peffgcg(A$emk)jr{9&p>1T(Yv-kU(UUO)8}ViDo@KIZs3?fBrk6W2_;~H91FzsP zgM+F{5n})o%rVq<&j_M5Czt(~V2nY!S4lOc^nl4a$**27IRE!7qHt^w$7oTt2287~ zVFegOyKtm=D%*b@FVAD=NnC(MvWN(B2|}7paM(wnXK0#nhRJatl>tZ*FisJrSVas_ z^uodvsi1oS4m=xQjw#ZyOcinzyji=O>%jlTjXJO};VTc`1E)~4Q24qq;fNBb1rPjs ztFj*U-eZuNStg>(e&ueZc4clQescq?IRCN;7VhAthK85~H-0bR#&7S;0XNp2@!%9R zw2XsCC?gx<*Aa(Cz!2g6$&jRb!4Z3R#UFAGut37W-ultO)_!My_xToB11CsP;K|B^ z_o|ghLZ$Y2ArX#L)Pt7B*Hs;paPT-|cLaD!1E3HSjwvL3-_THXbL)fkqvwa6_08?} zj-zB<(8L=?u~LyX)qNG^7$rfdxP;o}054Ts34T8Ky(t4f9TIqqR0;gm1^D~w{NQif zRWT#{xecOCp==3*W5S^K{zi1D)-B#QDCQU0QA?<<{Ljt@6-870Pe z7;`2P_B2-ZLV%a{;USl>)?K?)dEZ4ehVxu0Tn4&hZ?Ka22FwrCIO5dC>IEd zv_N3@n%vE}^|8=t^j6tvvzw+1HW54UA)BqBZdin;Pp?j4yO5VTG4|-&YHIB9_9MY; z@6BV)BFC}E*4fqjIp@r+DPL~^X7D? z?d5DckCB<8PSe_nB*OUD8r>%9BZuU<{izL zydO^06EdvAFj8n;732^4XQ5(cVXbK^R@ z>7MGIGOiMdevvck!9Cz?+9H>C_0D?iG*t1I*DYwl&Fn7_SS0B~}joD48I&;)=V5S2hb!KRNOn5`ZPQbcB&^d2t zm>@@y27x+i==dC|3S>xBaO3&z#daD#Y4YIz?btMe;e*zT#qu31g1G}Nym|O}C10nh0$li}Yvq^L2^S4|> zQK-S#MjvAGlw$SR9?^=$xS>m&=${IP^P<>Jw9{Oa7QfbYi(r1y2nA)OQ>bB8XojDo5(!2M>1$D?M8cKF!b!BqcmSSx{`E0aa$1wyCqW-GClcP}Q-hRkua< zgSI+O;4;yOB{9sd;Xh6YU=Au7)ou*7n|v1Sn*stiuF_ew;EdCey=pS5V4-k{@e{}R zl=E*RVsgHY#!Ub_9e}W1a-?F&QN0G%K+GN$NH(CReHZioCeq+V75eGbnh&nQ{8Z(A z%xZOa?p&>QbauQ6Ijq)x*m|`3<630ma8Ns_I5SaxClP-fJX7pP670DnV9&qHNL$fH zax$$9Y&>w#4vzM=_BX!0-rniNcvS0rb)}WzZ{^`gF5CEfT9%gF0kH(0sOS`I*aMo` zV}tjRH@57zV*fHJQjw_{7f1yMfvrbI|1#Kj#t;Jr?<0DK8Cv$F`<1k}_jdOW*LMyd zIeyMik#^lSQ3LQ4V$C7#_WHY>%YN~WUSUDMw&vI{B`%UDanpqU&5R-UGGItb)c%(O zmi(*?ujK$s@))(x0Wi%gvR}g69O12&1>TavWPcOD?ayVH>5}Yh=ow=&F&a7m;b>_S&T90ovqip5=k6<&Om%-@* zPk8g$^E9n-G0QDYKCrvzvfPCn6|Aj+`>p7?s4<4CJ7hu5woQc)@@{4bNnPXmg#aU; zlyT{+10y+dl&1wXGaCA(i#-n)d(}G#X=z%@)*)h$hGia8s3Q5&+u~OQh+AANqtiKn z%xUkGpTVipV&2T_q!jFmfMGju6E6f7f=Z#{2}g5hvv)VGrmP>5NRu(8`{&XrMEs)_ zdB3+V)3)PTco-n-$2GvX7w_e3vvj^c4l=vj3MaqXG^0DXCw()=6y1)1zLM|%n?NJJ;Q;?t-BpHFR1sTR&+62@6 zt{@nf`ji}_ARHqj*h--@f7b&FIuA@y)A8WnOz_|>%arWce&g$c>;9!TO4q$~-AmWK zblvCgy8Bfe9qk>ASwXZFF)>$=AJjptjX5F=X)=!XKHOj5lu8kjt|IXWe|xPcsYr~9 zgc)MiePzf>ETdra_0TR#W|PEhLRJ#2HhFdu)J@P?TQ@bNBf_DbHv=OHANbk{T@%}U zU$U#~z^>j&VOPnAofdy2u&nL*Es%I8NiN5-q-UT?wpFsNl5Lf2D^F}|5SpgXwsKBR zl20n;j&69p=cW(nU3mI2#FMAU{ Date: Thu, 20 Mar 2025 23:53:10 -0400 Subject: [PATCH 6/8] Fix lint --- packages/core/solidity/src/erc20.test.ts | 12 ++++++++++-- packages/core/solidity/src/erc20.ts | 6 +++--- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/packages/core/solidity/src/erc20.test.ts b/packages/core/solidity/src/erc20.test.ts index 167139eda..f609e42dc 100644 --- a/packages/core/solidity/src/erc20.test.ts +++ b/packages/core/solidity/src/erc20.test.ts @@ -92,9 +92,17 @@ function testPremint(scenario: string, premint: string, expectedError?: string) } testPremint('max literal', '115792089237316195423570985008687907853269984665640564039457.584007913129639935'); // 2^256 - 1, shifted by 18 decimals -testPremint('max literal + 1', '115792089237316195423570985008687907853269984665640564039457.584007913129639936', 'Value is greater than uint256 max value'); +testPremint( + 'max literal + 1', + '115792089237316195423570985008687907853269984665640564039457.584007913129639936', + 'Value is greater than uint256 max value', +); testPremint('no arithmetic overflow', '115792089237316195423570985008687907853269984665640564039457'); // 2^256 - 1, truncated by 18 decimals -testPremint('arithmetic overflow', '115792089237316195423570985008687907853269984665640564039458', 'Amount would overflow uint256 after applying decimals'); +testPremint( + 'arithmetic overflow', + '115792089237316195423570985008687907853269984665640564039458', + 'Amount would overflow uint256 after applying decimals', +); testPremint('e notation', '1e59'); testPremint('e notation arithmetic overflow', '1e60', 'Amount would overflow uint256 after applying decimals'); diff --git a/packages/core/solidity/src/erc20.ts b/packages/core/solidity/src/erc20.ts index 3d73f2857..7e7bd9712 100644 --- a/packages/core/solidity/src/erc20.ts +++ b/packages/core/solidity/src/erc20.ts @@ -183,9 +183,9 @@ function toUint256(value: string, field: string): bigint { function scaleByPowerOfTen(base: bigint, exponent: number): bigint { if (exponent < 0) { - return base / (BigInt(10) ** BigInt(-exponent)); + return base / BigInt(10) ** BigInt(-exponent); } else { - return base * (BigInt(10) ** BigInt(exponent)); + return base * BigInt(10) ** BigInt(exponent); } } @@ -211,7 +211,7 @@ function addPremint( const validatedBaseUnits = toUint256(units, 'premint'); // Check for potential overflow assuming decimals() = 18 - const assumedExp = decimalPlace <= 0 ? 18 : (18 - decimalPlace); + const assumedExp = decimalPlace <= 0 ? 18 : 18 - decimalPlace; const calculatedValue = scaleByPowerOfTen(validatedBaseUnits, assumedExp); if (calculatedValue > UINT256_MAX) { From 35a36dbf5b98a72141ee87064babf8c96e34ec82 Mon Sep 17 00:00:00 2001 From: Eric Lau Date: Thu, 20 Mar 2025 23:55:15 -0400 Subject: [PATCH 7/8] Update changelog --- packages/core/solidity/CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/core/solidity/CHANGELOG.md b/packages/core/solidity/CHANGELOG.md index 746dde13a..ba49d7ad9 100644 --- a/packages/core/solidity/CHANGELOG.md +++ b/packages/core/solidity/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## Unreleased + +- Add validation for ERC20 premint field. ([#488](https://github.com/OpenZeppelin/contracts-wizard/pull/488)) + ## 0.5.3 (2025-03-13) - Add ERC20 Cross-Chain Bridging, SuperchainERC20. ([#436](https://github.com/OpenZeppelin/contracts-wizard/pull/436)) From e98385ffb0ded745a705bad0c4ea3d0e746e67a3 Mon Sep 17 00:00:00 2001 From: Eric Lau Date: Tue, 25 Mar 2025 11:28:40 -0400 Subject: [PATCH 8/8] Apply review comments --- packages/core/solidity/src/erc20.ts | 49 ++++++++----------- .../src/utils/convert-strings.test.ts | 23 +++++++++ .../solidity/src/utils/convert-strings.ts | 27 ++++++++++ 3 files changed, 70 insertions(+), 29 deletions(-) create mode 100644 packages/core/solidity/src/utils/convert-strings.test.ts create mode 100644 packages/core/solidity/src/utils/convert-strings.ts diff --git a/packages/core/solidity/src/erc20.ts b/packages/core/solidity/src/erc20.ts index 7e7bd9712..450225479 100644 --- a/packages/core/solidity/src/erc20.ts +++ b/packages/core/solidity/src/erc20.ts @@ -13,6 +13,7 @@ import type { ClockMode } from './set-clock-mode'; import { clockModeDefault, setClockMode } from './set-clock-mode'; import { supportsInterface } from './common-functions'; import { OptionsError } from './error'; +import { toUint256, UINT256_MAX } from './utils/convert-strings'; export const crossChainBridgingOptions = [false, 'custom', 'superchain'] as const; export type CrossChainBridging = (typeof crossChainBridgingOptions)[number]; @@ -163,24 +164,6 @@ export function isValidChainId(str: string): boolean { return chainIdPattern.test(str); } -const UINT256_MAX = BigInt(2) ** BigInt(256) - BigInt(1); - -function toUint256(value: string, field: string): bigint { - const isValidNumber = /^\d+$/.test(value); - if (!isValidNumber) { - throw new OptionsError({ - [field]: 'Not a valid number', - }); - } - const numValue = BigInt(value); - if (numValue > UINT256_MAX) { - throw new OptionsError({ - [field]: 'Value is greater than uint256 max value', - }); - } - return numValue; -} - function scaleByPowerOfTen(base: bigint, exponent: number): bigint { if (exponent < 0) { return base / BigInt(10) ** BigInt(-exponent); @@ -207,18 +190,8 @@ function addPremint( const units = integer + decimals + zeroes; const exp = decimalPlace <= 0 ? 'decimals()' : `(decimals() - ${decimalPlace})`; - // Validate base units is within uint256 range const validatedBaseUnits = toUint256(units, 'premint'); - - // Check for potential overflow assuming decimals() = 18 - const assumedExp = decimalPlace <= 0 ? 18 : 18 - decimalPlace; - const calculatedValue = scaleByPowerOfTen(validatedBaseUnits, assumedExp); - - if (calculatedValue > UINT256_MAX) { - throw new OptionsError({ - premint: 'Amount would overflow uint256 after applying decimals', - }); - } + checkPotentialPremintOverflow(validatedBaseUnits, decimalPlace); c.addConstructorArgument({ type: 'address', name: 'recipient' }); @@ -251,6 +224,24 @@ function addPremint( } } +/** + * Check for potential premint overflow assuming the user's contract has decimals() = 18 + * + * @param baseUnits The base units of the token, before applying power of 10 + * @param decimalPlace If positive, the number of assumed decimal places in the least significant digits of `validatedBaseUnits`. Ignored if <= 0. + * @throws OptionsError if the calculated value would overflow uint256 + */ +function checkPotentialPremintOverflow(baseUnits: bigint, decimalPlace: number) { + const assumedExp = decimalPlace <= 0 ? 18 : 18 - decimalPlace; + const calculatedValue = scaleByPowerOfTen(baseUnits, assumedExp); + + if (calculatedValue > UINT256_MAX) { + throw new OptionsError({ + premint: 'Amount would overflow uint256 after applying decimals', + }); + } +} + function addMintable(c: ContractBuilder, access: Access) { requireAccessControl(c, functions.mint, access, 'MINTER', 'minter'); c.addFunctionCode('_mint(to, amount);', functions.mint); diff --git a/packages/core/solidity/src/utils/convert-strings.test.ts b/packages/core/solidity/src/utils/convert-strings.test.ts new file mode 100644 index 000000000..241589949 --- /dev/null +++ b/packages/core/solidity/src/utils/convert-strings.test.ts @@ -0,0 +1,23 @@ +import test from 'ava'; + +import { toUint256, UINT256_MAX } from './convert-strings'; +import { OptionsError } from '../error'; + +test('toUint256', t => { + t.is(toUint256('123', 'foo'), BigInt(123)); +}); + +test('toUint256 - not number', t => { + const error = t.throws(() => toUint256('abc', 'foo'), { instanceOf: OptionsError }); + t.is(error.messages.foo, 'Not a valid number'); +}); + +test('toUint256 - negative', t => { + const error = t.throws(() => toUint256('-1', 'foo'), { instanceOf: OptionsError }); + t.is(error.messages.foo, 'Not a valid number'); +}); + +test('toUint256 - too large', t => { + const error = t.throws(() => toUint256(String(UINT256_MAX + BigInt(1)), 'foo'), { instanceOf: OptionsError }); + t.is(error.messages.foo, 'Value is greater than uint256 max value'); +}); diff --git a/packages/core/solidity/src/utils/convert-strings.ts b/packages/core/solidity/src/utils/convert-strings.ts new file mode 100644 index 000000000..04d03329c --- /dev/null +++ b/packages/core/solidity/src/utils/convert-strings.ts @@ -0,0 +1,27 @@ +import { OptionsError } from '../error'; + +export const UINT256_MAX = BigInt(2) ** BigInt(256) - BigInt(1); + +/** + * Checks that a string is a valid `uint256` value and converts it to bigint. + * + * @param value The value to check. + * @param field The field name to use in the error if the value is invalid. + * @throws OptionsError if the value is not a valid number or is greater than the maximum value for `uint256`. + * @returns The value as a bigint. + */ +export function toUint256(value: string, field: string): bigint { + const isValidNumber = /^\d+$/.test(value); + if (!isValidNumber) { + throw new OptionsError({ + [field]: 'Not a valid number', + }); + } + const numValue = BigInt(value); + if (numValue > UINT256_MAX) { + throw new OptionsError({ + [field]: 'Value is greater than uint256 max value', + }); + } + return numValue; +}