From ff1bfe114b63cdb5a2ebd56a864b5027d9aff236 Mon Sep 17 00:00:00 2001 From: Juan Ignacio Ubeira Date: Tue, 30 Jul 2024 17:46:38 -0300 Subject: [PATCH 1/3] Omit second approval call in `forceApprove` for `value == 0`. --- contracts/token/ERC20/utils/SafeERC20.sol | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) 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); + } } } From ead7ac758b6c17a316f24d51c3dace5dcc85fe4e Mon Sep 17 00:00:00 2001 From: Juan Ignacio Ubeira Date: Tue, 30 Jul 2024 17:49:54 -0300 Subject: [PATCH 2/3] Changeset. --- .changeset/cuddly-masks-live.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/cuddly-masks-live.md 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`. From c3c4388f3659e510c15ca33e155ce3b74ea0133d Mon Sep 17 00:00:00 2001 From: Juan Ignacio Ubeira Date: Tue, 30 Jul 2024 18:05:19 -0300 Subject: [PATCH 3/3] Add test for usdt / 0 value. --- test/token/ERC20/utils/SafeERC20.test.js | 5 +++++ 1 file changed, 5 insertions(+) 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); + }); }); });