diff --git a/.changeset/cuddly-masks-live.md b/.changeset/cuddly-masks-live.md new file mode 100644 index 00000000000..c7dd8acdbee --- /dev/null +++ b/.changeset/cuddly-masks-live.md @@ -0,0 +1,5 @@ +--- +'openzeppelin-solidity': patch +--- + +`forceApprove` gas optimization: skip second approval call whenever `value == 0`. diff --git a/contracts/token/ERC20/utils/SafeERC20.sol b/contracts/token/ERC20/utils/SafeERC20.sol index ed41fb042c9..5e05403802a 100644 --- a/contracts/token/ERC20/utils/SafeERC20.sol +++ b/contracts/token/ERC20/utils/SafeERC20.sol @@ -76,7 +76,9 @@ library SafeERC20 { if (!_callOptionalReturnBool(token, approvalCall)) { _callOptionalReturn(token, abi.encodeCall(token.approve, (spender, 0))); - _callOptionalReturn(token, approvalCall); + if (value > 0) { + _callOptionalReturn(token, approvalCall); + } } } diff --git a/test/token/ERC20/utils/SafeERC20.test.js b/test/token/ERC20/utils/SafeERC20.test.js index 16b72bd6b1b..6921e58b4f7 100644 --- a/test/token/ERC20/utils/SafeERC20.test.js +++ b/test/token/ERC20/utils/SafeERC20.test.js @@ -159,6 +159,11 @@ describe('SafeERC20', function () { await this.mock.$forceApprove(this.token, this.spender, 200n); expect(await this.token.allowance(this.mock, this.spender)).to.equal(200n); }); + + it('forceApprove works for zero allowance', async function () { + await this.mock.$forceApprove(this.token, this.spender, 0); + expect(await this.token.allowance(this.mock, this.spender)).to.equal(0); + }); }); });