Skip to content

Commit adc64e1

Browse files
CJ42b00ste
andauthored
perf: use try / catch in LSP20 verification to optimise gas cost (#768)
* perf: use `try / catch` in LSP20 to reduce runtime cost Co-authored-by: b00ste.lyx <[email protected]> * perf: use `try / catch` in `_verifyCallResult` * test: fix revert reason when `bytes4` returnStatus cannot be decoded * docs: update ABI + internal functions docs * refactor: replace error parameter to `bytes4` --------- Co-authored-by: b00ste.lyx <[email protected]> Co-authored-by: b00ste <[email protected]>
1 parent 492efab commit adc64e1

File tree

6 files changed

+64
-115
lines changed

6 files changed

+64
-115
lines changed

contracts/LSP20CallVerification/LSP20CallVerification.sol

+43-49
Original file line numberDiff line numberDiff line change
@@ -20,48 +20,61 @@ import {
2020
abstract contract LSP20CallVerification {
2121
/**
2222
* @dev Calls {lsp20VerifyCall} function on the logicVerifier.
23-
* Reverts in case the value returned does not match the success value (lsp20VerifyCall selector)
24-
* Returns whether a verification after the execution should happen based on the last byte of the returnedStatus
23+
*
24+
* @custom:info
25+
* - Reverts in case the value returned does not match the returned status (lsp20VerifyCall selector).
26+
* - Returns whether a verification after the execution should happen based on the last byte of the `returnedStatus`.
27+
* - Reverts with no reason if the data returned by `ILSP20(logicVerifier).lsp20VerifyCall(...)` cannot be decoded (_e.g:_ any other data type besides `bytes4`).
28+
* See this link for more info: https://forum.soliditylang.org/t/call-for-feedback-the-future-of-try-catch-in-solidity/1497.
2529
*/
2630
function _verifyCall(
2731
address logicVerifier
2832
) internal virtual returns (bool verifyAfter) {
29-
if (logicVerifier.code.length == 0)
33+
if (logicVerifier.code.length == 0) {
3034
revert LSP20EOACannotVerifyCall(logicVerifier);
35+
}
3136

32-
(bool success, bytes memory returnedData) = logicVerifier.call(
33-
abi.encodeWithSelector(
34-
ILSP20.lsp20VerifyCall.selector,
37+
// Reverts with no reason if the returned data type is not a `bytes4` value
38+
try
39+
ILSP20(logicVerifier).lsp20VerifyCall(
3540
msg.sender,
3641
address(this),
3742
msg.sender,
3843
msg.value,
3944
msg.data
4045
)
41-
);
42-
43-
_validateCall(false, success, returnedData);
44-
45-
bytes4 returnedStatus = abi.decode(returnedData, (bytes4));
46+
returns (bytes4 returnedStatus) {
47+
if (
48+
bytes3(returnedStatus) !=
49+
bytes3(ILSP20.lsp20VerifyCall.selector)
50+
) {
51+
revert LSP20CallVerificationFailed({
52+
postCall: false,
53+
returnedStatus: returnedStatus
54+
});
55+
}
4656

47-
if (bytes3(returnedStatus) != bytes3(ILSP20.lsp20VerifyCall.selector)) {
48-
revert LSP20CallVerificationFailed(false, returnedData);
57+
return returnedStatus[3] == 0x01;
58+
} catch (bytes memory errorData) {
59+
_revertWithLSP20DefaultError(false, errorData);
4960
}
50-
51-
return returnedStatus[3] == 0x01;
5261
}
5362

5463
/**
5564
* @dev Calls {lsp20VerifyCallResult} function on the logicVerifier.
56-
* Reverts in case the value returned does not match the success value (lsp20VerifyCallResult selector)
65+
*
66+
* @custom:info
67+
* - Reverts in case the value returned does not match the returned status (lsp20VerifyCallResult selector).
68+
* - Reverts with no reason if the data returned by `ILSP20(logicVerifier).lsp20VerifyCallResult(...)` cannot be decoded (_e.g:_ any other data type besides `bytes4`).
69+
* See this link for more info: https://forum.soliditylang.org/t/call-for-feedback-the-future-of-try-catch-in-solidity/1497.
5770
*/
5871
function _verifyCallResult(
5972
address logicVerifier,
6073
bytes memory callResult
6174
) internal virtual {
62-
(bool success, bytes memory returnedData) = logicVerifier.call(
63-
abi.encodeWithSelector(
64-
ILSP20.lsp20VerifyCallResult.selector,
75+
// Reverts with no reason if the returned data type is not a `bytes4` value
76+
try
77+
ILSP20(logicVerifier).lsp20VerifyCallResult(
6578
keccak256(
6679
abi.encodePacked(
6780
msg.sender,
@@ -73,37 +86,18 @@ abstract contract LSP20CallVerification {
7386
),
7487
callResult
7588
)
76-
);
77-
78-
_validateCall(true, success, returnedData);
79-
80-
if (
81-
abi.decode(returnedData, (bytes4)) !=
82-
ILSP20.lsp20VerifyCallResult.selector
83-
)
84-
revert LSP20CallVerificationFailed({
85-
postCall: true,
86-
returnedData: returnedData
87-
});
88-
}
89-
90-
function _validateCall(
91-
bool postCall,
92-
bool success,
93-
bytes memory returnedData
94-
) internal pure virtual {
95-
if (!success) _revertWithLSP20DefaultError(postCall, returnedData);
89+
returns (bytes4 returnedStatus) {
90+
if (returnedStatus != ILSP20.lsp20VerifyCallResult.selector) {
91+
revert LSP20CallVerificationFailed({
92+
postCall: true,
93+
returnedStatus: returnedStatus
94+
});
95+
}
9696

97-
// check if the returned data contains at least 32 bytes, potentially an abi encoded bytes4 value
98-
// check if the returned data has in the first 32 bytes an abi encoded bytes4 value
99-
if (
100-
returnedData.length < 32 ||
101-
bytes28(bytes32(returnedData) << 32) != bytes28(0)
102-
)
103-
revert LSP20CallVerificationFailed({
104-
postCall: postCall,
105-
returnedData: returnedData
106-
});
97+
return;
98+
} catch (bytes memory errorData) {
99+
_revertWithLSP20DefaultError(true, errorData);
100+
}
107101
}
108102

109103
function _revertWithLSP20DefaultError(

contracts/LSP20CallVerification/LSP20Errors.sol

+2-2
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@ error LSP20CallingVerifierFailed(bool postCall);
1010
/**
1111
* @dev reverts when the call to the owner does not return the LSP20 success value
1212
* @param postCall True if the execution call was done, False otherwise
13-
* @param returnedData The data returned by the call to the logic verifier
13+
* @param returnedStatus The bytes4 decoded data returned by the logic verifier.
1414
*/
15-
error LSP20CallVerificationFailed(bool postCall, bytes returnedData);
15+
error LSP20CallVerificationFailed(bool postCall, bytes4 returnedStatus);
1616

1717
/**
1818
* @dev Reverts when the logic verifier is an Externally Owned Account (EOA) that cannot return the LSP20 success value.

docs/contracts/LSP0ERC725Account/LSP0ERC725Account.md

+7-22
Original file line numberDiff line numberDiff line change
@@ -1225,8 +1225,6 @@ function _verifyCall(
12251225
```
12261226

12271227
Calls [`lsp20VerifyCall`](#lsp20verifycall) function on the logicVerifier.
1228-
Reverts in case the value returned does not match the success value (lsp20VerifyCall selector)
1229-
Returns whether a verification after the execution should happen based on the last byte of the returnedStatus
12301228

12311229
<br/>
12321230

@@ -1240,19 +1238,6 @@ function _verifyCallResult(
12401238
```
12411239

12421240
Calls [`lsp20VerifyCallResult`](#lsp20verifycallresult) function on the logicVerifier.
1243-
Reverts in case the value returned does not match the success value (lsp20VerifyCallResult selector)
1244-
1245-
<br/>
1246-
1247-
### \_validateCall
1248-
1249-
```solidity
1250-
function _validateCall(
1251-
bool postCall,
1252-
bool success,
1253-
bytes returnedData
1254-
) internal pure;
1255-
```
12561241

12571242
<br/>
12581243

@@ -1840,23 +1825,23 @@ Reverts when trying to renounce ownership before the initial confirmation delay.
18401825

18411826
- Specification details: [**LSP-0-ERC725Account**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-0-ERC725Account.md#lsp20callverificationfailed)
18421827
- Solidity implementation: [`LSP0ERC725Account.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP0ERC725Account/LSP0ERC725Account.sol)
1843-
- Error signature: `LSP20CallVerificationFailed(bool,bytes)`
1844-
- Error hash: `0x00c28d0f`
1828+
- Error signature: `LSP20CallVerificationFailed(bool,bytes4)`
1829+
- Error hash: `0x9d6741e3`
18451830

18461831
:::
18471832

18481833
```solidity
1849-
error LSP20CallVerificationFailed(bool postCall, bytes returnedData);
1834+
error LSP20CallVerificationFailed(bool postCall, bytes4 returnedStatus);
18501835
```
18511836

18521837
reverts when the call to the owner does not return the LSP20 success value
18531838

18541839
#### Parameters
18551840

1856-
| Name | Type | Description |
1857-
| -------------- | :-----: | ---------------------------------------------------- |
1858-
| `postCall` | `bool` | True if the execution call was done, False otherwise |
1859-
| `returnedData` | `bytes` | The data returned by the call to the logic verifier |
1841+
| Name | Type | Description |
1842+
| ---------------- | :------: | ------------------------------------------------------- |
1843+
| `postCall` | `bool` | True if the execution call was done, False otherwise |
1844+
| `returnedStatus` | `bytes4` | The bytes4 decoded data returned by the logic verifier. |
18601845

18611846
<br/>
18621847

docs/contracts/LSP20CallVerification/LSP20CallVerification.md

-15
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,6 @@ function _verifyCall(
3333
```
3434

3535
Calls [`lsp20VerifyCall`](#lsp20verifycall) function on the logicVerifier.
36-
Reverts in case the value returned does not match the success value (lsp20VerifyCall selector)
37-
Returns whether a verification after the execution should happen based on the last byte of the returnedStatus
3836

3937
<br/>
4038

@@ -48,19 +46,6 @@ function _verifyCallResult(
4846
```
4947

5048
Calls [`lsp20VerifyCallResult`](#lsp20verifycallresult) function on the logicVerifier.
51-
Reverts in case the value returned does not match the success value (lsp20VerifyCallResult selector)
52-
53-
<br/>
54-
55-
### \_validateCall
56-
57-
```solidity
58-
function _validateCall(
59-
bool postCall,
60-
bool success,
61-
bytes returnedData
62-
) internal pure;
63-
```
6449

6550
<br/>
6651

docs/contracts/UniversalProfile.md

+7-22
Original file line numberDiff line numberDiff line change
@@ -1168,8 +1168,6 @@ function _verifyCall(
11681168
```
11691169

11701170
Calls [`lsp20VerifyCall`](#lsp20verifycall) function on the logicVerifier.
1171-
Reverts in case the value returned does not match the success value (lsp20VerifyCall selector)
1172-
Returns whether a verification after the execution should happen based on the last byte of the returnedStatus
11731171

11741172
<br/>
11751173

@@ -1183,19 +1181,6 @@ function _verifyCallResult(
11831181
```
11841182

11851183
Calls [`lsp20VerifyCallResult`](#lsp20verifycallresult) function on the logicVerifier.
1186-
Reverts in case the value returned does not match the success value (lsp20VerifyCallResult selector)
1187-
1188-
<br/>
1189-
1190-
### \_validateCall
1191-
1192-
```solidity
1193-
function _validateCall(
1194-
bool postCall,
1195-
bool success,
1196-
bytes returnedData
1197-
) internal pure;
1198-
```
11991184

12001185
<br/>
12011186

@@ -1783,23 +1768,23 @@ Reverts when trying to renounce ownership before the initial confirmation delay.
17831768

17841769
- Specification details: [**UniversalProfile**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-3-UniversalProfile-Metadata.md#lsp20callverificationfailed)
17851770
- Solidity implementation: [`UniversalProfile.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/UniversalProfile.sol)
1786-
- Error signature: `LSP20CallVerificationFailed(bool,bytes)`
1787-
- Error hash: `0x00c28d0f`
1771+
- Error signature: `LSP20CallVerificationFailed(bool,bytes4)`
1772+
- Error hash: `0x9d6741e3`
17881773

17891774
:::
17901775

17911776
```solidity
1792-
error LSP20CallVerificationFailed(bool postCall, bytes returnedData);
1777+
error LSP20CallVerificationFailed(bool postCall, bytes4 returnedStatus);
17931778
```
17941779

17951780
reverts when the call to the owner does not return the LSP20 success value
17961781

17971782
#### Parameters
17981783

1799-
| Name | Type | Description |
1800-
| -------------- | :-----: | ---------------------------------------------------- |
1801-
| `postCall` | `bool` | True if the execution call was done, False otherwise |
1802-
| `returnedData` | `bytes` | The data returned by the call to the logic verifier |
1784+
| Name | Type | Description |
1785+
| ---------------- | :------: | ------------------------------------------------------- |
1786+
| `postCall` | `bool` | True if the execution call was done, False otherwise |
1787+
| `returnedStatus` | `bytes4` | The bytes4 decoded data returned by the logic verifier. |
18031788

18041789
<br/>
18051790

tests/LSP20CallVerification/LSP20CallVerification.behaviour.ts

+5-5
Original file line numberDiff line numberDiff line change
@@ -266,9 +266,9 @@ export const shouldBehaveLikeLSP20 = (buildContext: () => Promise<LSP20TestConte
266266
const dataKey = ethers.utils.keccak256(ethers.utils.toUtf8Bytes('RandomKey1'));
267267
const dataValue = ethers.utils.hexlify(ethers.utils.randomBytes(50));
268268

269-
await expect(context.universalProfile.setData(dataKey, dataValue))
270-
.to.be.revertedWithCustomError(context.universalProfile, 'LSP20CallVerificationFailed')
271-
.withArgs(false, '0x');
269+
await expect(
270+
context.universalProfile.setData(dataKey, dataValue),
271+
).to.be.revertedWithoutReason();
272272
});
273273
});
274274

@@ -335,7 +335,7 @@ export const shouldBehaveLikeLSP20 = (buildContext: () => Promise<LSP20TestConte
335335

336336
await expect(
337337
context.universalProfile.setData(dataKey, dataValue),
338-
).to.be.revertedWithCustomError(context.universalProfile, 'LSP20CallVerificationFailed');
338+
).to.be.revertedWithoutReason();
339339
});
340340
});
341341

@@ -368,7 +368,7 @@ export const shouldBehaveLikeLSP20 = (buildContext: () => Promise<LSP20TestConte
368368

369369
await expect(context.universalProfile.setData(dataKey, dataValue))
370370
.to.be.revertedWithCustomError(context.universalProfile, 'LSP20CallVerificationFailed')
371-
.withArgs(false, '0xaabbccdd' + '0'.repeat(56));
371+
.withArgs(false, '0xaabbccdd');
372372
});
373373
});
374374

0 commit comments

Comments
 (0)