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

Support Saturation Arithmetic Operations #5029

Open
wants to merge 6 commits into
base: master
Choose a base branch
from

Conversation

Lohann
Copy link
Contributor

@Lohann Lohann commented Apr 29, 2024

Description

Adds gas efficient saturating arithmetic operators, those operators are useful when both overflow and revert are not desired.
This PR also optimizes and remove unnecessary branching from various Math methods.

Motivation

If you want to write some formula, but don't want neither wrapping and reverts, the only option is using the Math.try* methods, which can't be used in chain and make the code less readable.

Ex: Make sure there're at least 100_000 gas units left before calling an external contract, otherwise revert.

// “all but one 64th", reference: https://eips.ethereum.org/EIPS/eip-150
uint256 gasAvailable = gasleft().saturatingSub(5000).saturatingMul(63) / 64;
require(gasAvailable >= 100_000, "not enough gas left to call contract");
// ... call contract

The equivalent code using Math.try* is less readable and less efficient.

(bool success, uint256 gasAvailable) = gasleft().trySub(5000);
gasAvailable = Math.ternary(success, gasAvailable, 0);

// “all but one 64th", reference: https://eips.ethereum.org/EIPS/eip-150
(success, gasAvailable) = gasAvailable.tryMul(63);
gasAvailable = Math.ternary(success, gasAvailable, type(uint256).max);
gasAvailable /= 64;

require(gasAvailable >= 100_000, "not enough gas left to call contract");
// ... call contract

Copy link

changeset-bot bot commented Apr 29, 2024

🦋 Changeset detected

Latest commit: d513400

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
openzeppelin-solidity Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@Lohann Lohann changed the title Add Saturation Arithmetic Operations Support Saturation Arithmetic Operations Apr 29, 2024
Comment on lines 48 to 49
// equivalent to: a >= b ? a - b : 0
return (a - b) * SafeCast.toUint(a > b);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could also do.

Suggested change
// equivalent to: a >= b ? a - b : 0
return (a - b) * SafeCast.toUint(a > b);
return ternary(a > b, a - b, 0);

Copy link
Contributor Author

@Lohann Lohann Apr 29, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's actually a good point, because the ternary operator looks like:

b ^ ((a ^ b) * SafeCast.toUint(condition))

Whoever if a or b is zero, it can be simplified to:

// if `b` is zero, then `conditon ? a : 0` is equivalent to:
SafeCast.toUint(condition) * a;

// if `a` is zero, then `conditon ? 0 : b` is equivalent to:
SafeCast.toUint(!condition) * b;

So it works like an and operator, I really wish the compiler could do this kind of optimization for us, I was wondering if creating a new operator would be confusing, like:

// if `condition` is true, returns `a`, otherwise return zero.
function and(bool condition, uint256 a) internal pure returns (uint256) {
        unchecked {
            return a * SafeCast.toUint(condition));
        }
}

Copy link
Contributor Author

@Lohann Lohann Apr 29, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Otherwise I can refactor it for use the ternary at a little gas cost but increased code readability.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants