Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions packages/core/solidity/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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))
Expand Down
37 changes: 37 additions & 0 deletions packages/core/solidity/src/erc20.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,43 @@ 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',
Expand Down
63 changes: 63 additions & 0 deletions packages/core/solidity/src/erc20.test.ts.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Binary file modified packages/core/solidity/src/erc20.test.ts.snap
Binary file not shown.
39 changes: 39 additions & 0 deletions packages/core/solidity/src/erc20.ts
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,32 @@ 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);
} else {
return base * BigInt(10) ** BigInt(exponent);
}
}

function addPremint(
c: ContractBuilder,
amount: string,
Expand All @@ -181,6 +207,19 @@ 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',
});
}

c.addConstructorArgument({ type: 'address', name: 'recipient' });

const mintLine = `_mint(recipient, ${units} * 10 ** ${exp});`;
Expand Down
Loading