Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Procedural SafeCast.sol generation #3245

Merged
merged 23 commits into from May 21, 2022
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
734 changes: 731 additions & 3 deletions contracts/utils/math/SafeCast.sol

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions package.json
Expand Up @@ -25,6 +25,7 @@
"clean": "hardhat clean && rimraf build contracts/build",
"prepare": "npm run clean && env COMPILE_MODE=production npm run compile",
"prepack": "scripts/prepack.sh",
"generate": "scripts/generate/run.js",
"release": "scripts/release/release.sh",
"version": "scripts/release/version.sh",
"test": "hardhat test",
Expand Down
57 changes: 57 additions & 0 deletions scripts/generate/run.js
@@ -0,0 +1,57 @@
#!/usr/bin/env node
Amxx marked this conversation as resolved.
Show resolved Hide resolved

const fs = require('fs');

const {
toInt,
toUint,
toIntDownCast,
toUintDownCast,
} = require('./templates');
Amxx marked this conversation as resolved.
Show resolved Hide resolved

function genSafeCast (path) {
Amxx marked this conversation as resolved.
Show resolved Hide resolved
const content = [];

// Header
content.push([
'// SPDX-License-Identifier: MIT',
'',
'pragma solidity ^0.8.0;',
'',
'/**',
' * @dev Wrappers over Solidity\'s uintXX/intXX casting operators with added overflow',
' * checks.',
' *',
' * Downcasting from uint256/int256 in Solidity does not revert on overflow. This can',
' * easily result in undesired exploitation or bugs, since developers usually',
' * assume that overflows raise errors. `SafeCast` restores this intuition by',
' * reverting the transaction when such an operation overflows.',
' *',
' * Using this library instead of the unchecked operations eliminates an entire',
' * class of bugs, so it\'s recommended to use it always.',
' *',
' * Can be combined with {SafeMath} and {SignedSafeMath} to extend it to smaller types, by performing',
' * all math on `uint256` and `int256` and then downcasting.',
' */',
].join('\n'));

// Library
content.push('library SafeCast {');
for (let size = 224; size > 0; size -= 8) {
content.push(toUintDownCast(size));
content.push('');
}
content.push(toUint(256));
content.push('');
for (let size = 224; size > 0; size -= 8) {
content.push(toIntDownCast(size));
content.push('');
}
content.push(toInt(256));
content.push('}');
content.push('');

fs.writeFileSync(path, content.join('\n'));
}

genSafeCast('./contracts/utils/math/SafeCast.sol');
6 changes: 6 additions & 0 deletions scripts/generate/templates/index.js
@@ -0,0 +1,6 @@
module.exports = {
toInt: require('./toInt'),
toUint: require('./toUint'),
toIntDownCast: require('./toIntDownCast'),
toUintDownCast: require('./toUintDownCast'),
};
14 changes: 14 additions & 0 deletions scripts/generate/templates/toInt.js
@@ -0,0 +1,14 @@
module.exports = length => [
' /**',
` * @dev Converts an unsigned uint${length} into a signed int${length}.`,
' *',
' * Requirements:',
' *',
` * - input must be less than or equal to maxInt${length}.`,
' */',
` function toInt${length}(uint${length} value) internal pure returns (int${length}) {`,
` // Note: Unsafe cast below is okay because \`type(int${length}).max\` is guaranteed to be positive`,
` require(value <= uint${length}(type(int${length}).max), "SafeCast: value doesn't fit in an int${length}");`,
` return int${length}(value);`,
' }',
].join('\n');
20 changes: 20 additions & 0 deletions scripts/generate/templates/toIntDownCast.js
@@ -0,0 +1,20 @@
module.exports = length => [
Amxx marked this conversation as resolved.
Show resolved Hide resolved
' /**',
` * @dev Returns the downcasted int${length} from int256, reverting on`,
` * overflow (when the input is less than smallest int${length} or`,
` * greater than largest int${length}).`,
' *',
` * Counterpart to Solidity's \`int${length}\` operator.`,
' *',
' * Requirements:',
' *',
` * - input must fit into ${length} bits`,
' *',
' * _Available since v3.1._',
' */',
` function toInt${length}(int256 value) internal pure returns (int${length}) {`,
// eslint-disable-next-line max-len
` require(value >= type(int${length}).min && value <= type(int${length}).max, "SafeCast: value doesn't fit in ${length} bits");`,
` return int${length}(value);`,
' }',
].join('\n');
13 changes: 13 additions & 0 deletions scripts/generate/templates/toUint.js
@@ -0,0 +1,13 @@
module.exports = length => [
' /**',
` * @dev Converts a signed int${length} into an unsigned uint${length}.`,
' *',
' * Requirements:',
' *',
' * - input must be greater than or equal to 0.',
' */',
` function toUint${length}(int${length} value) internal pure returns (uint${length}) {`,
' require(value >= 0, "SafeCast: value must be positive");',
` return uint${length}(value);`,
' }',
].join('\n');
16 changes: 16 additions & 0 deletions scripts/generate/templates/toUintDownCast.js
@@ -0,0 +1,16 @@
module.exports = length => [
' /**',
` * @dev Returns the downcasted uint${length} from uint256, reverting on`,
` * overflow (when the input is greater than largest uint${length}).`,
' *',
` * Counterpart to Solidity's \`uint${length}\` operator.`,
' *',
' * Requirements:',
' *',
` * - input must fit into ${length} bits`,
' */',
` function toUint${length}(uint256 value) internal pure returns (uint${length}) {`,
` require(value <= type(uint${length}).max, "SafeCast: value doesn't fit in ${length} bits");`,
` return uint${length}(value);`,
' }',
].join('\n');