Platform: Code4rena
Start Date: 04/03/2024
Pot Size: $140,000 USDC
Total HM: 19
Participants: 69
Period: 21 days
Judge: 0xean
Total Solo HM: 4
Id: 343
League: ETH
Rank: 34/69
Findings: 2
Award: $246.86
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: DadeKuma
Also found by: 0x11singh99, 0xAnah, 0xhacksmithh, Auditor2947, IllIllI, K42, MrPotatoMagic, Pechenite, SAQ, SM3_SS, SY_S, Sathish9098, albahaca, caglankaan, cheatc0d3, clara, dharma09, hihen, hunter_w3b, oualidpro, pavankv, pfapostol, rjs, slvDev, sxima, unique, zabihullahazadzoi
187.8678 USDC - $187.87
I have conducted a thorough audit of Taiko contest, focusing on optimizing gas saving for efficient and cost-effective operations. After rigorous analysis, I am pleased to present the following key findings and recommendations to help improve the gas optmization of Taiko
:
When doing the refactoring, we’ve avoided including any changes that were reported by the
4naly3er and we encourage you to go through the automated findings to maximum the amount of gas being saved.
Num | Finding Issue | Instances |
---|---|---|
1 | Use SSTORE2 or SSTORE3 to store a lot of data | 1 |
2 | Use local variables for emitting | 6 |
3 | Use the inputs/results of assignments rather than re-reading state variables | 12 |
4 | The result of function calls should be cached rather than re-calling the function | 33 |
5 | Internal functions only called once can be inlined to save gas | 23 |
6 | Splitting require() statements that use && saves gas | 4 |
7 | require() or revert() statements that check input arguments should be at the top of the function | 4 |
8 | Using mappings instead of arrays to avoid length checks | 1 |
9 | Do-While loops are cheaper than for loops | 35 |
10 | selfbalance is cheaper than address(this).balance | 2 |
11 | REQUIRE()/REVERT() strings longer than 32 bytes cost extra Gas | 1 |
12 | Don't Initialize Variables with Default Value | 15 |
13 | Use assembly to check for address(0) | 55 |
14 | internal functions not called by the contract should be removed to save deployment gas | 13 |
15 | Calldata is cheaper than memory | 59 |
16 | State variables only set in the constructor should be declared immutable | 2 |
17 | Stack variable used as a cheaper cache for a state variable is only used once | 2 |
18 | Make constructors payable | 3 |
19 | Multiple address/ID mappings can be combined into a single mapping of an address/ID to a struct | 7 |
20 | Using storage instead of memory for structs/arrays saves gas | 2 |
21 | Use assembly to emit events | 55 |
22 | Superfluous event fields | 1 |
23 | Usage of uints /ints smaller than 32 bytes (256 bits) incurs overhead | 23 |
24 | Emit Used In Loop | 3 |
25 | Private functions used once can be inlined | 40 |
26 | Use SUB or XOR instead of ISZERO(EQ()) to check for inequality | 3 |
27 | Use assembly to calculate hashes to save gas | 28 |
28 | Do not calculate constants | 2 |
29 | Assigning state variables directly with named struct constructors wastes gas | 15 |
30 | The use of a logical AND in place of double if is slightly less gas efficient | 23 |
31 | Avoid fetching a low-level call's return data by using assembly | 1 |
32 | Duplicated require() /revert() checks should be refactored to a modifier or function | 3 |
33 | State variable read in a loop | 15 |
34 | Storage re-read via storage pointer | 4 |
35 | Inverting the condition of an if-else-statement | 2 |
36 | Timestamps and block numbers in storage do not need to be uint256 | 12 |
37 | Use fallback or receive instead of deposit() when transferring Ether | 2 |
38 | Consider using alternatives to OpenZeppelin | 56 |
39 | A MODIFIER used only once and not being inherited should be inlined to save gas | 3 |
40 | Expressions for constant values such as a call to KECCAK256 (), should use IMMUTABLE rather than CONSTANT | 3 |
41 | Consider using OZ EnumerateSet in place of nested mappings | 5 |
42 | Shorten the array rather than copying to a new one | 4 |
Total: 42
Instances.
SSTORE2
or SSTORE3
to store a lot of dataIssue Description
The use of SSTORE
opcode for storing persistent data on a key-value basis in EVM
is very expensive in terms of gas consumption.
Proposed Optimization
Instead of using SSTORE
for storing data, it is recommended to store the data in the contract's bytecode during deployment. This approach is more gas-efficient as writing to the bytecode costs only 200 gas per byte, compared to 22,100
gas for writing 32 bytes using SSTORE
.
Estimated Gas Savings
Writing 32 bytes using SSTORE
costs 22,100
gas, which translates to approximately 690 gas per byte. On the other hand, writing the same data to the contract's bytecode during deployment would cost only 200 gas per byte. Therefore, the estimated gas savings by using this optimization would be (690 - 200) = 490
gas per byte of data stored.
Attachments
File: contracts/signal/SignalService.sol assembly { sstore(slot_, _value) }
emitting
Issue Description
The issue is related to the inefficient use of state variables when emitting events in contracts. Directly accessing state variables when emitting events can lead to unnecessary gas consumption due to additional read operations (e.g., Gwarmaccess or Gcoldsload).
Proposed Optimization
Instead of directly accessing state variables when emitting events, it is recommended to use local variables or function parameters. By doing so, the contract can avoid unnecessary read operations from storage, resulting in gas savings.
Estimated Gas Savings
The gas savings depend on the specific context and usage of the state variables. However, the following estimates can be provided:
Attachments
Code Snippets
<details> <summary>Click to show 6 findings</summary>File: contracts/L1/provers/Guardians.sol 95 emit GuardiansUpdated(version, _newGuardians);
File: contracts/L2/TaikoL2.sol 157 emit Anchored(blockhash(parentId), gasExcess);
File: contracts/tokenvault/BridgedERC20Base.sol 63 emit MigratedTo(migratingAddress, _account, _amount); 80 emit MigratedTo(migratingAddress, _account, _amount);
https://github.com/code-423n4/2024-03-taiko/blob/main/tokenvault/BridgedERC20Base.sol#L80
File: contracts/verifiers/SgxVerifier.sol 109 emit InstanceDeleted(idx, instances[idx].addr); 220 emit InstanceAdded(nextInstanceId, _instances[i], address(0), validSince);
https://github.com/code-423n4/2024-03-taiko/blob/main/verifiers/SgxVerifier.sol#L220
inputs/results
of assignments rather than re-reading
state variablesIssue Description
The issue relates to the inefficient re-reading
of state variables after they have been assigned a value within the same function. Re-reading state variables incurs additional gas costs due to the need to perform a storage read operation (Gwarmaccess
).
Proposed Optimization
Instead of re-reading state variables after they have been assigned a value, it is recommended to use the assigned value directly or store it in a local variable. This optimization avoids the unnecessary storage read operation and can lead to gas savings.
Estimated Gas Savings
The estimated gas savings for each instance where a state variable is re-read
after being assigned is 100 gas (Gwarmaccess). This saving applies to each occurrence of re-reading
the state variable within the same function or modifier.
Attachments
Code Snippets
<details> <summary>Click to show 12 findings</summary>File: contracts/L1/TaikoL1.sol 67 (meta_, deposits_) = LibProposing.proposeBlock(state, config, this, _params, _txList); 94 uint8 maxBlocksToVerify = LibProving.proveBlock(state, config, this, meta, tran, proof); 181 a_ = state.slotA;
https://github.com/code-423n4/2024-03-taiko/blob/main/main/L1/TaikoL1.sol#L181
File: contracts/L1/provers/Guardians.sol 88 guardianIds[guardian] = guardians.length;
File: contracts/L2/TaikoL2.sol 265 uint256 excess = uint256(gasExcess) + _parentGasUsed; 262 if (gasExcess > 0) {
File: contracts/common/AddressResolver.sol 83 addr_ = payable(IAddressManager(addressManager).getAddress(_chainId, _name));
File: contracts/team/TimelockTokenPool.sol 219 IERC20(taikoToken).transferFrom(sharedVault, _to, amountToWithdraw);
File: contracts/team/airdrop/ERC20Airdrop.sol 63 IERC20(token).transferFrom(vault, user, amount);
File: contracts/tokenvault/BridgedERC20Base.sol 63 emit MigratedTo(migratingAddress, _account, _amount); 80 emit MigratedTo(migratingAddress, _account, _amount);
</details>File: contracts/verifiers/SgxVerifier.sol 218 ids[i] = nextInstanceId;
re-calling
the functionIssue Description
The issue is related to the inefficient practice of calling the same function multiple times within a contract, leading to unnecessary gas consumption. When a function is called, its code is executed, and if the function is called multiple times, the same code is executed repeatedly, resulting in higher gas costs.
Proposed Optimization
Instead of calling the same function multiple times, it is recommended to cache the result of the function call in a local variable. By doing so, the contract can avoid executing the function code repeatedly, leading to gas savings.
Estimated Gas Savings
The estimated gas savings depend on the complexity and gas cost of the function being called. In general, the gas savings
will be proportional to the number of times the function is called after the initial call. For each subsequent call, the gas cost of executing the function code is saved.
Attachments
Code Snippets
<details> <summary>Click to show 33 findings</summary>File: contracts/automata-attestation/lib/PEMCertChainLib.sol 111 tbsPtr = der.nextSiblingOf(tbsPtr); 112 tbsPtr = der.nextSiblingOf(tbsPtr); 117 issuerPtr = der.firstChildOf(issuerPtr); 127 tbsPtr = der.nextSiblingOf(tbsPtr); 130 uint256 notBeforePtr = der.firstChildOf(tbsPtr); 144 tbsPtr = der.nextSiblingOf(tbsPtr); 147 uint256 subjectPtr = der.firstChildOf(tbsPtr); 149 subjectPtr = der.firstChildOf(subjectPtr); 157 tbsPtr = der.nextSiblingOf(tbsPtr); 161 uint256 subjectPublicKeyInfoPtr = der.firstChildOf(tbsPtr); 176 sigPtr = der.nextSiblingOf(sigPtr); 177 bytes memory sigY = _trimBytes(der.bytesAt(sigPtr), 32); 186 tbsPtr = der.nextSiblingOf(tbsPtr); 193 tbsPtr = der.firstChildOf(tbsPtr); 194 tbsPtr = der.firstChildOf(tbsPtr); 310 if (BytesUtils.compareBytes(der.bytesAt(extnValueOidPtr), PCEID_OID)) { 316 if (BytesUtils.compareBytes(der.bytesAt(extnValueOidPtr), FMSPC_OID)) { 318 uint256 fmspcPtr = der.nextSiblingOf(extnValueOidPtr);
</details>File: contracts/automata-attestation/utils/Asn1Decode.sol 101 || ((j.ixf() <= i.ixs()) && (i.ixl() <= j.ixl())) 101 || ((j.ixf() <= i.ixs()) && (i.ixl() <= j.ixl())) 112 return der.substring(ptr.ixf(), ptr.ixl() + 1 - ptr.ixf()); 122 return der.substring(ptr.ixs(), ptr.ixl() + 1 - ptr.ixs()); 132 return der.readBytesN(ptr.ixf(), ptr.ixl() + 1 - ptr.ixf()); 144 uint256 len = ptr.ixl() + 1 - ptr.ixf(); 145 return uint256(der.readBytesN(ptr.ixf(), len) >> (32 - len) * 8); 157 uint256 valueLength = ptr.ixl() + 1 - ptr.ixf(); 158 if (der[ptr.ixf()] == 0) { 159 return der.substring(ptr.ixf() + 1, valueLength - 1); 161 return der.substring(ptr.ixf(), valueLength); 166 return der.keccak(ptr.ixf(), ptr.ixl() + 1 - ptr.ixf()); 170 return der.keccak(ptr.ixs(), ptr.ixl() + 1 - ptr.ixs()); 183 uint256 valueLength = ptr.ixl() + 1 - ptr.ixf(); 184 return der.substring(ptr.ixf() + 1, valueLength - 1);
inlined
to save gasIssue Description
The issue is related to the gas cost associated with function calls in contracts. When a function is not inlined, it requires additional instructions and stack operations, resulting in higher gas consumption.
Proposed Optimization
To optimize gas usage, it is recommended to inline small functions whenever possible. Inlining a function eliminates the need for additional instructions and stack operations associated with function calls, leading to gas savings.
Estimated Gas Savings
Not inlining a function costs approximately 20 to 40 gas due to the following factors:
By inlining small functions, the estimated gas savings range from 20 to 40 gas per function call. Attachments
Code Snippets
<details> <summary>Click to show 23 findings</summary>File: contracts/L1/TaikoL1.sol 220 function _authorizePause(address)
File: contracts/L1/TaikoToken.sol 105 function _mint( 115 function _burn(
File: contracts/L1/libs/LibDepositing.sol 122 function canDepositEthToL2(
File: contracts/L1/libs/LibProposing.sol 287 function isBlobReusable(
File: contracts/L1/libs/LibUtils.sol 70 function getTransitionId(
File: contracts/automata-attestation/AutomataDcapV3Attestation.sol 126 function _attestationTcbIsValid(TCBInfoStruct.TCBStatus status)
File: contracts/automata-attestation/lib/QuoteV3Auth/V3Parser.sol 267 function parseCerificationChainBytes(
File: contracts/automata-attestation/utils/BytesUtils.sol 56 function compare(
File: contracts/automata-attestation/utils/RsaVerify.sol 43 function pkcs1Sha256( 212 function pkcs1Sha1(
File: contracts/automata-attestation/utils/X509DateUtils.sol 34 function toUnixTimestamp(
File: contracts/common/EssentialContract.sol 109 function __Essential_init(address _owner) internal virtual {
File: contracts/libs/LibAddress.sol 42 function sendEther(address _to, uint256 _amount) internal {
File: contracts/signal/SignalService.sol 206 function _verifyHopProof(
File: contracts/team/airdrop/MerkleClaimable.sol 77 function _verifyMerkleProof(
File: contracts/thirdparty/optimism/Bytes.sol 91 function slice(bytes memory _bytes, uint256 _start) internal pure returns (bytes memory) {
File: contracts/thirdparty/optimism/rlp/RLPReader.sol 102 function readList(bytes memory _in) internal pure returns (RLPItem[] memory out_) { 128 function readBytes(bytes memory _in) internal pure returns (bytes memory out_) {
File: contracts/thirdparty/optimism/rlp/RLPWriter.sol 13 function writeBytes(bytes memory _in) internal pure returns (bytes memory out_) {
File: contracts/thirdparty/optimism/trie/MerkleTrie.sol 68 function get(
</details>File: contracts/tokenvault/BridgedERC20.sol 163 function _mint( 173 function _burn(
require()
statements that use &&
saves gasIssue Description
The issue is related to the gas cost associated with the usage of the &&
operator in require statements in contracts. When multiple conditions are combined using the && operator in a single require statement, it can lead to higher gas consumption.
Proposed Optimization
To optimize gas usage, it is recommended to split the require statements that use the &&
operator into multiple require statements, each with a single condition. This approach can save gas in the long run, despite having a larger deployment gas cost.
Estimated Gas Savings
Splitting a require statement that uses the && operator saves approximately 3 gas per runtime call. However, it does incur a larger deployment gas cost due to the increased bytecode size.
The actual gas savings depend on the number of runtime calls to the contract. If there are enough runtime calls, the change ends up being cheaper overall due to the cumulative gas savings of 3 gas per call.
Attachments
Code Snippets
<details> <summary>Click to show 4 findings</summary>File: contracts/automata-attestation/lib/QuoteV3Auth/V3Parser.sol 77 require( 78 localEnclaveReport.reserved3.length == 96 && localEnclaveReport.reserved4.length == 60 79 && localEnclaveReport.reportData.length == 64, 80 "local QE report has wrong length" 81 ); 82 require( 83 pckSignedQeReport.reserved3.length == 96 && pckSignedQeReport.reserved4.length == 60 84 && pckSignedQeReport.reportData.length == 64, 85 "QE report has wrong length" 86 ); 94 require( 95 v3Quote.v3AuthData.ecdsa256BitSignature.length == 64 96 && v3Quote.v3AuthData.ecdsaAttestationKey.length == 64 97 && v3Quote.v3AuthData.qeReportSignature.length == 64, 98 "Invalid ECDSA signature format" 99 );
</details>File: contracts/automata-attestation/utils/BytesUtils.sol 335 require(char >= 0x30 && char <= 0x7A, "invalid char");
require()
or revert()
statements that check input arguments should be at the top of the functionIssue Description
The issue is related to the order of checks performed in functions. When a function performs multiple checks, the order in which these checks are executed can impact gas consumption. Checks involving constants should be performed before checks involving state variables, function calls, and calculations.
Proposed Optimization
To optimize gas usage, it is recommended to order the checks in a function such that checks involving constants are performed first, followed by checks involving state variables, function calls, and calculations. By performing the constant checks first, the function can revert early without incurring the gas cost of loading state variables or executing complex operations.
Estimated Gas Savings
The estimated gas savings depend on the specific checks involved and the number of times the function is called. However, the following estimates can be provided:
-If a function performs a check involving a state variable or a function call, and this check is executed before constant checks, the gas cost of loading the state variable (Gcoldsload
) or executing the function call is incurred unnecessarily if the function ultimately reverts due to a failed constant check.
-The gas cost of Gcoldsload is approximately 2100 gas.
Attachments
Code Snippets
<details> <summary>Click to show 4 findings</summary></details>File: contracts/automata-attestation/lib/QuoteV3Auth/V3Parser.sol 91 require( 92 v3Quote.v3AuthData.certification.decodedCertDataArray.length == 3, "3 certs in chain" 93 ); 100 require( 101 v3Quote.v3AuthData.qeAuthData.parsedDataSize 102 == v3Quote.v3AuthData.qeAuthData.data.length, 103 "Invalid QEAuthData size" 104 ); 94 require( 95 v3Quote.v3AuthData.ecdsa256BitSignature.length == 64 96 && v3Quote.v3AuthData.ecdsaAttestationKey.length == 64 97 && v3Quote.v3AuthData.qeReportSignature.length == 64, 98 "Invalid ECDSA signature format" 99 ); 87 require( 88 v3Quote.v3AuthData.certification.certType == 5, 89 "certType must be 5: Concatenated PCK Cert Chain (PEM formatted)" 90 );
Issue Description
When fetching an element from an array, Solidity adds bytecode
that checks if the index being accessed is within the valid range of the array's length. This bounds check is performed to prevent reading from unallocated or allocated storage/memory locations, but it incurs additional gas costs.
Proposed Optimization
To optimize gas usage, it is recommended to use mappings
instead of arrays when storing and fetching elements with fixed keys or indexes. Mappings
do not have the same bounds checking as arrays, allowing direct access to storage slots without the additional gas cost of bounds checking.
Estimated Gas Savings
The estimated gas savings by using mappings instead of arrays for fetching elements can be significant. According to the provided example, the gas cost for fetching an element from an array get(0)
is 4860
gas, while the gas cost for fetching an element from a mapping (get(0)) is 2758 gas. This results in a gas saving of approximately
2102 gas per element fetch operation.
Attachments
File: contracts/L1/provers/Guardians.sol address[] public guardians;
Do-While
loops are cheaper than for loopsIssue Description
The choice between using a for loop or a do-while loop can impact gas consumption.
Proposed Optimization
To optimize gas usage, it is recommended to use do-while
loops instead of for loops, even if an additional condition check is required to handle the case where the loop does not execute at all. Despite the additional condition check, do-while
loops are generally cheaper in terms of gas cost compared to for loops.
Estimated Gas Savings\
Attachments
Code Snippets
<details> <summary>Click to show 29 findings</summary>File: contracts/L1/hooks/AssignmentHook.sol 172 for (uint256 i; i < _tierFees.length; ++i) {
File: contracts/L1/libs/LibDepositing.sol 86 for (uint256 i; i < deposits_.length;) {
File: contracts/L1/libs/LibProposing.sol 244 for (uint256 i; i < params.hookCalls.length; ++i) {
File: contracts/L1/provers/Guardians.sol 74 for (uint256 i; i < guardians.length; ++i) { 80 for (uint256 i = 0; i < _newGuardians.length; ++i) {
File: contracts/automata-attestation/AutomataDcapV3Attestation.sol 80 for (uint256 i; i < serialNumBatch.length; ++i) { 95 for (uint256 i; i < serialNumBatch.length; ++i) { 191 for (uint256 i; i < enclaveId.tcbLevels.length; ++i) { 214 for (uint256 i; i < tcb.tcbLevels.length; ++i) {
File: contracts/automata-attestation/lib/PEMCertChainLib.sol 244 for (uint256 i; i < split.length; ++i) {
File: contracts/automata-attestation/lib/QuoteV3Auth/V3Parser.sol 153 for (uint256 i; i < encoded.length; ++i) {
File: contracts/automata-attestation/utils/RsaVerify.sol 174 for (uint256 i; i < _sha256.length; ++i) { 283 for (uint256 i; i < sha1Prefix.length; ++i) { 290 for (uint256 i; i < _sha1.length; ++i) {
File: contracts/bridge/Bridge.sol 90 for (uint256 i; i < _msgHashes.length; ++i) {
File: contracts/signal/SignalService.sol 104 for (uint256 i; i < hopProofs.length; ++i) {
File: contracts/team/airdrop/ERC721Airdrop.sol 59 for (uint256 i; i < tokenIds.length; ++i) {
File: contracts/thirdparty/optimism/rlp/RLPWriter.sol 66 for (uint256 j = 0; j < out_.length; j++) {
File: contracts/thirdparty/optimism/trie/MerkleTrie.sol 85 for (uint256 i = 0; i < proof.length; i++) {
File: contracts/tokenvault/ERC1155Vault.sol 47 for (uint256 i; i < _op.amounts.length; ++i) { 251 for (uint256 i; i < _op.tokenIds.length; ++i) { 269 for (uint256 i; i < _op.tokenIds.length; ++i) {
File: contracts/tokenvault/ERC721Vault.sol 34 for (uint256 i; i < _op.tokenIds.length; ++i) { 170 for (uint256 i; i < _tokenIds.length; ++i) { 175 for (uint256 i; i < _tokenIds.length; ++i) { 197 for (uint256 i; i < _op.tokenIds.length; ++i) { 210 for (uint256 i; i < _op.tokenIds.length; ++i) {
</details>File: contracts/verifiers/SgxVerifier.sol 104 for (uint256 i; i < _ids.length; ++i) { 210 for (uint256 i; i < _instances.length; ++i) {
selfbalance
is cheaper than address(this).balance
(more efficient in certain scenarios)Issue Description
The choice between using address(this).balance or the selfbalance() function from the assembly language can impact gas consumption.
Proposed Optimization
To optimize gas usage, it is recommended to use the selfbalance()
function from Yul instead of address(this).balance
when retrieving the balance of a contract. The selfbalance() function can be more efficient in certain scenarios.
Estimated Gas Savings
The estimated gas savings from using selfbalance() instead of address(this).balance can vary depending on the specific use case and the overall complexity of the contract. However, in general, selfbalance() can save a few gas units compared to address(this).balance.
Attachments
File: contracts/L1/hooks/AssignmentHook.sol 125 if (address(this).balance > 0) {
File: contracts/L1/libs/LibProposing.sol 260 if (address(this).balance != 0) {
REQUIRE()/REVERT()
strings longer than 32 bytes cost extra GasIssue Description
When using the REQUIRE()
strings longer than 32 bytes incur additional gas costs due to the way they are stored and handled.
Proposed Optimization
Refactor the code to avoid passing strings longer than 32 bytes to the REQUIRE()
or REVERT() functions. Instead, consider using more concise error messages or splitting longer messages into multiple smaller ones.
Estimated Gas Savings\
Attachments
File: contracts/thirdparty/optimism/trie/MerkleTrie.sol require(currentKeyIndex <= key.length, "MerkleTrie: key index exceeds total key length");
File: contracts/L1/provers/Guardians.sol for (uint256 i = 0; i < _newGuardians.length; ++i) {
File: contracts/automata-attestation/lib/PEMCertChainLib.sol uint256 index = 0;
File: contracts/automata-attestation/lib/QuoteV3Auth/V3Parser.sol bytes4 internal constant SUPPORTED_TEE_TYPE = 0;
File: contracts/automata-attestation/utils/BytesUtils.sol for (uint256 idx = 0; idx < shortest; idx += 32) { uint256 ret = 0;
File: contracts/automata-attestation/utils/X509DateUtils.sol uint256 timestamp = 0;
File: contracts/thirdparty/optimism/rlp/RLPReader.sol uint256 itemCount = 0;
File: contracts/thirdparty/optimism/rlp/RLPWriter.sol uint256 i = 0; for (uint256 j = 0; j < out_.length; j++) {
</details>File: contracts/thirdparty/optimism/trie/MerkleTrie.sol uint8 internal constant PREFIX_EXTENSION_EVEN = 0; uint256 currentKeyIndex = 0; for (uint256 i = 0; i < proof.length; i++) { for (uint256 i = 0; i < length;) {
address(0)
Issue Description
Currently, the contracts may be using the address(0)
check conventionally, which can consume unnecessary gas.
Proposed Optimization
Utilize assembly to check for the zero address address(0)
more efficiently, resulting in gas savings.
Estimated Gas Savings
Implementing this optimization can save approximately 6
gas per instance where the zero address check is performed.
Attachments
Code Snippets
<details> <summary>Click to show 55 findings</summary>File: contracts/L1/hooks/AssignmentHook.sol 109 if (assignment.feeToken == address(0)) { 120 if (input.tip != 0 && block.coinbase != address(0)) {
File: contracts/L1/libs/LibDepositing.sol 44 address recipient_ = _recipient == address(0) ? msg.sender : _recipient;
File: contracts/L1/libs/LibProposing.sol 81 if (params.assignedProver == address(0)) { 85 if (params.coinbase == address(0)) { 310 if (proposerOne != address(0) && msg.sender != proposerOne) { 316 return proposer == address(0) || msg.sender == proposer;
File: contracts/L1/libs/LibProving.sol 163 if (verifier != address(0)) { 224 assert(ts.validityBond == 0 && ts.contestBond == 0 && ts.contester == address(0)); 239 if (ts.contester != address(0)) revert L1_ALREADY_CONTESTED(); 363 if (_ts.contester != address(0)) {
File: contracts/L1/libs/LibVerifying.sol 145 if (ts.contester != address(0)) { 148 if (tierProvider == address(0)) {
File: contracts/L1/provers/Guardians.sol 82 if (guardian == address(0)) revert INVALID_GUARDIAN();
File: contracts/L2/TaikoL2.sol 172 if (_to == address(0)) revert L2_INVALID_PARAM(); 173 if (_token == address(0)) {
File: contracts/bridge/Bridge.sol 124 if (_message.srcOwner == address(0) || _message.destOwner == address(0)) { 124 if (_message.srcOwner == address(0) || _message.destOwner == address(0)) { 270 _message.to == address(0) || _message.to == address(this) 291 _message.refundTo == address(0) ? _message.destOwner : _message.refundTo; 398 enabled_ = destBridge_ != address(0);
File: contracts/common/AddressResolver.sol 81 if (addressManager == address(0)) revert RESOLVER_INVALID_MANAGER(); 85 if (!_allowZeroAddress && addr_ == address(0)) {
File: contracts/common/EssentialContract.sol 105 if (_addressManager == address(0)) revert ZERO_ADDR_MANAGER(); 110 _transferOwnership(_owner == address(0) ? msg.sender : _owner);
File: contracts/libs/LibAddress.sol 24 if (_to == address(0)) revert ETH_TRANSFER_FAILED();
File: contracts/signal/SignalService.sol 36 if (_app == address(0)) revert SS_INVALID_SENDER();
File: contracts/team/TimelockTokenPool.sol 121 if (_taikoToken == address(0)) revert INVALID_PARAM(); 124 if (_costToken == address(0)) revert INVALID_PARAM(); 127 if (_sharedVault == address(0)) revert INVALID_PARAM(); 136 if (_recipient == address(0)) revert INVALID_PARAM(); 169 if (_to == address(0)) revert INVALID_PARAM();
File: contracts/tokenvault/BaseNFTVault.sol 149 if (_op.token == address(0)) revert VAULT_INVALID_TOKEN();
File: contracts/tokenvault/BridgedERC20Base.sol 102 return migratingAddress != address(0) && !migratingInbound;
File: contracts/tokenvault/ERC1155Vault.sol 64 destOwner: _op.destOwner != address(0) ? _op.destOwner : msg.sender, 108 if (to == address(0) || to == address(this)) revert VAULT_INVALID_TO(); 249 if (bridgedToCanonical[_op.token].addr != address(0)) { 293 if (btoken_ == address(0)) {
File: contracts/tokenvault/ERC20Vault.sol 158 if (_btokenNew == address(0) || bridgedToCanonical[_btokenNew].addr != address(0)) { 158 if (_btokenNew == address(0) || bridgedToCanonical[_btokenNew].addr != address(0)) { 170 if (btokenOld_ != address(0)) { 215 if (_op.token == address(0)) revert VAULT_INVALID_TOKEN(); 227 destOwner: _op.destOwner != address(0) ? _op.destOwner : msg.sender, 267 if (to == address(0) || to == address(this)) revert VAULT_INVALID_TO(); 358 if (bridgedToCanonical[_token].addr != address(0)) { 397 if (btoken == address(0)) {
File: contracts/tokenvault/ERC721Vault.sol 50 destOwner: _op.destOwner != address(0) ? _op.destOwner : msg.sender, 91 if (to == address(0) || to == address(this)) revert VAULT_INVALID_TO(); 195 if (bridgedToCanonical[_op.token].addr != address(0)) { 230 if (btoken_ == address(0)) {
File: contracts/tokenvault/LibBridgedToken.sol 21 _srcToken == address(0) || _srcChainId == 0 || _srcChainId == block.chainid
</details>File: contracts/verifiers/SgxVerifier.sol 107 if (instances[idx].addr == address(0)) revert SGX_INVALID_INSTANCE(); 124 if (automataDcapAttestation == address(0)) { 215 if (_instances[i] == address(0)) revert SGX_INVALID_INSTANCE(); 234 if (instance == address(0)) return false;
internal
functions not called by the contract should be removed to save deployment gasIssue Description
Unused internal functions within the contract contribute to unnecessary deployment gas costs.
Proposed Optimization
Remove internal
functions that are not called within the contract. If these functions are required by an interface, consider inheriting from that interface and using the override keyword to maintain compatibility.
Estimated Gas Savings
Removing unused internal
functions can result in significant deployment gas savings, depending on the number and complexity of the functions removed. The exact savings would vary based on the size and structure of the contract.
Attachments
Code Snippets
<details> <summary>Click to show 13 findings</summary>File: contracts/L1/TaikoToken.sol function _beforeTokenTransfer( address _from, address _to, uint256 _amount ) internal override(ERC20Upgradeable, ERC20SnapshotUpgradeable) { super._beforeTokenTransfer(_from, _to, _amount); } function _afterTokenTransfer( address _from, address _to, uint256 _amount ) internal override(ERC20Upgradeable, ERC20VotesUpgradeable) { super._afterTokenTransfer(_from, _to, _amount); }
File: contracts/L1/gov/TaikoGovernor.sol function _execute( uint256 _proposalId, address[] memory _targets, uint256[] memory _values, bytes[] memory _calldatas, bytes32 _descriptionHash ) internal override(GovernorUpgradeable, GovernorTimelockControlUpgradeable) { super._execute(_proposalId, _targets, _values, _calldatas, _descriptionHash); } function _cancel( address[] memory _targets, uint256[] memory _values, bytes[] memory _calldatas, bytes32 _descriptionHash ) internal override(GovernorUpgradeable, GovernorTimelockControlUpgradeable) returns (uint256) { return super._cancel(_targets, _values, _calldatas, _descriptionHash); } function _executor() internal view override(GovernorUpgradeable, GovernorTimelockControlUpgradeable) returns (address) { return super._executor(); }
File: contracts/common/AddressManager.sol function _authorizePause(address) internal pure override { revert AM_UNSUPPORTED(); }
File: contracts/signal/SignalService.sol function _authorizePause(address) internal pure override { revert SS_UNSUPPORTED(); }
File: contracts/tokenvault/BridgedERC20.sol function _mintToken(address _account, uint256 _amount) internal override { _mint(_account, _amount); } function _burnToken(address _from, uint256 _amount) internal override { _burn(_from, _amount); } function _beforeTokenTransfer( address _from, address _to, uint256 _amount ) internal override(ERC20Upgradeable, ERC20SnapshotUpgradeable) { if (_to == address(this)) revert BTOKEN_CANNOT_RECEIVE(); if (paused()) revert INVALID_PAUSE_STATUS(); super._beforeTokenTransfer(_from, _to, _amount); } function _afterTokenTransfer( address _from, address _to, uint256 _amount ) internal override(ERC20Upgradeable, ERC20VotesUpgradeable) { super._afterTokenTransfer(_from, _to, _amount); }
</details>File: contracts/tokenvault/adapters/USDCAdapter.sol function _mintToken(address _account, uint256 _amount) internal override { usdc.mint(_account, _amount); } function _burnToken(address _from, uint256 _amount) internal override { usdc.transferFrom(_from, address(this), _amount); usdc.burn(_amount); }
Calldata
is cheaper than memory
Issue Description
The contract functions are currently using memory
to load and process data. Memory is more expensive in terms of gas costs compared to calldata, especially when the data does not need to be modified within the function.
Proposed Optimization
To optimize the gas costs, we can use calldata
instead of memory for loading function inputs or data that does not require modification. Calldata is cheaper as it involves fewer operations and lower gas costs compared to memory.
Estimated Gas Savings
The exact gas savings will depend on the size of the data being processed and the frequency of function calls. However, using calldata can result in significant gas savings, especially for larger data and high-frequency function calls.
Attachments
Code Snippets
<details> <summary>Click to show 59 findings</summary>File: contracts/L1/gov/TaikoGovernor.sol 49 address[] memory _targets, 50 uint256[] memory _values, 51 bytes[] memory _calldatas, 70 address[] memory _targets, 71 uint256[] memory _values, 72 string[] memory _signatures, 73 bytes[] memory _calldatas,
File: contracts/L1/hooks/AssignmentHook.sol 65 bytes memory _data 63 TaikoData.Block memory _blk, 64 TaikoData.BlockMetadata memory _meta, 138 ProverAssignment memory _assignment,
File: contracts/L1/hooks/IHook.sol 14 TaikoData.Block memory _blk, 16 bytes memory _data 15 TaikoData.BlockMetadata memory _meta,
File: contracts/L1/libs/LibUtils.sol 54 TaikoData.Config memory _config, 25 TaikoData.Config memory _config,
File: contracts/L1/libs/LibVerifying.sol 49 TaikoData.Config memory _config,
File: contracts/L1/provers/Guardians.sol 54 address[] memory _newGuardians,
File: contracts/L2/TaikoL2EIP1559Configurable.sol 26 Config memory _newConfig,
File: contracts/automata-attestation/lib/PEMCertChainLib.sol 41 bytes memory pemChain, 75 bytes memory der,
File: contracts/automata-attestation/utils/SigVerifyLib.sol 114 bytes memory tbs, 98 bytes memory signature, 27 PublicKey memory publicKey, 116 bytes memory publicKey 25 bytes memory tbs, 97 bytes memory tbs, 115 bytes memory signature, 26 bytes memory signature, 81 bytes memory signature, 99 bytes memory publicKey 80 bytes memory tbs, 55 bytes memory tbs, 82 bytes memory publicKey 57 PublicKey memory publicKey, 56 bytes memory signature,
File: contracts/bridge/Bridge.sol 449 function hashMessage(Message memory _message) public pure returns (bytes32) {
File: contracts/bridge/IBridge.sol 155 function hashMessage(Message memory _message) external pure returns (bytes32);
File: contracts/team/TimelockTokenPool.sol 168 function withdraw(address _to, bytes memory _sig) external { 135 function grant(address _recipient, Grant memory _grant) external onlyOwner {
File: contracts/tokenvault/BridgedERC1155.sol 43 string memory _symbol, 85 uint256[] memory _tokenIds, 86 uint256[] memory _amounts 44 string memory _name
File: contracts/tokenvault/BridgedERC20.sol 59 string memory _name 58 string memory _symbol,
File: contracts/tokenvault/BridgedERC721.sol 37 string memory _name 36 string memory _symbol,
File: contracts/tokenvault/ERC1155Vault.sol 39 function sendToken(BridgeTransferOp memory _op)
File: contracts/tokenvault/ERC721Vault.sol 26 function sendToken(BridgeTransferOp memory _op)
</details>File: contracts/verifiers/SgxVerifier.sol 172 TaikoData.Transition memory _tran,
constructor
should be declared immutable
Issue Description
The contracts is currently using state variables that are only set in the constructor
but are not declared as immutable. This results in unnecessary gas costs for setting and accessing these variables.
Proposed Optimization
To optimize the gas costs, we can declare state variables that are only set in the constructor as immutable. This will avoid the gas cost of setting the variable in the constructor (Gsset - 20000 gas
) and replace the first access in each transaction (Gcoldsload - 2100 gas) and each access thereafter (Gwarmaccess - 100 gas) with a PUSH32 (3 gas).
Estimated Gas Savings
Gsset - 20000 gas
Attachments
File: contracts/automata-attestation/AutomataDcapV3Attestation.sol 52 address public owner;
File: contracts/automata-attestation/utils/SigVerifyLib.sol 18 address private ES256VERIFIER;
Stack variable
used as a cheaper cache for a state variable is only used onceIssue Description
If the variable is only accessed once, it's cheaper to use the state variable directly that one time, and save the 3 gas the extra stack assignment would spend.
Attachments
File: contracts/L1/provers/Guardians.sol 133 for (uint256 i; i < guardiansLength; ++i) {
File: contracts/team/TimelockTokenPool.sol 152 uint128 amountVoided = _voidGrant(r.grant);
constructors
payableIssue Description
Payable functions cost less gas to execute, since the compiler does not have to add extra checks to ensure that a payment wasn't provided. A constructor can safely be marked as payable, since only the deployer would be able to pass funds, and the project itself would not pass any funds.
Proposed Optimization\
// SPDX-License-Identifier: UNLICENSED pragma solidity 0.8.20; contract A {} contract B { constructor() payable {} }
Estimated Gas Savings
Making the constructor payable saved 200 gas on deployment. This is because non-payable functions have an implicit require(msg.value == 0)
inserted in them. Additionally, fewer bytecode at deploy time mean less gas cost due to smaller calldata.
Attachments
File: contracts/automata-attestation/AutomataDcapV3Attestation.sol 54 constructor(address sigVerifyLibAddr, address pemCertLibAddr) { 55 sigVerifyLib = ISigVerifyLib(sigVerifyLibAddr); 56 pemCertLib = PEMCertChainLib(pemCertLibAddr); 57 owner = msg.sender; 58 }
File: contracts/automata-attestation/utils/SigVerifyLib.sol 20 constructor(address es256Verifier) { 21 ES256VERIFIER = es256Verifier; 22 }
File: contracts/common/EssentialContract.sol 64 constructor() { 65 _disableInitializers(); 66 }
address/ID
mappings can be combined into a single mapping of an address/ID
to a struct, where appropriateIssue Description
Saves a storage slot for the mapping. Depending on the circumstances and sizes of types, can avoid a Gsset 20000 gas
per mapping combined. Reads and subsequent writes can also be cheaper when a function requires both values and they both fit in the same storage slot. Finally, if both fields are accessed in the same function, can save ~42
gas per access due to not having to recalculate the key's keccak256 hash Gkeccak256 - 30 gas
and that calculation's associated stack operations.
Proposed Optimization
To optimize the gas costs, we can combine multiple address/ID mappings into a single mapping of an address/ID to a struct, where appropriate. This can save a storage slot for the mapping and avoid a Gsset (20000 gas) per mapping combined. Reads and subsequent writes can also be cheaper when a function requires both values and they both fit in the same storage slot. Finally, if both fields are accessed in the same function, this can save ~42 gas per access due to not having to recalculate the key's keccak256 hash (Gkeccak256 - 30 gas) and that calculation's associated stack operations.
Attachments
File: contracts/L1/TaikoData.sol 181 mapping(uint64 blockId_mod_blockRingBufferSize => Block blk) blocks; 183 mapping(uint64 blockId => mapping(bytes32 parentHash => uint32 transitionId)) transitionIds;
File: contracts/bridge/Bridge.sol 35 mapping(bytes32 msgHash => Status status) public messageStatus; 46 mapping(bytes32 msgHash => ProofReceipt receipt) public proofReceipt; 42 mapping(address addr => bool banned) public addressBanned;
File: contracts/team/airdrop/ERC20Airdrop2.sol 22 mapping(address addr => uint256 amountClaimed) public claimedAmount; 25 mapping(address addr => uint256 amountWithdrawn) public withdrawnAmount;
memory
for structs/arrays saves gasWhen fetching data from a storage location, assigning the data to a memory variable causes all fields of the struct/array to be read from storage, which incurs a Gcoldsload (2100 gas) for each field of the struct/array. If the fields are read from the new memory variable, they incur an additional MLOAD rather than a cheap stack read. Instead of declearing the variable with the memory keyword, declaring the variable with the storage keyword and caching any fields that need to be re-read in stack variables, will be much cheaper, only incuring the Gcoldsload for the fields actually read. The only time it makes sense to read the whole struct/array into a memory variable, is if the full struct/array is being returned by the function, is being passed to a function that requires memory, or if the array/struct is being read from another memory array/struct
File: contracts/automata-attestation/AutomataDcapV3Attestation.sol 180 EnclaveIdStruct.EnclaveId memory enclaveId = qeIdentity;
File: contracts/tokenvault/ERC20Vault.sol 171 CanonicalERC20 memory ctoken = bridgedToCanonical[btokenOld_];
Issue Description
Using the scratch space for event arguments (two words or fewer) will save gas over needing Solidity's full abi memory expansion used for emitting normally.
Attachments
Code Snippets
<details> <summary>Click to show 55 findings</summary>File: contracts/L1/hooks/AssignmentHook.sol 129 emit BlockAssigned(_blk.assignedProver, _meta, assignment);
File: contracts/L1/libs/LibDepositing.sol 50 emit EthDeposited( 51 TaikoData.EthDeposit({ 52 recipient: recipient_, 53 amount: uint96(msg.value), 54 id: _state.slotA.numEthDeposits 55 }) 56 );
File: contracts/L1/libs/LibProposing.sol 166 emit BlobCached(meta_.blobHash); 273 emit BlockProposed({ 274 blockId: blk.blockId, 275 assignedProver: blk.assignedProver, 276 livenessBond: _config.livenessBond, 277 meta: meta_, 278 depositsProcessed: deposits_ 279 });
File: contracts/L1/libs/LibProving.sol 80 emit ProvingPaused(_pause); 209 emit TransitionProved({ 210 blockId: blk.blockId, 211 tran: _tran, 212 prover: msg.sender, 213 validityBond: tier.validityBond, 214 tier: _proof.tier 215 }); 230 emit TransitionProved({ 231 blockId: blk.blockId, 232 tran: _tran, 233 prover: msg.sender, 234 validityBond: 0, 235 tier: _proof.tier 236 }); 254 emit TransitionContested({ 255 blockId: blk.blockId, 256 tran: _tran, 257 contester: msg.sender, 258 contestBond: tier.contestBond, 259 tier: _proof.tier 260 });
File: contracts/L1/libs/LibVerifying.sol 73 emit BlockVerified({ 74 blockId: 0, 75 assignedProver: address(0), 76 prover: address(0), 77 blockHash: _genesisBlockHash, 78 stateRoot: 0, 79 tier: 0, 80 contestations: 0 81 }); 198 emit BlockVerified({ 199 blockId: blockId, 200 assignedProver: blk.assignedProver, 201 prover: ts.prover, 202 blockHash: blockHash, 203 stateRoot: stateRoot, 204 tier: ts.tier, 205 contestations: ts.contestations 206 });
File: contracts/L1/provers/GuardianProver.sol 54 emit GuardianApproval(msg.sender, _meta.id, _tran.blockHash, approved_);
File: contracts/L1/provers/Guardians.sol 95 emit GuardiansUpdated(version, _newGuardians); 121 emit Approved(_operationId, _approval, approved_);
File: contracts/L2/CrossChainOwned.sol 53 emit TransactionExecuted(nextTxId++, bytes4(txdata));
File: contracts/L2/TaikoL2.sol 157 emit Anchored(blockhash(parentId), gasExcess);
File: contracts/L2/TaikoL2EIP1559Configurable.sol 39 emit ConfigAndExcessChanged(_newConfig, _newGasExcess);
File: contracts/bridge/Bridge.sol 93 emit MessageSuspended(msgHash, _suspend); 111 emit AddressBanned(_addr, _ban); 151 emit MessageSent(msgHash_, message_); 208 emit MessageRecalled(msgHash); 210 emit MessageReceived(msgHash, _message, true); 301 emit MessageExecuted(msgHash); 303 emit MessageReceived(msgHash, _message, false); 336 emit MessageRetried(msgHash); 519 emit MessageStatusChanged(_msgHash, _status);
File: contracts/common/AddressManager.sol 50 emit AddressSet(_chainId, _name, _newAddress, oldAddress);
File: contracts/common/EssentialContract.sol 71 emit Paused(msg.sender); 80 emit Unpaused(msg.sender);
File: contracts/signal/SignalService.sol 59 emit Authorized(_addr, _authorize); 250 emit ChainDataSynced(_chainId, _blockId, _kind, _chainData, signal_); 268 emit SignalSent(_app, _signal, slot_, _value);
File: contracts/team/TimelockTokenPool.sol 143 emit Granted(_recipient, _grant); 157 emit Voided(_recipient, amountVoided); 222 emit Withdrawn(_recipient, _to, amountToWithdraw, costToWithdraw);
File: contracts/team/airdrop/ERC20Airdrop2.sol 93 emit Withdrawn(user, amount);
File: contracts/team/airdrop/MerkleClaimable.sol 74 emit Claimed(hash);
File: contracts/tokenvault/BridgedERC20Base.sol 51 emit MigrationStatusChanged(_migratingAddress, _migratingInbound); 63 emit MigratedTo(migratingAddress, _account, _amount); 80 emit MigratedTo(migratingAddress, _account, _amount);
File: contracts/tokenvault/ERC1155Vault.sol 80 emit TokenSent({ 81 msgHash: msgHash, 82 from: message_.srcOwner, 83 to: _op.to, 84 destChainId: message_.destChainId, 85 ctoken: ctoken.addr, 86 token: _op.token, 87 tokenIds: _op.tokenIds, 88 amounts: _op.amounts 89 }); 114 emit TokenReceived({ 115 msgHash: ctx.msgHash, 116 from: from, 117 to: to, 118 srcChainId: ctx.srcChainId, 119 ctoken: ctoken.addr, 120 token: token, 121 tokenIds: tokenIds, 122 amounts: amounts 123 }); 149 emit TokenReleased({ 150 msgHash: msgHash, 151 from: message.srcOwner, 152 ctoken: ctoken.addr, 153 token: token, 154 tokenIds: tokenIds, 155 amounts: amounts 156 }); 314 emit BridgedTokenDeployed({ 315 chainId: _ctoken.chainId, 316 ctoken: _ctoken.addr, 317 btoken: btoken_, 318 ctokenSymbol: _ctoken.symbol, 319 ctokenName: _ctoken.name 320 });
File: contracts/tokenvault/ERC20Vault.sol 191 emit BridgedTokenChanged({ 192 srcChainId: _ctoken.chainId, 193 ctoken: _ctoken.addr, 194 btokenOld: btokenOld_, 195 btokenNew: _btokenNew, 196 ctokenSymbol: _ctoken.symbol, 197 ctokenName: _ctoken.name, 198 ctokenDecimal: _ctoken.decimals 199 }); 241 emit TokenSent({ 242 msgHash: msgHash, 243 from: message_.srcOwner, 244 to: _op.to, 245 destChainId: _op.destChainId, 246 ctoken: ctoken.addr, 247 token: _op.token, 248 amount: balanceChange 249 }); 273 emit TokenReceived({ 274 msgHash: ctx.msgHash, 275 from: from, 276 to: to, 277 srcChainId: ctx.srcChainId, 278 ctoken: ctoken.addr, 279 token: token, 280 amount: amount 281 }); 306 emit TokenReleased({ 307 msgHash: _msgHash, 308 from: _message.srcOwner, 309 ctoken: ctoken.addr, 310 token: token, 311 amount: amount 312 }); 425 emit BridgedTokenDeployed({ 426 srcChainId: ctoken.chainId, 427 ctoken: ctoken.addr, 428 btoken: btoken, 429 ctokenSymbol: ctoken.symbol, 430 ctokenName: ctoken.name, 431 ctokenDecimal: ctoken.decimals 432 });
File: contracts/tokenvault/ERC721Vault.sol 64 emit TokenSent({ 65 msgHash: msgHash, 66 from: message_.srcOwner, 67 to: _op.to, 68 destChainId: message_.destChainId, 69 ctoken: ctoken.addr, 70 token: _op.token, 71 tokenIds: _op.tokenIds, 72 amounts: _op.amounts 73 }); 97 emit TokenReceived({ 98 msgHash: ctx.msgHash, 99 from: from, 100 to: to, 101 srcChainId: ctx.srcChainId, 102 ctoken: ctoken.addr, 103 token: token, 104 tokenIds: tokenIds, 105 amounts: new uint256[](tokenIds.length) 106 }); 131 emit TokenReleased({ 132 msgHash: _msgHash, 133 from: _message.srcOwner, 134 ctoken: ctoken.addr, 135 token: token, 136 tokenIds: tokenIds, 137 amounts: new uint256[](tokenIds.length) 138 }); 250 emit BridgedTokenDeployed({ 251 chainId: _ctoken.chainId, 252 ctoken: _ctoken.addr, 253 btoken: btoken_, 254 ctokenSymbol: _ctoken.symbol, 255 ctokenName: _ctoken.name 256 });
</details>File: contracts/verifiers/SgxVerifier.sol 109 emit InstanceDeleted(idx, instances[idx].addr); 220 emit InstanceAdded(nextInstanceId, _instances[i], address(0), validSince); 230 emit InstanceAdded(id, newInstance, oldInstance, block.timestamp);
event
fieldsIssue Description
block.timestamp
and block.number
are added to event information by default so adding them manually wastes gas.
Attachments
File: contracts/verifiers/SgxVerifier.sol 230 emit InstanceAdded(id, newInstance, oldInstance, block.timestamp);
uints
/ints
smaller than 32 bytes (256 bits) incurs overheadIssue Description
Usage of uints/ints smaller than 32 bytes (256 bits) in the contract can incur overhead and increase gas usage. This is because the EVM operates on 32 bytes at a time, and if the element is smaller than that, the EVM must use more operations to reduce the size of the element from 32 bytes to the desired size.
Proposed Optimization
To optimize the gas usage, use a larger size (uint256 or int256) and then downcast to the desired size where needed. This can save gas as each operation involving a smaller size (e.g. uint8) can cost an extra 22-28 gas (depending on whether the other operand is also a variable of the same type) compared to operations involving uint256 or int256.
Estimated Gas Savings
The estimated gas savings will depend on the number of operations involving smaller uints/ints in the contract. However, using a larger size and downcasting where needed can save an estimated 22-28
gas per operation.
Attachments
Code Snippets
<details> <summary>Click to show 16 findings</summary>File: contracts/L1/TaikoL1.sol 150 uint64 slot;
File: contracts/L1/libs/LibDepositing.sol 83 uint96 fee = uint96(_config.ethDepositMaxFee.min(block.basefee * _config.ethDepositGas)); 93 uint96 _fee = deposits_[i].amount > fee ? fee : deposits_[i].amount;
File: contracts/L1/libs/LibUtils.sol 42 uint32 tid = getTransitionId(_state, blk, slot, _parentHash);
File: contracts/L1/libs/LibVerifying.sol 107 uint32 tid = blk.verifiedTransitionId; 117 uint64 numBlocksVerified; 234 (uint64 lastSyncedBlock,) = signalService.getSyncedChainData(
File: contracts/L2/CrossChainOwned.sol 42 (uint64 txId, bytes memory txdata) = abi.decode(_data, (uint64, bytes));
File: contracts/automata-attestation/lib/QuoteV3Auth/V3Parser.sol 106 uint32 totalQuoteSize = 48 // header
File: contracts/automata-attestation/utils/BytesUtils.sol 332 uint8 decoded;
File: contracts/automata-attestation/utils/X509DateUtils.sol 15 uint8 offset;
File: contracts/bridge/Bridge.sol 89 uint64 _timestamp = _suspend ? type(uint64).max : uint64(block.timestamp);
File: contracts/team/TimelockTokenPool.sol 197 uint128 _amountUnlocked = amountUnlocked / 1e18; // divide first 226 uint128 amountOwned = _getAmountOwned(_grant);
</details>File: contracts/thirdparty/optimism/trie/MerkleTrie.sol 134 uint8 branchKey = uint8(key[currentKeyIndex]); 141 uint8 prefix = uint8(path[0]);
Issue Description
Emitting an event inside a loop performs a LOG op N times, where N is the loop length. Consider refactoring the code to emit the event only once at the end of loop. Gas savings should be multiplied by the average loop length.
Attachments
File: contracts/bridge/Bridge.sol 93 emit MessageSuspended(msgHash, _suspend);
File: contracts/verifiers/SgxVerifier.sol 109 emit InstanceDeleted(idx, instances[idx].addr); 220 emit InstanceAdded(nextInstanceId, _instances[i], address(0), validSince);
Issue Description
Private functions that are used only once in the contract can consume more gas than necessary. This is because the EVM must perform a JUMP operation to call the function, which incurs a gas cost.
Proposed Optimization
To optimize the gas usage, private functions that are used only once can be inlined. This means that the code of the function is copied directly into the calling function, eliminating the need for a JUMP operation.
Estimated Gas Savings
The estimated gas savings will depend on the size and complexity of the private function being inlined. However, inlining a private function can save an estimated 20-40 gas per call.
Attachments
Code Snippets
<details> <summary>Click to show 41 findings</summary>File: contracts/L1/hooks/AssignmentHook.sol 164 function _getProverFee(
File: contracts/L1/libs/LibProposing.sol 299 function _isProposerPermitted(
File: contracts/L1/libs/LibProving.sol 269 function _createTransition( 270 TaikoData.State storage _state, 271 TaikoData.Block storage _blk, 272 TaikoData.Transition memory _tran, 273 uint64 slot 274 ) 275 private 347 } 350 function _overrideWithHigherProof( 351 TaikoData.TransitionState storage _ts, 352 TaikoData.Transition memory _tran, 353 TaikoData.TierProof memory _proof, 354 ITierProvider.Tier memory _tier, 355 IERC20 _tko, 356 bool _sameTransition 357 ) 358 private 359 { 401 function _checkProverPermission( 402 TaikoData.State storage _state, 403 TaikoData.Block storage _blk, 404 TaikoData.TransitionState storage _ts, 405 uint32 _tid, 406 ITierProvider.Tier memory _tier 407 ) 408 private 409 view 410 {
File: contracts/L1/libs/LibVerifying.sol 224 function _syncChainData( 225 TaikoData.Config memory _config, 226 IAddressResolver _resolver, 227 uint64 _lastVerifiedBlockId, 228 bytes32 _stateRoot 229 ) 230 private 231 { 245 function _isConfigValid(TaikoData.Config memory _config) private view returns (bool) {
File: contracts/L2/Lib1559Math.sol 33 function _ethQty( 34 uint256 _gasExcess, 35 uint256 _adjustmentFactor 36 ) 37 private 38 pure 39 returns (uint256) 40 {
File: contracts/automata-attestation/AutomataDcapV3Attestation.sol 162 function _verify(bytes calldata quote) private view returns (bool, bytes memory) { 175 function _verifyQEReportWithIdentity(V3Struct.EnclaveReport memory quoteEnclaveReport) 176 private 177 view 178 returns (bool, EnclaveIdStruct.EnclaveIdStatus status) 179 { 206 function _checkTcbLevels( 207 IPEMCertChainLib.PCKCertificateField memory pck, 208 TCBInfoStruct.TCBInfo memory tcb 209 ) 210 private 211 pure 212 returns (bool, TCBInfoStruct.TCBStatus status) 213 { 229 function _isCpuSvnHigherOrGreater( 230 uint256[] memory pckCpuSvns, 231 uint8[] memory tcbCpuSvns 232 ) 233 private 234 pure 235 returns (bool) 236 { 248 function _verifyCertChain(IPEMCertChainLib.ECSha256Certificate[] memory certs) 249 private 250 view 251 returns (bool) 303 function _enclaveReportSigVerification( 304 bytes memory pckCertPubKey, 305 bytes memory signedQuoteData, 306 V3Struct.ECDSAQuoteV3AuthData memory authDataV3, 307 V3Struct.EnclaveReport memory qeEnclaveReport 308 ) 309 private 310 view 311 returns (bool) 312 {
File: contracts/automata-attestation/lib/PEMCertChainLib.sol 216 function _removeHeadersAndFooters(string memory pemData) 217 private 218 pure 219 returns (bool success, bytes memory extracted, uint256 endIndex) 220 { 269 function _findPckTcbInfo( 270 bytes memory der, 271 uint256 tbsPtr, 272 uint256 tbsParentPtr 273 ) 274 private 275 pure 339 } 341 function _findTcb( 342 bytes memory der, 343 uint256 oidPtr 344 ) 345 private 346 pure 347 returns (bool success, uint256 pcesvn, uint256[] memory cpusvns) 348 {
File: contracts/automata-attestation/lib/QuoteV3Auth/V3Parser.sol 165 function parseAndVerifyHeader(bytes memory rawHeader) 166 private 167 pure 168 returns (bool success, V3Struct.Header memory header) 169 { 203 function parseAuthDataAndVerifyCertType( 204 bytes memory rawAuthData, 205 address pemCertLibAddr 206 ) 207 private 208 pure 209 returns (bool success, V3Struct.ECDSAQuoteV3AuthData memory authDataV3) 210 {
File: contracts/automata-attestation/utils/BytesUtils.sol 272 function memcpy(uint256 dest, uint256 src, uint256 len) private pure {
File: contracts/bridge/Bridge.sol 555 function _loadContext() private view returns (Context memory) {
File: contracts/signal/SignalService.sol 271 function _cacheChainData( 272 HopProof memory _hop, 273 uint64 _chainId, 274 uint64 _blockId, 275 bytes32 _signalRoot, 276 bool _isFullProof, 277 bool _isLastHop 278 ) 279 private 280 {
File: contracts/team/TimelockTokenPool.sol 225 function _voidGrant(Grant storage _grant) private returns (uint128 amountVoided) { 239 function _getAmountUnlocked(Grant memory _grant) private view returns (uint128) { 267 function _validateGrant(Grant memory _grant) private pure {
File: contracts/thirdparty/optimism/rlp/RLPWriter.sol 32 function _writeLength(uint256 _len, uint256 _offset) private pure returns (bytes memory out_) { 55 function _toBinary(uint256 _x) private pure returns (bytes memory out_) {
File: contracts/thirdparty/optimism/trie/MerkleTrie.sol 205 function _parseProof(bytes[] memory _proof) private pure returns (TrieNode[] memory proof_) { 227 function _getNodePath(TrieNode memory _node) private pure returns (bytes memory nibbles_) { 235 function _getSharedNibbleLength( 236 bytes memory _a, 237 bytes memory _b 238 ) 239 private 240 pure 241 returns (uint256 shared_) 242 {
File: contracts/tokenvault/ERC1155Vault.sol 240 function _handleMessage( 241 address _user, 242 BridgeTransferOp memory _op 243 ) 244 private 245 returns (bytes memory msgData_, CanonicalNFT memory ctoken_) 246 { 288 function _getOrDeployBridgedToken(CanonicalNFT memory _ctoken) 289 private 290 returns (address btoken_) 291 { 303 function _deployBridgedToken(CanonicalNFT memory _ctoken) private returns (address btoken_) {
File: contracts/tokenvault/ERC20Vault.sol 348 function _handleMessage( 349 address _user, 350 address _token, 351 address _to, 352 uint256 _amount 353 ) 354 private 355 returns (bytes memory msgData_, CanonicalERC20 memory ctoken_, uint256 balanceChange_) 356 { 391 function _getOrDeployBridgedToken(CanonicalERC20 memory ctoken) 392 private 393 returns (address btoken) 394 { 407 function _deployBridgedToken(CanonicalERC20 memory ctoken) private returns (address btoken) {
File: contracts/tokenvault/ERC721Vault.sol 187 function _handleMessage( 188 address _user, 189 BridgeTransferOp memory _op 190 ) 191 private 192 returns (bytes memory msgData_, CanonicalNFT memory ctoken_) 193 { 224 function _getOrDeployBridgedToken(CanonicalNFT memory _ctoken) 225 private 226 returns (address btoken_) 227 { 240 function _deployBridgedToken(CanonicalNFT memory _ctoken) private returns (address btoken_) {
</details>File: contracts/verifiers/SgxVerifier.sol 226 function _replaceInstance(uint256 id, address oldInstance, address newInstance) private { 233 function _isInstanceValid(uint256 id, address instance) private view returns (bool) {
SUB
or XOR
instead of ISZERO(EQ())
to check for inequalityIssue Description
When using inline assembly to compare the equality of two values (e.g if owner is the same as caller()), It is sometimes more efficient to do
Proposed Optimization\
if sub(caller, sload(owner.slot)) { revert(0x00, 0x00) } over doing this if eq(caller, sload(owner.slot)) { revert(0x00, 0x00) }
xor can accomplish the same thing, but be aware that xor will consider a value with all the bits flipped to be equal also, so make sure that this cannot become an attack vector.
Attachments
File: contracts/thirdparty/optimism/Bytes.sol 33 switch iszero(_length) 53 let mc := add(add(tempBytes, lengthmod), mul(0x20, iszero(lengthmod))) 59 let cc := add(add(add(_bytes, lengthmod), mul(0x20, iszero(lengthmod))), _start)
Issue Description
Solidity writes to memory by expanding it, which can be inefficient for operations on data that are 96 bytes in size or less. This is because Solidity reserves the first 64 bytes of memory as scratch space, the next 32 bytes of memory for the free memory pointer, and the next 32 bytes of memory as the zero slot for uninitialized dynamic memory data.
Proposed Optimization
To optimize memory operations on data that are 96 bytes in size or less, we can utilize inline assembly to perform operations on the scratch space and the free memory pointer space. This can help avoid unnecessary memory expansion and save gas.
Estimated Gas Savings
Using assembly to calculate hashes can save 80 gas per instance
Attachments
Code Snippets
<details> <summary>Click to show 28 findings</summary>File: contracts/L1/hooks/AssignmentHook.sol 146 return keccak256(
File: contracts/L1/libs/LibProposing.sol 126 depositsHash: keccak256(abi.encode(deposits_)), 189 meta_.blobHash = keccak256(_txList); 204 meta_.difficulty = keccak256(abi.encodePacked(block.prevrandao, b.numBlocks, block.number)); 213 metaHash: keccak256(abi.encode(meta_)),
File: contracts/L1/libs/LibProving.sol 20 bytes32 public constant RETURN_LIVENESS_BOND = keccak256("RETURN_LIVENESS_BOND"); 121 if (blk.blockId != _meta.id || blk.metaHash != keccak256(abi.encode(_meta))) {
File: contracts/L1/provers/GuardianProver.sol 46 bytes32 hash = keccak256(abi.encode(_meta, _tran));
File: contracts/automata-attestation/AutomataDcapV3Attestation.sol 292 bytes32 issuerPubKeyHash = keccak256(issuer.pubKey);
File: contracts/automata-attestation/utils/BytesUtils.sol 372 return keccak256(a) == keccak256(b); 372 return keccak256(a) == keccak256(b);
File: contracts/bridge/Bridge.sol 450 return keccak256(abi.encode("TAIKO_MESSAGE", _message));
File: contracts/signal/LibSignals.sol 8 bytes32 public constant STATE_ROOT = keccak256("STATE_ROOT"); 11 bytes32 public constant SIGNAL_ROOT = keccak256("SIGNAL_ROOT");
File: contracts/signal/SignalService.sol 186 return keccak256(abi.encode(_chainId, _kind, _blockId)); 203 return keccak256(abi.encodePacked("SIGNAL", _chainId, _app, _signal));
File: contracts/team/TimelockTokenPool.sol 170 bytes32 hash = keccak256(abi.encodePacked("Withdraw unlocked Taiko token to: ", _to));
File: contracts/team/airdrop/MerkleClaimable.sol 68 bytes32 hash = keccak256(abi.encode("CLAIM_TAIKO_AIRDROP", data));
File: contracts/thirdparty/optimism/Bytes.sol 150 return keccak256(_bytes) == keccak256(_other); 150 return keccak256(_bytes) == keccak256(_other);
File: contracts/thirdparty/optimism/trie/MerkleTrie.sol 94 Bytes.equal(abi.encodePacked(keccak256(currentNode.encoded)), currentNodeID), 100 Bytes.equal(abi.encodePacked(keccak256(currentNode.encoded)), currentNodeID),
File: contracts/thirdparty/optimism/trie/SecureMerkleTrie.sol 55 hash_ = abi.encodePacked(keccak256(_key));
File: contracts/tokenvault/ERC20Vault.sol 176 || keccak256(bytes(ctoken.symbol)) != keccak256(bytes(_ctoken.symbol)) 176 || keccak256(bytes(ctoken.symbol)) != keccak256(bytes(_ctoken.symbol)) 177 || keccak256(bytes(ctoken.name)) != keccak256(bytes(_ctoken.name)) 177 || keccak256(bytes(ctoken.name)) != keccak256(bytes(_ctoken.name))
</details>File: contracts/verifiers/SgxVerifier.sol 182 return keccak256(
Issue Description
Due to how constant variables are implemented (replacements at compile-time), an expression assigned to a constant variable is recomputed each time that the variable is used, which wastes some gas.
Attachments
File: contracts/L1/libs/LibProposing.sol 21 uint256 public constant MAX_BYTES_PER_BLOB = 4096 * 32;
File: contracts/thirdparty/optimism/trie/MerkleTrie.sol 24 uint256 internal constant BRANCH_NODE_LENGTH = TREE_RADIX + 1;
Issue Description
Using named arguments for struct means that the compiler needs to organize the fields in memory before doing the assignment, which wastes gas. Set each field directly in storage (use dot-notation), or use the unnamed version of the constructor.
Proposed Optimization
To optimize gas usage, set each field directly in storage using dot-notation, or use the unnamed version of the constructor. This will avoid the need for the compiler to organize the fields in memory before doing the assignment, resulting in gas savings.
Attachments
Code Snippets
<details> <summary>Click to show 15 findings</summary>File: contracts/automata-attestation/lib/QuoteV3Auth/V3Parser.sol 54 v3ParsedQuote = V3Struct.ParsedV3QuoteStruct({ 190 header = V3Struct.Header({
File: contracts/bridge/Bridge.sol 243 proofReceipt[msgHash] = ProofReceipt({ 342 return ISignalService(resolve("signal_service", false)).isSignalSent({
File: contracts/L1/libs/LibProposing.sol 121 meta_ = TaikoData.BlockMetadata({ 212 TaikoData.Block memory blk = TaikoData.Block({
File: contracts/L1/tiers/DevnetTierProvider.sol 22 return ITierProvider.Tier({ 33 return ITierProvider.Tier({
File: contracts/L1/tiers/MainnetTierProvider.sol 22 return ITierProvider.Tier({ 33 return ITierProvider.Tier({ 44 return ITierProvider.Tier({
File: contracts/thirdparty/optimism/rlp/RLPReader.sol 47 out_ = RLPItem({ length: _in.length, ptr: ptr }); 84 out_[itemCount] = RLPItem({
File: contracts/thirdparty/optimism/trie/MerkleTrie.sol 209 proof_[i] = TrieNode({ encoded: _proof[i], decoded: RLPReader.readList(_proof[i]) });
File: contracts/tokenvault/ERC20Vault.sol 365 ctoken_ = CanonicalERC20({
Issue Description
Using a double if statement instead of logical AND (&&) can provide similar short-circuiting behavior whereas double if is slightly more efficient.
Attachments
Code Snippets
<details> <summary>Click to show 23 findings</summary>File: contracts/L1/hooks/AssignmentHook.sol 82 block.timestamp > assignment.expiry 83 || assignment.metaHash != 0 && _blk.metaHash != assignment.metaHash 120 if (input.tip != 0 && block.coinbase != address(0)) {
File: contracts/L1/libs/LibProposing.sol 108 if (params.parentMetaHash != 0 && parentMetaHash != params.parentMetaHash) { 164 if (_config.blobReuseEnabled && params.cacheBlobForReuse) { 310 if (proposerOne != address(0) && msg.sender != proposerOne) {
File: contracts/L1/libs/LibProving.sol 419 if (_tid == 1 && _ts.tier == 0 && inProvingWindow) {
File: contracts/L2/TaikoL2.sol 117 _l1BlockHash == 0 || _l1StateRoot == 0 || _l1BlockId == 0 118 || (block.number != 1 && _parentGasUsed == 0) 141 if (!skipFeeCheck() && block.basefee != basefee) { 275 if (lastSyncedBlock > 0 && _l1BlockId > lastSyncedBlock) {
File: contracts/automata-attestation/AutomataDcapV3Attestation.sol 220 if (pceSvnIsHigherOrGreater && cpuSvnsAreHigherOrGreater) {
File: contracts/automata-attestation/lib/PEMCertChainLib.sol 134 if ( 135 (notBeforeTag != 0x17 && notBeforeTag == 0x18)
File: contracts/bridge/Bridge.sol 250 if (invocationDelay != 0 && msg.sender != proofReceipt[msgHash].preferredExecutor) { 260 if (_message.gasLimit == 0 && msg.sender != _message.destOwner) { 439 } else if (block.chainid >= 32_300 && block.chainid <= 32_400) { 490 if ( 491 _message.data.length >= 4 // msg can be empty 492 && bytes4(_message.data) != IMessageInvocable.onMessageInvocation.selector 493 && _message.to.isContract()
File: contracts/common/AddressResolver.sol 85 if (!_allowZeroAddress && addr_ == address(0)) {
File: contracts/common/EssentialContract.sol 42 if (msg.sender != owner() && msg.sender != resolve(_name, true)) revert RESOLVER_DENIED();
File: contracts/signal/SignalService.sol 285 if (cacheStateRoot && _isFullProof && !_isLastHop) { 293 if (cacheSignalRoot && (_isFullProof || !_isLastHop)) {
File: contracts/team/TimelockTokenPool.sol 277 if (_cliff > 0 && _cliff <= _start) revert INVALID_GRANT();
File: contracts/thirdparty/optimism/rlp/RLPWriter.sol 14 if (_in.length == 1 && uint8(_in[0]) < 128) {
File: contracts/tokenvault/BridgedERC20.sol 38 if (msg.sender != owner() && msg.sender != snapshooter) {
</details>File: contracts/tokenvault/BridgedERC20Base.sol 45 if (_migratingAddress == migratingAddress && _migratingInbound == migratingInbound) {
low-level
call's return data by using assemblyIssue Description
In Solidity, when making a low-level call using the call() function, the second return value (i.e., the return data) gets copied to memory even if it is not assigned to a variable. This can result in unnecessary gas usage.
Proposed Optimization
To optimize gas usage, use assembly to prevent the return data from being copied to memory. This can save 159
gas.
Estimated Gas Savings
The estimated gas savings for this optimization is 159 gas.
Attachments
File: contracts/L2/CrossChainOwned.sol 50 (bool success,) = address(this).call(txdata);
require()
/revert()
checks should be refactored to a modifier or functionSaves deployment costs
Attachments
File: contracts/automata-attestation/utils/Asn1Decode.sol 142 require(der[ptr.ixs()] == 0x02, "Not type INTEGER"); 143 require(der[ptr.ixf()] & 0x80 == 0, "Not positive");
File: contracts/automata-attestation/utils/SigVerifyLib.sol 50 revert("Unsupported algorithm");
State variable
read in a loopIssue Description
The state variable should be cached in and read from a local variable, or accumulated in a local variable then written to storage once outside of the loop, rather than reading/updating
it on every iteration of the loop, which will replace each Gwarmaccess
(100 gas) with a much cheaper stack read.
Proposed Optimization
To optimize gas usage, cache the state variable in a local variable and read from it within the loop, or accumulate the values in a local variable and write to storage once outside of the loop. This will replace each Gwarmaccess operation with a much cheaper stack read.
Estimated Gas Savings
The estimated gas savings for this optimization will depend on the number of iterations in the loop. However, each Gwarmaccess
operation that is replaced with a stack read can result in a gas savings of 95
gas.
Attachments
Code Snippets
<details> <summary>Click to show 15 findings</summary>File: contracts/L1/provers/Guardians.sol 74 for (uint256 i; i < guardians.length; ++i) { 84 if (guardianIds[guardian] != 0) revert INVALID_GUARDIAN_SET(); 135 if (count == minGuardians) return true;
File: contracts/automata-attestation/AutomataDcapV3Attestation.sol 81 if (_serialNumIsRevoked[index][serialNumBatch[i]]) { 96 if (!_serialNumIsRevoked[index][serialNumBatch[i]]) { 240 for (uint256 i; i < CPUSVN_LENGTH; ++i) { 268 certRevoked = _serialNumIsRevoked[uint256(IPEMCertChainLib.CRL.ROOT)][certs[i] 424 (certDecodedSuccessfully, parsedQuoteCerts[i]) = pemCertLib.decodeCert(
File: contracts/automata-attestation/lib/PEMCertChainLib.sol 354 for (uint256 i; i < SGX_TCB_CPUSVN_SIZE + 1; ++i) {
File: contracts/automata-attestation/utils/BytesUtils.sol 336 decoded = uint8(BASE32_HEX_TABLE[uint256(uint8(char)) - 0x30]);
File: contracts/bridge/Bridge.sol 92 proofReceipt[msgHash].receivedAt = _timestamp;
File: contracts/team/airdrop/ERC721Airdrop.sol 60 IERC721(token).safeTransferFrom(vault, user, tokenIds[i]);
File: contracts/thirdparty/optimism/trie/MerkleTrie.sol 111 if (currentNode.decoded.length == BRANCH_NODE_LENGTH) {
</details>File: contracts/verifiers/SgxVerifier.sol 107 if (instances[idx].addr == address(0)) revert SGX_INVALID_INSTANCE(); 211 if (addressRegistered[_instances[i]]) revert SGX_ALREADY_ATTESTED();
re-read
via storage pointerIssue Description
The instances below point to the second+ access of a state variable, via a storage pointer, within a function. Caching the value replaces each Gwarmaccess
(100 gas) with a much cheaper stack read.
Proposed Optimization
To optimize gas usage, cache the value of the state variable in a local variable and read from it within the function. This will replace each Gwarmaccess operation with a much cheaper stack read.
Estimated Gas Savings
The estimated gas savings for this optimization will depend on the number of times the state variable is read within the function. However, each Gwarmaccess operation that is replaced with a stack read can result in a gas savings of 95
gas.
Attachments
Code Snippets
<details> <summary>Click to show 4 findings</summary>File: contracts/L1/libs/LibProving.sol 192 bool returnLivenessBond = blk.livenessBond > 0 && _proof.data.length == 32
File: contracts/L1/libs/LibVerifying.sol 107 uint32 tid = blk.verifiedTransitionId; 184 if (ts.prover != blk.assignedProver) {
</details>File: contracts/team/TimelockTokenPool.sol 198 costToWithdraw = _amountUnlocked * r.grant.costPerToken - r.costPaid;
if-else-statement
Issue Description
Flipping the true and false blocks instead saves 3 gas.
Estimated Gas Savings
saves 3 gas
Attachments
File: contracts/bridge/Bridge.sol 209 } else if (!isMessageProven) { 210 emit MessageReceived(msgHash, _message, true); 211 } else { 212 revert B_INVOCATION_TOO_EARLY(); 213 } 302 } else if (!isMessageProven) { 303 emit MessageReceived(msgHash, _message, false); 304 } else { 305 revert B_INVOCATION_TOO_EARLY(); 306 }
uint256
Issue Description
A timestamp of size uint48
will work for millions of years into the future. A block number increments once every 12 seconds. This should give you a sense of the size of numbers that are sensible.
Attachments
File: contracts/bridge/Bridge.sol 146 message_.srcChainId = uint64(block.chainid); 183 receivedAt = uint64(block.timestamp); 240 receivedAt = uint64(block.timestamp);
File: contracts/L1/TaikoL1.sol 126 state.slotB.lastUnpausedAt = uint64(block.timestamp);
File: contracts/L1/libs/LibProposing.sol timestamp: uint64(block.timestamp), l1Height: uint64(block.number - 1), proposedIn: uint64(block.number),
File: contracts/L1/libs/LibProving.sol 264 ts.timestamp = uint64(block.timestamp);
File: contracts/L1/libs/LibVerifying.sol 57 _state.slotA.genesisHeight = uint64(block.number); 58 _state.slotA.genesisTimestamp = uint64(block.timestamp); 64 blk.proposedAt = uint64(block.timestamp); 71 ts.timestamp = uint64(block.timestamp);
deposit()
when transferring EtherIssue Description
Similar to above, you can “just transfer” ether to a contract and have it respond to the transfer instead of using a payable function. This of course, depends on the rest of the contract’s architecture.
Example Deposit in AAVE
contract AddLiquidity{ receive() external payable { IWETH(weth).deposit{msg.value}(); AAVE.deposit(weth, msg.value, msg.sender, REFERRAL_CODE) } }
The fallback function is capable of receiving bytes data which can be parsed with abi.decode. This servers as an alternative to supplying arguments to a deposit function.
Attachments
File: contracts/L1/TaikoL1.sol 119 function depositEtherToL2(address _recipient) external payable nonReentrant whenNotPaused {
File: contracts/L1/libs/LibDepositing.sol 29 function depositEtherToL2(
OpenZeppelin
Issue Description
OpenZeppelin is a great and popular smart contract library, but there are other alternatives that are worth considering. These alternatives offer better gas efficiency and have been tested and recommended by developers.
Two examples of such alternatives are Solmate and Solady.
Solmate is a library that provides a number of gas-efficient
implementations of common smart contract patterns. Solady is another gas-efficient
library that places a strong emphasis on using assembly.
Attachments
import "@openzeppelin/contracts/";
MODIFIER
used only once and not being inherited should be inlined to save gasIssue Description
When a modifier is used only once in a contract and is not being inherited by other contracts, it can be inlined to save gas.
Proposed Optimization
To optimize gas usage, inline the modifier code directly into the function it modifies, rather than using a separate modifier function.
Attachments
File: contracts/team/airdrop/ERC20Airdrop2.sol 39 modifier ongoingWithdrawals() { 41 if (claimEnd > block.timestamp || claimEnd + withdrawalWindow < block.timestamp) { 42 revert WITHDRAWALS_NOT_ONGOING(); 43 } 44 _; 45 }
only Used in this function
function withdraw(address user) external ongoingWithdrawals {
File: contracts/team/airdrop/MerkleClaimable.sol 33 modifier ongoingClaim() { 34 if ( 35 merkleRoot == 0x0 || claimStart == 0 || claimEnd == 0 || claimStart > block.timestamp 36 || claimEnd < block.timestamp 37 ) revert CLAIM_NOT_ONGOING(); 38 _; 39 }
File: contracts/tokenvault/BridgedERC20.sol 37 modifier onlyOwnerOrSnapshooter() { 38 if (msg.sender != owner() && msg.sender != snapshooter) { 39 revert BTOKEN_UNAUTHORIZED(); 40 } 41 _; 42 }
Issue Description
Immutable variables are more gas-efficient than constant variables for expressions that are computed at compile-time, such as a call to KECCAK256()
.
Proposed Optimization
To optimize gas usage, use immutable instead of constant for expressions that are computed at compile-time.
Attachments
File: contracts/L1/libs/LibProving.sol 20 bytes32 public constant RETURN_LIVENESS_BOND = keccak256("RETURN_LIVENESS_BOND");
File: contracts/signal/LibSignals.sol 8 bytes32 public constant STATE_ROOT = keccak256("STATE_ROOT"); 11 bytes32 public constant SIGNAL_ROOT = keccak256("SIGNAL_ROOT");
Issue Description
Nested mappings and multi-dimensional arrays in Solidity operate through a process of double hashing, which can be gas expensive due to the double-hashing operation and subsequent storage operation (sstore).
Proposed Optimization
Consider using OpenZeppelin's
EnumerableSet in place of nested mappings or multi-dimensional arrays. EnumerableSet creates a data structure that combines the benefits of set operations with the ability to enumerate stored elements, providing a more gas-efficient and collision-resistant alternative in certain scenarios.
Attachments
File: contracts/automata-attestation/AutomataDcapV3Attestation.sol 47 mapping(uint256 idx => mapping(bytes serialNum => bool revoked)) private _serialNumIsRevoked;
File: contracts/common/AddressManager.sol 12 mapping(uint256 chainId => mapping(bytes32 name => address addr)) private __addresses;
File: contracts/L1/TaikoData.sol 183 mapping(uint64 blockId => mapping(bytes32 parentHash => uint32 transitionId)) transitionIds;
File: contracts/signal/SignalService.sol 17 mapping(uint64 chainId => mapping(bytes32 kind => uint64 blockId)) public topBlockId;
File: contracts/tokenvault/ERC20Vault.sol 49 mapping(uint256 chainId => mapping(address ctoken => address btoken)) public canonicalToBridged;
Issue Description
ASSEMBLY can be used to shorten the array by changing the length slot, so that the entries don't have to be copied to a new, shorter array
Attachments
File: contracts/L1/gov/TaikoTimelockController.sol 17 address[] memory nil = new address[](0);
File: contracts/L1/tiers/DevnetTierProvider.sol 48 tiers_ = new uint16[](2);
File: contracts/L1/tiers/MainnetTierProvider.sol 59 tiers_ = new uint16[](3);
File: 59 tiers_ = new uint16[](3);
#0 - c4-judge
2024-04-10T10:10:55Z
0xean marked the issue as grade-a
#1 - c4-sponsor
2024-04-11T10:05:08Z
dantaik (sponsor) acknowledged
🌟 Selected for report: kaveyjoe
Also found by: 0xbrett8571, 0xepley, JCK, LinKenji, MrPotatoMagic, Myd, Sathish9098, aariiif, albahaca, cheatc0d3, clara, emerald7017, fouzantanveer, foxb868, hassanshakeel13, hunter_w3b, joaovwfreire, pavankv, popeye, roguereggiant, yongskiws
58.9933 USDC - $58.99
Taiko
ContestTaiko
ContestTaiko is an innovative Ethereum layer 2 scaling solution that aims to reduce transaction fees while maintaining Ethereum's core properties, such as censorship resistance, permissionlessness, and security. It employs a two-pronged approach: the Based Contestable Rollup (BCR) and the Based Booster Rollup (BBR).
Based Contestable Rollup (BCR): This is Taiko's initial milestone, introducing a permissionless and sequencer-free Layer 2 network. It uses a system of Liveness, Validity, and Contestation Bonds in the Taiko token (TKO) to ensure timely and accurate proof submissions. Network participants, known as provers, can engage in this process, enhancing decentralization and security. The BCR lays the foundation for Taiko's trust-minimized Layer 2 infrastructure.
Based Booster Rollup (BBR): This innovative approach extends the BCR's functionality, allowing any rollup, whether optimistic or ZK, to be enhanced with BBR functionality, directly bolstering Ethereum's scalability.
The Taiko token (TKO) plays a crucial role in the contestable rollup design, underpinning critical bond systems in both BCR and BBR. These bonds, held in TKO, ensure the integrity and timeliness of network operations. Forfeited bonds are redirected to Taiko's Treasury on Layer 1, ensuring their value is conserved and utilized as per community consensus.
Taiko's governance model is decentralized, with the Taiko DAO comprising TKO holders who can influence network upgrades and manage the Taiko treasury on both Layer 1 and Layer 2. The total supply of TKO is fixed at 1 billion, with 18 decimals, and minting or burning is strictly governed to maintain transparency and consensus.
Taiko is a fully open-source, permissionless, Ethereum-equivalent ZK-Rollup, with no centralized actors operating the network. All operations are run permissionlessly by the community, with organizations like Taiko Labs, Taiko Treasury, Taiko DAO, Taiko Foundation, and Taiko Security Council playing essential roles in the ecosystem's development and governance.
One of Taiko's major differentiators is that it is a "based rollup," meaning its sequencing is driven by the base Ethereum Layer 1, rather than a centralized sequencer. This role falls on the shoulders of Ethereum Layer 1 validators, contributing to Taiko's decentralized and trustless nature.
This AddressManager
contract implements an address mapping
system for different chain IDs
and names
, allowing for setting and retrieving addresses while inheriting from EssentialContract
and implementing the IAddressManager interface, with additional features such as ownership
control and error handling for invalid parameters and unsupported operations.
The AddressResolver
contract, initialized with an AddressManager
reference, serves as an abstract implementation of IAddressResolver
, facilitating the resolution of named addresses while enforcing access control and chain ID validation.
The EssentialContract
is an abstract contract that provides essential functionality for other contracts in a system. It includes:
Ownable2StepUpgradeable
for multi-step ownership transfer.AddressResolver
for resolving named addresses._loadReentryLock
calls.Key Features
onlyFromOwnerOrNamed
: Ensures that only the owner or an address resolved by a given name can call a function.nonReentrant
: Prevents reentrant calls.whenPaused
: Only allows function execution when the contract is paused.whenNotPaused
: Only allows function execution when the contract is not paused.pause
: Pauses the contract.unpause
: Unpauses the contract.paused
: Returns true
if the contract is paused, false
otherwise.__Essential_init
: Initializes the contract with the owner and address manager.Upgradeability
-The contract inherits from UUPSUpgradeable
, which allows it to be upgraded using the OpenZeppelin upgradeability mechanism.
Overall, the EssentialContract
is a well-designed and secure base contract.
This Lib4844
library provides functions for handling EIP-4844
blobs by evaluating points using a precompile, with constants defined for specific parameters such as the precompile address, field elements per blob, and the modulus of the BLS curve
, along with error handling for failed evaluations and boundary checks for point coordinates.
This LibAddress
library provides utilities for address-related
operations such as sending Ether safely, checking contract support for specific interfaces, validating signatures using EIP-1271
or ECDSA
, and verifying if the sender is an externally owned account EOA
, with error handling for failed Ether transfers and contract interactions.
This LibMath
library provides functions for comparing and returning the smaller or larger of two uint256
values, enhancing mathematical capabilities within contracts.
The LibTrieProof
library provides functions for verifying Merkle proofs in a Taiko Protocol. Specifically, it allows for verifying
the value of a slot in the storage of an account.
Key Features
verifyMerkleProof
: Verifies a Merkle proof for a given account and slot.The verifyMerkleProof
function takes the following parameters:
_rootHash
: The Merkle root hash of the tree.
_addr
: The address of the account.
_slot
: The slot in the account's storage.
_value
: The expected value of the slot.
_accountProof
: The Merkle proof for the account.
_storageProof
: The Merkle proof for the storage slot.
The function first checks if the account proof is empty
. If it is, then the root hash
is used as the account's storage root. Otherwise, the account proof is used to retrieve the account's storage root.
Next, the function verifies the storage proof using the SecureMerkleTrie
library. If the proof is valid, then the function returns true
. Otherwise, it reverts.
Overall, the LibTrieProof
library is a well-designed and secure library for verifying Merkle proofs in protocol.
TaikoGovernor
contract inherits from various OpenZeppelin
governor-related
contracts to create a comprehensive governance system, allowing for proposal creation
, execution
, and management
, with configurable parameters such as voting delay
, voting period
, and proposal threshold
for Taiko token holders.
TaikoTimelockController
extends the OpenZeppelin TimelockControllerUpgradeable
contract and provides additional functionality for initializing the contract with a minimum delay and allowing the admin to bypass the minimum delay requirement when necessary.
AssignmentHook
serves as a hook for verifying
prover assignments and processing fees, interacting with TaikoL1
contracts, implementing assignment validation logic, signature verification
, and fee processing
for assigned provers based on specified conditions and fee tiers
.
The LibDepositing
library provides functions for handling Ether deposits in the Taiko protocol. It includes functionality for:
Key Features
depositEtherToL2
: Deposits Ether to Layer 2.processDeposits
: Processes batched Ether deposits.canDepositEthToL2
: Checks if Ether deposit is allowed for Layer 2._encodeEthDeposit
: Encodes the given deposit into a uint256.The depositEtherToL2
function checks if the deposit is valid and then sends the Ether to Layer 2. The processDeposits
function processes batched Ether deposits and sends them to Layer 2. The canDepositEthToL2
function checks if Ether deposit is allowed for Layer 2. The _encodeEthDeposit
function encodes the given deposit into a uint256.
The canDepositEthToL2
function takes the following parameters:
_state
: The current TaikoData.State._config
: The current TaikoData.Config._amount
: The amount of Ether to deposit.The LibProposing
library provides functions for handling block proposals in the Taiko protocol. It includes functionality for:
Key Features
proposeBlock
: Proposes a Taiko L2 block.isBlobReusable
: Checks if a blob is reusable._isProposerPermitted
: Determines if the proposer is permitted.The proposeBlock
function takes the following parameters:
_state
: The current TaikoData.State.
_config
: The current TaikoData.Config.
_resolver
: The address resolver interface.
_data
: Encoded data bytes containing the block params.
_txList
: Transaction list bytes (if not blob).
The function first checks if the proposer is permitted. If the proposer is not permitted, the function reverts.
Next, the function initializes the block metadata and updates certain meta fields. The function then creates the block that will be stored on-chain and stores the block in the ring buffer.
Finally, the function runs all hooks and checks that the Taiko Token balance of this contract have increased by the same amount as _config.livenessBond
.
The isBlobReusable
function takes the following parameters:
_state
: The current TaikoData.State.
_config
: The TaikoData.Config.
_blobHash
: The blob hash.
The function checks if the blob is reusable. If the blob is reusable, the function returns true
. Otherwise, the function returns false
.
The _isProposerPermitted
function takes the following parameters:
_slotB
: The current TaikoData.SlotB.
_resolver
: The address resolver interface.
The function checks if the proposer is permitted. If the proposer is permitted, the function returns true
. Otherwise, the function returns false
.
Tthe LibProving
library, which handles block contestation
and proving in the Taiko protocol. It includes functions for pausing
or unpausing
the proving process, proving or contesting a block transition, and handling the transition initialization logic.
Key Functions:
provingPaused
flag in the TaikoData.State struct._createTransition
: Initializes a transition state if it doesn't exist for the given parent hash, block, and slot. It assigns an ID to the transition and initializes its fields._overrideWithHigherProof
: Handles the case when a higher tier proof overwrites a lower tier proof. It updates the transition state, transfers rewards, and adjusts the validity bond and contest bond._checkProverPermission
: Checks if the prover is allowed to submit a proof for the block based on the tier and proving window rules.Data Structures:
TaikoData.State: Stores the current state of the Taiko protocol, including the provingPaused
flag and other configuration parameters.
TaikoData.Block: Represents a block in the Taiko blockchain, including its metadata, proposed timestamp, and assigned prover.
TaikoData.Transition: Represents a transition in the Taiko blockchain, including its parent hash, block hash, state root, and other transition-related data.
TaikoData.TierProof: Represents a proof for a transition, including the tier of the proof and the proof data.
ITierProvider.Tier: Represents a tier in the Taiko protocol, including its name, verifier contract name, validity bond, contest bond, maximum blocks to verify per proof, and proving window duration.
Events:
TransitionProved: Emitted when a transition is proved, including the block ID, transition data, prover address, validity bond, and tier.
TransitionContested: Emitted when a transition is contested, including the block ID, transition data, contester address, contest bond, and tier.
ProvingPaused: Emitted when proving is paused or unpaused, indicating the pause status.
Errors:
LibUtils
library which offers helper functions for working with Taiko data structures. It includes functions for retrieving transitions
and blocks based on their IDs or parent hashes.
Key Functions:
getTransition: Retrieves the transition state data for a block with a given ID and parent hash. It checks that the block exists and reverts if the transition is not found.
getBlock: Retrieves the block data for a block with a given ID. It checks that the block exists and reverts if it does not.
getTransitionId: Retrieves the ID of the transition with a given parent hash. It returns 0 if the transition is not found.
Data Structures:
TaikoData.State: Stores the current state of the Taiko protocol, including the block ring buffer and transition states.
TaikoData.Config: Stores the configuration parameters of the Taiko protocol, including the block ring buffer size.
TaikoData.Block: Represents a block in the Taiko blockchain, including its metadata, proposed timestamp, and assigned prover.
TaikoData.TransitionState: Represents a transition in the Taiko blockchain, including its parent hash, block hash, state root, and other transition-related data.
Errors:
L1_BLOCK_MISMATCH: Raised when the block data provided does not match the stored block data.
L1_INVALID_BLOCK_ID: Raised when the provided block ID is invalid.
L1_TRANSITION_NOT_FOUND: Raised when the transition with the given parent hash is not found.
L1_UNEXPECTED_TRANSITION_ID: Raised when the transition ID is unexpected or out of range.
LibVerifying
library which handles block verification
in the Taiko protocol. It includes functions for verifying blocks
, initializing the protocol state, and syncing chain data.
Key Functions:
init: Initializes the Taiko protocol state with the genesis block hash and configuration parameters.
verifyBlocks: Verifies up to a specified number of blocks, updating the last verified block ID and state root. It also handles bond distribution to provers and syncs chain data if necessary.
_syncChainData
: Syncs the latest state root to the Signal Service if the last verified block ID exceeds a threshold.
Data Structures:
TaikoData.State: Stores the current state of the Taiko protocol, including the block ring buffer and transition states.
TaikoData.Config: Stores the configuration parameters of the Taiko protocol, including the block ring buffer size, block gas limit, and bond amounts.
TaikoData.Block: Represents a block in the Taiko blockchain, including its metadata, proposed timestamp, and assigned prover.
TaikoData.TransitionState: Represents a transition in the Taiko blockchain, including its parent hash, block hash, state root, and other transition-related data.
Errors:
L1_BLOCK_MISMATCH: Raised when the block data provided does not match the stored block data.
L1_INVALID_CONFIG: Raised when the provided configuration parameters are invalid.
L1_TRANSITION_ID_ZERO: Raised when the transition ID is zero.
GuardianProver
extends functionality from Guardians to allow guardians to approve guardian proofs
, triggering the proving transaction and emitting
events based on successful approvals or rejections, while integrating with TaikoL1 contracts and handling block metadata, transitions
, and tier proofs
.
Guardians
manages a set of guardians and their approvals through mappings
and arrays, providing functions to set guardians
, check approvals
, and handle guardian-related
operations while ensuring minimum guardian requirements and emitting relevant events.
TaikoData
contract defines various data structures such as configuration parameters
, block metadata
, transition states
, and Ethereum deposits
, used within the Taiko protocol, specifying their attributes and relationships for efficient contract operations.
TaikoL1
contract which serves as the base layer contract
of the Taiko protocol. It handles block proposing
, proving
, and verifying
, as well as Ether and Taiko token deposits and withdrawals.
Key Functions:
proposeBlock: Proposes a new block to the Taiko blockchain. It includes the block metadata and a list of transactions.
proveBlock: Proves a proposed block using a state transition and a tier proof.
verifyBlocks: Verifies a specified number of blocks, updating the last verified block ID and state root.
pauseProving: Pauses or unpauses block proving.
depositEtherToL2: Deposits Ether to Layer 2.
unpause: Unpauses the contract and updates the lastUnpausedAt
timestamp.
canDepositEthToL2: Checks if Ether deposit is allowed for Layer 2.
isBlobReusable: Checks if a blob hash can be reused for block proposal.
getBlock: Retrieves a block and its associated transition state.
getTransition: Retrieves the transition state for a specific block and parent hash.
getStateVariables: Retrieves the state variables of the contract.
Data Structures:
TaikoData.State: Stores the current state of the Taiko protocol, including the block ring buffer and transition states.
TaikoData.Config: Stores the configuration parameters of the Taiko protocol, including the block ring buffer size, block gas limit, and bond amounts.
TaikoData.Block: Represents a block in the Taiko blockchain, including its metadata, proposed timestamp, and assigned prover.
TaikoData.TransitionState: Represents a transition in the Taiko blockchain, including its parent hash, block hash, state root, and other transition-related data.
Errors:
L1_INVALID_BLOCK_ID: Raised when the provided block ID is invalid.
L1_PROVING_PAUSED: Raised when block proving is paused.
L1_RECEIVE_DISABLED: Raised when receiving Ether is disabled.
Events:
BlockProposed: Emitted when a new block is proposed.
BlockProven: Emitted when a block is proven.
BlocksVerified: Emitted when multiple blocks are verified.
ProvingPaused: Emitted when block proving is paused.
ProvingUnpaused: Emitted when block proving is unpaused.
EtherDepositedToL2: Emitted when Ether is deposited to Layer 2.
TaikoToken
is an ERC20 token with additional features such as snapshotting
, voting delegation
, and permit support
, used within the Taiko
protocol for prover collateral in the form of bonds
, featuring a minting function
, burning tokens
, and transfer restrictions to prevent sending tokens to the token contract itself.
DevnetTierProvider
implements the ITierProvider
interface to provide tier information such as verifier names
, bond amounts
, cooldown windows
, proving windows
, and maximum blocks
to verify per proof for the Optimistic and Guardian tiers within the Taiko protocol.
This contract MainnetTierProvider
implements the ITierProvider
interface to provide tier information such as verifier names
, bond amounts
, cooldown windows
, proving windows
, and maximum blocks
to verify per proof
for the SGX
, SGX with ZKVM, and Guardian tiers within the Taiko protocol, with specific conditions for determining the minimum required tier based on a random value.
This contract TestnetTierProvider
provide tier information such as verifier names
, bond amounts, cooldown windows, proving windows, and maximum blocks to verify per proof for the Optimistic, SGX, and Guardian tiers within the Taiko protocol, with specific conditions for determining the minimum required tier based on a random value, where 10%
of blocks require SGX
proofs and the rest are optimistic without validity proofs.
CrossChainOwned
is an abstract contract that allows its owner to be either a local address
or an address on another chain, using signals for transaction approval
, and it defines functionality related to transaction execution
based on specific conditions such as the owner chain ID
and transaction ID
validation.
The Lib1559Math
library which implements an exponential bonding curve
for EIP-1559
. This curve is used to calculate the base fee for transactions based on the amount of excess gas issued.
Key Functions:
Mathematical Formula:
basefee = eth_qty(excess_gas_issued) / (TARGET * ADJUSTMENT_QUOTIENT)
where:
eth_qty
is a function that converts the excess gas issued to an Ethereum quantity using an exponential bonding curve.
TARGET
is the target gas usage for a block.
ADJUSTMENT_QUOTIENT
is a constant that adjusts the curve's sensitivity.
Implementation Details:
_ethQty
function uses the exp
function from the LibFixedPointMath
library to calculate the Ethereum quantity based on the excess gas issued and the adjustment factor.basefee
function divides the result of _ethQty
by the adjustment factor and the scaling factor to obtain the base fee.The TaikoL2
contract which is responsible for anchoring the latest L1
block details to L2
for cross-layer
communication, managing EIP-1559
gas pricing for Layer 2 (L2) operations, and storing verified L1 block information.
Key Functions:
anchor: Anchors the latest L1
block details to L2
, including the block hash, state root, block ID, and parent gas used.
withdraw: Withdraws tokens or Ether from the contract.
getBasefee: Calculates the base fee per gas using EIP-1559 configuration for the given parameters.
getBlockHash: Retrieves the block hash for a given L2 block number.
getConfig: Returns the EIP-1559 related configurations.
skipFeeCheck: Indicates whether to skip checking base fee mismatch (for simulation purposes).
Data Structures:
Errors:
L2_BASEFEE_MISMATCH: Raised when the base fee per gas is incorrect.
L2_INVALID_CHAIN_ID: Raised when the chain ID is invalid.
L2_INVALID_PARAM: Raised when invalid parameters are provided.
L2_INVALID_SENDER: Raised when the sender is not authorized.
L2_PUBLIC_INPUT_HASH_MISMATCH: Raised when the public input hash does not match.
L2_TOO_LATE: Raised when the contract is initialized too late.
This TaikoL2EIP1559Configurable
contract extends the TaikoL2
contract and introduces a setter function to change EIP-1559
configurations and states, emitting an event when these changes occur.
The provided SignalService
contract which is responsible for sending
and receiving
signals (messages) across different chains and layers. It also provides functionality for synchronizing chain data and verifying signal proofs
.
Key Functions:
sendSignal: Sends a signal to the contract's own chain.
syncChainData: Synchronizes chain data from another chain, such as the state root or a specific block hash.
proveSignalReceived: Verifies a proof that a signal was received on another chain.
isChainDataSynced: Checks if a specific chain data is synchronized with the contract.
isSignalSent: Checks if a signal has been sent from a specific address.
getSyncedChainData: Retrieves the synchronized chain data for a given chain ID, kind, and block ID.
signalForChainData: Calculates the signal hash for a specific chain data.
Events:
SignalSent: Emitted when a signal is sent.
ChainDataSynced: Emitted when chain data is synchronized.
Errors:
SS_EMPTY_PROOF: Raised when the proof is empty.
SS_INVALID_SENDER: Raised when the sender is not authorized.
SS_INVALID_LAST_HOP_CHAINID: Raised when the last hop chain ID is invalid.
SS_INVALID_MID_HOP_CHAINID: Raised when a mid-hop chain ID is invalid.
SS_INVALID_STATE: Raised when the state is invalid.
SS_INVALID_VALUE: Raised when the value is invalid.
SS_SIGNAL_NOT_FOUND: Raised when the signal is not found.
SS_UNAUTHORIZED: Raised when the sender is not authorized.
SS_UNSUPPORTED: Raised when a function is not supported.
The Bridge
contract is a core component of the Taiko cross-chain
messaging system. It serves as a gateway for sending and receiving messages between different blockchain networks. The contract manages the lifecycle of messages, including their creation, transmission, and execution.
Key Features
NEW
, DONE
, FAILED
, and RETRIABLE
.Events:
MessageSent
: Emitted when a message is sent.MessageReceived
: Emitted when a message is received.MessageExecuted
: Emitted when a message is executed.MessageStatusChanged
: Emitted when the status of a message changes.AddressBanned
: Emitted when an address is banned.MessageSuspended
: Emitted when a message is suspended.MessageRecalled
: Emitted when a message is recalled.MessageRetried
: Emitted when a message is retried.Functions:
sendMessage
: Allows users to send messages.recallMessage
: Allows users to recall messages before they are executed.processMessage
: Handles the receipt and execution of messages.retryMessage
: Allows users to retry failed or retriable messages.isMessageSent
: Checks if a message has been sent.proveMessageFailed
: Checks if a message has failed on the destination chain.proveMessageReceived
: Checks if a message has been received on the current chain.isDestChainEnabled
: Checks if a destination chain is enabled.context
: Returns the current call context.getInvocationDelays
: Returns the invocation delay values.hashMessage
: Computes the hash of a message.signalForFailedMessage
: Computes the signal representing a failed message.Limitations
The bridge relies on the reliability of the underlying blockchain networks and the signal service used for message transmission.
The invocation delay can potentially delay the execution of critical messages.
The contract does not support the transfer of arbitrary data types. such as:
Invocation Delays: Delays before messages can be executed to prevent premature execution.
Message Status Tracking: Tracks the status of each message to ensure it is processed correctly.
Address Banning: Allows administrators to ban addresses from sending messages.
USDCAdapter
contract which is a bridged
ERC20 token adapter for USDC. It allows for minting and burning of USDC tokens on the local chain, while the actual minting and burning operations are performed on the remote chain.Is an upgradeable ERC20 contract that represents tokens bridged from another chain. It is an implementation of the BridgedERC20Base
contract which provides the basic functionality for a bridged token.
The BridgedERC20 contract includes the following additional features:
The BridgedERC20 contract is initialized with the following parameters:
_owner
: The owner of the contract._addressManager
: The address of the AddressManager contract._srcToken
: The source token address._srcChainId
: The source chain ID._decimals
: The number of decimal places of the source token._symbol
: The symbol of the token._name
: The name of the token.The BridgedERC20 contract has the following public functions:
init
: Initializes the contract.setSnapshoter
: Sets the snapshoter address.snapshot
: Creates a new token snapshot.name
: Gets the name of the token.symbol
: Gets the symbol of the bridged token.decimals
: Gets the number of decimal places of the token.canonical
: Gets the canonical token's address and chain ID.The BridgedERC20 contract has the following internal functions:
_mintToken
: Mints a new token._burnToken
: Burns a token._beforeTokenTransfer
: Called before a token transfer._afterTokenTransfer
: Called after a token transfer._mint
: Mints a new token._burn
: Burns a token.The BridgedERC20 contract has the following errors:
_beforeTokenTransfer
: Implements ERC20 and ERC20Votes transfer hooks._afterTokenTransfer
: Implements ERC20 and ERC20Votes transfer hooks._mint
: Implements ERC20 and ERC20Votes mint hooks._burn
: Implements ERC20 and ERC20Votes burn hooks.BridgedERC20Base is a base contract for bridged ERC20 tokens. It provides the following functionality:
The BridgedERC20Base contract has the following public functions:
The BridgedERC20Base contract has the following internal functions:
_mintToken
: Mints tokens to the specified account._burnToken
: Burns tokens from the specified account._isMigratingOut
: Returns true if the contract is migrating tokens out, false otherwise.The BridgedERC20Base contract has the following errors:
Bridging ERC721
tokens across different chains providing the functionality to mint
and burn
tokens while maintaining a connection to the source token and chain ID. chain ID.
The BridgedERC721 contract has the following public functions:
The BridgedERC721 contract has the following internal functions:
_beforeTokenTransfer
: Called before any token transfer.The BridgedERC721 contract has the following errors:
Bridging ERC1155
tokens across different chains providing the functionality to mint and burn tokens while maintaining a connection to the source token and chain ID
.
The BridgedERC1155
contract has the following public functions:
The BridgedERC1155 contract has the following internal functions:
_beforeTokenTransfer
: Called before any token transfer.The BridgedERC1155 contract has the following errors:
BaseNFTVault
is an abstract contract for bridging NFTs across different chains.
The BaseNFTVault contract has the following public functions:
The BaseNFTVault contract has the following internal functions:
_sendToken
: Sends a token to another chain._releaseToken
: Releases a token on the current chain.The BaseNFTVault contract has the following events:
The BaseNFTVault contract has the following errors:
BaseVault
is an abstract contract that provides a base implementation for vaults. Vaults are used to manage the bridging of assets between different chains.
The BaseVault contract has the following public functions:
The BaseVault contract has the following internal functions:
The BaseVault contract has the following errors:
The ERC1155Vault
contract holds ERC1155
tokens deposited by users and manages the mapping between canonical tokens and their bridged tokens. It allows users to send ERC1155 tokens to other chains by invoking the sendToken
function and receive bridged tokens on the destination chain by invoking the onMessageInvocation
function.
Key Features
Functions
Events
The ERC20Vault
contract which serves as a central repository for ERC20
tokens within a cross-chain
bridge system. Its primary purpose is to facilitate the transfer of ERC20 tokens between different chains while maintaining accurate token balances and ensuring the integrity of the bridging process.
Key Features
Token Deposits and Withdrawals
Cross-Chain Token Transfers
sendToken
function allows users to initiate cross-chain token transfers by providing the destination chain ID, recipient address, token address, and amount.Bridged Token Management
changeBridgedToken
function allows the contract owner to update the bridged token address for a given canonical token.The ERC721Vault
contract serves as a central repository for ERC721
tokens within a cross-chain
bridge system. Its primary purpose is to facilitate the transfer of ERC721
tokens between different chains while maintaining accurate token balances and ensuring the integrity of the bridging process.
Key Features
Token Deposits and Withdrawals
Cross-Chain Token Transfers
sendToken
function allows users to initiate cross-chain token transfers by providing the destination chain ID, recipient address, token address, token IDs, and amounts.Bridged Token Management
changeBridgedToken
function allows the contract owner to update the bridged token address for a given canonical token.This LibBridgedToken
contract provides functions to validate input parameters, build names
, symbols
, and URIs for bridged tokens, ensuring correct formatting and integrity for cross-chain
token representation and interaction.
This library ExcessivelySafeCall
provides a way to make calls to external contracts with an additional safety layer to limit the amount of returndata copied to prevent potential out-of-gas
issues caused by malicious contracts returning excessively large data.
The Bytes
library provides functions for manipulating
byte arrays, including slicing a byte array
, converting it into nibbles, and comparing byte arrays by comparing their keccak256 hashes.
The RLPReader
library is designed to parse RLP-encoded
byte arrays including functions for converting bytes to RLP
items, reading RLP lists and bytes, decoding RLP item lengths
, and copying bytes from memory locations.
This RLPWriter
library is designed to encode some types such as byte strings
and uint256
values into RLP
format, with functions to handle encoding lengths
and converting integers
to big-endian binary form without leading zeroes.
The MerkleTrie
library provides functionality for verifying
inclusion proofs and retrieving values
associated with keys in a Merkle-Patricia trie
, handling various trie node types, node IDs, and path comparisons efficiently.
SecureMerkleTrie
library is a wrapper
around the MerkleTrie library
that hashes input keys using Keccak256
before passing them to the MerkleTrie
functions for verifying inclusion proofs and retrieving values from a Merkle trie, ensuring compatibility with Ethereum's state trie behavior.
LibFixedPointMath
library provides a fixed-point
math function exp(int256 x)
that computes the exponential function e^x
in 1e18
fixed-point
format, with rigorous handling of input ranges and precision adjustments to ensure accurate results within the specified bounds.
The GuardianVerifier
contract is an implementation of the IVerifier
interface that restricts access to the verifyProof
function based on the caller's identity, requiring it to match a specific address resolved through the address manager.
The SgxVerifier
contract serves as a component within a cross-chain
bridge system. Its primary purpose is to verify the authenticity of SGX
instances and their proofs to ensure the integrity of cross-chain
message attestations.
Key Features
SGX Instance Management
Proof Verification
This ERC20Airdrop
facilitates the claiming
of airdropped tokens by eligible users within a specified period and allows them to delegate their voting power
to a delegatee using a provided merkle proof and delegation data
, enhancing user interaction and governance functionalities for the Taiko token ecosystem.
ERC20Airdrop2
manages a Taiko token airdrop for eligible users with a withdrawal window mechanism, allowing users to claim tokens within a specified period and withdraw them gradually over time, enhancing token distribution control and user experience within the Taiko token ecosystem.
ERC721Airdrop
facilitates the claiming of ERC721
tokens from a designated vault by eligible users using a merkle proof mechanism, streamlining the distribution process within the Taiko token ecosystem.
MerkleClaimable
provides functionalities for managing
a Taiko token airdrop for eligible users
, including verification of merkle proofs
, setting configuration parameters, and maintaining claim status, enhancing security and control over the airdrop process within the Taiko token ecosystem.
The TimelockTokenPool
contract serves as a mechanism for managing and distributing Taiko tokens
to various recipients, including investors
, team members
, and grant program grantees.
It implements a three-state lifecycle for tokens:
Key Features
Grant Management
Token Vesting and Withdrawal
Cost Token Support
Additional Notes
getMyGrantSummary
function for recipients to view their current token balances and withdrawal status.AutomataDcapV3Attestation
provides a mechanism for verifying the authenticity and integrity of SGX quotes
using the DCAPV3
protocol. It allows users to attest to the validity of an SGX
quote and obtain a verification result.
Key Features:
Verification Process:
The verifyAttestation
function is the entry point for verifying an SGX quote. It performs the following steps:
V3Parser
library.EnclaveIdStruct
data structure.IPEMCertChainLib
.TCBInfoStruct
data structure.Trusted Enclave and Signer Management:
setMrSigner
and setMrEnclave
functions. This allows the contract to validate the authenticity of quotes based on trusted identities.TCB Info Management:
configureTcbInfoJson
and configureQeIdentityJson
functions. This information is used to determine the validity of SGX quotes.Revocation Management:
addRevokedCertSerialNum
and removeRevokedCertSerialNum
functions. This allows the contract to invalidate quotes based on revoked certificates.PEMCertChainLib
is a library that provides functions for decoding
and processing PEM-encoded
certificate chains, extracting information such as issuer names, validity dates, public keys, and SGX-specific information like TCB levels, PCE IDs, and FMS PC values, enhancing the verification and analysis capabilities of certificate chains within the context of SGX
.
Functions
splitCertificateChain
decodeCert
ECSha256Certificate
object._removeHeadersAndFooters
_trimBytes
_findPckTcbInfo
PCESVN
, an array of SVNs, the FMSPC, and the PCeID._findTcb
This V3Parser
library provides functionality for parsing
and validating SGX
Enclave Quote data in the V3 format.
Functions:
V3Struct.ParsedV3QuoteStruct
if the quote is valid.Parsing and Validation Process:
1. parseInput:
2. validateParsedInput:
Additional Notes:
littleEndianDecode
function to decode little-endian encoded values.Base64
library from solady
to decode the certificate chain data.PEMCertChainLib
library to parse the certificate chain data.This Asn1Decode
library provides functionality for decoding DER-encoded
ASN.
Functions:
Decoding Process:
The library uses the NodePtr
library to represent ASN.1 nodes as pointers with three indices:
ixs
: Index of the first byte of the node.ixf
: Index of the first byte of the node's content.ixl
: Index of the last byte of the node's content.BytesUtils
library provides utility functions for working with byte arrays, including hashing byte ranges
, checking byte equality, reading integers and byte values at specified indices, copying substrings, and decoding base32-encoded data, enhancing the functionality of contracts to handle and manipulate byte data efficiently and securely.This library RsaVerify
implements functions for verifying RSA
signatures using the PKCSv1.5 padding scheme with SHA256 and SHA1 hashing algorithms. It includes methods to verify signatures with explicit SHA256 or SHA1 hashes, using the RSA public key components (exponent and modulus) and the signature itself. The library ensures that the signature is correctly formatted according to PKCSv1.5
standards and performs the necessary checks to validate the signature against the provided data and public key parameters, enhancing the security and integrity verification capabilities of smart contracts when dealing with RSA signatures.
Functions:
Verification Process:
The verification process involves the following steps:
Input Parameters:
_sha256
: The SHA-256 hash of the data (for pkcs1Sha256
)._data
: The data to verify (for pkcs1Sha256Raw
and pkcs1Sha1Raw
)._s
: The signature._e
: The exponent._m
: The modulus.Additional Notes:
SHA1
library for SHA-1 hashing.This SHA1
library hashing algorithm using low-level assembly code to efficiently compute the hash value for a given input data, providing a secure and reliable way to generate SHA-1 hashes.
SigVerifyLib
is a library for verifying signatures using different cryptographic algorithms such as RS256
, ES256
, and RS1
, providing support for verifying signatures on attestation statements and certificates based on the specified algorithms and public keys.
Taiko's multi-proof approach, SGX is used to generate a different type of proof alongside ZK (Zero-Knowledge) proofs. The same code that would be executed on a zkVM (Zero-knowledge Virtual Machine) is run within an SGX enclave. This code functions somewhat like a light execution client, verifying the underlying state transitions in the rollup.
The necessary data from the execution is then signed within the SGX enclave using a standard ECDSA (Elliptic Curve Digital Signature Algorithm) signature, with a private key that is exclusive to the SGX enclave. This signature is then verified within the smart contract on the Ethereum mainnet.
The use of SGX in Taiko's multi-proof approach provides an additional layer of security and diversity. By utilizing different proof systems that all verify the same underlying light client's execution, the risk associated with bugs or vulnerabilities in any single proof system is reduced. This contributes to the overall robustness and reliability of the Taiko rollup.
The Taiko protocol enables secure cross-chain messaging through its Ethereum-equivalence design and the use of Merkle proofs. Taiko stores block hashes of each chain in two smart contracts: TaikoL1 and TaikoL2. TaikoL1 is deployed on Ethereum and stores a mapping of blockNumber to blockHash (l2Hashes), while TaikoL2 is deployed on Taiko and stores a similar mapping (l1Hashes).
Merkle trees are used to verify values exist on the other chain. These trees allow a lot of data to be fingerprinted with a single hash, known as the Merkle root. Verifiers can confirm that some value exists within this large data structure without needing access to the entire Merkle tree. They just need the Merkle root, the value, and a list of intermediate sibling hashes.
Taiko's signal service is a smart contract available on both L1 and L2, which uses Merkle proofs to provide a service for secure cross-chain messaging. It allows you to store signals and check if a signal was sent from an address. The function isSignalReceived
can prove on either chain that you sent a signal to the Signal Service on the other chain.
The bridge in Taiko is a set of smart contracts and a frontend web app that allow you to send testnet ETH and ERC-20, ERC-1155, and ERC-721 tokens between Ethereum and Taiko. The user sends their funds to the Bridge contract, which locks the Ether and stores a message by calling sendSignal(message)
on the SignalService contract. The user receives Ether on the destination chain if they (or another) provide a valid Merkle proof that the message was received on the source chain.
For ERC-20 (or ERC-721, ERC-1155) bridging, a new BridgedERC20 contract needs to be deployed on the destination chain. The vault contract (via the Bridge) sends a message to the Signal Service, which contains metadata related to the bridge request and the calldata for the receiveToken
method. This process mints ERC-20 (or ERC-721, ERC-1155) on the BridgedERC20 contract to the recipient's address on the destination chain.
Systemic Risks
Not obvious Systemic risk
Centralization Risks
onlyOwner
modifier on the setAddress
function, which means only the owner can change the addresses. This centralizes control and could lead to censorship or single point of failure. If the owner's account is compromised or the owner becomes malicious, they could change the addresses to malicious ones.Technical Risks
setAddress
function does not have a check to ensure that the new address is a valid contract address. This could potentially lead to the setting of an incorrect or malicious address.Integration Risks
Not obvious Systemic risk
Systemic Risks
Use of onlyFromNamed
Modifier: The contract uses the onlyFromNamed
modifier to restrict certain functions to be called only by a specific address. However, the modifier does not check if the _name
parameter is valid or not. If an attacker can find a valid _name
that has not been assigned to any address, they could potentially bypass the modifier and call the restricted function.
Use of _allowZeroAddress
Parameter: The contract has a _allowZeroAddress
parameter that allows zero addresses to be returned in certain cases. However, the contract does not check if the returned address is a contract address or an externally owned account. If the returned address is an externally owned account, it could potentially lead to issues with the contract's functionality.
Centralization Risks
Technical Risks
Not obvious Technical risk
Integration Risks
Not obvious Integration risk
Systemic Risks
Incorrect Initialization
: The contract has an initialization function __Essential_init
that takes an _addressManager
parameter. If this parameter is not set correctly during deployment, it could lead to unintended behavior or even render some functions unusable.
function __Essential_init( address _owner, address _addressManager ) internal virtual onlyInitializing { __Essential_init(_owner); if (_addressManager == address(0)) revert ZERO_ADDR_MANAGER(); __AddressResolver_init(_addressManager); }
Re-entrancy Guard Inconsistency
: The contract uses a custom re-entrancy
guard with different storage mechanisms based on the chain ID. This could lead to inconsistencies in re-entrancy protection if the contract is interacted with across different chains or if the chain ID is not correctly identified.
function _loadReentryLock() internal view virtual returns (uint8 reentry_) { if (block.chainid == 1) { assembly { reentry_ := tload(_REENTRY_SLOT) } } else { reentry_ = __reentry; } } function _inNonReentrant() internal view returns (bool) { return _loadReentryLock() == _TRUE; }
Centralization Risks
The onlyFromOwnerOrNamed
modifier restricts function calls to either the contract owner or a resolved address with a specific name. This centralization of control could lead to censorship or single point of failure issues.
modifier onlyFromOwnerOrNamed(bytes32 _name) { if (msg.sender != owner() && msg.sender != resolve(_name, true)) revert RESOLVER_DENIED(); _; }
The pause
and unpause
functions are controlled by the contract owner, which could lead to centralization risks if the owner decides to pause the contract indefinitely or maliciously.
Technical Risks
The _storeReentryLock
and _loadReentryLock
functions use different storage mechanisms based on the chain ID. This could lead to inconsistencies or bugs if the chain ID is not properly handled or if the contract is deployed on an unsupported chain.
The contract uses a custom error code (INVALID_PAUSE_STATUS
) for both paused and unpaused states, which might lead to confusion or misinterpretation of the contract state.
Integration Risks
UUPSUpgradeable
, Ownable2StepUpgradeable
, and AddressResolver
), which could increase its complexity and make it harder to understand and maintain.Systemic Risks
Unbounded Gas Usage
: In the sendEther
function without the _gasLimit
parameter, the gasleft()
function is used to determine the gas limit. This could lead to unbounded gas usage if the contract's gas consumption is not properly managed, potentially causing transaction reverts or out-of-gas errors.
Magic Value Dependency: The isValidSignature
function relies on the _EIP1271_MAGICVALUE
constant when checking for EIP-1271 signatures. If this magic value changes in the future or if there are compatibility issues with contracts implementing EIP-1271, it could lead to incorrect signature validation.
EIP-1271 Signature Validation
: The isValidSignature
function assumes that a contract implementing EIP-1271 will return the magic value (_EIP1271_MAGICVALUE
) when the signature is valid. If a contract implements EIP-1271 but does not follow this convention, it could lead to incorrect signature validation.
function isValidSignature( address _addr, bytes32 _hash, bytes memory _sig ) internal view returns (bool) { if (Address.isContract(_addr)) { return IERC1271(_addr).isValidSignature(_hash, _sig) == _EIP1271_MAGICVALUE; } else { return ECDSA.recover(_hash, _sig) == _addr; } }
False Positive EOA Detection
: The isSenderEOA
function checks if the transaction sender is an externally owned account (EOA) by comparing msg.sender
and tx.origin
. However, this check might not be reliable in some cases, such as when using meta-transactions or certain proxy contracts, leading to false positives.
Centralization Risks
Not obvious Centralization risk
Technical Risks
sendEther
function, the gas limit is passed as a parameter, which could lead to potential issues if the gas limit is not correctly estimated or if it is manipulated by an attacker.Integration Risks
The isValidSignature
function checks for both ECDSA and EIP-1271 signatures. If the contracts being interacted with do not correctly implement these signatures or have compatibility issues, it could lead to signature validation issues or other integration problems.
Unused Import
: The library imports the IERC165
interface, but it is not used anywhere in the code. This could lead to confusion during code audits or maintenance and might indicate that some functionality is incomplete or not properly implemented.
Systemic Risks
Unchecked RLP Item Length
: The contract does not check the length of the RLPReader.RLPItem[] memory accountState
array. If the RLP-encoded account data does not contain the expected number of fields, it could lead to out-of-bounds access in the following line:
storageRoot_ = bytes32(RLPReader.readBytes(accountState[_ACCOUNT_FIELD_INDEX_STORAGE_HASH]));
Use of abi.encodePacked
: The LibTrieProof contract uses abi.encodePacked
to encode the address when fetching the account data from the Merkle tree:
bytes memory rlpAccount = SecureMerkleTrie.get(abi.encodePacked(_addr), _accountProof, _rootHash);
Using abi.encodePacked
can potentially lead to issues when encoding addresses, as it does not include padding. This could result in incorrect data being fetched from the Merkle tree if the encoding does not match the encoding used when the data was stored. It would be safer to use abi.encode
instead, which includes padding and is less prone to errors.
Centralization Risks
Not obvious Centralization risk
Technical Risks
The contract does not check the length of the _storageProof
parameter. If an empty or incomplete proof is provided, it could cause unexpected behavior.
The contract does not validate the _addr
parameter. If an invalid address is provided, it could lead to issues.
Integration Risks
verifyMerkleProof
function, it could lead to unintended behavior. Additionally, the contract assumes that the provided proofs are generated correctly. If the system generating these proofs has issues, it could impact the functionality of this contract.Systemic Risks
Fixed Voting Delay and Voting Period
: The votingDelay()
and votingPeriod()
functions return hardcoded values of 1 day and 1 week, respectively. These fixed values could lead to risks if the governance process needs to be adjusted in the future. For example, a longer voting period might be required for certain proposals, or a shorter voting delay might be desired to expedite the voting process. The contract would need to be updated to accommodate any such changes.
Fixed Proposal Threshold
: The proposalThreshold()
function returns a hardcoded value of 0.01% of the total Taiko Token supply. if the governance process needs to be adjusted in the future. For example, if the Taiko Token supply changes or if the community decides to lower or raise the proposal threshold, the contract would need to be updated to accommodate these changes.
Centralization Risks
timelock controller
, which could pose a centralization risk if only a limited number of addresses have the ability to execute proposals after the timelock has passed.Technical Risks
The contract uses the ether
unit to calculate the proposalThreshold()
. While this is not an issue by itself, it could lead to confusion or errors if someone misunderstands the unit or if the unit is changed in the future. It would be clearer to use the wei
unit and specify the exact number of wei required for the proposal threshold.
propose()
and _execute()
. If invalid parameters are provided, it could cause unexpected behavior or revert the function call.Integration Risks
Not obvious Integration risk
Systemic Risks
Incorrect signature verification
: The contract verifies the signature of the assigned prover in the onBlockProposed
function. If the signature verification logic is incorrect, it can lead to unauthorized provers being assigned.
fee calculation
: The contract calculates the prover fee in the _getProverFee
function. If the fee calculation logic is incorrect, it can lead to underpayment or overpayment of fees.
```solidity function _getProverFee( TaikoData.TierFee[] memory _tierFees, uint16 _tierId ) private pure returns (uint256) { for (uint256 i; i < _tierFees.length; ++i) { if (_tierFees[i].tier == _tierId) return _tierFees[i].fee; } revert HOOK_TIER_NOT_FOUND(); } ```
Centralization Risks
Not obvious Centralization risk
Technical Risks
Time dependency
: The contract uses block.timestamp
to check the expiry of the assignment. However, miners can manipulate the timestamp to some extent, which may lead to potential vulnerabilities.
Unchecked-send
: The contract uses .sendEther()
to transfer Ether, which can fail silently if the call stack depth is above 1024, potentially leading to loss of funds.
Integration Risks
Compatibility issues
: If the interfaces of the external contracts (TaikoL1
, IERC20
, AddressManager
) change, this contract may no longer work as expected.Systemic Risks
Incorrect calculation of pending deposits
: The contract calculates the number of pending deposits as _state.slotA.numEthDeposits - _state.slotA.nextEthDepositToProcess
. If the nextEthDepositToProcess
variable is not updated correctly, it can lead to an incorrect calculation of pending deposits, which can cause deposits to be skipped or processed multiple times.
return _amount >= _config.ethDepositMinAmount && _amount <= _config.ethDepositMaxAmount && _state.slotA.numEthDeposits - _state.slotA.nextEthDepositToProcess < _config.ethDepositRingBufferSize - 1;
deposit slot calculation
: The contract uses the slot
variable to store the position of the deposit in the ring buffer. If the slot calculation logic is incorrect, it can lead to deposits being stored in the wrong position in the ring buffer, which can cause deposits to be overwritten or lost.
handling of deposit Ringbuffer
: The contract uses a ring buffer to store pending deposits. If the ring buffer handling logic is incorrect, it can lead to deposits being overwritten or lost, especially in cases where the ring buffer is full.
Centralization Risks
_recipient
parameter is used to determine the recipient address for deposits. If _recipient
is not specified address(0)
, the default recipient becomes msg.sender. This design limits flexibility and decentralization since all deposits without a specified recipient will go to msg.sender. It doesn't allow for dynamic selection of recipients based on conditions or external factors.Technical Risks
Unchecked-send
: The contract uses .sendEther()
to transfer Ether, which can fail silently if the call stack depth is above 1024, potentially leading to loss of funds.Integration Risks
data feeding
: The contract relies on external data provided by the address resolver contract and the configuration contract. If the data is incorrect or manipulated, the contract's functionality can be affected.Systemic Risks
Liveness Bond Not Received
: The contract assumes that the liveness bond will be received after the hooks are executed. If this does not happen, the contract will revert, potentially causing loss of funds.Centralization Risks
Taiko token
, tier provider
, and proposer
, which could lead to centralization risks.Technical Risks
Unchecked-send
: The contract uses the low-level .sendEther()
function, which can fail silently and cause unpredictable behavior.
Use of block.timestamp
: The contract uses block.timestamp
for various purposes, which can be manipulated by miners to some extent.
Integration Risks
Incorrect Assumptions
: The contract assumes that the external contracts it interacts with will behave as expected. If these contracts behave unexpectedly, it could lead to integration issues.Systemic Risks
Assigned Prover Not Allowed
: The LibProving
contract assumes that the assigned prover will not submit a proof for a block. If this is not the case, the contract will revert.Missing Verifier: The contract assumes that a verifier will always be present for a given tier. If this is not the case, the contract will revert, potentially causing loss of funds.
bool inProvingWindow = uint256(_ts.timestamp).max(_state.slotB.lastUnpausedAt) + _tier.provingWindow * 60 >= block.timestamp; bool isAssignedPover = msg.sender == _blk.assignedProver; // The assigned prover can only submit the very first transition. if (_tid == 1 && _ts.tier == 0 && inProvingWindow) { if (!isAssignedPover) revert L1_NOT_ASSIGNED_PROVER(); } else { // Disallow the same address to prove the block so that we can detect that the // assigned prover should not receive his liveness bond back if (isAssignedPover) revert L1_ASSIGNED_PROVER_NOT_ALLOWED(); }
Centralization Risks
Centralized Control
: The contract has a centralized point of control in the form of the address resolver. The resolver can change the addresses of the Taiko token, tier provider, and verifier contracts, which could lead to centralization risks.Technical Risks
transferFrom
: The contract uses the transferFrom
function to transfer tokens from the message sender's account, which could potentially fail if the sender does not have enough tokens or has not approved the contract to transfer the tokens.Systemic Risks
Unexpected Transition ID
: The LibUtils
contract assumes that transition IDs will be within a certain range. If a transition ID is unexpected, the contract will revert with the L1_UNEXPECTED_TRANSITION_ID
error. This could potentially be exploited if an attacker can manipulate the transition IDs in a way that causes the contract to revert for all users.Centralization Risks
Not obvious Centralization risk
Technical Risks
Invalid Input
: The contract assumes that the input parameters are valid and does not perform extensive input validation. If invalid input is provided, the contract may revert or produce incorrect results.Integration Risks
Incompatible Data Structures: If the data structures in the TaikoData
contract are changed, this contract may need to be updated as well to ensure compatibility.
TaikoData
contract is updated or replaced, this contract may need to be updated as well to maintain compatibility and functionality.Systemic Risks
The init
function can only be called once during contract deployment, and it sets critical state variables like genesisBlockHash
, genesisHeight
, and genesisTimestamp
. If these values are incorrectly set during deployment, the Taiko protocol may not function as intended.
The _isConfigValid
function checks for valid configuration values. If the function logic does not cover all possible edge cases, the contract may be initialized with invalid configuration values.
function _isConfigValid(TaikoData.Config memory _config) private view returns (bool) { if ( _config.chainId <= 1 || _config.chainId == block.chainid // || _config.blockMaxProposals == 1 || _config.blockRingBufferSize <= _config.blockMaxProposals + 1 || _config.blockMaxGasLimit == 0 || _config.blockMaxTxListBytes == 0 || _config.blockMaxTxListBytes > 128 * 1024 // calldata up to 128K || _config.livenessBond == 0 || _config.ethDepositRingBufferSize <= 1 || _config.ethDepositMinCountPerBlock == 0 // Audit recommendation, and gas tested. Processing 32 deposits (as initially set in // TaikoL1.sol) costs 72_502 gas. || _config.ethDepositMaxCountPerBlock > 32 || _config.ethDepositMaxCountPerBlock < _config.ethDepositMinCountPerBlock || _config.ethDepositMinAmount == 0 || _config.ethDepositMaxAmount <= _config.ethDepositMinAmount || _config.ethDepositMaxAmount > type(uint96).max || _config.ethDepositGas == 0 || _config.ethDepositMaxFee == 0 || _config.ethDepositMaxFee > type(uint96).max / _config.ethDepositMaxCountPerBlock ) return false; return true; }
Centralization Risks
_resolver.resolve("tier_provider", false)
function call may return a centralized tier provider, which could lead to centralization risks if the provider has too much control over the system.Technical Risks
The verifyBlocks
function has a while loop that may lead to gas limit issues if the loop iterates too many times.
while (blockId < b.numBlocks && numBlocksVerified < _maxBlocksToVerify) { slot = blockId % _config.blockRingBufferSize; blk = _state.blocks[slot]; if (blk.blockId != blockId) revert L1_BLOCK_MISMATCH(); tid = LibUtils.getTransitionId(_state, blk, slot, blockHash); // When `tid` is 0, it indicates that there is no proven // transition with its parentHash equal to the blockHash of the // most recently verified block. if (tid == 0) break;
The contract uses the transfer
function from the IERC20 contract, which may fail if the recipient contract does not properly handle ERC20 token transfers.
tko.transfer(ts.prover, bondToReturn);
Integration Risks
AddressResolver
contract for resolving contract addresses like the tier provider, signal service, and Taiko token. If the AddressResolver contract is not properly configured or has issues, the Taiko protocol may be affected.Systemic Risks
The contract uses a mapping guardianIds
to store the index of each guardian in the guardians
array. This mapping is not updated when a new set of guardians is set using the setGuardians
function. This could potentially lead to inconsistencies or errors if the new set of guardians includes addresses that were also in the old set.
mapping(address guardian => uint256 id) public guardianIds;
The contract uses a constant (MIN_NUM_GUARDIANS
) to enforce a minimum number of guardians. This constant is set to 5, which could potentially be too low for some use cases, leading to a lack of security or robustness. On the other hand, it could potentially be too high for other use cases, leading to unnecessary complexity or inefficiency.
Centralization Risks
The setGuardians
function is only callable by the owner of the contract (as indicated by the onlyOwner
modifier). This means that the power to change the set of guardians is centralized to a single entity, which could potentially lead to censorship or manipulation.
The contract does not provide a mechanism for adding or removing guardians one at a time. The setGuardians
function requires a completely new set of guardians to be provided each time it is called, which could lead to centralization if the owner of the contract decides to set a small or biased set of guardians.
Technical Risks
The setGuardians
function does not check if the new set of guardians contains the address of the contract itself. This could potentially allow the owner to add the contract as a guardian, which could lead to unintended behavior or vulnerabilities.
The approve
function does not check if the given hash has already been approved. This could potentially allow a guardian to approve the same hash multiple times, which could lead to unintended behavior or vulnerabilities.
Integration Risks
approve
function. If this function is called with a hash that was not generated by the expected process, it could lead to vulnerabilities.Systemic Risks
Block proposing and proving: The contract's functionality of LibProposing
and proving blocks
could be exploited if not properly secured, leading to risks like block manipulation or false proofs.
Pausing functionality
: The contract allows pausing of the proving process. If misused, it could lead to service disruption or other attacks.
Economic model risks
: The contract involves staking Taiko tokens and Ether deposits. If the economic model is not well-designed, it could lead to systemic risks like token devaluation or economic attacks.
Centralization Risks
Admin control
: The contract has an owner/admin
who can pause the proving process. This centralizes control and could potentially be misused.Technical Risks
Not obvious Technical risk
Integration Risks
Compatibility issues
: The contract could face integration issues with other contracts or systems it interacts with, especially during upgrades or changes in those systems.Systemic Risks
Token Minting
: The contract mints a fixed amount of 1 billion tokens at initialization. If there's a need for more tokens in the future, a new contract would have to be deployed, which could lead to compatibility issues and complexities.
Token Transfer Restrictions
: The contract does not seem to have any token transfer restrictions or whitelisting. If such restrictions are needed in the future, they would have to be implemented in a new contract or upgrade.
Token Voting
: The contract uses ERC20VotesUpgradeable
, which means the token is used for voting. If the voting process is not properly secured and managed, it could lead to governance attacks
or manipulation
.
Token Value Dependence
: The value of the TaikoToken
is dependent on the overall performance and adoption of the Taiko protocol. Any issues or negative perceptions about the protocol could lead to a decrease in the token's value.
Token Economic Model
: The token economic model, including minting
, burning
, and distribution policies
, could lead to systemic risks if not properly designed and balanced.
Centralization Risks
Owner Control
: The contract owner has the power to burn tokens, which could lead to centralization risks if misused.
Snapshotter Role
: The contract allows a named role snapshooter
to create token snapshots. If this role is controlled by a single entity, it could lead to centralization risks.
Technical Risks
Not obvious Technical risk
Integration Risks
Compatibility Issues
: The contract could face integration issues with other contracts or systems it interacts with, especially during upgrades or changes in those systems.Systemic Risks
Bonding Requirements
: The contract specifies bonding requirements for different tiers. If these requirements are not properly balanced, they could lead to economic attacks or token devaluation.
if (_tierId == LibTiers.TIER_OPTIMISTIC) { return ITierProvider.Tier({ verifierName: "tier_optimistic", validityBond: 250 ether, // TKO contestBond: 500 ether, // TKO cooldownWindow: 1440, //24 hours provingWindow: 120, // 2 hours maxBlocksToVerifyPerProof: 16 }); } if (_tierId == LibTiers.TIER_GUARDIAN) { return ITierProvider.Tier({ verifierName: "tier_guardian", validityBond: 0, // must be 0 for top tier contestBond: 0, // must be 0 for top tier cooldownWindow: 60, //1 hours provingWindow: 2880, // 48 hours maxBlocksToVerifyPerProof: 16 });
Proving Window
: The contract specifies a proving window for each tier. If the proving window is too short, it could lead to liveness issues. If it's too long, it could lead to delayed finality and increased risk of chain reorgs.
Hardcoded Values
: The contract has hardcoded values for tier parameters. If these values need to be changed in the future, a new contract would have to be deployed, which could lead to compatibility issues and complexities.
Centralization Risks
Owner Control
: The contract has an owner who can potentially control or influence the contract's behavior, leading to centralization risks.Technical Risks
Limited Tiers
: The contract only supports two tiers. If more tiers are needed in the future, the contract would have to be upgraded or replaced.
Max Blocks to Verify Per Proof
: The contract specifies a maximum number
of blocks that can be verified per proof for each tier. If this number is not properly set, it could lead to liveness issues or proof manipulation
.
Integration Risks
Not obvious Integration risk
Systemic Risks
The getMinTier
function uses a random number (_rand
) to determine the minimum tier. If the source of this random number is not truly random or can be manipulated, this could lead to potential security risks or unfair distribution of tiers.
function getMinTier(uint256 _rand) public pure override returns (uint16) { // 0.1% require SGX + ZKVM; all others require SGX if (_rand % 1000 == 0) return LibTiers.TIER_SGX_ZKVM; else return LibTiers.TIER_SGX; }
The revert TIER_NOT_FOUND()
statement in the getTier
function could potentially be used to perform a Denial of Service attack if an attacker can repeatedly trigger this revert, causing the contract to consume all available gas.
Centralization Risks
tier information is hardcoded
in the contract, which means that any changes to the tier parameters
would require a new deployment of the contract. This could lead to centralization, as only the person or entity with the ability to deploy new contracts can make these changes.Technical Risks
Not obvious Technical risk
Integration Risks
initializer
modifier for the init
function, which might not be compatible with all contract deployment frameworks or libraries. This could lead to integration issues when deploying the contract.Systemic Risks:
update
the tier parameters. If these parameters
need to be changed in the future, a new contract would have to be deployed.Centralization Risks:
initialized
with an owner, which could potentially have centralization risks if the owner has special privileges not shown in this contract. The owner could potentially manipulate the contract to their advantage.Technical Risks:
The getTier
function uses a series of if statements to return the tier. If a new tier is added and not accounted for in this function, it could lead to a TIER_NOT_FOUND
revert. This could be mitigated by using a more scalable data structure or function.
The getMinTier
function uses a simple modulo operation to determine the minimum tier. This could lead to predictable results and potential manipulation if the source of randomness (_rand
) is not truly random.
Integration Risks:
The contract assumes that the _rand
parameter in the getMinTier
function is a reliable source of randomness. If this is not the case, the function may not behave as expected.
The contract uses ether
values for bonds. If the contract interacts with other contracts that do not use the same ether values, it could lead to inconsistencies or errors.
Systemic Risks:
The _ethQty
function uses the exp
function from LibFixedPointMath
. This function is known to have precision loss for large inputs. This could potentially lead to inaccurate results in certain situations.
function _ethQty( uint256 _gasExcess, uint256 _adjustmentFactor ) private pure returns (uint256) { uint256 input = _gasExcess * LibFixedPointMath.SCALING_FACTOR / _adjustmentFactor; if (input > LibFixedPointMath.MAX_EXP_INPUT) { input = LibFixedPointMath.MAX_EXP_INPUT; } return uint256(LibFixedPointMath.exp(int256(input))); }
Centralization Risks:
Not obvious Centralization risk
Technical Risks:
The basefee
function divides by _adjustmentFactor
. If this value is 0, the function will revert. However, if this value is very small, it could potentially lead to a large result that could cause issues in other parts of the system.
function basefee( uint256 _gasExcess, uint256 _adjustmentFactor ) internal pure returns (uint256) { if (_adjustmentFactor == 0) { revert EIP1559_INVALID_PARAMS(); } return _ethQty(_gasExcess, _adjustmentFactor) / LibFixedPointMath.SCALING_FACTOR / _adjustmentFactor; }
Integration Risks:
The contract assumes that the _gasExcess
and _adjustmentFactor
parameters are accurate and properly scaled. If these values are not correct, the functions may not behave as expected.
The contract uses the LibFixedPointMath.SCALING_FACTOR
constant for calculations. If other contracts or systems that interact with this contract use a different scaling factor, it could lead to inconsistencies or errors.
Systemic Risks:
The contract uses the block.chainid
to get the current chain ID. If this value is not properly validated, it could potentially lead to issues.
The contract uses the skipFeeCheck
function to determine whether to check the base fee. If this function is overridden in a malicious way, it could potentially lead to issues.
if (!skipFeeCheck() && block.basefee != basefee) { revert L2_BASEFEE_MISMATCH(); }
The contract uses the blockhash
function to get the hash of a block, which could potentially be manipulated by miners.
function getBlockHash(uint64 _blockId) public view returns (bytes32) { if (_blockId >= block.number) return 0; if (_blockId + 256 >= block.number) return blockhash(_blockId); return l2Hashes[_blockId]; }
Centralization Risks:
The contract has an owner, who has special privileges such as the ability to withdraw tokens or Ether from the contract. This could potentially lead to centralization risks if the owner misuses their power.
The anchor
function can only be called by the GOLDEN_TOUCH_ADDRESS
, which could potentially lead to centralization risks if this address is controlled by a single entity.
Technical Risks:
The getConfig
function hardcodes the gasTargetPerL1Block
and basefeeAdjustmentQuotient
values. If these values need to be changed in the future, the contract would need to be updated.
The _calc1559BaseFee
function uses the basefee
function from Lib1559Math
, which could potentially lead to issues if the input parameters are not properly validated.
Integration Risks:
The contract uses the sendEther
and safeTransfer
functions to transfer Ether and tokens, respectively. If these functions are not properly integrated with the rest of the system, it could lead to issues.
if (_token == address(0)) { _to.sendEther(address(this).balance); } else { IERC20(_token).safeTransfer(_to, IERC20(_token).balanceOf(address(this))); }
Systemic Risks:
The contract uses the onlyOwner
modifier for the setConfigAndExcess
function. If the owner's address is compromised, an attacker could potentially change the EIP-1559 configuration and gas excess.
The setConfigAndExcess
function allows the owner to change the EIP-1559 configuration and gas excess at any time. If these values are changed frequently or unexpectedly, it could potentially disrupt the functioning of the system.
The contract does not have any mechanism to prevent the owner from setting an unreasonably high or low gas excess value, which could potentially lead to issues.
Centralization Risks:
setConfigAndExcess
function. This could potentially lead to centralization risks if the owner misuses their power.Technical Risks:
setConfigAndExcess
function checks if the gasTargetPerL1Block
and basefeeAdjustmentQuotient
values are non-zero. However, it does not validate the values beyond this. If the values are not properly validated, it could potentially lead to issues.Integration Risks:
_newConfig
parameter passed to the setConfigAndExcess
function is accurate. If this value is not correct, the function may not behave as expected.Systemic Risks
The contract uses a validSender
modifier for several functions. However, this modifier only checks if the sender is not a zero address. It does not check if the sender is authorized, which may allow unauthorized addresses to call these functions.
modifier validSender(address _app) { if (_app == address(0)) revert SS_INVALID_SENDER(); _; }
The contract uses the assembly
keyword in the _sendSignal
and _loadSignalValue
functions to interact with storage. If the assembly code is not written correctly, it may lead to storage collisions or other unexpected behavior.
Centralization Risks
The contract has an onlyOwner
modifier for the authorize
function. This means that only the contract owner can authorize or deauthorize addresses. This centralizes control and may lead to censorship or single point of failure.
function authorize(address _addr, bool _authorize) external onlyOwner { if (isAuthorized[_addr] == _authorize) revert SS_INVALID_STATE(); isAuthorized[_addr] = _authorize; emit Authorized(_addr, _authorize); }
The contract uses a mapping isAuthorized
to store authorized addresses. If the contract owner's private key is compromised, an attacker can authorize malicious addresses.
mapping(address addr => bool authorized) public isAuthorized;
Technical Risks
The sendSignal
function stores the signal value in storage without any validation checks. If a malicious actor sends a large amount of data as a signal, it could potentially lead to a denial of service attack.
function sendSignal(bytes32 _signal) external returns (bytes32) { return _sendSignal(msg.sender, _signal, _signal); }
The proveSignalReceived
function decodes hopProofs
from the provided proof without any length check. If a malicious actor provides a large proof, it could potentially lead to a denial of service attack.
function proveSignalReceived( uint64 _chainId, address _app, bytes32 _signal, bytes calldata _proof ) public virtual validSender(_app) nonZeroValue(_signal) { HopProof[] memory hopProofs = abi.decode(_proof, (HopProof[])); if (hopProofs.length == 0) revert SS_EMPTY_PROOF(); uint64 chainId = _chainId; address app = _app; bytes32 signal = _signal; bytes32 value = _signal; address signalService = resolve(chainId, "signal_service", false); HopProof memory hop; for (uint256 i; i < hopProofs.length; ++i) { hop = hopProofs[i]; bytes32 signalRoot = _verifyHopProof(chainId, app, signal, value, hop, signalService); bool isLastHop = i == hopProofs.length - 1; if (isLastHop) { if (hop.chainId != block.chainid) revert SS_INVALID_LAST_HOP_CHAINID(); signalService = address(this); } else { if (hop.chainId == 0 || hop.chainId == block.chainid) { revert SS_INVALID_MID_HOP_CHAINID(); } signalService = resolve(hop.chainId, "signal_service", false); } bool isFullProof = hop.accountProof.length > 0; _cacheChainData(hop, chainId, hop.blockId, signalRoot, isFullProof, isLastHop); bytes32 kind = isFullProof ? LibSignals.STATE_ROOT : LibSignals.SIGNAL_ROOT; signal = signalForChainData(chainId, kind, hop.blockId); value = hop.rootHash; chainId = hop.chainId; app = signalService; } if (value == 0 || value != _loadSignalValue(address(this), signal)) { revert SS_SIGNAL_NOT_FOUND(); } }
Integration Risks
Not obvious Integration risk
Systemic Risks
The contract uses a sameChain
modifier for several functions. However, this modifier only checks if the provided chain ID is the same as the current chain ID. It does not check if the chain is enabled, which may allow messages to be processed on a disabled chain.
modifier sameChain(uint64 _chainId) { if (_chainId != block.chainid) revert B_INVALID_CHAINID(); _; }
Lack of Input Validation
: The suspendMessages()
function does not validate the input array length, potentially allowing an attacker to cause a Denial of Service attack by providing an excessively large array.
function suspendMessages( bytes32[] calldata _msgHashes, bool _suspend ) external onlyFromOwnerOrNamed("bridge_watchdog") { uint64 _timestamp = _suspend ? type(uint64).max : uint64(block.timestamp); for (uint256 i; i < _msgHashes.length; ++i) { bytes32 msgHash = _msgHashes[i]; proofReceipt[msgHash].receivedAt = _timestamp; emit MessageSuspended(msgHash, _suspend); } }
Use of block.timestamp
: The contract uses block.timestamp
in several places, which can be manipulated by miners to some extent. This could lead to potential issues in functions like banAddress()
and recallMessage()
where the timestamp is used for logic related to banning addresses or message recall.
Incorrect Error Handling: In the recallMessage()
function, the contract checks if a message has been sent by calling isSignalSent()
. If the message has not been sent, the contract should revert with an appropriate error message. However, the contract does not revert in this case, potentially leading to unexpected behavior.
function recallMessage( Message calldata _message, bytes calldata _proof ) external nonReentrant whenNotPaused sameChain(_message.srcChainId) { ...
Centralization Risks
The contract has an onlyFromOwnerOrNamed
modifier for the suspendMessages
and banAddress
functions. This means that only the contract owner or a specific named address can suspend messages or ban addresses. This centralizes control and may lead to censorship or single point of failure.
The contract uses a mapping addressBanned
to store banned addresses. If the contract owner's private key is compromised, an attacker can ban or unban addresses.
Technical Risks
The sendMessage
function stores the message in storage without any validation checks. If a malicious actor sends a large amount of data as a message, it could potentially lead to a denial of service attack.
The recallMessage
and processMessage
functions decode proof from the provided data without any length check. If a malicious actor provides a large proof, it could potentially lead to a denial of service attack.
Integration Risks
Not obvious Integration risk
Systemic Risks
The contract has a BTOKEN_CANNOT_RECEIVE
error, which is used to prevent the contract from receiving tokens. However, this error is only checked in the _beforeTokenTransfer
function, which means that it may still be possible to send tokens to the contract in certain circumstances.
function _beforeTokenTransfer( address _from, address _to, uint256 _amount ) internal override(ERC20Upgradeable, ERC20SnapshotUpgradeable) { if (_to == address(this)) revert BTOKEN_CANNOT_RECEIVE(); if (paused()) revert INVALID_PAUSE_STATUS(); super._beforeTokenTransfer(_from, _to, _amount); }
The contract does not have a mechanism for recovering lost or stolen tokens. If a user loses access to their tokens or their tokens are stolen, there is no way to recover them.
The contract does not have a mechanism for handling token transfers that are front-run or sandwiched by malicious actors. If a token transfer is front-run or sandwiched, the user may lose their tokens or pay a higher fee than necessary.
Centralization Risks
The contract has an onlyOwner
modifier for the setSnapshoter
function. This means that only the contract owner can set the snapshoter address, which centralizes control.
The contract has an onlyOwnerOrSnapshooter
modifier for the snapshot
function. This means that only the contract owner or the snapshoter can create a new token snapshot, which centralizes control.
Technical Risks
The contract uses the ERC20SnapshotUpgradeable
and ERC20VotesUpgradeable
contracts to provide snapshot and voting functionalities. If there are any vulnerabilities in these contracts, this contract may also be affected.
The contract uses the ERC20Permit
contract to allow token holders to permit other addresses to spend their tokens. If there are any vulnerabilities in the ERC20Permit
contract, this contract may also be affected.
Integration Risks
Systemic Risks
Minting restrictions
: Minting is disallowed while migrating outbound, which could lead to issues if the migration process takes longer than expected or if the migration status is changed unexpectedly.
Outbound migration permissions
: During outbound migration, only the token owner can call the burn
function. This restriction could lead to issues if a user loses access to their tokens or if the migration process is disrupted.
Centralization Risks
Owner control
: The contract's owner has the power to change the migration status and address, potentially allowing them to manipulate the token migration process.
Vault control
: The "erc20_vault" contract has special permissions in the 'mint' and 'burn' functions. If the vault contract is compromised, it could lead to unauthorized token minting or burning.
Technical Risks
Not obvious Technical risk
Integration Risks
Migration process
: The contract's primary function is to facilitate token migration between different contracts. Any issues in the integration with the source or destination contracts could result in token loss or other issues.Systemic Risks
The contract does not have a mechanism to ensure that the mint
and burn
functions are called only when the corresponding actions are performed on the source chain. This could lead to token supply inconsistencies between the source and destination chains.
function mint( address _account, uint256 _tokenId ) public nonReentrant whenNotPaused onlyFromNamed("erc721_vault") { _safeMint(_account, _tokenId); } /// @dev Burns tokens. /// @param _account Address from which the token is burned. /// @param _tokenId ID of the token to burn. function burn( address _account, uint256 _tokenId ) public nonReentrant whenNotPaused onlyFromNamed("erc721_vault") { // Check if the caller is the owner of the token. if (ownerOf(_tokenId) != _account) { revert BTOKEN_INVALID_BURN(); } _burn(_tokenId); }
The BridgedERC721
contract does not have a built-in mechanism to handle token metadata changes on the source chain. This could lead to outdated or inconsistent token URIs on the destination chain.
Centralization Risks
onlyFromNamed
modifier is used in the mint
and burn
functions, restricting these actions to be called only by an address named "erc721_vault". Centralization risks may arise if this address has too much control over the token supply or if it becomes compromised.Technical Risks
Not obvious Technical risk
Integration Risks
bridge ERC721
tokens across different chains. Integration risks may arise when interacting with various token contracts, especially if they have different implementations or contain vulnerabilities.Systemic Risks
The contract does not have a mechanism to ensure that the mint
, mintBatch
, and burn
functions are called only when the corresponding actions are performed on the source chain. This could lead to token supply inconsistencies between the source and destination chains.
The contract uses placeholder data foo
for the symbol and name validation in the validateInputs
function. This could lead to unintended consequences if the function is updated in the future or if it has side effects that depend on the input data.
LibBridgedToken.validateInputs(_srcToken, _srcChainId, "foo", "foo");
The contract does not have a built-in mechanism to handle token metadata changes on the source chain. This could lead to outdated or inconsistent token URIs on the destination chain.
Centralization Risks
onlyFromNamed
modifier is used in the mint
, mintBatch
, and burn
functions, restricting these actions to be called only by an address named "erc1155_vault". Centralization risks may arise if this address has too much control over the token supply or if it becomes compromised.Technical Risks
Not obvious Systemic risk
Integration Risks
Systemic Risks
Not obvious Systemic risk
Centralization Risks
The onlyFromBridge
modifier is used in the checkProcessMessageContext
and checkRecallMessageContext
functions, restricting these actions to be called only by the address resolved as "bridge". Centralization risks may arise if this address has too much control over the vault or if it becomes compromised.
modifier onlyFromBridge() { if (msg.sender != resolve("bridge", false)) { revert VAULT_PERMISSION_DENIED(); } _; }
Technical Risks
Not obvious Technical risk
Integration Risks
Systemic Risks
Bridged token deployment
: The contract deploys new bridged tokens when required, but this process might be vulnerable to attacks or failures if not properly managed. For example, a malicious user could try to deploy a fake bridged token contract with the same address.
Unknown token contracts
: The ERC1155Vault
contract interacts with arbitrary token contracts, which could have malicious code or vulnerabilities that could be exploited to steal funds or disrupt the system. It's important to ensure that only trusted and verified token contracts are used with this contract.
Unvalidated refundTo
address: The contract does not validate the refundTo
address provided in the BridgeTransferOp
struct. If an attacker provides a malicious contract address as the refundTo
address, it could lead to unintended consequences, such as reentrancy attacks or loss of funds.
Unvalidated destOwner
address: The contract allows the destOwner
address to be the same as the msg.sender
if the _op.destOwner
is not provided. This could lead to confusion and potential misdirection of funds if the user unintentionally provides an incorrect destOwner
address.
Lack of rate limits: The contract does not impose any rate limits on the sendToken
, onMessageInvocation
, or onMessageRecalled
functions. This could make the contract vulnerable to spamming or denial-of-service attacks.
Misuse of abi.encodeCall
: The contract uses abi.encodeCall
to encode the call data for the msgData_
variable. If the function signature or parameters are not correctly specified, it could lead to incorrect call data and potential failures in the contract's logic.
Centralization Risks
Centralized bridge contract
: The bridge contract used for interoperability between chains could become a central point of control and failure if it's not properly decentralized.Technical Risks
Incorrect handling of ERC1155 tokens
: The contract assumes that all tokens that support the ERC1155 interface follow the standard correctly. However, some tokens might have custom implementations that could lead to unexpected behavior.Integration Risks
Incorrect bridge contract integration
: If the bridge contract is not properly integrated with this contract, it could result in the loss or incorrect transfer of tokens between chains.Systemic Risks
Blacklisting bridged tokens
: The contract has a blacklist mechanism for bridged tokens, but the process for adding or removing tokens from the blacklist is not specified. This could lead to tokens being incorrectly blacklisted or remaining blacklisted even after issues have been resolved.
Canonical token mismatch
: The changeBridgedToken
function checks for canonical token mismatch using keccak256 hashes of symbol and name. However, this approach might not be foolproof, as two different strings could have the same hash, although the probability is extremely low.
Decimal mismatch
: The contract does not validate the decimals of the canonical token when burning or minting tokens. A mismatch in decimals could lead to incorrect token amounts being transferred.
Centralization Risks
onlyOwner
modifier for the changeBridgedToken
function, which means only the owner can change the bridged token. This centralizes control over the contract and could lead to censorship or misuse.Technical Risks
Not obvious Technical risk
Integration Risks
Incompatible token contracts
: If the contract interacts with ERC20 or IBridgedERC20 contracts that have non-standard implementations, it could lead to unexpected behavior or token loss.
Bridge contract integration
: If the bridge contract's interface or behavior changes, it could break the integration with the ERC20Vault contract, leading to potential loss of tokens or other issues.
Systemic Risks
Mismatched token IDs
: When transferring ERC721
tokens, the contract assumes that the token IDs
are unique and valid. If there is a mismatch between the token IDs on the source and target chains or if an invalid token ID is used, it could lead to the loss or theft of tokens.
Token duplication
: If there is an issue with the deployment of the BridgedERC721
contract, it could potentially lead to the duplication of tokens, which would have serious consequences for the integrity and value of the tokens on both the source and target chains.
Unvalidated input data
: The ERC721Vault contract does not perform extensive validation of input data
, such as token IDs
or addresses
. This could lead to unexpected behavior, security risks, or loss of tokens if invalid data is provided.
Centralization Risks
Centralized control over the vault
: The ERC721Vault contract has an onlyOwner
modifier for certain functions, which means that only the owner of the contract can perform specific actions. This centralization of control could lead to potential abuse or mismanagement.Technical Risks
Unverified contracts
: The ERC721Vault
contract does not verify the authenticity or security of the contracts it interacts with. This means that it could potentially interact with malicious or compromised contracts, leading to potential loss or theft of tokens.Integration Risks
Compatibility issues
: The ERC721Vault contract assumes that the ERC721 tokens it interacts with adhere to the ERC721 standard. However, some tokens may have custom implementations or extensions that are not compatible with the ERC721Vault
contract, potentially leading to unexpected behavior.Systemic Risks
Incorrect keccak256 comparison
: The equal
function compares two byte arrays by comparing their keccak256 hashes. This approach assumes that there are no hash collisions, which is highly unlikely but theoretically possible. In the rare case of a hash collision, the function would return true even though the byte arrays are not equal.
Incorrect input handling: The toNibbles
function assumes that the input byte array is well-formed and does not contain any invalid data. If the input byte array contains unexpected data, the function might produce an incorrect output or behave unexpectedly.
Memory management
: The contract uses manual memory management in assembly code, which could lead to potential errors or vulnerabilities if not handled correctly. For example, memory overlaps, underflows, or overflows could occur if memory pointers are not properly managed.
Centralization Risks
Not obvious Centralization risk
Technical Risks
Input validation
: The contract doesn't perform extensive input validation for its functions. This could lead to unexpected behavior or errors if invalid inputs are provided.Integration Risks
Not obvious Integration risk
Systemic Risks
Incorrect decoding of RLP items
: The contract's _decodeLength
function is responsible for decoding the length of RLP items. If this function has any errors or edge cases that are not properly handled, it could lead to incorrect decoding of RLP items.Centralization Risks
Not obvious Centralization risk
Technical Risks
Incorrect input handling
: The contract assumes that the input byte arrays are correctly RLP-encoded
. If the input is malformed or not properly encoded, it could lead to unexpected behavior or errors.
Limited list length
: The contract has a maximum list length of 32 items. If a longer list is encountered, it could lead to truncation or incorrect parsing of the data.
Integration Risks
Compatibility issues
: The contract may not be compatible with other contracts or systems that use different encoding standards or formats for byte arrays.Systemic Risks
The contract does not perform any checks on the provided _root
parameter. If an incorrect root is provided, the contract will still execute without any errors, potentially leading to incorrect results or false positives in proof verification.
The contract assumes that the Merkle trie
used for proofs follows the Ethereum state trie structure. However, if the trie is constructed differently (e.g., using different node types or encoding schemes), the contract may not function correctly and could lead to incorrect results or errors.
The contract's get
function does not handle the case where a key exists in the trie but its value is set to an empty byte array (bytes(0)
). In such cases, the function will revert with an error, even though the key is technically present in the trie. This behavior might not be desirable for some use cases.
function get( bytes memory _key, bytes[] memory _proof, bytes32 _root ) internal pure returns (bytes memory value_) { require(_key.length > 0, "MerkleTrie: empty key"); TrieNode[] memory proof = _parseProof(_proof); bytes memory key = Bytes.toNibbles(_key); bytes memory currentNodeID = abi.encodePacked(_root); uint256 currentKeyIndex = 0; // Proof is top-down, so we start at the first element (root). for (uint256 i = 0; i < proof.length; i++) { TrieNode memory currentNode = proof[i];
verifyInclusionProof
function relies solely on the equality of the provided _value
and the value obtained from the trie using the get
function. If the provided _value
is incorrect or tampered with, the function will return an incorrect result. This risk can be mitigated by ensuring that the _value
parameter is obtained from a trusted source.function verifyInclusionProof( bytes memory _key, bytes memory _value, bytes[] memory _proof, bytes32 _root ) internal pure returns (bool valid_) { valid_ = Bytes.equal(_value, get(_key, _proof, _root)); }
The contract's _getSharedNibbleLength
function uses a loop to iterate through the nibbles of two byte arrays, which could be inefficient for large arrays and lead to high gas consumption. An alternative implementation using assembly or a more efficient algorithm could help mitigate this issue.
function _getSharedNibbleLength( bytes memory _a, bytes memory _b ) private pure returns (uint256 shared_) { uint256 max = (_a.length < _b.length) ? _a.length : _b.length; for (; shared_ < max && _a[shared_] == _b[shared_];) { unchecked { ++shared_; } } }
Centralization Risks
Not obvious Centralization risk
Technical Risks
The contract assumes a hexary trie (with a radix of 16), which may not be suitable for all use cases. If the contract is used with a trie of a different radix, it could lead to incorrect behavior.
The verifyInclusionProof
and get
functions do not have any bounds checking for the _proof
array. If an excessively large proof is provided, it could lead to high gas consumption or out-of-gas errors.
The contract uses the bytes
type for some function parameters and return values, which can be inefficient in terms of gas consumption and storage. Using bytes32
or other fixed-size types might be more appropriate, depending on the use case.
Integration Risks
Systemic Risks
Misuse of attestation: The
SgxVerifier` contract checks if an address has already been attested to prevent bypassing the contract's expiry check. However, it is possible to register old addresses during proving, which could be misused.
Misuse of instance replacement
: The contract allows replacing an instance, which could be misused if not properly controlled. For example, an attacker could replace an instance with a malicious one if they have control over the old instance's address
.
Misuse of instance validity dela
y: The SgxVerifier
has a delay until an instance is enabled when using onchain
RA verification. This delay could be misused by an attacker to perform a denial-of-service attack by repeatedly registering instances.
Centralization Risks
Owner control
: The contract has an owner who can add or delete instances. This centralizes control and could lead to censorship or manipulation if the owner's account is compromised.Technical Risks
Unhandled exceptions
: The contract does not have error handling for all external calls
. If an external call fails, it could cause the contract to revert or behave unexpectedly.Integration Risks
Incorrect data formats
: The contract expects data in specific formats, such as the V3Struct.ParsedV3QuoteStruct
for attestation and the TaikoData.Transition
for proof verification. If the data is not in the correct format, it could lead to errors or unexpected behavior.Systemic Risks
claim period
: The contract allows users to claim the airdrop only during the claim period. This could be misused by an attacker who manipulates the blockchain's time to claim the airdrop outside of the claim period.Centralization Risks
Owner control
: The contract has an owner who can control the contract's functionality. This centralizes control and could lead to censorship or manipulation if the owner's account is compromised.Technical Risks
Not obvious Technical risk
Integration Risks
Not obvious Integration risk
Systemic Risks
claimedAmount and withdrawnAmount mappings
: The contract stores the claimed amount and withdrawn amount for each user in mappings. This could be misused by an attacker who manipulates these mappings to steal tokens from other users or to claim more tokens than they are eligible for.Centralization Risks
Owner control
: The contract has an owner who can control the contract's functionality. This centralizes control and could lead to censorship or manipulation if the owner's account is compromised.Technical Risks
Not obvious Technical risk
Integration Risks
Not obvious Integration risk
Systemic Risks
Not obvious Systemic risk
Centralization Risks
setConfig function
: The contract's setConfig
function is only callable by the owner. This could be misused by the owner to change the configuration parameters in a way that benefits themselves or harms other users.
function setConfig( uint64 _claimStart, uint64 _claimEnd, bytes32 _merkleRoot ) external onlyOwner { _setConfig(_claimStart, _claimEnd, _merkleRoot); }
Technical Risks
Not obvious Systemic risk
Integration Risks
MerkleProof library
: The contract uses the MerkleProof
library to verify merkle proofs. This library could have vulnerabilities or be compromised, which could affect the contract's functionality.Systemic Risks
The _voidGrant
function sets the grantStart
and grantPeriod
of a grant to 0 when voiding the grant. This could potentially cause confusion or unexpected behavior if these fields are used elsewhere in the system.
The _calcAmount
function has complex logic involving multiple parameters (_amount
, _start
, _cliff
, _period
). This complexity could lead to unexpected behavior or bugs if not properly understood or implemented.
Centralization Risks
onlyOwner
modifier for the grant
and void
functions. This means that only the contract owner can grant tokens or void grants. This centralization of power could lead to misuse or abuse.Technical Risks
The _calcAmount
function uses block.timestamp
to calculate the amount of tokens that can be withdrawn. However, block.timestamp
can be manipulated by miners to a certain extent, which could lead to unexpected behavior.
The ECDSA.recover
function used in the withdraw
function could potentially be vulnerable to replay attacks if the same signature is used twice.
Integration Risks
Not obvious Integration risk
Systemic Risks
sha256
function for hashing data, but it does not include a salt or nonce. This could potentially lead to hash collisions or pre-image attacks.Centralization Risks
msg.sender
as the owner during initialization. If the owner's account is compromised, an attacker could gain control over the contract.Technical Risks
block.timestamp
for certificate validity checks. However, block.timestamp
can be manipulated by miners to a certain extent, potentially leading to security issues.Integration Risks
Not obvious Integration risk
Systemic Risks
Not obvious Systemic risk
Centralization Risks
Not obvious Centralization risk
Technical Risks
The contract uses complex parsing and decoding logic, which could lead to potential bugs or errors in the processing of certificates.
The contract assumes that the input data is well-formed and does not perform extensive input validation, which could lead to unexpected behavior or errors.
Integration Risks
Systemic Risks
decode
' function of the 'Base64' library to decode base64 encoded strings. If the input strings are not base64 encoded, the function could fail or behave unexpectedly.Centralization Risks
Not obvious Centralization risk
Technical Risks
Not obvious Technical risk
Integration Risks
Incorrect Assumptions
: The contract assumes that the input quote will always be larger than MINIMUM_QUOTE_LENGTH
and that the quote length minus 436
will always equal localAuthDataSize
. If these assumptions are incorrect, the contract could fail or behave unexpectedly.Systemic Risks
The specific parsing and handling of cryptographic elements (exponents, moduli, signatures) in functions like verifyRS256Signature
, verifyRS1Signature
, verifyES256Signature
introduce unique risks related to cryptographic implementation correctness and efficiency.
The reliance on specific cryptographic algorithms and formats may limit interoperability or compatibility with systems using different standards or protocols.
Centralization Risks
ES256VERIFIER
address for verifying ES256 signatures, which introduces centralization risks if this verifier is controlled or compromised by a single entity.Technical Risks:
Potential vulnerabilities in signature parsing and verification logic (verifyRS256Signature
, verifyRS1Signature
, verifyES256Signature
) could lead to signature forgery or unauthorized access.
Incomplete input validation in functions like verifyES256Signature
(e.g., not checking all possible edge cases) may lead to unexpected behavior or vulnerabilities.
Integration Risks:
Not obvious Integration risk
File Name | Core Functionality | Technical Characteristics | Importance and Management |
---|---|---|---|
EssentialContract.sol | This EssentialContract contract defines essential functionality such as pausing and unpausing operations, managing ownership, and enforcing access control through modifiers. | It uses OpenZeppelin libraries like UUPSUpgradeable , Ownable2StepUpgradeable , and AddressResolver , implements modifiers for reentrancy checks, and leverages assembly code for efficiency in certain storage operations. | These functionalities are crucial for managing contracts securely, controlling critical operations, and ensuring that certain functions can only be executed by authorized entities, enhancing the contract's robustness and security.Management: The contract manages ownership transfer, upgrade authorization, pause/unpause states, and address resolution, providing a structured approach to contract governance and control over critical contract functions. |
LibTrieProof.sol | The contract provides a library LibTrieProof that allows verifying Merkle proofs for storage slots in Ethereum accounts, ensuring data integrity and security. | It leverages third-party libraries like RLPReader , RLPWriter , and SecureMerkleTrie for parsing RLP-encoded data and performing Merkle proof verification , enhancing the contract's functionality and security. | This library is essential for applications requiring secure verification of storage values and ensuring that data accessed from Ethereum accounts is valid and tamper-proof , thereby contributing to the overall integrity and reliability of blockchain-based systems.Management: The contract manages the verification process for Merkle proofs, handling errors such as invalid account proofs or inclusion proofs, ensuring that only valid and verified data is accepted, which is critical for maintaining the integrity of smart contract interactions. |
LibDepositing.sol | The LibDepositing library provides functions for depositing Ether to Layer 2 in the Taiko protocol, managing deposits, processing them in batches , and ensuring deposit limits and fees are adhered to. | It uses the LibAddress and LibMath libraries for address and math operations, incorporates storage management techniques such as ring buffers for deposits, and efficiently processes multiple deposits in a batched manner to optimize gas usage. | This library is crucial for the Taiko protocol as it handles the crucial task of depositing Ether to Layer 2, ensuring that deposits are within specified limits, calculating fees accurately, and managing deposit processing to maintain protocol efficiency and integrity.Management: The library manages the depositing process , including checking deposit limits , processing deposits in batches , encoding deposit data for storage, and emitting events to track deposit activities, contributing to effective and secure deposit handling within the Taiko protocol. |
LibProposing.sol | The LibProposing library facilitates block proposals in the Taiko protocol, handling various aspects such as validating proposers, managing block metadata, processing deposits, and executing hooks. | It uses several libraries and interfaces such as LibAddress , IAddressResolver , ITierProvider , and IERC20 for address handling, resolution, tier determination, and ERC20 token operations, respectively. The library also employs data encoding and decoding techniques, blob caching mechanisms, and checks for valid block parameters and configurations. | This library plays a crucial role in the Taiko protocol by enabling permissionless block proposals , ensuring integrity through metadata and hash validations, managing hooks for additional functionalities, processing deposits , and handling token balances, contributing to the protocol's security, functionality, and performance.Management: The library manages block proposal logic, including checks for valid proposers , processing and caching blobs , handling metadata and block data, executing hooks securely, verifying token balances, and emitting events to track block proposal activities, ensuring efficient and secure operation within the Taiko protocol. |
LibProving.sol | The LibProving library provides functionality for handling block contestation and proving within the Taiko protocol. It includes methods for proving or contesting block transitions, pausing or unpausing the proving process, and managing transition states. | This library uses various Solidity features such as storage variables , structs (TaikoData), events (TransitionProved, TransitionContested, ProvingPaused), error handling (revert statements with custom errors), and interfaces (IERC20 , IAddressResolver , IVerifier , ITierProvider ). It also employs mathematical operations (LibMath ) and integrates with other contracts through imports and function calls. | The LibProving library plays a crucial role in the Taiko protocol by ensuring the integrity and validity of block transitions through proof verification and contestation. It manages transition states, handles tier-based proofs, and enforces rules for proving and contesting transitions, which are fundamental for the security and reliability of the protocol.Management: The library manages transition states within the TaikoData storage, interacts with external contracts such as verifiers and tier providers via the IAddressResolver, and handles token transfers using IERC20 for managing rewards and bonds. It also manages the pausing and unpausing of the proving process through the ProvingPaused event and pauseProving function. Overall, it provides a structured and controlled environment for proving and contesting block transitions within the Taiko protocol. |
LibVerifying.sol | The LibVerifying library handles block verification within the Taiko protocol, including initializing the protocol state, verifying blocks, and syncing chain data. | This library utilizes Solidity features such as storage variables (TaikoData ), events (BlockVerified ), error handling (revert statements with custom errors), interfaces (IERC20 , IAddressResolver , ISignalService , ITierProvider ), mathematical operations (LibMath ), and external calls to other contracts (_syncChainData , getSyncedChainData , syncChainData ). | The LibVerifying library is crucial for ensuring the integrity and correctness of the Taiko protocol by verifying blocks, validating transitions, and syncing chain data. It plays a fundamental role in maintaining consensus and security across the protocol's layers.Management: This library manages protocol-level states such as block verification status (lastVerifiedBlockId ), handles interactions with external contracts (IERC20 , ISignalService , ITierProvider ), and implements logic for validating configurations (_isConfigValid ). It also emits events (BlockVerified ) to notify about block verifications and coordinates the verification process based on specified criteria and thresholds. Overall, it provides essential functionalities for managing and maintaining the Taiko protocol's operational integrity. |
Lib1559Math.sol | The Lib1559Math library implements a bonding curve based on the mathematical function e^(x) for EIP-1559 , specifically for calculating the base fee in Ethereum transactions. | This library uses mathematical functions like exponential calculation _ethQty based on gas parameters and adjustment factors. It imports LibFixedPointMath from a third-party library to perform fixed-point arithmetic operations necessary for accurate fee calculations. | The Lib1559Math library is crucial for implementing the fee calculation mechanism proposed in EIP-1559 for Ethereum transactions. It helps determine the base fee dynamically based on network demand, aiming to achieve better fee predictability and transaction inclusion.Management: This library manages mathematical computations related to fee calculation, ensuring that the base fee calculation follows the EIP-1559 specifications and handles exceptional cases such as invalid parameters through error handling EIP1559_INVALID_PARAMS . It provides a foundational component for managing transaction fees in a more efficient and predictable manner on the Ethereum network. |
TaikoL2.sol | The TaikoL2 contract is designed to handle cross-layer message verification and manage EIP-1559 gas pricing for Layer 2 operations. It anchors the latest L1 block details to L2 for cross-layer communication, manages EIP-1559 parameters for gas pricing, and stores verified L1 block information. | This contract leverages various Solidity features and libraries such as SafeERC20 for safe token transfers, LibAddress for address-related operations, LibMath for mathematical calculations, and Lib1559Math for EIP-1559 base fee calculations using mathematical functions like exponential computation. | The TaikoL2 contract plays a crucial role in bridging communication between Layer 1 and Layer 2 of the Ethereum network, ensuring accurate gas pricing through EIP-1559 parameters, and maintaining synchronization of block details between the two layers for efficient transaction processing and state management.Management: This contract manages several key functionalities including anchoring L1 block details to L2, verifying base fees using EIP-1559 calculations, handling token withdrawals, and managing ownership and access control through inheritance from CrossChainOwned. It incorporates error handling for various scenarios such as invalid parameters, sender verification, and base fee mismatches, ensuring the integrity and security of its operations. |
SignalService.sol | The SignalService contract facilitates signal propagation and chain data synchronization across different chains by managing signals , authorizing addresses for data syncing, and verifying Merkle proofs to ensure data integrity and authenticity during synchronization processes. | This contract employs mappings to store top block IDs and authorized addresses, utilizes error handling mechanisms for various invalid states and unauthorized access , and integrates with essential libraries like LibTrieProof for verifying Merkle proofs and SafeCast for safe type conversions. It also implements modifiers to ensure valid senders and non-zero values for critical operations. | The SignalService contract plays a vital role in maintaining the integrity and synchronization of chain data across multiple chains within a decentralized ecosystem. It provides a secure mechanism for signaling and syncing data, enabling cross-chain communication and interoperability, which are crucial aspects of blockchain scalability and usability.Management: This contract is managed through functions such as initialization for contract setup, authorization of addresses for data syncing, sending signals and syncing chain data, verifying Merkle proofs for signal integrity, and handling error conditions to maintain contract security and reliability. It includes emit events for tracking authorized actions and synced data, enhancing transparency and auditability within the system. |
Bridge.sol | The Bridge contract facilitates cross-chain message passing by managing message statuses, proofs , and handling message execution and recalls based on specified conditions. It provides methods like sendMessage , recallMessage , processMessage , and retryMessage to manage message execution across different chains, allowing messages to be sent, recalled, processed, or retried based on certain criteria. | The contract imports various libraries and interfaces such as Address, EssentialContract , LibAddress , ISignalService , and IBridge .It employs storage mappings (messageStatus , addressBanned , proofReceipt ) to store message status, banned addresses , and proof receipts, respectively .The contract utilizes modifiers like sameChain to validate chain IDs and various error states to handle exceptional conditions during message processing.It implements functions for message sending , recalling, processing , retrying , and handling message statuses and proofs using merkle inclusion proofs and signals . | The Bridge contract plays a crucial role in interoperability between different blockchain networks by enabling the transfer of messages and data securely and efficiently.It ensures that messages are processed correctly based on predefined conditions, manages message statuses to prevent duplicate or unauthorized executions, and handles proofs to verify message authenticity and execution.By providing methods to suspend messages, ban addresses, and manage message retries, the contract enhances the reliability and security of cross-chain communication, reducing potential risks and ensuring smooth message execution. |
BridgedERC20.sol | The contract BridgedERC20 is an upgradeable ERC20 token contract designed to represent tokens bridged from another chain. It includes functionalities such as token initialization , snapshot creation , metadata retrieval , and token transfer management. | This contract imports various libraries and contracts from OpenZeppelin , including IERC20MetadataUpgradeable , Strings , ERC20SnapshotUpgradeable , and ERC20VotesUpgradeable . It implements specific functions such as init, setSnapshoter , snapshot , and internal token minting/burning functions. | The importance of BridgedERC20.sol contract lies in its ability to facilitate the representation and management of tokens bridged from another blockchain within the Ethereum ecosystem. It provides essential features like metadata handling , snapshot creation for governance purposes, and token transfer logic while maintaining upgradability.The contract includes a snapshooter address that can be set by the owner, allowing designated entities to create token snapshots . Ownership and authorization are managed through modifiers like onlyOwner and onlyOwnerOrSnapshooter , ensuring controlled access to critical functions like snapshot creation and token transfers. |
BridgedERC20Base.sol | The contract BridgedERC20Base serves as an abstract base contract for bridged ERC20 tokens, implementing functionalities related to token migration, minting , burning , and ownership management. | This Solidity contract imports EssentialContract and IBridgedERC20 interfaces and includes internal functions such as _mintToken , _burnToken , and _isMigratingOut for token management during migration processes. | The contract's importance lies in its role as a foundational component for managing bridged ERC20 tokens, enabling features like changing migration status, minting and burning tokens during migration, and ensuring ownership integrity through access control mechanisms.Management aspects are handled through functions like changeMigrationStatus for updating migration status, mint and burn functions with appropriate checks during migration scenarios, and ownership retrieval via the owner function inherited from OwnableUpgradeable. Access control is enforced through modifiers like nonReentrant , whenNotPaused , and onlyFromOwnerOrNamed ("erc20_vault"). |
BridgedERC721.sol | The contract BridgedERC721 facilitates the bridging of ERC721 tokens between different blockchain networks by allowing minting , burning , and querying token-related information. | It is utilizes OpenZeppelin's ERC721Upgradeable and Strings libraries, implements essential contract functionalities, and employs a bridging mechanism via LibBridgedToken. | This contract is crucial for interoperability between blockchain networks as it enables the transfer and management of ERC721 tokens across different chains, enhancing token utility and accessibility.The contract incorporates access control mechanisms such as pausing functionality and permission-based minting and burning operations, ensuring secure and controlled token management. Additionally, it defines specific error cases to handle exceptional conditions like invalid burns or token transfers to the contract itself. |
BridgedERC1155.sol | The contract facilitates the bridging of ERC721 tokens across different chains by allowing minting and burning of tokens according to specified rules. | It implements the ERC721 standard, utilizing OpenZeppelin's ERC721Upgradeable contract and Strings library for string manipulation. The contract also utilizes upgradability patterns for potential future enhancements. | This contract plays a crucial role in interoperability between different blockchain networks by enabling the transfer of NFTs across chains, which is essential for maintaining liquidity and utility of NFT assets.Management: The contract is managed by an essential contract (EssentialContract ) and implements access control mechanisms, allowing only designated addresses such as erc721_vault to mint and burn tokens. It also incorporates a pause mechanism to control contract operations during certain conditions, enhancing security and operational control. |
BaseNFTVault.sol | The contract serves as an abstract base for bridging NFTs across different blockchain networks, facilitating token transfers and management. | It includes mappings and structs to store information about bridged NFTs and their canonical counterparts, as well as structs to represent bridged token transfer operations. Additionally, it defines interface IDs for ERC1155 and ERC721 standards and includes event logging for token-related actions. | This contract plays a critical role in enabling cross-chain interoperability for NFTs , allowing users to transfer NFTs between different blockchain networks, which is essential for expanding the utility and accessibility of NFT assets.Management: The contract implements modifiers and error handling to ensure the validity of token transfer operations, including checks for token array mismatches, maximum token transfer limits, and the validity of token addresses. It also emits events to provide transparency and track token-related actions on the contract. |
BaseVault.sol | This abstract contract serves as a base implementation for vaults , providing functionality for initialization, interface support checking , and context validation for processing and recalling messages from a bridge. | Includes inheritance from OpenZeppelin's upgradeable contracts for essential contract functionalities and ERC165 for interface support checking. The contract also employs modifiers for access control and error handling. | The contract is crucial for creating vaults that interact with bridges to facilitate cross-chain communication and asset transfers. It ensures that only authorized entities, such as bridges , can interact with the vault , enhancing security and integrity in cross-chain operations.Management: Access to critical functions is restricted through the 'onlyFromBridge' modifier, which verifies that the caller is the designated bridge contract. Error handling ensures that unauthorized access attempts are reverted, maintaining the integrity and security of the vault operations. Additionally, the contract provides an initialization function for setting the owner and address manager, allowing for proper configuration upon deployment. |
ERC1155Vault.sol | The contract acts as a vault for ERC1155 tokens, facilitating their deposit , withdrawal , and transfer across different blockchain networks via a bridge . It also manages the mapping between canonical ERC1155 tokens and their bridged counterparts. | It imports OpenZeppelin's ERC1155 contracts for token handling and ERC1155ReceiverUpgradeable for receiving ERC1155 tokens. Additionally, it uses an interface for ERC1155 contracts with name() and symbol() functions and incorporates proxy deployment using ERC1967Proxy for bridged token deployment. | The contract plays a crucial role in enabling cross-chain token transfers by acting as a centralized repository for ERC1155 tokens and facilitating their conversion to bridged tokens for interoperability across disparate blockchain networks. Its ability to deploy and manage bridged token contracts enhances the scalability and accessibility of tokenized assets across multiple chains.Management: Access to critical functions like token transfer and message handling is restricted to authorized users, ensuring secure and controlled token management. The contract maintains mappings to track canonical and bridged tokens, providing transparency and auditability in token handling operations. Events are emitted to notify stakeholders about token transfers and contract deployments, enhancing visibility and accountability in the token bridging process. |
ERC20Vault.sol | The contract serves as a vault for ERC20 tokens, excluding Ether, allowing users to deposit , withdraw , and transfer these tokens between different blockchain networks via a bridge. It also manages the mapping between canonical ERC20 tokens and their bridged counterparts. | It imports OpenZeppelin's ERC20 contracts for token handling and uses SafeERC20 for safe token transfers. Additionally, it utilizes a struct to represent canonical ERC20 tokens and another struct to represent bridge transfer operations. The contract also implements functions for changing bridged tokens, handling token transfers, and deploying bridged ERC20 tokens using proxy deployment. | This contract is essential for facilitating cross-chain interoperability for ERC20 tokens, enabling seamless token transfers between different blockchain networks. It ensures proper handling of token transfers and mappings between canonical and bridged tokens, which is crucial for maintaining token integrity and accessibility across chains.Management: Access to critical functions is restricted through modifiers such as onlyOwner to ensure that only authorized entities can interact with the vault and manage bridged tokens. Error handling mechanisms are implemented to ensure the validity of token transfer operations and prevent unauthorized access. Additionally, the contract emits events to provide transparency and track token-related actions. |
ERC721Vault.sol | The contract acts as a vault for ERC721 tokens, receiving and managing deposits of these tokens from users, and facilitating their transfer between different blockchain networks via a bridge . It maintains mappings between canonical ERC721 tokens and their bridged counterparts. | It implements functions for handling ERC721 token transfers, including receiving tokens from users , sending tokens to other chains, and recalling tokens back to the original chain. The contract also utilizes proxy deployment for deploying bridged ERC721 token contracts and initializes them accordingly. | This contract plays a vital role in enabling cross-chain interoperability for ERC721 tokens, allowing users to transfer their tokens seamlessly between different blockchain networks. By managing the mapping between canonical and bridged tokens, it ensures the integrity and accessibility of ERC721 tokens across chains.Management: Access to critical functions is restricted through modifiers such as 'onlyOwner' to ensure that only authorized entities can interact with the vault and manage bridged tokens. Error handling mechanisms are implemented to ensure the validity of token transfer operations and prevent unauthorized access. Additionally, the contract emits events to provide transparency and track token-related actions. |
SgxVerifier.sol | The SgxVerifier contract facilitates the verification of SGX signature proofs on-chain , ensuring the integrity and validity of attestation quotes provided by SGX instances. It manages the registration , validation , and replacement of trusted SGX instances, maintaining a registry of ECDSA public keys associated with valid SGX instances. | Leveraging cryptographic primitives from OpenZeppelin's ECDSA library, the contract verifies SGX proofs submitted during state transitions, confirming the authenticity of the transition and the legitimacy of the associated SGX instance. It interacts with the Automata Attestation contract to verify attestation quotes, ensuring the trustworthiness of SGX instances. Gas-saving techniques, such as using instance IDs for storage efficiency, are implemented to optimize contract functionality. | The contract plays a critical role in enhancing the security and reliability of off-chain computations by verifying SGX proofs on-chain, mitigating the risk of malicious behavior or compromised SGX instances. By maintaining a registry of trusted SGX instances and enforcing expiration checks, the contract fosters a secure environment for on-chain interactions relying on SGX-based computations, bolstering the integrity of decentralized systems.Management: Access to critical functions like adding and deleting SGX instances is restricted to authorized users, ensuring controlled management of the instance registry. The contract emits events to notify stakeholders about the addition, replacement, or deletion of SGX instances, enhancing transparency and accountability in the management process. Gas-efficient storage mechanisms are employed to optimize resource utilization and minimize transaction costs associated with instance management operations. |
TimelockTokenPool.sol | The TimelockTokenPool contract manages Taiko tokens allocated to different roles and individuals, facilitating a three-state lifecycle for token management: "allocated " to "granted , owned, and locked ," and finally to "granted, owned, and unlocked ." It allows for conditional allocation of tokens, with the ability to cancel allocations and ensures irreversible ownership once tokens are granted and owned. | Utilizing OpenZeppelin's SafeERC20 library, the contract interacts with ERC20-compliant tokens (Taiko tokens and a cost token) to handle token transfers securely. It implements functions to grant, void , and withdraw tokens based on predefined schedules, enforcing constraints such as start times, cliffs, and periods for token unlocking and ownership. | Deploying multiple instances of this contract for different roles (e.g., investors , team members , grant program grantees ) enables precise management of token allocations , fostering accountability and transparency in token distribution. By enforcing predetermined unlocking schedules and ownership constraints, the contract ensures fair and controlled token distribution while mitigating the risk of unauthorized token withdrawals or voiding.Management: Access to critical functions such as granting and voiding tokens is restricted to the contract owner, ensuring centralized control over token allocation and management. The contract emits events to track grant, void, and withdrawal actions, providing stakeholders with visibility into token-related activities. Gas-efficient storage techniques, including using a storage gap array, are employed to optimize resource utilization and minimize transaction costs associated with token management operations. |
Launching a DoS attack on the core protocol.
Exploiting bugs in Merkle proof verification logic.
Exploiting potential bugs in multi-hop bridging, for example, use a multi-hop that contains a loop.
Constructing bridge messages whose hashes collide.
Continuously contesting valid proofs to delay block confirmation.
Exhausting the L1 block proposing ring-buffer to halt the chain.
Exploring bugs in L2's anchor transaction.
40 hours
#0 - c4-judge
2024-04-10T10:59:23Z
0xean marked the issue as grade-b